Fixing a few bugs in the statistics printout.
[jpf-core.git] / src / main / gov / nasa / jpf / listener / StateReducer.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0.
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.listener;
19
20 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.JPF;
22 import gov.nasa.jpf.ListenerAdapter;
23 import gov.nasa.jpf.search.Search;
24 import gov.nasa.jpf.jvm.bytecode.*;
25 import gov.nasa.jpf.vm.*;
26 import gov.nasa.jpf.vm.bytecode.ReadInstruction;
27 import gov.nasa.jpf.vm.bytecode.WriteInstruction;
28 import gov.nasa.jpf.vm.choice.IntChoiceFromSet;
29 import gov.nasa.jpf.vm.choice.IntIntervalGenerator;
30
31 import java.io.PrintWriter;
32 import java.util.*;
33
34 // TODO: Fix for Groovy's model-checking
35 // TODO: This is a setter to change the values of the ChoiceGenerator to implement POR
36 /**
37  * Simple tool to log state changes.
38  *
39  * This DPOR implementation is augmented by the algorithm presented in this SPIN paper:
40  * http://spinroot.com/spin/symposia/ws08/spin2008_submission_33.pdf
41  *
42  * The algorithm is presented on page 11 of the paper. Basically, we create a graph G
43  * (i.e., visible operation dependency graph)
44  * that maps inter-related threads/sub-programs that trigger state changes.
45  * The key to this approach is that we evaluate graph G in every iteration/recursion to
46  * only update the backtrack sets of the threads/sub-programs that are reachable in graph G
47  * from the currently running thread/sub-program.
48  */
49 public class StateReducer extends ListenerAdapter {
50
51   // Debug info fields
52   private boolean debugMode;
53   private boolean stateReductionMode;
54   private final PrintWriter out;
55   private String detail;
56   private int depth;
57   private int id;
58   private Transition transition;
59
60   // State reduction fields
61   private Integer[] choices;
62   private Integer[] refChoices;
63   private IntChoiceFromSet currCG;
64   private int choiceCounter;
65   private Integer choiceUpperBound;
66   private Integer maxUpperBound;
67   private boolean isInitialized;
68   private boolean isResetAfterAnalysis;
69   private boolean isBooleanCGFlipped;
70   private HashMap<IntChoiceFromSet, Integer> cgMap;
71   // Record the mapping between event number and field accesses (Read and Write)
72   private HashMap<Integer, ReadWriteSet> readWriteFieldsMap;
73   // The following is the backtrack map (set) that stores all the backtrack information
74   // e.g., event number 1 can have two backtrack sequences: {3,1,2,4,...} and {2,1,3,4,...}
75   private HashMap<Integer, LinkedList<Integer[]>> backtrackMap;
76   // Stores explored backtrack lists in the form of HashSet of Strings
77   private HashSet<String> backtrackSet;
78   private HashMap<Integer, HashSet<Integer>> conflictPairMap;
79
80   // Map that represents graph G
81   // (i.e., visible operation dependency graph (VOD Graph)
82   private HashMap<Integer, HashSet<Integer>> vodGraphMap;
83   // Set that represents hash table H
84   // (i.e., hash table that records encountered states)
85   // VOD graph is updated when the state has not yet been seen
86   // Current state
87   private HashSet<Integer> justVisitedStates;
88   // Previous choice number
89   private int prevChoiceValue;
90   // HashSet that stores references to unused CGs
91   private HashSet<IntChoiceFromSet> unusedCG;
92
93   //private HashMap<Integer, ConflictTracker.Node> stateGraph;
94   private HashMap<Integer, HashSet<Integer>> stateToEventMap;
95   // Map state to event
96   // Visited states in the previous and current executions/traces for terminating condition
97   private HashSet<Integer> prevVisitedStates;
98   private HashSet<Integer> currVisitedStates;
99
100   public StateReducer(Config config, JPF jpf) {
101     debugMode = config.getBoolean("debug_state_transition", false);
102     stateReductionMode = config.getBoolean("activate_state_reduction", true);
103     if (debugMode) {
104       out = new PrintWriter(System.out, true);
105     } else {
106       out = null;
107     }
108     detail = null;
109     depth = 0;
110     id = 0;
111     transition = null;
112     isBooleanCGFlipped = false;
113     vodGraphMap = new HashMap<>();
114     justVisitedStates = new HashSet<>();
115     prevChoiceValue = -1;
116     cgMap = new HashMap<>();
117     readWriteFieldsMap = new HashMap<>();
118     backtrackMap = new HashMap<>();
119     backtrackSet = new HashSet<>();
120     conflictPairMap = new HashMap<>();
121     unusedCG = new HashSet<>();
122     stateToEventMap = new HashMap<>();
123     prevVisitedStates = new HashSet<>();
124     currVisitedStates = new HashSet<>();
125     initializeStateReduction();
126   }
127
128   private void initializeStateReduction() {
129     if (stateReductionMode) {
130       choices = null;
131       refChoices = null;
132       currCG = null;
133       choiceCounter = 0;
134       choiceUpperBound = 0;
135       maxUpperBound = 0;
136       isInitialized = false;
137       isResetAfterAnalysis = false;
138       cgMap.clear();
139       resetReadWriteAnalysis();
140       backtrackMap.clear();
141       backtrackSet.clear();
142       stateToEventMap.clear();
143       prevVisitedStates.clear();
144       currVisitedStates.clear();
145     }
146   }
147
148   @Override
149   public void stateRestored(Search search) {
150     if (debugMode) {
151       id = search.getStateId();
152       depth = search.getDepth();
153       transition = search.getTransition();
154       detail = null;
155       out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
156               " and depth: " + depth + "\n");
157     }
158   }
159
160   //--- the ones we are interested in
161   @Override
162   public void searchStarted(Search search) {
163     if (debugMode) {
164       out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
165     }
166   }
167
168   private void resetReadWriteAnalysis() {
169     // Reset the following data structure when the choice counter reaches 0 again
170     conflictPairMap.clear();
171     readWriteFieldsMap.clear();
172   }
173
174   private IntChoiceFromSet setNewCG(IntChoiceFromSet icsCG) {
175     icsCG.setNewValues(choices);
176     icsCG.reset();
177     // Use a modulo since choiceCounter is going to keep increasing
178     int choiceIndex = choiceCounter % choices.length;
179     icsCG.advance(choices[choiceIndex]);
180     if (choiceIndex == 0) {
181       resetReadWriteAnalysis();
182     }
183     return icsCG;
184   }
185
186   private Integer[] copyChoices(Integer[] choicesToCopy) {
187
188     Integer[] copyOfChoices = new Integer[choicesToCopy.length];
189     System.arraycopy(choicesToCopy, 0, copyOfChoices, 0, choicesToCopy.length);
190     return copyOfChoices;
191   }
192
193   private void continueExecutingThisTrace(IntChoiceFromSet icsCG) {
194       // We repeat the same trace if a state match is not found yet
195       IntChoiceFromSet setCG = setNewCG(icsCG);
196       unusedCG.add(setCG);
197   }
198
199   private void initializeChoiceGenerators(IntChoiceFromSet icsCG, Integer[] cgChoices) {
200     if (choiceCounter <= choiceUpperBound && !cgMap.containsValue(choiceCounter)) {
201       // Update the choices of the first CG and add '-1'
202       if (choices == null) {
203         // Initialize backtrack set that stores all the explored backtrack lists
204         maxUpperBound = cgChoices.length;
205         // All the choices are always the same so we only need to update it once
206         // Get the choice array and final choice in the array
207         choices = cgChoices;
208         // Make a copy of choices as reference
209         refChoices = copyChoices(choices);
210         String firstChoiceListString = buildStringFromChoiceList(choices);
211         backtrackSet.add(firstChoiceListString);
212       }
213       IntChoiceFromSet setCG = setNewCG(icsCG);
214       cgMap.put(setCG, refChoices[choiceCounter]);
215     } else {
216       continueExecutingThisTrace(icsCG);
217     }
218   }
219
220   private void manageChoiceGeneratorsInSubsequentTraces(IntChoiceFromSet icsCG) {
221     // If this is the first iteration of the trace then set other CGs done
222     if (choiceCounter <= choiceUpperBound) {
223       icsCG.setDone();
224     } else {
225       // If this is the subsequent iterations of the trace then set up new CGs to continue the execution
226       continueExecutingThisTrace(icsCG);
227     }
228   }
229
230   @Override
231   public void choiceGeneratorRegistered(VM vm, ChoiceGenerator<?> nextCG, ThreadInfo currentThread, Instruction executedInstruction) {
232     if (stateReductionMode) {
233       // Initialize with necessary information from the CG
234       if (nextCG instanceof IntChoiceFromSet) {
235         IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG;
236         // Check if CG has been initialized, otherwise initialize it
237         Integer[] cgChoices = icsCG.getAllChoices();
238         if (!isInitialized) {
239           // Get the upper bound from the last element of the choices
240           choiceUpperBound = cgChoices[cgChoices.length - 1];
241           isInitialized = true;
242         }
243         // Record the subsequent Integer CGs only until we hit the upper bound
244         if (!isResetAfterAnalysis) {
245           initializeChoiceGenerators(icsCG, cgChoices);
246         } else {
247           // Set new CGs to done so that the search algorithm explores the existing CGs
248           //icsCG.setDone();
249           manageChoiceGeneratorsInSubsequentTraces(icsCG);
250         }
251       }
252     }
253   }
254
255   private void setDoneUnusedCG() {
256     // Set done every CG in the unused CG set
257     for (IntChoiceFromSet cg : unusedCG) {
258       cg.setDone();
259     }
260     unusedCG.clear();
261   }
262
263   private void resetAllCGs() {
264
265     isResetAfterAnalysis = true;
266     // Extract the event numbers that have backtrack lists
267     Set<Integer> eventSet = backtrackMap.keySet();
268     // Return if there is no conflict at all (highly unlikely)
269     if (eventSet.isEmpty()) {
270       // Set every CG to done!
271       for (IntChoiceFromSet cg : cgMap.keySet()) {
272         cg.setDone();
273       }
274       return;
275     }
276     // Reset every CG with the first backtrack lists
277     for (IntChoiceFromSet cg : cgMap.keySet()) {
278       int event = cgMap.get(cg);
279       LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
280       if (choiceLists != null && choiceLists.peekFirst() != null) {
281         Integer[] choiceList = choiceLists.removeFirst();
282         // Deploy the new choice list for this CG
283         cg.setNewValues(choiceList);
284         cg.reset();
285       } else {
286         cg.setDone();
287       }
288     }
289     setDoneUnusedCG();
290     saveVisitedStates();
291   }
292
293   // Detect cycles in the current execution/trace
294   // We terminate the execution iff:
295   // (1) the state has been visited in the current execution
296   // (2) the state has one or more cycles that involve all the events
297   // With simple approach we only need to check for a re-visited state.
298   // Basically, we have to check that we have executed all events between two occurrences of such state.
299   private boolean containsCyclesWithAllEvents(int stId) {
300
301     // False if the state ID hasn't been recorded
302     if (!stateToEventMap.containsKey(stId)) {
303       return false;
304     }
305     HashSet<Integer> visitedEvents = stateToEventMap.get(stId);
306     boolean containsCyclesWithAllEvts = false;
307     if (checkIfAllEventsInvolved(visitedEvents)) {
308       containsCyclesWithAllEvts = true;
309     }
310
311     return containsCyclesWithAllEvts;
312   }
313
314   private boolean checkIfAllEventsInvolved(HashSet<Integer> visitedEvents) {
315
316     // Check if this set contains all the event choices
317     // If not then this is not the terminating condition
318     for(int i=0; i<=choiceUpperBound; i++) {
319       if (!visitedEvents.contains(i)) {
320         return false;
321       }
322     }
323     return true;
324   }
325
326   private void saveVisitedStates() {
327     // CG is being reset
328     // Save all the visited states
329     prevVisitedStates.addAll(currVisitedStates);
330     currVisitedStates.clear();
331   }
332
333   private void updateChoicesForNewExecution(IntChoiceFromSet icsCG) {
334     if (choices == null || choices != icsCG.getAllChoices()) {
335       currCG = icsCG;
336       choices = icsCG.getAllChoices();
337       refChoices = copyChoices(choices);
338       // Reset a few things for the sub-graph
339       resetReadWriteAnalysis();
340       choiceCounter = 0;
341     }
342   }
343
344   private void checkAndEnforceFairScheduling(IntChoiceFromSet icsCG) {
345     // Check the next choice and if the value is not the same as the expected then force the expected value
346     int choiceIndex = choiceCounter % refChoices.length;
347     int nextChoice = icsCG.getNextChoice();
348     if (refChoices[choiceIndex] != nextChoice) {
349       int expectedChoice = refChoices[choiceIndex];
350       int currCGIndex = icsCG.getNextChoiceIndex();
351       if ((currCGIndex >= 0) && (currCGIndex < refChoices.length)) {
352         icsCG.setChoice(currCGIndex, expectedChoice);
353       }
354     }
355   }
356
357   private void mapStateToEvent(int nextChoiceValue) {
358     // Update all states with this event/choice
359     // This means that all past states now see this transition
360     Set<Integer> stateSet = stateToEventMap.keySet();
361     for(Integer stateId : stateSet) {
362       HashSet<Integer> eventSet = stateToEventMap.get(stateId);
363       eventSet.add(nextChoiceValue);
364     }
365   }
366
367   private void updateVODGraph(int currChoiceValue) {
368     // Update the graph when we have the current choice value
369     HashSet<Integer> choiceSet;
370     if (vodGraphMap.containsKey(prevChoiceValue)) {
371       // If the key already exists, just retrieve it
372       choiceSet = vodGraphMap.get(prevChoiceValue);
373     } else {
374       // Create a new entry
375       choiceSet = new HashSet<>();
376       vodGraphMap.put(prevChoiceValue, choiceSet);
377     }
378     choiceSet.add(currChoiceValue);
379     prevChoiceValue = currChoiceValue;
380   }
381
382   private boolean terminateCurrentExecution() {
383     // We need to check all the states that have just been visited
384     // Often a transition (choice/event) can result into forwarding/backtracking to a number of states
385     for(Integer stateId : justVisitedStates) {
386       if (prevVisitedStates.contains(stateId) || containsCyclesWithAllEvents(stateId)) {
387         return true;
388       }
389     }
390     return false;
391   }
392
393   private void exploreNextBacktrackSets(IntChoiceFromSet icsCG) {
394     // Traverse the sub-graphs
395     if (isResetAfterAnalysis) {
396       // Do this for every CG after finishing each backtrack list
397       // We try to update the CG with a backtrack list if the state has been visited multiple times
398       if (icsCG.getNextChoiceIndex() > 0 && cgMap.containsKey(icsCG)) {
399         int event = cgMap.get(icsCG);
400         LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
401         if (choiceLists != null && choiceLists.peekFirst() != null) {
402           Integer[] choiceList = choiceLists.removeFirst();
403           // Deploy the new choice list for this CG
404           icsCG.setNewValues(choiceList);
405           icsCG.reset();
406         } else {
407           // Set done if this was the last backtrack list
408           icsCG.setDone();
409         }
410         setDoneUnusedCG();
411         saveVisitedStates();
412       }
413     } else {
414       // Update and reset the CG if needed (do this for the first time after the analysis)
415       // Start backtracking if this is a visited state and it is not a repeating state
416       resetAllCGs();
417     }
418   }
419
420   @Override
421   public void choiceGeneratorAdvanced(VM vm, ChoiceGenerator<?> currentCG) {
422
423     if (stateReductionMode) {
424       // Check the boolean CG and if it is flipped, we are resetting the analysis
425       if (currentCG instanceof BooleanChoiceGenerator) {
426         if (!isBooleanCGFlipped) {
427           isBooleanCGFlipped = true;
428         } else {
429           initializeStateReduction();
430         }
431       }
432       // Check every choice generated and make sure that all the available choices
433       // are chosen first before repeating the same choice of value twice!
434       if (currentCG instanceof IntChoiceFromSet) {
435         IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG;
436         // Update the current pointer to the current set of choices
437         updateChoicesForNewExecution(icsCG);
438         // If we don't see a fair scheduling of events/choices then we have to enforce it
439         checkAndEnforceFairScheduling(icsCG);
440         // Map state to event
441         mapStateToEvent(icsCG.getNextChoice());
442         // Update the VOD graph always with the latest
443         updateVODGraph(icsCG.getNextChoice());
444         // Check if we have seen this state or this state contains cycles that involve all events
445         if (terminateCurrentExecution()) {
446           exploreNextBacktrackSets(icsCG);
447         }
448         justVisitedStates.clear();
449         choiceCounter++;
450       }
451     }
452   }
453
454   private void checkAndRecordNewState(int stateId) {
455     // Insert state ID into the map if it is new
456     if (!stateToEventMap.containsKey(stateId)) {
457       HashSet<Integer> eventSet = new HashSet<>();
458       stateToEventMap.put(stateId, eventSet);
459     }
460   }
461
462   private void updateStateInfo(Search search) {
463     if (stateReductionMode) {
464       // Update the state variables
465       // Line 19 in the paper page 11 (see the heading note above)
466       int stateId = search.getStateId();
467       currVisitedStates.add(stateId);
468       checkAndRecordNewState(stateId);
469       justVisitedStates.add(stateId);
470     }
471   }
472
473   @Override
474   public void stateAdvanced(Search search) {
475     if (debugMode) {
476       id = search.getStateId();
477       depth = search.getDepth();
478       transition = search.getTransition();
479       if (search.isNewState()) {
480         detail = "new";
481       } else {
482         detail = "visited";
483       }
484
485       if (search.isEndState()) {
486         out.println("\n==> DEBUG: This is the last state!\n");
487         detail += " end";
488       }
489       out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
490               " which is " + detail + " Transition: " + transition + "\n");
491     }
492     updateStateInfo(search);
493   }
494
495   @Override
496   public void stateBacktracked(Search search) {
497     if (debugMode) {
498       id = search.getStateId();
499       depth = search.getDepth();
500       transition = search.getTransition();
501       detail = null;
502
503       out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
504               " and depth: " + depth + "\n");
505     }
506     updateStateInfo(search);
507   }
508
509   @Override
510   public void searchFinished(Search search) {
511     if (debugMode) {
512       out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
513     }
514   }
515
516   // This class compactly stores Read and Write field sets
517   // We store the field name and its object ID
518   // Sharing the same field means the same field name and object ID
519   private class ReadWriteSet {
520     private HashMap<String, Integer> readSet;
521     private HashMap<String, Integer> writeSet;
522
523     public ReadWriteSet() {
524       readSet = new HashMap<>();
525       writeSet = new HashMap<>();
526     }
527
528     public void addReadField(String field, int objectId) {
529       readSet.put(field, objectId);
530     }
531
532     public void addWriteField(String field, int objectId) {
533       writeSet.put(field, objectId);
534     }
535
536     public boolean readFieldExists(String field) {
537       return readSet.containsKey(field);
538     }
539
540     public boolean writeFieldExists(String field) {
541       return writeSet.containsKey(field);
542     }
543
544     public int readFieldObjectId(String field) {
545       return readSet.get(field);
546     }
547
548     public int writeFieldObjectId(String field) {
549       return writeSet.get(field);
550     }
551   }
552
553   private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
554     // Do the analysis to get Read and Write accesses to fields
555     ReadWriteSet rwSet = getReadWriteSet(currentChoice);
556     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
557     // Record the field in the map
558     if (executedInsn instanceof WriteInstruction) {
559       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
560       for (String str : EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
561         if (fieldClass.startsWith(str)) {
562           return;
563         }
564       }
565       rwSet.addWriteField(fieldClass, objectId);
566     } else if (executedInsn instanceof ReadInstruction) {
567       rwSet.addReadField(fieldClass, objectId);
568     }
569   }
570
571   private boolean recordConflictPair(int currentEvent, int eventNumber) {
572     HashSet<Integer> conflictSet;
573     if (!conflictPairMap.containsKey(currentEvent)) {
574       conflictSet = new HashSet<>();
575       conflictPairMap.put(currentEvent, conflictSet);
576     } else {
577       conflictSet = conflictPairMap.get(currentEvent);
578     }
579     // If this conflict has been recorded before, we return false because
580     // we don't want to service this backtrack point twice
581     if (conflictSet.contains(eventNumber)) {
582       return false;
583     }
584     // If it hasn't been recorded, then do otherwise
585     conflictSet.add(eventNumber);
586     return true;
587   }
588
589   private String buildStringFromChoiceList(Integer[] newChoiceList) {
590
591     // When we see a choice list shorter than the upper bound, e.g., [3,2] for choices 0,1,2, and 3,
592     //  then we have to pad the beginning before we store it, because [3,2] actually means [0,1,3,2]
593     int actualListLength = newChoiceList.length;
594     int diff = maxUpperBound - actualListLength;
595     StringBuilder sb = new StringBuilder();
596     // Pad the beginning if necessary
597     for (int i = 0; i < diff; i++) {
598       sb.append(i);
599     }
600     // Then continue with the actual choice list
601     // We don't include the '-1' at the end
602     for (int i = 0; i < newChoiceList.length; i++) {
603       sb.append(newChoiceList[i]);
604     }
605     return sb.toString();
606   }
607
608   private void checkAndAddBacktrackList(LinkedList<Integer[]> backtrackChoiceLists, Integer[] newChoiceList) {
609
610     String newChoiceListString = buildStringFromChoiceList(newChoiceList);
611     // Add only if we haven't seen this combination before
612     if (!backtrackSet.contains(newChoiceListString)) {
613       backtrackSet.add(newChoiceListString);
614       backtrackChoiceLists.addLast(newChoiceList);
615     }
616   }
617
618   private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
619
620     LinkedList<Integer[]> backtrackChoiceLists;
621     // Create a new list of choices for backtrack based on the current choice and conflicting event number
622     // If we have a conflict between 1 and 3, then we create the list {3, 1, 2, 4, 5} for backtrack
623     // The backtrack point is the CG for event number 1 and the list length is one less than the original list
624     // (originally of length 6) since we don't start from event number 0
625     if (!isResetAfterAnalysis) {
626       // Check if we have a list for this choice number
627       // If not we create a new one for it
628       if (!backtrackMap.containsKey(conflictEventNumber)) {
629         backtrackChoiceLists = new LinkedList<>();
630         backtrackMap.put(conflictEventNumber, backtrackChoiceLists);
631       } else {
632         backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
633       }
634       int maxListLength = choiceUpperBound + 1;
635       int listLength = maxListLength - conflictEventNumber;
636       Integer[] newChoiceList = new Integer[listLength];
637       // Put the conflicting event numbers first and reverse the order
638       newChoiceList[0] = refChoices[currentChoice];
639       newChoiceList[1] = refChoices[conflictEventNumber];
640       // Put the rest of the event numbers into the array starting from the minimum to the upper bound
641       for (int i = conflictEventNumber + 1, j = 2; j < listLength; i++) {
642         if (refChoices[i] != refChoices[currentChoice]) {
643           newChoiceList[j] = refChoices[i];
644           j++;
645         }
646       }
647       checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
648       // The start index for the recursion is always 1 (from the main branch)
649     } else { // This is a sub-graph
650       // There is a case/bug that after a re-initialization, currCG is not yet initialized
651       if (currCG != null && cgMap.containsKey(currCG)) {
652         int backtrackListIndex = cgMap.get(currCG);
653         backtrackChoiceLists = backtrackMap.get(backtrackListIndex);
654         int listLength = refChoices.length;
655         Integer[] newChoiceList = new Integer[listLength];
656         // Copy everything before the conflict number
657         for (int i = 0; i < conflictEventNumber; i++) {
658           newChoiceList[i] = refChoices[i];
659         }
660         // Put the conflicting events
661         newChoiceList[conflictEventNumber] = refChoices[currentChoice];
662         newChoiceList[conflictEventNumber + 1] = refChoices[conflictEventNumber];
663         // Copy the rest
664         for (int i = conflictEventNumber + 1, j = conflictEventNumber + 2; j < listLength - 1; i++) {
665           if (refChoices[i] != refChoices[currentChoice]) {
666             newChoiceList[j] = refChoices[i];
667             j++;
668           }
669         }
670         checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
671       }
672     }
673   }
674
675   // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure
676   private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST =
677           // Java and Groovy libraries
678           { "java", "org", "sun", "com", "gov", "groovy"};
679   private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST =
680           // Groovy library created fields
681           {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__",
682           // Infrastructure
683           "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList",
684           "eventList", "valueList", "settings", "printToConsole", "app1", "app2"};
685   private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"};
686   private final static String[] EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"};
687
688   private boolean excludeThisForItStartsWith(String[] excludedStrings, String className) {
689     for (String excludedField : excludedStrings) {
690       if (className.startsWith(excludedField)) {
691         return true;
692       }
693     }
694     return false;
695   }
696
697   private boolean excludeThisForItEndsWith(String[] excludedStrings, String className) {
698     for (String excludedField : excludedStrings) {
699       if (className.endsWith(excludedField)) {
700         return true;
701       }
702     }
703     return false;
704   }
705
706   private boolean excludeThisForItContains(String[] excludedStrings, String className) {
707     for (String excludedField : excludedStrings) {
708       if (className.contains(excludedField)) {
709         return true;
710       }
711     }
712     return false;
713   }
714
715   private boolean isFieldExcluded(String field) {
716     // Check against "starts-with", "ends-with", and "contains" list
717     if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, field) ||
718         excludeThisForItEndsWith(EXCLUDED_FIELDS_ENDS_WITH_LIST, field) ||
719         excludeThisForItContains(EXCLUDED_FIELDS_CONTAINS_LIST, field)) {
720       return true;
721     }
722
723     return false;
724   }
725
726   // This method checks whether a choice is reachable in the VOD graph from a reference choice
727   // This is a BFS search
728   private boolean isReachableInVODGraph(int checkedChoice, int referenceChoice) {
729     // Record visited choices as we search in the graph
730     HashSet<Integer> visitedChoice = new HashSet<>();
731     visitedChoice.add(referenceChoice);
732     LinkedList<Integer> nodesToVisit = new LinkedList<>();
733     // If the state doesn't advance as the threads/sub-programs are executed (basically there is no new state),
734     // there is a chance that the graph doesn't have new nodes---thus this check will return a null.
735     if (vodGraphMap.containsKey(referenceChoice)) {
736       nodesToVisit.addAll(vodGraphMap.get(referenceChoice));
737       while(!nodesToVisit.isEmpty()) {
738         int currChoice = nodesToVisit.getFirst();
739         if (currChoice == checkedChoice) {
740           return true;
741         }
742         if (visitedChoice.contains(currChoice)) {
743           // If there is a loop then we don't find it
744           return false;
745         }
746         // Continue searching
747         visitedChoice.add(currChoice);
748         HashSet<Integer> currChoiceNextNodes = vodGraphMap.get(currChoice);
749         if (currChoiceNextNodes != null) {
750           // Add only if there is a mapping for next nodes
751           for (Integer nextNode : currChoiceNextNodes) {
752             // Skip cycles
753             if (nextNode == currChoice) {
754               continue;
755             }
756             nodesToVisit.addLast(nextNode);
757           }
758         }
759       }
760     }
761     return false;
762   }
763
764   private int getCurrentChoice(VM vm) {
765     ChoiceGenerator<?> currentCG = vm.getChoiceGenerator();
766     // This is the main event CG
767     if (currentCG instanceof IntChoiceFromSet) {
768       return ((IntChoiceFromSet) currentCG).getNextChoiceIndex();
769     } else {
770       // This is the interval CG used in device handlers
771       ChoiceGenerator<?> parentCG = ((IntIntervalGenerator) currentCG).getPreviousChoiceGenerator();
772       return ((IntChoiceFromSet) parentCG).getNextChoiceIndex();
773     }
774   }
775
776   private final static String GROOVY_CALLSITE_LIB = "org.codehaus.groovy.runtime.callsite";
777   private final static String JAVA_STRING_LIB = "java.lang.String";
778   private final static String JAVA_INTEGER = "int";
779   private final static String DO_CALL_METHOD = "doCall";
780   private final static String GET_PROPERTY_METHOD =
781           "invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.callGetProperty";
782
783   private ReadWriteSet getReadWriteSet(int currentChoice) {
784     // Do the analysis to get Read and Write accesses to fields
785     ReadWriteSet rwSet;
786     // We already have an entry
787     if (readWriteFieldsMap.containsKey(refChoices[currentChoice])) {
788       rwSet = readWriteFieldsMap.get(refChoices[currentChoice]);
789     } else { // We need to create a new entry
790       rwSet = new ReadWriteSet();
791       readWriteFieldsMap.put(refChoices[currentChoice], rwSet);
792     }
793     return rwSet;
794   }
795
796   private void analyzeReadWriteAccesses(Instruction instruction, ThreadInfo ti, int currentChoice) {
797     // Get method name
798     INVOKEINTERFACE insn = (INVOKEINTERFACE) instruction;
799     if (insn.toString().startsWith(GET_PROPERTY_METHOD) &&
800             insn.getMethodInfo().getName().equals(DO_CALL_METHOD)) {
801       // Extract info from the stack frame
802       StackFrame frame = ti.getTopFrame();
803       int[] frameSlots = frame.getSlots();
804       // Get the Groovy callsite library at index 0
805       ElementInfo eiCallsite = VM.getVM().getHeap().get(frameSlots[0]);
806       if (!eiCallsite.getClassInfo().getName().startsWith(GROOVY_CALLSITE_LIB)) {
807         return;
808       }
809       // Get the iterated object whose property is accessed
810       ElementInfo eiAccessObj = VM.getVM().getHeap().get(frameSlots[1]);
811       // We exclude library classes (they start with java, org, etc.) and some more
812       String objClassName = eiAccessObj.getClassInfo().getName();
813       if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, objClassName) ||
814           excludeThisForItStartsWith(EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST, objClassName)) {
815         return;
816       }
817       // Extract fields from this object and put them into the read write
818       int numOfFields = eiAccessObj.getNumberOfFields();
819       for(int i=0; i<numOfFields; i++) {
820         FieldInfo fieldInfo = eiAccessObj.getFieldInfo(i);
821         if (fieldInfo.getType().equals(JAVA_STRING_LIB) || fieldInfo.getType().equals(JAVA_INTEGER)) {
822           String fieldClass = fieldInfo.getFullName();
823           ReadWriteSet rwSet = getReadWriteSet(currentChoice);
824           int objectId = fieldInfo.getClassInfo().getClassObjectRef();
825           // Record the field in the map
826           rwSet.addReadField(fieldClass, objectId);
827         }
828       }
829
830     }
831   }
832
833   @Override
834   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
835     if (stateReductionMode) {
836       // Has to be initialized and a integer CG
837       ChoiceGenerator<?> cg = vm.getChoiceGenerator();
838       if (isInitialized && (cg instanceof IntChoiceFromSet || cg instanceof IntIntervalGenerator)) {
839         int currentChoice = getCurrentChoice(vm);
840         if (currentChoice < 0) { // If choice is -1 then skip
841           return;
842         }
843         // Record accesses from executed instructions
844         if (executedInsn instanceof JVMFieldInstruction) {
845           // Analyze only after being initialized
846           String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName();
847           // We don't care about libraries
848           if (!isFieldExcluded(fieldClass)) {
849             analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice);
850           }
851         } else if (executedInsn instanceof INVOKEINTERFACE) {
852           // Handle the read/write accesses that occur through iterators
853           analyzeReadWriteAccesses(executedInsn, ti, currentChoice);
854         }
855         // Analyze conflicts from next instructions
856         if (nextInsn instanceof JVMFieldInstruction) {
857           // The constructor is only called once when the object is initialized
858           // It does not have shared access with other objects
859           MethodInfo mi = nextInsn.getMethodInfo();
860           if (!mi.getName().equals("<init>")) {
861             String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName();
862             // We don't care about libraries
863             if (!isFieldExcluded(fieldClass)) {
864               // Check for conflict (go backward from currentChoice and get the first conflict)
865               // If the current event has conflicts with multiple events, then these will be detected
866               // one by one as this recursively checks backward when backtrack set is revisited and executed.
867               for (int eventNumber = currentChoice - 1; eventNumber >= 0; eventNumber--) {
868                 // Skip if this event number does not have any Read/Write set
869                 if (!readWriteFieldsMap.containsKey(refChoices[eventNumber])) {
870                   continue;
871                 }
872                 ReadWriteSet rwSet = readWriteFieldsMap.get(refChoices[eventNumber]);
873                 int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef();
874                 // 1) Check for conflicts with Write fields for both Read and Write instructions
875                 if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) &&
876                         rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) ||
877                         (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) &&
878                                 rwSet.readFieldObjectId(fieldClass) == currObjId)) {
879                   // We do not record and service the same backtrack pair/point twice!
880                   // If it has been serviced before, we just skip this
881                   if (recordConflictPair(currentChoice, eventNumber)) {
882                     // Lines 4-8 of the algorithm in the paper page 11 (see the heading note above)
883                     if (vm.isNewState() || isReachableInVODGraph(refChoices[currentChoice], refChoices[currentChoice-1])) {
884                       createBacktrackChoiceList(currentChoice, eventNumber);
885                       // Break if a conflict is found!
886                       break;
887                     }
888                   }
889                 }
890               }
891             }
892           }
893         }
894       }
895     }
896   }
897 }