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