Starting a new DPOR implementation.
[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       if (vm.getNextChoiceGenerator() instanceof BooleanChoiceGenerator) {
425         System.out.println("Next CG is a booleanCG");
426       }
427       // Check the boolean CG and if it is flipped, we are resetting the analysis
428       if (currentCG instanceof BooleanChoiceGenerator) {
429         if (!isBooleanCGFlipped) {
430           isBooleanCGFlipped = true;
431         } else {
432           initializeStateReduction();
433         }
434       }
435       // Check every choice generated and make sure that all the available choices
436       // are chosen first before repeating the same choice of value twice!
437       if (currentCG instanceof IntChoiceFromSet) {
438         IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG;
439         // Update the current pointer to the current set of choices
440         updateChoicesForNewExecution(icsCG);
441         // If we don't see a fair scheduling of events/choices then we have to enforce it
442         checkAndEnforceFairScheduling(icsCG);
443         // Map state to event
444         mapStateToEvent(icsCG.getNextChoice());
445         // Update the VOD graph always with the latest
446         updateVODGraph(icsCG.getNextChoice());
447         // Check if we have seen this state or this state contains cycles that involve all events
448         if (terminateCurrentExecution()) {
449           exploreNextBacktrackSets(icsCG);
450         }
451         justVisitedStates.clear();
452         choiceCounter++;
453       }
454     }
455   }
456
457   private void checkAndRecordNewState(int stateId) {
458     // Insert state ID into the map if it is new
459     if (!stateToEventMap.containsKey(stateId)) {
460       HashSet<Integer> eventSet = new HashSet<>();
461       stateToEventMap.put(stateId, eventSet);
462     }
463   }
464
465   private void updateStateInfo(Search search) {
466     if (stateReductionMode) {
467       // Update the state variables
468       // Line 19 in the paper page 11 (see the heading note above)
469       int stateId = search.getStateId();
470       currVisitedStates.add(stateId);
471       checkAndRecordNewState(stateId);
472       justVisitedStates.add(stateId);
473     }
474   }
475
476   @Override
477   public void stateAdvanced(Search search) {
478     if (debugMode) {
479       id = search.getStateId();
480       depth = search.getDepth();
481       transition = search.getTransition();
482       if (search.isNewState()) {
483         detail = "new";
484       } else {
485         detail = "visited";
486       }
487
488       if (search.isEndState()) {
489         out.println("\n==> DEBUG: This is the last state!\n");
490         detail += " end";
491       }
492       out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
493               " which is " + detail + " Transition: " + transition + "\n");
494     }
495     updateStateInfo(search);
496   }
497
498   @Override
499   public void stateBacktracked(Search search) {
500     if (debugMode) {
501       id = search.getStateId();
502       depth = search.getDepth();
503       transition = search.getTransition();
504       detail = null;
505
506       out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
507               " and depth: " + depth + "\n");
508     }
509     updateStateInfo(search);
510   }
511
512   @Override
513   public void searchFinished(Search search) {
514     if (debugMode) {
515       out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
516     }
517   }
518
519   // This class compactly stores Read and Write field sets
520   // We store the field name and its object ID
521   // Sharing the same field means the same field name and object ID
522   private class ReadWriteSet {
523     private HashMap<String, Integer> readSet;
524     private HashMap<String, Integer> writeSet;
525
526     public ReadWriteSet() {
527       readSet = new HashMap<>();
528       writeSet = new HashMap<>();
529     }
530
531     public void addReadField(String field, int objectId) {
532       readSet.put(field, objectId);
533     }
534
535     public void addWriteField(String field, int objectId) {
536       writeSet.put(field, objectId);
537     }
538
539     public boolean readFieldExists(String field) {
540       return readSet.containsKey(field);
541     }
542
543     public boolean writeFieldExists(String field) {
544       return writeSet.containsKey(field);
545     }
546
547     public int readFieldObjectId(String field) {
548       return readSet.get(field);
549     }
550
551     public int writeFieldObjectId(String field) {
552       return writeSet.get(field);
553     }
554   }
555
556   private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
557     // Do the analysis to get Read and Write accesses to fields
558     ReadWriteSet rwSet = getReadWriteSet(currentChoice);
559     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
560     // Record the field in the map
561     if (executedInsn instanceof WriteInstruction) {
562       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
563       for (String str : EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
564         if (fieldClass.startsWith(str)) {
565           return;
566         }
567       }
568       rwSet.addWriteField(fieldClass, objectId);
569     } else if (executedInsn instanceof ReadInstruction) {
570       rwSet.addReadField(fieldClass, objectId);
571     }
572   }
573
574   private boolean recordConflictPair(int currentEvent, int eventNumber) {
575     HashSet<Integer> conflictSet;
576     if (!conflictPairMap.containsKey(currentEvent)) {
577       conflictSet = new HashSet<>();
578       conflictPairMap.put(currentEvent, conflictSet);
579     } else {
580       conflictSet = conflictPairMap.get(currentEvent);
581     }
582     // If this conflict has been recorded before, we return false because
583     // we don't want to service this backtrack point twice
584     if (conflictSet.contains(eventNumber)) {
585       return false;
586     }
587     // If it hasn't been recorded, then do otherwise
588     conflictSet.add(eventNumber);
589     return true;
590   }
591
592   private String buildStringFromChoiceList(Integer[] newChoiceList) {
593
594     // When we see a choice list shorter than the upper bound, e.g., [3,2] for choices 0,1,2, and 3,
595     //  then we have to pad the beginning before we store it, because [3,2] actually means [0,1,3,2]
596     int actualListLength = newChoiceList.length;
597     int diff = maxUpperBound - actualListLength;
598     StringBuilder sb = new StringBuilder();
599     // Pad the beginning if necessary
600     for (int i = 0; i < diff; i++) {
601       sb.append(i);
602     }
603     // Then continue with the actual choice list
604     // We don't include the '-1' at the end
605     for (int i = 0; i < newChoiceList.length; i++) {
606       sb.append(newChoiceList[i]);
607     }
608     return sb.toString();
609   }
610
611   private void checkAndAddBacktrackList(LinkedList<Integer[]> backtrackChoiceLists, Integer[] newChoiceList) {
612
613     String newChoiceListString = buildStringFromChoiceList(newChoiceList);
614     // Add only if we haven't seen this combination before
615     if (!backtrackSet.contains(newChoiceListString)) {
616       backtrackSet.add(newChoiceListString);
617       backtrackChoiceLists.addLast(newChoiceList);
618     }
619   }
620
621   private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
622
623     LinkedList<Integer[]> backtrackChoiceLists;
624     // Create a new list of choices for backtrack based on the current choice and conflicting event number
625     // If we have a conflict between 1 and 3, then we create the list {3, 1, 2, 4, 5} for backtrack
626     // The backtrack point is the CG for event number 1 and the list length is one less than the original list
627     // (originally of length 6) since we don't start from event number 0
628     if (!isResetAfterAnalysis) {
629       // Check if we have a list for this choice number
630       // If not we create a new one for it
631       if (!backtrackMap.containsKey(conflictEventNumber)) {
632         backtrackChoiceLists = new LinkedList<>();
633         backtrackMap.put(conflictEventNumber, backtrackChoiceLists);
634       } else {
635         backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
636       }
637       int maxListLength = choiceUpperBound + 1;
638       int listLength = maxListLength - conflictEventNumber;
639       Integer[] newChoiceList = new Integer[listLength];
640       // Put the conflicting event numbers first and reverse the order
641       newChoiceList[0] = refChoices[currentChoice];
642       newChoiceList[1] = refChoices[conflictEventNumber];
643       // Put the rest of the event numbers into the array starting from the minimum to the upper bound
644       for (int i = conflictEventNumber + 1, j = 2; j < listLength; i++) {
645         if (refChoices[i] != refChoices[currentChoice]) {
646           newChoiceList[j] = refChoices[i];
647           j++;
648         }
649       }
650       checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
651       // The start index for the recursion is always 1 (from the main branch)
652     } else { // This is a sub-graph
653       // There is a case/bug that after a re-initialization, currCG is not yet initialized
654       if (currCG != null && cgMap.containsKey(currCG)) {
655         int backtrackListIndex = cgMap.get(currCG);
656         backtrackChoiceLists = backtrackMap.get(backtrackListIndex);
657         int listLength = refChoices.length;
658         Integer[] newChoiceList = new Integer[listLength];
659         // Copy everything before the conflict number
660         for (int i = 0; i < conflictEventNumber; i++) {
661           newChoiceList[i] = refChoices[i];
662         }
663         // Put the conflicting events
664         newChoiceList[conflictEventNumber] = refChoices[currentChoice];
665         newChoiceList[conflictEventNumber + 1] = refChoices[conflictEventNumber];
666         // Copy the rest
667         for (int i = conflictEventNumber + 1, j = conflictEventNumber + 2; j < listLength - 1; i++) {
668           if (refChoices[i] != refChoices[currentChoice]) {
669             newChoiceList[j] = refChoices[i];
670             j++;
671           }
672         }
673         checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
674       }
675     }
676   }
677
678   // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure
679   private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST =
680           // Java and Groovy libraries
681           { "java", "org", "sun", "com", "gov", "groovy"};
682   private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST =
683           // Groovy library created fields
684           {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__",
685           // Infrastructure
686           "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList",
687           "eventList", "valueList", "settings", "printToConsole", "app1", "app2"};
688   private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"};
689   private final static String[] EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"};
690
691   private boolean excludeThisForItStartsWith(String[] excludedStrings, String className) {
692     for (String excludedField : excludedStrings) {
693       if (className.startsWith(excludedField)) {
694         return true;
695       }
696     }
697     return false;
698   }
699
700   private boolean excludeThisForItEndsWith(String[] excludedStrings, String className) {
701     for (String excludedField : excludedStrings) {
702       if (className.endsWith(excludedField)) {
703         return true;
704       }
705     }
706     return false;
707   }
708
709   private boolean excludeThisForItContains(String[] excludedStrings, String className) {
710     for (String excludedField : excludedStrings) {
711       if (className.contains(excludedField)) {
712         return true;
713       }
714     }
715     return false;
716   }
717
718   private boolean isFieldExcluded(String field) {
719     // Check against "starts-with", "ends-with", and "contains" list
720     if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, field) ||
721         excludeThisForItEndsWith(EXCLUDED_FIELDS_ENDS_WITH_LIST, field) ||
722         excludeThisForItContains(EXCLUDED_FIELDS_CONTAINS_LIST, field)) {
723       return true;
724     }
725
726     return false;
727   }
728
729   // This method checks whether a choice is reachable in the VOD graph from a reference choice
730   // This is a BFS search
731   private boolean isReachableInVODGraph(int checkedChoice, int referenceChoice) {
732     // Record visited choices as we search in the graph
733     HashSet<Integer> visitedChoice = new HashSet<>();
734     visitedChoice.add(referenceChoice);
735     LinkedList<Integer> nodesToVisit = new LinkedList<>();
736     // If the state doesn't advance as the threads/sub-programs are executed (basically there is no new state),
737     // there is a chance that the graph doesn't have new nodes---thus this check will return a null.
738     if (vodGraphMap.containsKey(referenceChoice)) {
739       nodesToVisit.addAll(vodGraphMap.get(referenceChoice));
740       while(!nodesToVisit.isEmpty()) {
741         int currChoice = nodesToVisit.getFirst();
742         if (currChoice == checkedChoice) {
743           return true;
744         }
745         if (visitedChoice.contains(currChoice)) {
746           // If there is a loop then we don't find it
747           return false;
748         }
749         // Continue searching
750         visitedChoice.add(currChoice);
751         HashSet<Integer> currChoiceNextNodes = vodGraphMap.get(currChoice);
752         if (currChoiceNextNodes != null) {
753           // Add only if there is a mapping for next nodes
754           for (Integer nextNode : currChoiceNextNodes) {
755             // Skip cycles
756             if (nextNode == currChoice) {
757               continue;
758             }
759             nodesToVisit.addLast(nextNode);
760           }
761         }
762       }
763     }
764     return false;
765   }
766
767   private int getCurrentChoice(VM vm) {
768     ChoiceGenerator<?> currentCG = vm.getChoiceGenerator();
769     // This is the main event CG
770     if (currentCG instanceof IntChoiceFromSet) {
771       return ((IntChoiceFromSet) currentCG).getNextChoiceIndex();
772     } else {
773       // This is the interval CG used in device handlers
774       ChoiceGenerator<?> parentCG = ((IntIntervalGenerator) currentCG).getPreviousChoiceGenerator();
775       return ((IntChoiceFromSet) parentCG).getNextChoiceIndex();
776     }
777   }
778
779   private final static String GROOVY_CALLSITE_LIB = "org.codehaus.groovy.runtime.callsite";
780   private final static String JAVA_STRING_LIB = "java.lang.String";
781   private final static String JAVA_INTEGER = "int";
782   private final static String DO_CALL_METHOD = "doCall";
783   private final static String GET_PROPERTY_METHOD =
784           "invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.callGetProperty";
785
786   private ReadWriteSet getReadWriteSet(int currentChoice) {
787     // Do the analysis to get Read and Write accesses to fields
788     ReadWriteSet rwSet;
789     // We already have an entry
790     if (readWriteFieldsMap.containsKey(refChoices[currentChoice])) {
791       rwSet = readWriteFieldsMap.get(refChoices[currentChoice]);
792     } else { // We need to create a new entry
793       rwSet = new ReadWriteSet();
794       readWriteFieldsMap.put(refChoices[currentChoice], rwSet);
795     }
796     return rwSet;
797   }
798
799   private void analyzeReadWriteAccesses(Instruction instruction, ThreadInfo ti, int currentChoice) {
800     // Get method name
801     INVOKEINTERFACE insn = (INVOKEINTERFACE) instruction;
802     if (insn.toString().startsWith(GET_PROPERTY_METHOD) &&
803             insn.getMethodInfo().getName().equals(DO_CALL_METHOD)) {
804       // Extract info from the stack frame
805       StackFrame frame = ti.getTopFrame();
806       int[] frameSlots = frame.getSlots();
807       // Get the Groovy callsite library at index 0
808       ElementInfo eiCallsite = VM.getVM().getHeap().get(frameSlots[0]);
809       if (!eiCallsite.getClassInfo().getName().startsWith(GROOVY_CALLSITE_LIB)) {
810         return;
811       }
812       // Get the iterated object whose property is accessed
813       ElementInfo eiAccessObj = VM.getVM().getHeap().get(frameSlots[1]);
814       // We exclude library classes (they start with java, org, etc.) and some more
815       String objClassName = eiAccessObj.getClassInfo().getName();
816       if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, objClassName) ||
817           excludeThisForItStartsWith(EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST, objClassName)) {
818         return;
819       }
820       // Extract fields from this object and put them into the read write
821       int numOfFields = eiAccessObj.getNumberOfFields();
822       for(int i=0; i<numOfFields; i++) {
823         FieldInfo fieldInfo = eiAccessObj.getFieldInfo(i);
824         if (fieldInfo.getType().equals(JAVA_STRING_LIB) || fieldInfo.getType().equals(JAVA_INTEGER)) {
825           String fieldClass = fieldInfo.getFullName();
826           ReadWriteSet rwSet = getReadWriteSet(currentChoice);
827           int objectId = fieldInfo.getClassInfo().getClassObjectRef();
828           // Record the field in the map
829           rwSet.addReadField(fieldClass, objectId);
830         }
831       }
832
833     }
834   }
835
836   @Override
837   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
838     if (stateReductionMode) {
839       // Has to be initialized and a integer CG
840       ChoiceGenerator<?> cg = vm.getChoiceGenerator();
841       if (isInitialized && (cg instanceof IntChoiceFromSet || cg instanceof IntIntervalGenerator)) {
842         int currentChoice = getCurrentChoice(vm);
843         if (currentChoice < 0) { // If choice is -1 then skip
844           return;
845         }
846         // Record accesses from executed instructions
847         if (executedInsn instanceof JVMFieldInstruction) {
848           // Analyze only after being initialized
849           String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName();
850           // We don't care about libraries
851           if (!isFieldExcluded(fieldClass)) {
852             analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice);
853           }
854         } else if (executedInsn instanceof INVOKEINTERFACE) {
855           // Handle the read/write accesses that occur through iterators
856           analyzeReadWriteAccesses(executedInsn, ti, currentChoice);
857         }
858         // Analyze conflicts from next instructions
859         if (nextInsn instanceof JVMFieldInstruction) {
860           // The constructor is only called once when the object is initialized
861           // It does not have shared access with other objects
862           MethodInfo mi = nextInsn.getMethodInfo();
863           if (!mi.getName().equals("<init>")) {
864             String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName();
865             // We don't care about libraries
866             if (!isFieldExcluded(fieldClass)) {
867               // Check for conflict (go backward from currentChoice and get the first conflict)
868               // If the current event has conflicts with multiple events, then these will be detected
869               // one by one as this recursively checks backward when backtrack set is revisited and executed.
870               for (int eventNumber = currentChoice - 1; eventNumber >= 0; eventNumber--) {
871                 // Skip if this event number does not have any Read/Write set
872                 if (!readWriteFieldsMap.containsKey(refChoices[eventNumber])) {
873                   continue;
874                 }
875                 ReadWriteSet rwSet = readWriteFieldsMap.get(refChoices[eventNumber]);
876                 int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef();
877                 // 1) Check for conflicts with Write fields for both Read and Write instructions
878                 if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) &&
879                         rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) ||
880                         (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) &&
881                                 rwSet.readFieldObjectId(fieldClass) == currObjId)) {
882                   // We do not record and service the same backtrack pair/point twice!
883                   // If it has been serviced before, we just skip this
884                   if (recordConflictPair(currentChoice, eventNumber)) {
885                     // Lines 4-8 of the algorithm in the paper page 11 (see the heading note above)
886                     if (vm.isNewState() || isReachableInVODGraph(refChoices[currentChoice], refChoices[currentChoice-1])) {
887                       createBacktrackChoiceList(currentChoice, eventNumber);
888                       // Break if a conflict is found!
889                       break;
890                     }
891                   }
892                 }
893               }
894             }
895           }
896         }
897       }
898     }
899   }
900 }