View Javadoc

1   /*
2    *
3    */
4   package org.softevo.jdynpur.eval;
5   
6   import java.io.BufferedInputStream;
7   import java.io.DataInputStream;
8   import java.io.EOFException;
9   import java.io.File;
10  import java.io.FileInputStream;
11  import java.io.IOException;
12  import java.io.Serializable;
13  import java.text.DecimalFormat;
14  import java.text.NumberFormat;
15  import java.util.Collections;
16  import java.util.HashMap;
17  import java.util.HashSet;
18  import java.util.Iterator;
19  import java.util.Stack;
20  import java.util.Vector;
21  
22  import java.lang.jdynpur.Tracer;
23  
24  import org.apache.commons.io.input.CountingInputStream;
25  import org.softevo.util.ObjectIdMapper;
26  import org.softevo.util.asm.FieldIdentifier;
27  import org.softevo.util.asm.MethodIdentifier;
28  import org.softevo.util.asm.MethodIdentifierMapGenerator;
29  
30  /**
31   * This class implements dynamic purity analysis of an execution trace file. The
32   * main idea of the analysis is as follows:
33   * <ul>
34   * <li>For every method, calculate the set of modified and the set of created
35   * objects.</li>
36   * <li>At the end of each method, add the set of created objects to the created
37   * objects of the calling method. </li>
38   * <li>At the end of each method, check whether any object not created during
39   * the execution of the method was modified. If this is the case, the execution
40   * was impure, otherwise pure.
41   * </ul>
42   * 
43   * @author dallmeier
44   */
45  public class PurityTraceAnalyser {
46  
47  	/**
48  	 * The name of the trace file to be analyzed.
49  	 */
50  	protected String fileName = null;
51  
52  	/**
53  	 * Name of the file where results are saved.
54  	 */
55  	protected String resultsFileName = null;
56  
57  	/**
58  	 * Identifier map used for this trace.
59  	 */
60  	protected MethodIdentifierMapGenerator identifierMap = null;
61  
62  	/**
63  	 * The set of method ids known to be impure.
64  	 */
65  	protected HashMap<Integer, HashSet<String>> impureMethodIds = null;
66  	
67  	/**
68  	 * The set of method ids that might be pure.
69  	 */
70  	protected HashMap<Integer, HashSet<String>> pureMethodIds = null;
71  
72  	/**
73  	 * The set of executed methods.
74  	 */
75  	protected HashMap<Integer, HashSet<String>> executedMethodIds = null;
76  
77  	/**
78  	 * The set of methods known to modify the object they were invoked on.
79  	 */
80  	protected HashMap<Integer, HashSet<String>> methodsModifyingThisObject = null;
81  
82  	/**
83  	 * The set of <code>MethodIdentifiers</code> known to be pure.
84  	 */
85  	protected HashMap<MethodIdentifier, HashSet<String>> pureMethodIdentifiers = null;
86  
87  	/**
88  	 * The set of <code>MethodIdentifiers</code> modifying a this object.
89  	 */
90  	protected HashMap<MethodIdentifier, HashSet<String>> thisModifyingMethodIdentifiers = null;
91  
92  	/**
93  	 * The set of <code>MethodIdentifiers</code> known to be impure.
94  	 */
95  	protected HashMap<MethodIdentifier, HashSet<String>> impureMethodIdentifiers = null;
96  
97  	/**
98  	 * The set of executed <code>MethodIdentifiers</code>.
99  	 */
100 	protected HashMap<MethodIdentifier, HashSet<String>> executedMethodIdentifiers = null;
101 
102 	/**
103 	 * A map of thread ids to a stack of object identifiers created in a method.
104 	 */
105 	protected HashMap<Integer, Stack<HashSet<Integer>>> threadIdToCreatedObjectSetStackMap = null;
106 
107 	protected HashMap<Integer, Stack<HashMap<Integer, Integer>>> threadIdToParameterSetStackMap = null;
108 
109 	protected ObjectIdMapper<FieldIdentifier> fieldIdentifierMap = null;
110 
111 	/**
112 	 * A map of thread ids to a stack of object identifiers modified in a method.
113 	 */
114 	protected HashMap<Integer, Stack<HashSet<Integer>>> threadIdToModifiedObjectSetStackMap = null;
115 
116 	/**
117 	 * A map of thread ids to a method call stack.
118 	 */
119 	protected HashMap<Integer, Stack<Integer>> threadIdToMethodCallStackMap = null;
120 
121 	/**
122 	 * A map of thread ids to a stack of this object identifiers for active
123 	 * non-static methods.
124 	 */
125 	protected HashMap<Integer, Stack<Integer>> threadIdToThisStackMap = null;
126 
127 	/**
128 	 * A map of thread ids to a stack of this type for active
129 	 * non-static methods.
130 	 */
131 	protected HashMap<Integer, Stack<String>> threadIdToThisTypeMap = null;
132 
133 	protected HashMap<MethodIdentifier, ParameterMutabilityInformation> parameterMutabilityInformation = null;
134 
135 	protected Vector<Long> mutabilityChangeEntries = null;
136 	
137 	protected Vector<Long> purityChangeEntries = null;
138 
139 	protected PurityResults results = null;
140 	
141 	/**
142 	 * Flag for verbose output.
143 	 */
144 	protected boolean verbose = false;
145 
146 	/**
147 	 * Flag if pure method identifiers should be printed to
148 	 * <code>System.out</code> .
149 	 */
150 	protected boolean printResults = false;
151 
152 	/**
153 	 * Flag if results shall be saved.
154 	 */
155 	protected boolean saveResults = false;
156 
157 	/**
158 	 * Flag indicating if API methods should be excluded from the result data
159 	 * structures.
160 	 */
161 	protected boolean excludeAPIMethodsFromResults = true;
162 
163 	/**
164 	 * Flag indicating if the effects of static initializers should be ignored. If
165 	 * you are not sure about this flag, set it to true.
166 	 */
167 	protected boolean ignoreEffectsOfStaticInitializers = true;
168 
169 	private HashMap<Integer, HashMap<Integer, Integer>> heap;
170 
171 	private boolean analyseParameterMutability;
172 
173 	private long loopCounter;
174 	
175 	/**
176 	 * Creates a new purity analyser for a trace file.
177 	 * 
178 	 * @param fileName
179 	 *          the name of the trace
180 	 * @param identifierMap
181 	 *          the identifier map generated together with the trace
182 	 */
183 	public PurityTraceAnalyser(String fileName, String resultsFileName,
184 	                           boolean saveResults, boolean analyseParameterMutability) {
185 		this.fileName = fileName;
186 		this.resultsFileName = resultsFileName;
187 		this.saveResults = saveResults;
188 		this.excludeAPIMethodsFromResults = Boolean.FALSE;
189 		this.analyseParameterMutability = analyseParameterMutability;
190 		this.identifierMap = new MethodIdentifierMapGenerator();
191 		this.fieldIdentifierMap = new ObjectIdMapper<FieldIdentifier>();
192 		impureMethodIds = new HashMap<Integer, HashSet<String>>();
193 		pureMethodIds = new HashMap<Integer, HashSet<String>>();
194 		executedMethodIds = new HashMap<Integer, HashSet<String>>();
195 		methodsModifyingThisObject = new HashMap<Integer, HashSet<String>>();
196 		threadIdToMethodCallStackMap = new HashMap<Integer, Stack<Integer>>();
197 		threadIdToCreatedObjectSetStackMap = new HashMap<Integer, Stack<HashSet<Integer>>>();
198 		threadIdToModifiedObjectSetStackMap = new HashMap<Integer, Stack<HashSet<Integer>>>();
199 		threadIdToThisStackMap = new HashMap<Integer, Stack<Integer>>();
200 		threadIdToThisTypeMap = new HashMap<Integer, Stack<String>>();
201 	  mutabilityChangeEntries = new Vector<Long>();
202 	  purityChangeEntries = new Vector<Long>();
203 		if (analyseParameterMutability) {
204 		  threadIdToParameterSetStackMap = new HashMap<Integer, Stack<HashMap<Integer,Integer>>>();
205 		  parameterMutabilityInformation = new HashMap<MethodIdentifier, ParameterMutabilityInformation>();
206 		  heap = new HashMap<Integer, HashMap<Integer,Integer>>(10000);
207 		}
208 	}
209 
210 	/**
211 	 * Starts analysis of the trace.
212 	 * 
213 	 * @param verbose
214 	 *          flag if output shall be verbose
215 	 * @param printResults
216 	 *          flag if results should be printed to <code>System.out</code>
217 	 * @throws IOException
218 	 *           if an exception occurs reading from the trace
219 	 */
220 	public void analyse(boolean verbose, boolean printResults) throws IOException {
221 		DataInputStream dataInputStream;
222 		int eventType;
223 		int value, value2, threadId;
224 		String className;
225 
226 		System.out.println("Analyzing trace " + fileName);
227 		this.verbose = verbose;
228 		this.printResults = printResults;
229 		File traceFile = new File(fileName);
230 		long fileSize = traceFile.length();
231 		CountingInputStream countingInputStream = new CountingInputStream(new BufferedInputStream(new FileInputStream(traceFile)));
232 		dataInputStream = new DataInputStream(countingInputStream);
233 		NumberFormat formatter = new DecimalFormat("00");
234 		String percentageString = null;
235 		loopCounter = 0;
236 		while (loopCounter >= 0) {
237 			loopCounter++;
238 			if (loopCounter > 0 && loopCounter % 100000 == 0) {
239 				double percentageDone = countingInputStream.getCount()*100d/(fileSize*1.0d);
240 				String newPercentageString = formatter.format(percentageDone);
241 				if (percentageString != null && !newPercentageString.equals(percentageString)) {
242 				  System.out.println(percentageString + "% done.");
243 				}
244 				percentageString = newPercentageString;
245 			}
246 			try {
247 				eventType = dataInputStream.readInt();
248 				if (eventType != Tracer.EV_METHODID && eventType != Tracer.EV_FIELDID) {
249 					threadId = dataInputStream.readInt();
250 					value = dataInputStream.readInt();
251 					switch (eventType) {
252 					case Tracer.EV_FIELDWRITE:
253 						handleFieldWrite(value, threadId);
254 						break;
255 					case Tracer.EV_DYNAMICMETHODEND:
256 						if (identifierMap.getMethodIdentifier(value) == null) {
257 							System.out.println("Ignoring method end " + value + " for thread " + threadId);
258 						} else {
259 							handleDynamicMethodEnd(value, threadId);
260 						}
261 						break;
262 					case Tracer.EV_STATICMETHODEND:
263 						handleStaticMethodEnd(value, threadId);
264 						break;
265 					case Tracer.EV_STATICMETHODSTART:
266 						handleStaticMethodStart(value, threadId);
267 						break;
268 					case Tracer.EV_DYNAMICMETHODSTART:
269 						value2 = dataInputStream.readInt();
270 						className = dataInputStream.readUTF().replace('.', '/');
271 						handleDynamicMethodStart(value, value2, threadId, className);
272 						break;
273 					case Tracer.EV_OBJECTCREATION:
274 						handleObjectCreation(value, threadId);
275 						break;
276 					case Tracer.EV_STATICFIELDWRITE:
277 						handleStaticFieldWrite(threadId);
278 						break;
279 					case Tracer.EV_ARRAYCREATED:
280 						handleArrayCreation(value, threadId);
281 						break;
282 					case Tracer.EV_ARRAYMODIFIED:
283 						handleArrayModification(value, threadId);
284 						break;
285 					case Tracer.EV_PARAMETER: {
286 						int objectId = dataInputStream.readInt();
287 						if (analyseParameterMutability) {
288 						  handleParameter(value, threadId, objectId);
289 						}
290 						break;
291 					}
292 					case Tracer.EV_OBJECTFIELDWRITE: {
293 						int fieldId = value;
294 						int changedObjectId = dataInputStream.readInt();
295 						int newValueObjectId = dataInputStream.readInt();
296 						handleObjectFieldWrite(fieldId, threadId, changedObjectId, newValueObjectId);
297 						break;
298 					}
299 					case Tracer.EV_OBJECTARRAYMODIFIED: {
300 						int changedObjectId = value;
301 						int arrayIndex = dataInputStream.readInt();
302 						int newObjectId = dataInputStream.readInt();
303 						handleObjectArrayModification(threadId, changedObjectId, arrayIndex, newObjectId);
304 						break;
305 					}
306 					default:
307 						throw new RuntimeException("Illegal trace event code " + eventType);
308 					}
309 				} else {
310 					if (eventType == Tracer.EV_METHODID) {
311 						int methodId = dataInputStream.readInt();
312 						int access = dataInputStream.readInt();
313 						String methodName = dataInputStream.readUTF();
314 						MethodIdentifier identifier = MethodIdentifier.parseFromIdentifier(methodName, access);
315 						identifierMap.add(methodId, identifier);
316 					} else if (eventType == Tracer.EV_FIELDID) {
317 						int fieldId = dataInputStream.readInt();
318 						String fieldIdentifier = dataInputStream.readUTF();
319 						FieldIdentifier identifier = FieldIdentifier.parseIdentifierFromString(fieldIdentifier);
320 						fieldIdentifierMap.put(fieldId, identifier);
321 					}
322 				}
323 			} catch (EOFException eof) {
324 				dataInputStream.close();
325 				handleTraceEnd();
326 				break;
327 			}
328 		}
329 		System.out.println(formatter.format(100d) + "% done.");
330 		System.out.println();
331 	}
332 
333 	private HashMap<Integer, Integer> getOrCreateMapForObject(int objectId) {
334 		HashMap<Integer, Integer> result = heap.get(objectId);
335 		if (result == null) {
336 			result = new HashMap<Integer, Integer>();
337 			heap.put(objectId, result);
338 		}
339 		return result;
340 	}
341 	
342 	protected HashSet<Integer> getTransitiveStateForObject(int objectId) {
343 		HashSet<Integer> result = new HashSet<Integer>(100);
344 		addStateRecursively(objectId, result);
345 		return result;
346 	}
347 	
348 	private void addStateRecursively(int objectId, HashSet<Integer> result) {
349 		if (!result.contains(objectId)) {
350 			result.add(objectId);
351 		  HashMap<Integer, Integer> state = heap.get(objectId);
352 		  if (state != null) {
353 			  for (Integer childObjectId: state.values()) {
354 				  addStateRecursively(childObjectId, result);
355 			  }
356 		  }
357 		}
358 	}
359 
360 	protected void handleObjectArrayModification(int threadId, int changedObjectId,
361 																							int arrayIndex, int newObjectId) {
362 		handleObjectFieldWrite(arrayIndex, threadId, changedObjectId, newObjectId);
363 	}
364 
365 	protected void handleObjectFieldWrite(int fieldId, int threadId,
366 																			int changedObjectId, int newValueObjectId) {
367 		if (analyseParameterMutability) {
368 		  HashMap<Integer, Integer> objectFields = getOrCreateMapForObject(changedObjectId);
369 		  objectFields.put(fieldId, newValueObjectId);
370 		}
371 		getModifiedObjectSetStack(threadId).peek().add(changedObjectId);
372 	}
373 
374 	protected void handleParameter(int index, int threadId, int objectId) {
375 		Stack<HashMap<Integer, Integer>> parameters = getParameterSetStack(threadId);
376 		assert (parameters.size() > 0) : "No parameter set found.";
377 		parameters.peek().put(index, objectId);
378 	}
379 
380 	protected Stack<String> getThisTypeStack(int threadId) {
381 		Stack<String> result;
382 
383 		result = threadIdToThisTypeMap.get(threadId);
384 		if (result == null) {
385 			result = new Stack<String>();
386 			threadIdToThisTypeMap.put(threadId, result);
387 		}
388 		return result;
389 	}
390 
391 	/**
392 	 * Gets the this object stack for a given thread.
393 	 * 
394 	 * @param threadId
395 	 *          the id of the thread
396 	 * @return the stack
397 	 */
398 	protected Stack<Integer> getThisObjectStack(int threadId) {
399 		Stack<Integer> result;
400 
401 		result = threadIdToThisStackMap.get(threadId);
402 		if (result == null) {
403 			result = new Stack<Integer>();
404 			threadIdToThisStackMap.put(threadId, result);
405 		}
406 		return result;
407 	}
408 
409 	/**
410 	 * Gets the method call stack for a given thread.
411 	 * 
412 	 * @param threadId
413 	 *          the id of the thread
414 	 * @return the stack
415 	 */
416 	protected Stack<Integer> getMethodCallStack(int threadId) {
417 		Stack<Integer> result;
418 
419 		result = threadIdToMethodCallStackMap.get(threadId);
420 		if (result == null) {
421 			result = new Stack<Integer>();
422 			threadIdToMethodCallStackMap.put(threadId, result);
423 		}
424 		return result;
425 	}
426 
427 	/**
428 	 * Gets the created object set stack for a given thread.
429 	 * 
430 	 * @param threadId
431 	 *          the id of the thread
432 	 * @return the stack
433 	 */
434 	protected Stack<HashSet<Integer>> getCreatedObjectSetStack(int threadId) {
435 		Stack<HashSet<Integer>> result;
436 
437 		result = threadIdToCreatedObjectSetStackMap.get(threadId);
438 		if (result == null) {
439 			result = new Stack<HashSet<Integer>>();
440 			threadIdToCreatedObjectSetStackMap.put(threadId, result);
441 		}
442 		return result;
443 	}
444 
445 	/**
446 	 * Gets the modified object set stack for a given thread.
447 	 * 
448 	 * @param threadId
449 	 *          the id of the thread
450 	 * @return the stack
451 	 */
452 	protected Stack<HashSet<Integer>> getModifiedObjectSetStack(int threadId) {
453 		Stack<HashSet<Integer>> result;
454 
455 		result = threadIdToModifiedObjectSetStackMap.get(threadId);
456 		if (result == null) {
457 			result = new Stack<HashSet<Integer>>();
458 			threadIdToModifiedObjectSetStackMap.put(threadId, result);
459 		}
460 		return result;
461 	}
462 
463 	protected Stack<HashMap<Integer,Integer>> getParameterSetStack(int threadId) {
464 		Stack<HashMap<Integer, Integer>> result;
465 
466 		result = threadIdToParameterSetStackMap.get(threadId);
467 		if (result == null) {
468 			result = new Stack<HashMap<Integer, Integer>>();
469 			threadIdToParameterSetStackMap.put(threadId, result);
470 		}
471 		return result;
472 	}
473 
474 	/**
475 	 * This method checks if the integer passed identifies a user or an API
476 	 * method. Identification of API methods is done by class name.
477 	 * 
478 	 * @param methodId
479 	 *          the method identifier
480 	 */
481 	protected boolean isAPIMethod(int methodId) {
482 		MethodIdentifier identifier;
483 
484 		identifier = identifierMap.getMethodIdentifier(methodId);
485 		if (identifier.getClassName().startsWith("java") || identifier.getClassName().startsWith("sun")) {
486 			return true;
487 		} else {
488 			return false;
489 		}
490 	}
491 
492 	protected HashMap<MethodIdentifier, HashSet<String>> convertMap(HashMap<Integer, HashSet<String>> map) {
493 		HashMap<MethodIdentifier, HashSet<String>> result;
494 		Iterator<Integer> iterator;
495 		int methodId;
496 
497 		result = new HashMap<MethodIdentifier, HashSet<String>>();
498 		iterator = map.keySet().iterator();
499 		while (iterator.hasNext()) {
500 			methodId = iterator.next();
501 			if (!excludeAPIMethodsFromResults || !isAPIMethod(methodId)) {
502 				result.put(identifierMap.getMethodIdentifier(methodId), map.get(methodId));
503 			}
504 		}
505 		return result;
506 	}
507 	
508 	boolean debug = false;
509 	/**
510 	 * This method is called upon end of the trace. It fills the result
511 	 * datastructures and prints information to <code>System.out</code> if the
512 	 * <code>printResults</code> flag is set.
513 	 */
514 	protected void handleTraceEnd() {
515 		Iterator<Integer> iterator;
516 		Iterator<String> methodNameIterator;
517 		Vector<String> pureMethodNames;
518 		int methodId;
519 
520 		iterator = executedMethodIds.keySet().iterator();
521 		pureMethodNames = new Vector<String>(executedMethodIds.size());
522 		pureMethodIdentifiers = new HashMap<MethodIdentifier, HashSet<String>>();
523 		impureMethodIdentifiers = convertMap(impureMethodIds);
524 		thisModifyingMethodIdentifiers = convertMap(methodsModifyingThisObject);
525 		executedMethodIdentifiers = convertMap(executedMethodIds);
526 		while (iterator.hasNext()) {
527 			methodId = iterator.next();
528 			if (!impureMethodIds.containsKey(methodId)) {
529 				pureMethodNames.add(identifierMap.getMethodIdentifier(methodId).toString());
530 
531 				if (!excludeAPIMethodsFromResults || !isAPIMethod(methodId)) {
532 					pureMethodIdentifiers.put(identifierMap.getMethodIdentifier(methodId), 
533 					                          executedMethodIds.get(methodId));
534 				}
535 			}
536 		}
537 		if (printResults) {
538 			Collections.sort(pureMethodNames);
539 			methodNameIterator = pureMethodNames.iterator();
540 			System.out.println("Pure methods:");
541 			while (methodNameIterator.hasNext()) {
542 				System.out.println(methodNameIterator.next());
543 			}
544 			System.out.println("Number of pure methods: " + pureMethodNames.size());
545 			System.out.println("Number of executed methods: " + executedMethodIds.size());
546 		}
547 		System.out.println("Saving results to " + resultsFileName);
548 		results = new PurityResults(pureMethodIdentifiers, impureMethodIdentifiers, thisModifyingMethodIdentifiers,
549 		                            executedMethodIdentifiers, parameterMutabilityInformation);
550 		if (debug) {
551 			System.out.println("Debugging");
552 			for (MethodIdentifier identifier : pureMethodIdentifiers.keySet()) {
553 				assert (!impureMethodIdentifiers.containsKey(identifier)) : "Identifier "  + identifier + " both impure and pure.";
554 				assert (executedMethodIdentifiers.containsKey(identifier)) : "Pure method identifier "  + identifier + " not executed.";
555 			}
556 			for (MethodIdentifier identifier : impureMethodIdentifiers.keySet()) {
557 				assert (!pureMethodIdentifiers.containsKey(identifier)) : "Identifier "  + identifier + " both impure and pure.";
558 				assert (executedMethodIdentifiers.containsKey(identifier)) : "Impure method identifier "  + identifier + " not executed.";
559 			}
560 		}
561 		if (saveResults) {
562 			try {
563 				results.saveXML(resultsFileName);
564 			} catch (Exception exception) {
565 				System.out.println("An exception occured saving results.");
566 				exception.printStackTrace(System.out);
567 			}
568 		}
569 	}
570 
571 	/**
572 	 * Called to handle an array creation event.
573 	 * 
574 	 * @param objectId
575 	 *          the id of the new array
576 	 * @param threadId
577 	 *          the thread id
578 	 */
579 	protected void handleArrayCreation(int objectId, int threadId) {
580 		if (verbose) {
581 			System.out.println("Array created with id " + objectId + " in thread " + threadId + ".");
582 		}
583 		getCreatedObjectSetStack(threadId).peek().add(objectId);
584 	}
585 
586 	/**
587 	 * Called to handle an array modification event.
588 	 * 
589 	 * @param objectId
590 	 *          the id of the modified array
591 	 * @param threadId
592 	 *          the thread id
593 	 */
594 	protected void handleArrayModification(int objectId, int threadId) {
595 		if (verbose) {
596 			System.out.println("Array " + objectId + " modified in thread " + threadId + ".");
597 		}
598 		getModifiedObjectSetStack(threadId).peek().add(objectId);
599 	}
600 
601 	/**
602 	 * Called to handle a static field write event.
603 	 * 
604 	 * @param threadId
605 	 *          the thread id
606 	 */
607 	protected void handleStaticFieldWrite(int threadId) {
608 		MethodIdentifier methodIdentifier;
609 
610 		if (verbose) {
611 			System.out.println("Static field written in thread " + threadId + ".");
612 		}
613 		methodIdentifier = identifierMap.getMethodIdentifier(getMethodCallStack(threadId).peek());
614 		if (ignoreEffectsOfStaticInitializers && methodIdentifier.getMethodName().equals("<clinit>")) {
615 			if (verbose) {
616 				System.out.println("Ignoring static field write in <clinit> of class " + methodIdentifier.getClassName());
617 			}
618 		} else {
619 			getModifiedObjectSetStack(threadId).peek().add(Tracer.STATIC);
620 		}
621 	}
622 
623 	/**
624 	 * Called to handle a field write event.
625 	 * 
626 	 * @param threadId
627 	 *          the thread id
628 	 * @param objectId
629 	 *          id of the modified object
630 	 */
631 	protected void handleFieldWrite(int objectId, int threadId) {
632 		if (verbose) {
633 			System.out.println("Field written in object " + objectId + " in thread " + threadId + ".");
634 		}
635 		getModifiedObjectSetStack(threadId).peek().add(objectId);
636 	}
637 
638 	/**
639 	 * Handles object creation event.
640 	 * 
641 	 * @param objectId
642 	 *          the id of the new object
643 	 * @param threadId
644 	 *          the thread id
645 	 */
646 	protected void handleObjectCreation(int objectId, int threadId) {
647 		if (verbose) {
648 			System.out.println("Object created with id " + objectId + " in thread " + threadId + ".");
649 		}
650 		getCreatedObjectSetStack(threadId).peek().add(objectId);
651 	}
652 
653 	protected void addToHashMap(HashMap<Integer, HashSet<String>> map, int methodId, String className) {
654 		HashSet<String> types;
655 
656 		types = map.get(methodId);
657 		if (types == null) {
658 			types = new HashSet<String>();
659 			map.put(methodId, types);
660 		}
661 		if (className != null) {
662 			types.add(className);
663 		}
664 	}
665 
666 	/**
667 	 * Handles a method start event. This method creates all neccessary
668 	 * datastructures to analyze execution of the method.
669 	 * 
670 	 * @param methodId
671 	 *          the id of the method invoked
672 	 * @param threadId
673 	 *          the thread id
674 	 */
675 	protected void handleMethodStart(int methodId, int threadId, String className) {
676 		if (verbose) {
677 			System.out.println("Method " + identifierMap.getMethodIdentifier(methodId) + " in thread " + threadId
678 			                   + " started.");
679 		}
680 		if (className != null) {
681 			addToHashMap(executedMethodIds, methodId, className);
682 		} else {
683 			System.out.println("Class name for executed method " + methodId + " is null.");
684 		}
685 		getMethodCallStack(threadId).push(methodId);
686 		getCreatedObjectSetStack(threadId).push(new HashSet<Integer>());
687 		getModifiedObjectSetStack(threadId).push(new HashSet<Integer>());
688 		if (analyseParameterMutability) {
689 		  getParameterSetStack(threadId).push(new HashMap<Integer, Integer>());
690 		}
691 	}
692 
693 	/**
694 	 * Handles start of a dynamic method. In addition to calling
695 	 * <code>handleMethodStart</code>, this method also modifieds the
696 	 * <code>thisObjectStack</code>.
697 	 * 
698 	 * @param methodId
699 	 *          the method identifier
700 	 * @param thisObjectId
701 	 *          the identifier of the this object
702 	 * @param threadId
703 	 *          the id of the thread
704 	 */
705 	protected void handleDynamicMethodStart(int methodId, int thisObjectId, 
706 	                                        int threadId, String className) {
707 		getThisObjectStack(threadId).push(thisObjectId);
708 		getThisTypeStack(threadId).push(className);
709 		handleMethodStart(methodId, threadId, className);
710 	}
711 
712 	/**
713 	 * Handles start of a static method. This method only calls
714 	 * <code>handleMethodStart</code>
715 	 * 
716 	 * @param methodId
717 	 *          the identifier of the method
718 	 * @param threadId
719 	 *          the thread id
720 	 */
721 	protected void handleStaticMethodStart(int methodId, int threadId) {
722 		MethodIdentifier identifier = identifierMap.getMethodIdentifier(methodId);
723 		handleMethodStart(methodId, threadId, identifier.getClassName());
724 	}
725 
726 	/**
727 	 * Handles end of a static method. This method only calls
728 	 * <code>handleMethodEnd</code>.
729 	 * 
730 	 * @param methodId
731 	 *          the id of the method
732 	 * @param threadId
733 	 *          the thread id
734 	 */
735 	protected void handleStaticMethodEnd(int methodId, int threadId) {
736 		handleMethodEnd(methodId, threadId, null);
737 	}
738 
739 	/**
740 	 * Handles end of a dynamic method. In addition to calling
741 	 * <code>handleMethodEnd</code>, this method also modifies the
742 	 * <code>thisObjectStack</code>
743 	 * 
744 	 * @param methodId
745 	 *          the id of the method
746 	 * @param threadId
747 	 *          the thread id
748 	 */
749 	protected void handleDynamicMethodEnd(int methodId, int threadId) {
750 		HashSet<Integer> modifiedObjects;
751 		int thisObjectId;
752 		String className;
753 
754 		if (verbose) {
755 			System.out.println("Dynamic method " + identifierMap.getMethodIdentifier(methodId) + " (" + methodId
756 			                   + ") in thread " + threadId + " finished.");
757 		}
758 		modifiedObjects = getModifiedObjectSetStack(threadId).peek();
759 		thisObjectId = getThisObjectStack(threadId).pop();
760 		className = getThisTypeStack(threadId).pop();
761 		if (modifiedObjects.contains(thisObjectId)) {
762 			addToHashMap(methodsModifyingThisObject, methodId, className);
763 		}
764 		handleMethodEnd(methodId, threadId, className);
765 	}
766 
767 	/**
768 	 * Handles method end events. This method evaluates the set of modified and
769 	 * created objects and classifies methods as impure.
770 	 * 
771 	 * @param methodId
772 	 *          the id of the method
773 	 * @param threadId
774 	 *          the thread id
775 	 */
776 	protected void handleMethodEnd(int methodId, int threadId, String className) {
777 		if (verbose) {
778 			System.out.println("Method " + identifierMap.getMethodIdentifier(methodId) + " in thread " + threadId
779 			                   + " finished.");
780 		}
781 		int activeMethodId = getMethodCallStack(threadId).pop();
782 		HashSet<Integer> createdObjects = getCreatedObjectSetStack(threadId).pop();
783 		HashSet<Integer> modifiedObjects = getModifiedObjectSetStack(threadId).pop();
784 		if (methodId != activeMethodId) {
785 			System.out.println("Method Start/End mismatch for thread " + threadId);
786 			System.out
787 			.println("Method that finished: " + identifierMap.getMethodIdentifier(methodId) + " (" + methodId + ")");
788 			System.out.println("Method that should have finished: " + identifierMap.getMethodIdentifier(activeMethodId)
789 			                   + " (" + activeMethodId + ")");
790 		}
791 		if (className == null) {
792 			MethodIdentifier identifier = identifierMap.getMethodIdentifier(methodId);
793 			className = identifier.getClassName();
794 		}
795 		// this assumes that all objects created in a sub method
796 		// may escape
797 		if (getCreatedObjectSetStack(threadId).size() > 0) {
798 			getCreatedObjectSetStack(threadId).peek().addAll(createdObjects);
799 		}
800 		if (getModifiedObjectSetStack(threadId).size() > 0) {
801 			getModifiedObjectSetStack(threadId).peek().addAll(modifiedObjects);
802 		}
803 		if (!createdObjects.containsAll(modifiedObjects)) {
804 			addToHashMap(impureMethodIds, activeMethodId, className);
805 			if (pureMethodIds.containsKey(methodId)) {
806 				pureMethodIds.remove(activeMethodId);
807 				purityChangeEntries.add(loopCounter);
808 			}
809 		} else {
810 			if (!impureMethodIds.containsKey(activeMethodId)) {
811 			  addToHashMap(pureMethodIds, activeMethodId, className);
812 			}
813 		}
814 		if (analyseParameterMutability) {
815 		  HashMap<Integer, Integer> parameters = getParameterSetStack(threadId).pop();
816 		  processParameterModification(parameters, modifiedObjects, methodId, className);
817 		}
818 	}
819 
820 	protected void processParameterModification(HashMap<Integer, Integer> parameters, HashSet<Integer> modifiedObjects, 
821 	                                            int methodId, String className) {
822 		HashSet<Integer> unmodifiedParameters = new HashSet<Integer>(parameters.size());
823 		for (int parameterIndex : parameters.keySet()) {
824 			int objectId = parameters.get(parameterIndex);
825 			if ((objectId != Tracer.NULL)) {
826 				HashSet<Integer> transitiveState = getTransitiveStateForObject(objectId);
827 				boolean isUnmodified = true;
828 				for (Integer stateObjectId : transitiveState) {
829 					if (modifiedObjects.contains(stateObjectId)) {
830 						isUnmodified = false;
831 						break;
832 					}
833 				}
834 				if (isUnmodified) {
835 					unmodifiedParameters.add(parameterIndex);
836 				}
837 			} else {
838 				unmodifiedParameters.add(parameterIndex);
839 				
840 			}
841 		}
842 		MethodIdentifier identifier = identifierMap.getMethodIdentifier(methodId);
843 		ParameterMutabilityInformation information = parameterMutabilityInformation.get(identifier);
844 		int oldNumberOfUnmodifiedParameters = -1;
845 		if (information == null) {
846 			information = new ParameterMutabilityInformation(identifier);
847 			parameterMutabilityInformation.put(identifier, information);
848 		} else {
849 			oldNumberOfUnmodifiedParameters = information.getUnmodifiedParameters().size();
850 		}
851 		information.updateUnmodifiedParameters(className, unmodifiedParameters);
852 		for (Integer parameterIndex : parameters.keySet()) {
853 			information.addParameter(parameterIndex);
854 		}
855 		if (oldNumberOfUnmodifiedParameters != -1) {
856 			int newNumberOfUnmodifiedParameters = information.getUnmodifiedParameters().size();
857 			assert (newNumberOfUnmodifiedParameters <= oldNumberOfUnmodifiedParameters) : "Illegal increase in number of unmodified parameters @" + loopCounter + " .";
858 			if (newNumberOfUnmodifiedParameters < oldNumberOfUnmodifiedParameters) {
859 				mutabilityChangeEntries.add(loopCounter);
860 			}
861 		}
862 	}
863 	
864 	/**
865 	 * Launches purity trace analysis. This method expects the name of the trace
866 	 * file and the name of the identifier map file.
867 	 * 
868 	 * @param args
869 	 *          command line arguments
870 	 */
871 	public static void main(String[] args) throws IOException {
872 		PurityTraceAnalyser traceAnalyser;
873 
874 		if (args.length != 3) {
875 			System.out
876 			.println("Usage: java org.softevo.jdynpur.PurityTraceAnalyser <traceFileName> <resultsFileName> <analyseParameterMutability>");
877 		} else {
878 			Boolean analyseParameterMutability = new Boolean(args[2]);
879 			if (analyseParameterMutability) {
880 				System.out.println("Parameter Mutability Analysis is turned on.");
881 			} else {
882 				System.out.println("Parameter Mutability Analysis is turned off.");
883 			}
884 			traceAnalyser = new PurityTraceAnalyser(args[0], args[1], true, analyseParameterMutability);
885 			traceAnalyser.analyse(false, true);
886 		}
887 	}
888 
889 	/**
890 	 * Gets the set of impure methods.
891 	 */
892 	public HashMap<MethodIdentifier, HashSet<String>> getImpureMethodIdentifiers() {
893 		return impureMethodIdentifiers;
894 	}
895 
896 	/**
897 	 * Gets the set of pure methods.
898 	 */
899 	public HashMap<MethodIdentifier, HashSet<String>> getPureMethodIdentifiers() {
900 		return pureMethodIdentifiers;
901 	}
902 
903 	/**
904 	 * Gets the set of executed methods.
905 	 */
906 	public HashMap<MethodIdentifier, HashSet<String>> getThisModifyingMethodIdentifiers() {
907 		return thisModifyingMethodIdentifiers;
908 	}
909 
910 	/**
911 	 * Gets the set of executed methods.
912 	 */
913 	public HashMap<MethodIdentifier, HashSet<String>> getExecutedMethodIdentifiers() {
914 		return executedMethodIdentifiers;
915 	}
916 
917 
918 	public static class ParameterMutabilityInformation implements Serializable {
919 
920 		private static final long serialVersionUID = 3550107163836583142L;
921 
922 		private MethodIdentifier methodIdentifier;
923 
924 		private HashMap<String, HashSet<Integer>> runtimeClassNameToUnmodifiedParametersMap;
925 
926 		private HashSet<Integer> parameters;
927 
928 		public ParameterMutabilityInformation(MethodIdentifier methodIdentifier) {
929 			this.methodIdentifier = methodIdentifier;
930 			this.runtimeClassNameToUnmodifiedParametersMap = new  HashMap<String, HashSet<Integer>>();
931 			this.parameters = new HashSet<Integer>();
932 		}
933 
934 		public MethodIdentifier getMethodIdentifier() {
935 			return methodIdentifier;
936 		}
937 
938 		public HashSet<Integer> getModifiedParameters() {
939 			HashSet<Integer> unmodifiedParameters = getUnmodifiedParameters();
940 			HashSet<Integer> result = new HashSet<Integer>(parameters);
941 			result.removeAll(unmodifiedParameters);
942 			return result;
943 		}
944 		
945 		public HashSet<Integer> getUnmodifiedParameters() {
946 			HashSet<Integer> result = new HashSet<Integer>();
947 			for (int parameterId : parameters) {
948 				boolean addParameterToResult = true;
949 				for (String runtimeClass : runtimeClassNameToUnmodifiedParametersMap.keySet()) {
950 					if (!runtimeClassNameToUnmodifiedParametersMap.get(runtimeClass).contains(parameterId)) {
951 						addParameterToResult = false;
952 					}
953 				}
954 				if (addParameterToResult) {
955 					result.add(parameterId);
956 				}
957 			}
958 			return result;
959 		}
960 		
961 		public void updateUnmodifiedParameters(String runtimeClassName, HashSet<Integer> unmodifiedParameters) {
962 			HashSet<Integer> oldUnmodifiedParameters = runtimeClassNameToUnmodifiedParametersMap.get(runtimeClassName);
963 			if (oldUnmodifiedParameters == null) {
964 				runtimeClassNameToUnmodifiedParametersMap.put(runtimeClassName, (HashSet<Integer>) unmodifiedParameters.clone());
965 			} else {
966 				HashSet<Integer> parametersToDelete = new HashSet<Integer>();
967 				for (Integer unmodifiedParameter : oldUnmodifiedParameters) {
968 					if (!unmodifiedParameters.contains(unmodifiedParameter)) {
969 						parametersToDelete.add(unmodifiedParameter);
970 					}
971 				}	
972 				for (Integer parameter : parametersToDelete) {
973 					oldUnmodifiedParameters.remove(parameter);
974 				}
975 			}
976 
977 		}
978 
979 		public void addParameter(int parameterIndex) {
980 			parameters.add(parameterIndex);
981 		}
982 
983 		public HashSet<Integer> getParameters() {
984 			return parameters;
985 		}
986 
987 		public HashMap<String, HashSet<Integer>> getRuntimeClassNameToUnmodifiedParametersMap() {
988 			return runtimeClassNameToUnmodifiedParametersMap;
989 		}
990 
991 	}
992 
993 	public PurityResults getResults() {
994 		return results;
995 	}
996 	
997 	public HashMap<MethodIdentifier, ParameterMutabilityInformation> getParameterMutabilityInformation() {
998 		return parameterMutabilityInformation;
999 	}
1000 }