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
796
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 }