94b78f8ea866da2629abb0a01463c51a88c894b0
[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;
561     // We already have an entry
562     if (readWriteFieldsMap.containsKey(refChoices[currentChoice])) {
563       rwSet = readWriteFieldsMap.get(refChoices[currentChoice]);
564     } else { // We need to create a new entry
565       rwSet = new ReadWriteSet();
566       readWriteFieldsMap.put(refChoices[currentChoice], rwSet);
567     }
568     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
569     // Record the field in the map
570     if (executedInsn instanceof WriteInstruction) {
571       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
572       for (String str : EXCLUDED_FIELDS_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
573         if (fieldClass.startsWith(str)) {
574           return;
575         }
576       }
577       rwSet.addWriteField(fieldClass, objectId);
578     } else if (executedInsn instanceof ReadInstruction) {
579       rwSet.addReadField(fieldClass, objectId);
580     }
581   }
582
583   private boolean recordConflictPair(int currentEvent, int eventNumber) {
584     HashSet<Integer> conflictSet;
585     if (!conflictPairMap.containsKey(currentEvent)) {
586       conflictSet = new HashSet<>();
587       conflictPairMap.put(currentEvent, conflictSet);
588     } else {
589       conflictSet = conflictPairMap.get(currentEvent);
590     }
591     // If this conflict has been recorded before, we return false because
592     // we don't want to service this backtrack point twice
593     if (conflictSet.contains(eventNumber)) {
594       return false;
595     }
596     // If it hasn't been recorded, then do otherwise
597     conflictSet.add(eventNumber);
598     return true;
599   }
600
601   private String buildStringFromChoiceList(Integer[] newChoiceList) {
602
603     // When we see a choice list shorter than the upper bound, e.g., [3,2] for choices 0,1,2, and 3,
604     //  then we have to pad the beginning before we store it, because [3,2] actually means [0,1,3,2]
605     int actualListLength = newChoiceList.length;
606     int diff = maxUpperBound - actualListLength;
607     StringBuilder sb = new StringBuilder();
608     // Pad the beginning if necessary
609     for (int i = 0; i < diff; i++) {
610       sb.append(i);
611     }
612     // Then continue with the actual choice list
613     // We don't include the '-1' at the end
614     for (int i = 0; i < newChoiceList.length; i++) {
615       sb.append(newChoiceList[i]);
616     }
617     return sb.toString();
618   }
619
620   private void checkAndAddBacktrackList(LinkedList<Integer[]> backtrackChoiceLists, Integer[] newChoiceList) {
621
622     String newChoiceListString = buildStringFromChoiceList(newChoiceList);
623     // Add only if we haven't seen this combination before
624     if (!backtrackSet.contains(newChoiceListString)) {
625       backtrackSet.add(newChoiceListString);
626       backtrackChoiceLists.addLast(newChoiceList);
627     }
628   }
629
630   private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
631
632     LinkedList<Integer[]> backtrackChoiceLists;
633     // Create a new list of choices for backtrack based on the current choice and conflicting event number
634     // If we have a conflict between 1 and 3, then we create the list {3, 1, 2, 4, 5} for backtrack
635     // The backtrack point is the CG for event number 1 and the list length is one less than the original list
636     // (originally of length 6) since we don't start from event number 0
637     if (!isResetAfterAnalysis) {
638       // Check if we have a list for this choice number
639       // If not we create a new one for it
640       if (!backtrackMap.containsKey(conflictEventNumber)) {
641         backtrackChoiceLists = new LinkedList<>();
642         backtrackMap.put(conflictEventNumber, backtrackChoiceLists);
643       } else {
644         backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
645       }
646       int maxListLength = choiceUpperBound + 1;
647       int listLength = maxListLength - conflictEventNumber;
648       Integer[] newChoiceList = new Integer[listLength];
649       // Put the conflicting event numbers first and reverse the order
650       newChoiceList[0] = refChoices[currentChoice];
651       newChoiceList[1] = refChoices[conflictEventNumber];
652       // Put the rest of the event numbers into the array starting from the minimum to the upper bound
653       for (int i = conflictEventNumber + 1, j = 2; j < listLength; i++) {
654         if (refChoices[i] != refChoices[currentChoice]) {
655           newChoiceList[j] = refChoices[i];
656           j++;
657         }
658       }
659       checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
660       // The start index for the recursion is always 1 (from the main branch)
661     } else { // This is a sub-graph
662       // There is a case/bug that after a re-initialization, currCG is not yet initialized
663       if (currCG != null && cgMap.containsKey(currCG)) {
664         int backtrackListIndex = cgMap.get(currCG);
665         backtrackChoiceLists = backtrackMap.get(backtrackListIndex);
666         int listLength = refChoices.length;
667         Integer[] newChoiceList = new Integer[listLength];
668         // Copy everything before the conflict number
669         for (int i = 0; i < conflictEventNumber; i++) {
670           newChoiceList[i] = refChoices[i];
671         }
672         // Put the conflicting events
673         newChoiceList[conflictEventNumber] = refChoices[currentChoice];
674         newChoiceList[conflictEventNumber + 1] = refChoices[conflictEventNumber];
675         // Copy the rest
676         for (int i = conflictEventNumber + 1, j = conflictEventNumber + 2; j < listLength - 1; i++) {
677           if (refChoices[i] != refChoices[currentChoice]) {
678             newChoiceList[j] = refChoices[i];
679             j++;
680           }
681         }
682         checkAndAddBacktrackList(backtrackChoiceLists, newChoiceList);
683       }
684     }
685   }
686
687   // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure
688   private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST =
689           // Java and Groovy libraries
690           { "java", "org", "sun", "com", "gov", "groovy"};
691   private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST =
692           // Groovy library created fields
693           {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__",
694                   // Infrastructure
695                   "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList",
696                   "eventList", "valueList", "settings", "printToConsole", "app1", "app2"};
697   private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"};
698   private final static String[] EXCLUDED_FIELDS_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"};
699
700   private boolean isFieldExcluded(String field) {
701     // Check against "starts-with" list
702     for(String str : EXCLUDED_FIELDS_STARTS_WITH_LIST) {
703       if (field.startsWith(str)) {
704         return true;
705       }
706     }
707     // Check against "ends-with" list
708     for(String str : EXCLUDED_FIELDS_ENDS_WITH_LIST) {
709       if (field.endsWith(str)) {
710         return true;
711       }
712     }
713     // Check against "contains" list
714     for(String str : EXCLUDED_FIELDS_CONTAINS_LIST) {
715       if (field.contains(str)) {
716         return true;
717       }
718     }
719
720     return false;
721   }
722
723   // This method checks whether a choice is reachable in the VOD graph from a reference choice
724   // This is a BFS search
725   private boolean isReachableInVODGraph(int checkedChoice, int referenceChoice) {
726     // Record visited choices as we search in the graph
727     HashSet<Integer> visitedChoice = new HashSet<>();
728     visitedChoice.add(referenceChoice);
729     LinkedList<Integer> nodesToVisit = new LinkedList<>();
730     // If the state doesn't advance as the threads/sub-programs are executed (basically there is no new state),
731     // there is a chance that the graph doesn't have new nodes---thus this check will return a null.
732     if (vodGraphMap.containsKey(referenceChoice)) {
733       nodesToVisit.addAll(vodGraphMap.get(referenceChoice));
734       while(!nodesToVisit.isEmpty()) {
735         int currChoice = nodesToVisit.getFirst();
736         if (currChoice == checkedChoice) {
737           return true;
738         }
739         if (visitedChoice.contains(currChoice)) {
740           // If there is a loop then we don't find it
741           return false;
742         }
743         // Continue searching
744         visitedChoice.add(currChoice);
745         HashSet<Integer> currChoiceNextNodes = vodGraphMap.get(currChoice);
746         if (currChoiceNextNodes != null) {
747           // Add only if there is a mapping for next nodes
748           for (Integer nextNode : currChoiceNextNodes) {
749             // Skip cycles
750             if (nextNode == currChoice) {
751               continue;
752             }
753             nodesToVisit.addLast(nextNode);
754           }
755         }
756       }
757     }
758     return false;
759   }
760
761   private int getCurrentChoice(VM vm) {
762     ChoiceGenerator<?> currentCG = vm.getChoiceGenerator();
763     // This is the main event CG
764     if (currentCG instanceof IntChoiceFromSet) {
765       return ((IntChoiceFromSet) currentCG).getNextChoiceIndex();
766     } else {
767       // This is the interval CG used in device handlers
768       ChoiceGenerator<?> parentCG = ((IntIntervalGenerator) currentCG).getPreviousChoiceGenerator();
769       return ((IntChoiceFromSet) parentCG).getNextChoiceIndex();
770     }
771   }
772
773   @Override
774   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
775     if (stateReductionMode) {
776       // Has to be initialized and a integer CG
777       ChoiceGenerator<?> cg = vm.getChoiceGenerator();
778       if (isInitialized && (cg instanceof IntChoiceFromSet || cg instanceof IntIntervalGenerator)) {
779         int currentChoice = getCurrentChoice(vm);
780         if (currentChoice < 0) { // If choice is -1 then skip
781           return;
782         }
783         // Record accesses from executed instructions
784         if (executedInsn instanceof JVMFieldInstruction) {
785           // Analyze only after being initialized
786           String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName();
787           // We don't care about libraries
788           if (!isFieldExcluded(fieldClass)) {
789             analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice);
790           }
791         }
792         // Analyze conflicts from next instructions
793         if (nextInsn instanceof JVMFieldInstruction) {
794           // The constructor is only called once when the object is initialized
795           // It does not have shared access with other objects
796           MethodInfo mi = nextInsn.getMethodInfo();
797           if (!mi.getName().equals("<init>")) {
798             String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName();
799             // We don't care about libraries
800             if (!isFieldExcluded(fieldClass)) {
801               // Check for conflict (go backward from currentChoice and get the first conflict)
802               // If the current event has conflicts with multiple events, then these will be detected
803               // one by one as this recursively checks backward when backtrack set is revisited and executed.
804               for (int eventNumber = currentChoice - 1; eventNumber >= 0; eventNumber--) {
805                 // Skip if this event number does not have any Read/Write set
806                 if (!readWriteFieldsMap.containsKey(refChoices[eventNumber])) {
807                   continue;
808                 }
809                 ReadWriteSet rwSet = readWriteFieldsMap.get(refChoices[eventNumber]);
810                 int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef();
811                 // 1) Check for conflicts with Write fields for both Read and Write instructions
812                 if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) &&
813                         rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) ||
814                         (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) &&
815                                 rwSet.readFieldObjectId(fieldClass) == currObjId)) {
816                   // We do not record and service the same backtrack pair/point twice!
817                   // If it has been serviced before, we just skip this
818                   if (recordConflictPair(currentChoice, eventNumber)) {
819                     // Lines 4-8 of the algorithm in the paper page 11 (see the heading note above)
820                     if (vm.isNewState() || isReachableInVODGraph(refChoices[currentChoice], refChoices[currentChoice-1])) {
821                       createBacktrackChoiceList(currentChoice, eventNumber);
822                       // Break if a conflict is found!
823                       break;
824                     }
825                   }
826                 }
827               }
828             }
829           }
830         }
831       }
832     }
833   }
834 }