View Javadoc

1   /*
2    *
3    */
4   package java.lang.jdynpur;
5   
6   import java.io.BufferedOutputStream;
7   import java.io.DataOutputStream;
8   import java.io.FileOutputStream;
9   import java.io.IOException;
10  import java.util.HashSet;
11  
12  import org.softevo.util.ObjectIdMapper;
13  import org.softevo.util.asm.FieldIdentifier;
14  import org.softevo.util.asm.MethodIdentifier;
15  import org.softevo.util.asm.MethodIdentifierMapGenerator;
16  
17  
18  /**
19   * Methods in this class are called by injected code to write trace events. <BR>
20   * This class continously writes a trace of events into file
21   * <code>trace.out</code>. If a suffix file (a file named
22   * <code>suffix_*</code> exists, then the suffix of the filename (e.g. file
23   * name suffix_0 -> suffix = 0 -> file name = trace.out_0) is appended to the
24   * name of the outputfile.
25   * 
26   * @author dallmeier
27   */
28  public class Tracer {
29  
30  	public static MethodIdentifierMapGenerator methodIdentifierMap = null;
31  
32  	public static ObjectIdMapper<FieldIdentifier> fieldIdentfierMap = null;
33  	
34  	private static HashSet<Integer> writtenMethodIdentifiers = new HashSet<Integer>(1000);
35  	
36  	private static HashSet<Integer> writtenFieldIdentifiers = new HashSet<Integer>(5000);
37  
38  	/**
39  	 * The stream used to write events. <BR>
40  	 */
41  	private static DataOutputStream dataOutputStream = null;
42  
43  	/**
44  	 * The size for the write buffer. <BR>
45  	 */
46  	private static final int BUFFERSIZE = 16777216;
47  
48  	/**
49  	 * The base name for the output file.
50  	 */
51  	public static final String OUTPUTFILENAME = "trace.out";
52  
53  	/**
54  	 * Constant for object creation event.
55  	 */
56  	public static final int EV_OBJECTCREATION = 1;
57  
58  	/**
59  	 * Constant for field write event.
60  	 */
61  	public static final int EV_FIELDWRITE = 2;
62  
63  	/**
64  	 * Constant for static field write event.
65  	 */
66  	public static final int EV_STATICFIELDWRITE = 3;
67  
68  	/**
69  	 * Constant for start of dynamic method event.
70  	 */
71  	public static final int EV_DYNAMICMETHODSTART = 4;
72  
73  	/**
74  	 * Constant for start of static method event.
75  	 */
76  	public static final int EV_STATICMETHODSTART = 5;
77  
78  	/**
79  	 * Constant for start of dynamic method event.
80  	 */
81  	public static final int EV_DYNAMICMETHODEND = 6;
82  
83  	/**
84  	 * Constant for end of static method end event.
85  	 */
86  	public static final int EV_STATICMETHODEND = 7;
87  
88  	/**
89  	 * Constant for array creation event.
90  	 */
91  	public static final int EV_ARRAYCREATED = 8;
92  
93  	/**
94  	 * Constant for array modification event.
95  	 */
96  	public static final int EV_ARRAYMODIFIED = 9;
97  
98  	public static final int EV_METHODID= 10;
99  
100 	public static final int EV_PARAMETER = 11;
101 
102 	public static final int EV_FIELDID = 12;
103 
104 	public static final int EV_OBJECTFIELDWRITE = 13;
105 	
106 	public static final int EV_OBJECTARRAYMODIFIED = 14;
107 	
108 	public static final boolean traceParameterMutability = System.getProperty("jdynpur.traceparametermutability", "false").equalsIgnoreCase("true");
109 	
110 	/**
111 	 * Constant indicating access to a static variable or execution of a static
112 	 * method.
113 	 */
114 	public static final int STATIC = -1;
115 
116 	/**
117 	 * Constant for null value.
118 	 */
119 	public static final int NULL = -1;
120 
121 
122 	/**
123 	 * Mutex object that protects access to the <code>DataOutputStream</code>
124 	 */
125 	private static Object mutex = null;
126 
127 	/*private static HashMap<Integer, Stack<Integer>> threadIdToActiveMethodStackMap = new HashMap<Integer, Stack<Integer>>();*/
128 
129 	private static boolean debug = false;
130 
131 	/**
132 	 * Static initializer creates the output file and adds a shutdown hook such
133 	 * that the class is notified when the vm exits.
134 	 */
135 	static {
136 		String fileName;
137 		// System.out.println("Using up 4 date version of Tracer.");
138 		mutex = new Object();
139 		try {
140 			fileName = System.getProperty("jdynpur.resultfilename", OUTPUTFILENAME);
141 			dataOutputStream = new DataOutputStream(new BufferedOutputStream(
142 			                                                                 new FileOutputStream(fileName), BUFFERSIZE));
143 			Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownThread()));
144 		} catch (IOException exception) {
145 			System.out.println("Unable to create trace outputStream.");
146 			exception.printStackTrace(System.out);
147 			System.exit(5);
148 		}
149 	}
150 
151 	/**
152 	 * This method is used to safely write events to the output stream. It is
153 	 * thread safe. If anything goes wrong writing to the stream, the vm is
154 	 * terminated with error code <code>5</code>.
155 	 * 
156 	 * @param eventId
157 	 *          the id of the event to be written
158 	 * @param value
159 	 *          the value of the event
160 	 */
161 	private static void writeEvent(int eventId, int value) {
162 		try {
163 			synchronized (mutex) {
164 				if (dataOutputStream != null) {
165 					dataOutputStream.writeInt(eventId);
166 					dataOutputStream.writeInt(System.identityHashCode(Thread
167 					                                                  .currentThread()));
168 					dataOutputStream.writeInt(value);
169 				}
170 			}
171 		} catch (IOException exception) {
172 			System.out.println("An exception occured writing to the trace.");
173 			exception.printStackTrace(System.out);
174 			System.exit(5);
175 		}
176 	}
177 
178 	/**
179 	 * This method writes events that have 2 parameters and adds
180 	 * the class of the receiver object as another parameter.
181 	 * 
182 	 * @param eventId
183 	 *          the id of the event
184 	 * @param value1
185 	 *          the first value
186 	 * @param value2
187 	 *          the second value
188 	 */
189 	private static void writeDynamicMethodEvent(int eventId, int value1, Object object) {
190 		try {
191 			synchronized (mutex) {
192 				if (dataOutputStream != null) {
193 					dataOutputStream.writeInt(eventId);
194 					dataOutputStream.writeInt(System.identityHashCode(Thread
195 					                                                  .currentThread()));
196 					dataOutputStream.writeInt(value1);
197 					dataOutputStream.writeInt(System.identityHashCode(object));
198 					dataOutputStream.writeUTF(object.getClass().getName());
199 				}
200 			}
201 		} catch (IOException exception) {
202 			System.out.println("An exception occured writing to the trace.");
203 			exception.printStackTrace(System.out);
204 			System.exit(5);
205 		}
206 	}
207 	/**
208 	 * This method writes events that have 2 parameters and adds
209 	 * the class of the receiver object as another parameter.
210 	 * 
211 	 * @param eventId
212 	 *          the id of the event
213 	 * @param value1
214 	 *          the first value
215 	 * @param value2
216 	 *          the second value
217 	 */
218 	private static void writeParameterEvent(int index, Object parameter) {
219 		try {
220 			synchronized (mutex) {
221 				if (dataOutputStream != null) {
222 					dataOutputStream.writeInt(EV_PARAMETER);
223 					dataOutputStream.writeInt(System.identityHashCode(Thread
224 					                                                  .currentThread()));
225 					dataOutputStream.writeInt(index);
226 					if (parameter != null) {
227 						dataOutputStream.writeInt(System.identityHashCode(parameter));
228 					} else {
229 						dataOutputStream.writeInt(System.identityHashCode(NULL));
230 					}
231 				}
232 			}
233 		} catch (IOException exception) {
234 			System.out.println("An exception occured writing to the trace.");
235 			exception.printStackTrace(System.out);
236 			System.exit(5);
237 		}
238 	}
239 	
240 	private static void writeMethodIdentifier(int methodId) {
241 		try {
242 			synchronized (mutex) {
243 				if (dataOutputStream != null) {
244 					if (!writtenMethodIdentifiers.contains(methodId)) {
245 						MethodIdentifier identifier = methodIdentifierMap.getMethodIdentifier(methodId);
246 						if (identifier != null) {
247 							String methodName = identifier.toString();
248 							dataOutputStream.writeInt(EV_METHODID);
249 							dataOutputStream.writeInt(methodId);
250 							dataOutputStream.writeInt(identifier.getAccess());
251 							dataOutputStream.writeUTF(methodName);
252 							writtenMethodIdentifiers.add(methodId);
253 						} else {
254 							System.out.println("Method identifier " + methodId + " not found in identifier map.");
255 							System.out.println("Will now exit!");
256 							System.out.flush();
257 							System.exit(-1);
258 						}
259 					}
260 				}
261 			}
262 		} catch (IOException exception) {
263 			System.out.println("An exception occured writing to the trace.");
264 			exception.printStackTrace(System.out);
265 			System.exit(5);
266 		}
267 
268 	}
269 	
270 	private static void writeFieldIdentifier(int fieldId) {
271 		try {
272 			synchronized (mutex) {
273 				if (dataOutputStream != null) {
274 					if (!writtenFieldIdentifiers.contains(fieldId)) {
275 						FieldIdentifier identifier = fieldIdentfierMap.getObject(fieldId);
276 						if (identifier != null) {
277 							String fieldName = identifier.toString();
278 							dataOutputStream.writeInt(EV_FIELDID);
279 							dataOutputStream.writeInt(fieldId);
280 							dataOutputStream.writeUTF(fieldName);
281 							writtenFieldIdentifiers.add(fieldId);
282 						} else {
283 							System.out.println("Field identifier " + fieldId + " not found in identifier map.");
284 							System.out.println("Will now exit!");
285 							System.out.flush();
286 							System.exit(-1);
287 						}
288 					}
289 				}
290 			}
291 		} catch (IOException exception) {
292 			System.out.println("An exception occured writing to the trace.");
293 			exception.printStackTrace(System.out);
294 			System.exit(5);
295 		}
296 
297 	}
298 
299 	public static void objectFieldWritten(Object receiver, Object value, int fieldId) {
300 		writeFieldIdentifier(fieldId);
301 		try {
302 			synchronized (mutex) {
303 				if (dataOutputStream != null) {
304 					dataOutputStream.writeInt(EV_OBJECTFIELDWRITE);
305 					dataOutputStream.writeInt(System.identityHashCode(Thread
306 					                                                  .currentThread()));
307 					dataOutputStream.writeInt(fieldId);
308 					if (receiver != null) {
309 						dataOutputStream.writeInt(System.identityHashCode(receiver));
310 					} else {
311 						dataOutputStream.writeInt(System.identityHashCode(NULL));
312 					}
313 					if (value != null) {
314 						dataOutputStream.writeInt(System.identityHashCode(value));
315 					} else {
316 						dataOutputStream.writeInt(System.identityHashCode(NULL));
317 					}
318 				}
319 			}
320 		} catch (IOException exception) {
321 			System.out.println("An exception occured writing to the trace.");
322 			exception.printStackTrace(System.out);
323 			System.exit(5);
324 		}
325 	}
326 	
327 	/**
328 	 * Called whenever a field is written in an object.
329 	 * 
330 	 * @param object
331 	 *          the modified object
332 	 */
333 	public static void fieldWritten(Object object) {
334 		if (debug) {
335 			System.out.println("Field written.");
336 		}
337 		writeEvent(EV_FIELDWRITE, System.identityHashCode(object));
338 	}
339 
340 	/**
341 	 * Called whenever a new object is created. This method is called after the
342 	 * constructor call.
343 	 * 
344 	 * @param object
345 	 *          the object that is created
346 	 */
347 	public static void objectCreated(Object object) {
348 		if (debug) {
349 			System.out.println("Object created.");
350 		}
351 		writeEvent(EV_OBJECTCREATION, System.identityHashCode(object));
352 	}
353 
354 	/**
355 	 * Called whenever a dynamic method is started.
356 	 * 
357 	 * @param methodId
358 	 *          the id of the method that is started
359 	 * @param thisObject
360 	 *          the object the method is invoked on
361 	 */
362 	public static void dynamicMethodStarted(int methodId, Object thisObject) {
363 		writeMethodIdentifier(methodId);
364 		if (debug) {
365 			System.out.println("Method started " + methodId);
366 		}
367 		writeDynamicMethodEvent(EV_DYNAMICMETHODSTART, methodId, thisObject);
368 	}
369 
370 	public static void parameterPassed(int index, Object parameter) {
371 		writeParameterEvent(index, parameter);
372 	}
373 
374 	/**
375 	 * Called whenever a static method is started.
376 	 * 
377 	 * @param methodId
378 	 *          the id of the method that is started
379 	 */
380 	public static void staticMethodStarted(int methodId) {
381 		writeMethodIdentifier(methodId);
382 		if (debug) {
383 			System.out.println("Static method started " + methodId);
384 		}
385 		writeEvent(EV_STATICMETHODSTART, methodId);
386 	}
387 
388 	/**
389 	 * Called whenever a dynamic method has just finished execution.
390 	 * 
391 	 * @param methodId
392 	 *          the id of the method
393 	 */
394 	public static void dynamicMethodEnded(int methodId) {
395 		if (debug) {
396 			System.out.println("Dynamic method ended " + methodId);
397 		}
398 		writeEvent(EV_DYNAMICMETHODEND, methodId);
399 	}
400 
401 	/**
402 	 * Called whenever a static method has just finished execution.
403 	 * 
404 	 * @param methodId
405 	 *          the id of the method
406 	 */
407 	public static void staticMethodEnded(int methodId) {
408 		if (debug) {
409 			System.out.println("Static method ended " + methodId);
410 		}
411 		writeEvent(EV_STATICMETHODEND, methodId);
412 	}
413 
414 	/**
415 	 * Called whenever a static field was written.
416 	 */
417 	public static void staticFieldWritten() {
418 		if (debug) {
419 			System.out.println("Static field written.");
420 		}
421 		writeEvent(EV_STATICFIELDWRITE, STATIC);
422 	}
423 
424 	/**
425 	 * Called whenever a new array was created.
426 	 * 
427 	 * @param object
428 	 *          the array created
429 	 */
430 	public static void arrayCreated(Object object) {
431 		if (debug) {
432 			System.out.println("Array created.");
433 		}
434 		writeEvent(EV_ARRAYCREATED, System.identityHashCode(object));
435 	}
436 
437 	/**
438 	 * Called whenever an entry in an array is modified.
439 	 * 
440 	 * @param object
441 	 *          the array that was modified
442 	 */
443 	public static void arrayModified(Object object) {
444 		if (debug) {
445 			System.out.println("Array modified.");
446 		}
447 		writeEvent(EV_ARRAYMODIFIED, System.identityHashCode(object));
448 	}
449 
450 	public static void objectArrayModified(Object array, int index, Object value) {
451 		try {
452 			synchronized (mutex) {
453 				if (dataOutputStream != null) {
454 					dataOutputStream.writeInt(EV_OBJECTARRAYMODIFIED);
455 					dataOutputStream.writeInt(System.identityHashCode(Thread
456 					                                                  .currentThread()));
457 					if (array != null) {
458 						dataOutputStream.writeInt(System.identityHashCode(array));
459 					} else {
460 						dataOutputStream.writeInt(System.identityHashCode(NULL));
461 					}
462 					dataOutputStream.writeInt(System.identityHashCode(index));
463 					if (value != null) {
464 						dataOutputStream.writeInt(System.identityHashCode(value));
465 					} else {
466 						dataOutputStream.writeInt(System.identityHashCode(NULL));
467 					}
468 				}
469 			}
470 		} catch (IOException exception) {
471 			System.out.println("An exception occured writing to the trace.");
472 			exception.printStackTrace(System.out);
473 			System.exit(5);
474 		}
475 	}
476 	
477 	/**
478 	 * This method is called to record creation of multi dimensional arrays. This
479 	 * is necessary because otherwise we don't know about the creation of all
480 	 * arrays.
481 	 * 
482 	 * @param array
483 	 *          the object array, possibly containing more object arrays
484 	 * @param depth
485 	 *          the recursion depth
486 	 */
487 	public static void addArraysRecursively(Object[] array, int depth) {
488 		for (int counter = 0; counter < array.length; counter++) {
489 			arrayCreated(array[counter]);
490 			objectArrayModified(array, counter, array[counter]);
491 			if (depth > 0) {
492 				addArraysRecursively((Object[]) array[counter], depth - 1);
493 			}
494 		}
495 	}
496 
497 	/**
498 	 * This class implements the shutdown hook needed to assure that all trace
499 	 * information gets written at vm shutdown time.
500 	 * 
501 	 * @author dallmeier
502 	 */
503 	private static class ShutdownThread implements Runnable {
504 
505 		/**
506 		 * Flushes the stream and closes it.
507 		 */
508 		public void run() {
509 			// System.out.println("Running shutdown thread version 1");
510 			synchronized (mutex) {
511 				try {
512 					if (dataOutputStream != null) {
513 						dataOutputStream.flush();
514 						dataOutputStream.close();
515 						dataOutputStream = null;
516 					}
517 				} catch (IOException finalizeException) {
518 					System.out
519 					.println("An exception occured finalizing trace output stream.");
520 					finalizeException.printStackTrace(System.out);
521 				}
522 			}
523 		}
524 		
525 	}
526 
527 }