Adding a counter for number of conflicts.
[jpf-core.git] / src / main / gov / nasa / jpf / listener / DPORStateReducer.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 DPORStateReducer extends ListenerAdapter {
50
51   // Information printout fields for verbose mode
52   private boolean verboseMode;
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   // DPOR-related fields
61   // Basic information
62   private Integer[] choices;
63   private Integer[] refChoices; // Second reference to a copy of choices (choices may be modified for fair scheduling)
64   private int choiceCounter;
65   private int maxEventChoice;
66   // Data structure to track the events seen by each state to track cycles (containing all events) for termination
67   private HashSet<Integer> currVisitedStates; // States being visited in the current execution
68   private HashSet<Integer> justVisitedStates; // States just visited in the previous choice/event
69   private HashSet<Integer> prevVisitedStates; // States visited in the previous execution
70   private HashMap<Integer, HashSet<Integer>> stateToEventMap;
71   // Data structure to analyze field Read/Write accesses and conflicts
72   private HashMap<Integer, LinkedList<Integer[]>> backtrackMap;   // Track created backtracking points
73   private PriorityQueue<Integer> backtrackStateQ;                 // Heap that returns the latest state
74   private ArrayList<BacktrackPoint> backtrackPointList;           // Record backtrack points (CG, state Id, and choice)
75   private HashMap<Integer, HashSet<Integer>> conflictPairMap;     // Record conflicting events
76   private HashSet<String> doneBacktrackSet;                       // Record state ID and trace already constructed
77   private HashMap<Integer,Integer> newStateEventMap;              // Record event producing a new state ID
78   private HashMap<Integer, ReadWriteSet> readWriteFieldsMap;      // Record fields that are accessed
79   private HashMap<Integer, RestorableVMState> restorableStateMap; // Maps state IDs to the restorable state object
80
81   // Visible operation dependency graph implementation (SPIN paper) related fields
82   private int currChoiceValue;
83   private int prevChoiceValue;
84   private HashMap<Integer, HashSet<Integer>> vodGraphMap; // Visible operation dependency graph (VOD graph)
85
86   // Boolean states
87   private boolean isBooleanCGFlipped;
88   private boolean isEndOfExecution;
89
90   // Statistics
91   private int numOfConflicts;
92   private int numOfTransitions;
93         
94   public DPORStateReducer(Config config, JPF jpf) {
95     verboseMode = config.getBoolean("printout_state_transition", false);
96     stateReductionMode = config.getBoolean("activate_state_reduction", true);
97     if (verboseMode) {
98       out = new PrintWriter(System.out, true);
99     } else {
100       out = null;
101     }
102     isBooleanCGFlipped = false;
103                 numOfConflicts = 0;
104                 numOfTransitions = 0;
105     restorableStateMap = new HashMap<>();
106     initializeStatesVariables();
107   }
108
109   @Override
110   public void stateRestored(Search search) {
111     if (verboseMode) {
112       id = search.getStateId();
113       depth = search.getDepth();
114       transition = search.getTransition();
115       detail = null;
116       out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
117               " and depth: " + depth + "\n");
118     }
119   }
120
121   @Override
122   public void searchStarted(Search search) {
123     if (verboseMode) {
124       out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
125     }
126   }
127
128   @Override
129   public void stateAdvanced(Search search) {
130     if (verboseMode) {
131       id = search.getStateId();
132       depth = search.getDepth();
133       transition = search.getTransition();
134       if (search.isNewState()) {
135         detail = "new";
136       } else {
137         detail = "visited";
138       }
139
140       if (search.isEndState()) {
141         out.println("\n==> DEBUG: This is the last state!\n");
142         detail += " end";
143       }
144       out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
145               " which is " + detail + " Transition: " + transition + "\n");
146     }
147     if (stateReductionMode) {
148       updateStateInfo(search);
149     }
150   }
151
152   @Override
153   public void stateBacktracked(Search search) {
154     if (verboseMode) {
155       id = search.getStateId();
156       depth = search.getDepth();
157       transition = search.getTransition();
158       detail = null;
159
160       out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
161               " and depth: " + depth + "\n");
162     }
163     if (stateReductionMode) {
164       updateStateInfo(search);
165     }
166   }
167
168   @Override
169   public void searchFinished(Search search) {
170     if (stateReductionMode) {
171       // Number of conflicts = first trace + subsequent backtrack points
172       numOfConflicts += 1 + doneBacktrackSet.size();
173     }
174     if (verboseMode) {
175       out.println("\n==> DEBUG: ----------------------------------- search finished");
176       out.println("\n==> DEBUG: State reduction mode  : " + stateReductionMode);
177       out.println("\n==> DEBUG: Number of conflicts   : " + numOfConflicts);
178       out.println("\n==> DEBUG: Number of transitions : " + numOfTransitions);
179       out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
180     }
181   }
182
183   @Override
184   public void choiceGeneratorRegistered(VM vm, ChoiceGenerator<?> nextCG, ThreadInfo currentThread, Instruction executedInstruction) {
185     if (stateReductionMode) {
186       // Initialize with necessary information from the CG
187       if (nextCG instanceof IntChoiceFromSet) {
188         IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG;
189         if (!isEndOfExecution) {
190           // Check if CG has been initialized, otherwise initialize it
191           Integer[] cgChoices = icsCG.getAllChoices();
192           // Record the events (from choices)
193           if (choices == null) {
194             choices = cgChoices;
195             // Make a copy of choices as reference
196             refChoices = copyChoices(choices);
197             // Record the max event choice (the last element of the choice array)
198             maxEventChoice = choices[choices.length - 1];
199           }
200           icsCG.setNewValues(choices);
201           icsCG.reset();
202           // Use a modulo since choiceCounter is going to keep increasing
203           int choiceIndex = choiceCounter % choices.length;
204           icsCG.advance(choices[choiceIndex]);
205         } else {
206           // Set done all CGs while transitioning to a new execution
207           icsCG.setDone();
208         }
209       }
210     }
211   }
212
213   @Override
214   public void choiceGeneratorAdvanced(VM vm, ChoiceGenerator<?> currentCG) {
215
216     if (stateReductionMode) {
217       // Check the boolean CG and if it is flipped, we are resetting the analysis
218       if (currentCG instanceof BooleanChoiceGenerator) {
219         if (!isBooleanCGFlipped) {
220           isBooleanCGFlipped = true;
221         } else {
222           // Number of conflicts = first trace + subsequent backtrack points
223           numOfConflicts = 1 + doneBacktrackSet.size();
224           // Allocate new objects for data structure when the boolean is flipped from "false" to "true"
225           initializeStatesVariables();
226         }
227       }
228       // Check every choice generated and ensure fair scheduling!
229       if (currentCG instanceof IntChoiceFromSet) {
230         IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG;
231         // If this is a new CG then we need to update data structures
232         resetStatesForNewExecution(icsCG, vm);
233         // If we don't see a fair scheduling of events/choices then we have to enforce it
234         fairSchedulingAndBacktrackPoint(icsCG, vm);
235         // Map state to event
236         mapStateToEvent(icsCG.getNextChoice());
237         // Explore the next backtrack point: 
238         // 1) if we have seen this state or this state contains cycles that involve all events, and
239         // 2) after the current CG is advanced at least once
240         if (terminateCurrentExecution() && choiceCounter > 0) {
241           exploreNextBacktrackPoints(vm, icsCG);
242         } else {
243           numOfTransitions++;
244         }
245         justVisitedStates.clear();
246         choiceCounter++;
247       }
248     } else {
249       numOfTransitions++;
250     }
251   }
252
253   @Override
254   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
255     if (stateReductionMode) {
256       if (!isEndOfExecution) {
257         // Has to be initialized and a integer CG
258         ChoiceGenerator<?> cg = vm.getChoiceGenerator();
259         if (cg instanceof IntChoiceFromSet || cg instanceof IntIntervalGenerator) {
260           int currentChoice = choiceCounter - 1;  // Accumulative choice w.r.t the current trace
261           if (currentChoice < 0) { // If choice is -1 then skip
262             return;
263           }
264           currentChoice = checkAndAdjustChoice(currentChoice, vm);
265           // Record accesses from executed instructions
266           if (executedInsn instanceof JVMFieldInstruction) {
267             // Analyze only after being initialized
268             String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName();
269             // We don't care about libraries
270             if (!isFieldExcluded(fieldClass)) {
271               analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice);
272             }
273           } else if (executedInsn instanceof INVOKEINTERFACE) {
274             // Handle the read/write accesses that occur through iterators
275             analyzeReadWriteAccesses(executedInsn, ti, currentChoice);
276           }
277           // Analyze conflicts from next instructions
278           if (nextInsn instanceof JVMFieldInstruction) {
279             // Skip the constructor because it is called once and does not have shared access with other objects
280             if (!nextInsn.getMethodInfo().getName().equals("<init>")) {
281               String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName();
282               if (!isFieldExcluded(fieldClass)) {
283                 // Check for conflict (go backward from current choice and get the first conflict)
284                 for (int eventCounter = currentChoice - 1; eventCounter >= 0; eventCounter--) {
285                   // Check for conflicts with Write fields for both Read and Write instructions
286                   // Check and record a backtrack set for just once!
287                   if (isConflictFound(nextInsn, eventCounter, currentChoice, fieldClass) &&
288                       isNewConflict(currentChoice, eventCounter)) {
289                     // Lines 4-8 of the algorithm in the paper page 11 (see the heading note above)
290                     if (vm.isNewState() || isReachableInVODGraph(currentChoice, vm)) {
291                       createBacktrackingPoint(currentChoice, eventCounter);
292                     }
293                   }
294                 }
295               }
296             }
297           }
298         }
299       }
300     }
301   }
302
303
304   // == HELPERS
305
306   // -- INNER CLASSES
307
308   // This class compactly stores Read and Write field sets
309   // We store the field name and its object ID
310   // Sharing the same field means the same field name and object ID
311   private class ReadWriteSet {
312     private HashMap<String, Integer> readSet;
313     private HashMap<String, Integer> writeSet;
314
315     public ReadWriteSet() {
316       readSet = new HashMap<>();
317       writeSet = new HashMap<>();
318     }
319
320     public void addReadField(String field, int objectId) {
321       readSet.put(field, objectId);
322     }
323
324     public void addWriteField(String field, int objectId) {
325       writeSet.put(field, objectId);
326     }
327
328     public boolean readFieldExists(String field) {
329       return readSet.containsKey(field);
330     }
331
332     public boolean writeFieldExists(String field) {
333       return writeSet.containsKey(field);
334     }
335
336     public int readFieldObjectId(String field) {
337       return readSet.get(field);
338     }
339
340     public int writeFieldObjectId(String field) {
341       return writeSet.get(field);
342     }
343   }
344
345   // This class compactly stores backtrack points: 1) backtrack state ID, and 2) backtracking choices
346   private class BacktrackPoint {
347     private IntChoiceFromSet backtrackCG; // CG at this backtrack point
348     private int stateId;                  // State at this backtrack point
349     private int choice;                   // Choice chosen at this backtrack point
350
351     public BacktrackPoint(IntChoiceFromSet cg, int stId, int cho) {
352       backtrackCG = cg;
353       stateId = stId;
354       choice = cho;
355     }
356
357     public IntChoiceFromSet getBacktrackCG() { return backtrackCG; }
358
359     public int getStateId() {
360       return stateId;
361     }
362
363     public int getChoice() {
364       return choice;
365     }
366   }
367
368   // -- CONSTANTS
369   private final static String DO_CALL_METHOD = "doCall";
370   // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure
371   private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"};
372   private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST =
373           // Groovy library created fields
374           {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__",
375           // Infrastructure
376           "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList",
377           "eventList", "valueList", "settings", "printToConsole", "app1", "app2"};
378   private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST =
379           // Java and Groovy libraries
380           { "java", "org", "sun", "com", "gov", "groovy"};
381   private final static String[] EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"};
382   private final static String GET_PROPERTY_METHOD =
383           "invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.callGetProperty";
384   private final static String GROOVY_CALLSITE_LIB = "org.codehaus.groovy.runtime.callsite";
385   private final static String JAVA_INTEGER = "int";
386   private final static String JAVA_STRING_LIB = "java.lang.String";
387
388   // -- FUNCTIONS
389   private void fairSchedulingAndBacktrackPoint(IntChoiceFromSet icsCG, VM vm) {
390     // Check the next choice and if the value is not the same as the expected then force the expected value
391     int choiceIndex = choiceCounter % refChoices.length;
392     int nextChoice = icsCG.getNextChoice();
393     if (refChoices[choiceIndex] != nextChoice) {
394       int expectedChoice = refChoices[choiceIndex];
395       int currCGIndex = icsCG.getNextChoiceIndex();
396       if ((currCGIndex >= 0) && (currCGIndex < refChoices.length)) {
397         icsCG.setChoice(currCGIndex, expectedChoice);
398       }
399     }
400     // Update current choice
401     currChoiceValue = refChoices[choiceIndex];
402     // Record state ID and choice/event as backtrack point
403     int stateId = vm.getStateId();
404     backtrackPointList.add(new BacktrackPoint(icsCG, stateId, refChoices[choiceIndex]));
405     // Store restorable state object for this state (always store the latest)
406     RestorableVMState restorableState = vm.getRestorableState();
407     restorableStateMap.put(stateId, restorableState);
408   }
409
410   private Integer[] copyChoices(Integer[] choicesToCopy) {
411
412     Integer[] copyOfChoices = new Integer[choicesToCopy.length];
413     System.arraycopy(choicesToCopy, 0, copyOfChoices, 0, choicesToCopy.length);
414     return copyOfChoices;
415   }
416
417   // --- Functions related to cycle detection
418
419   // Detect cycles in the current execution/trace
420   // We terminate the execution iff:
421   // (1) the state has been visited in the current execution
422   // (2) the state has one or more cycles that involve all the events
423   // With simple approach we only need to check for a re-visited state.
424   // Basically, we have to check that we have executed all events between two occurrences of such state.
425   private boolean containsCyclesWithAllEvents(int stId) {
426
427     // False if the state ID hasn't been recorded
428     if (!stateToEventMap.containsKey(stId)) {
429       return false;
430     }
431     HashSet<Integer> visitedEvents = stateToEventMap.get(stId);
432     // Check if this set contains all the event choices
433     // If not then this is not the terminating condition
434     for(int i=0; i<=maxEventChoice; i++) {
435       if (!visitedEvents.contains(i)) {
436         return false;
437       }
438     }
439     return true;
440   }
441
442   private void initializeStatesVariables() {
443     // DPOR-related
444     choices = null;
445     refChoices = null;
446     choiceCounter = 0;
447     maxEventChoice = 0;
448     // Cycle tracking
449     currVisitedStates = new HashSet<>();
450     justVisitedStates = new HashSet<>();
451     prevVisitedStates = new HashSet<>();
452     stateToEventMap = new HashMap<>();
453     // Backtracking
454     backtrackMap = new HashMap<>();
455     backtrackStateQ = new PriorityQueue<>(Collections.reverseOrder());
456     backtrackPointList = new ArrayList<>();
457     conflictPairMap = new HashMap<>();
458     doneBacktrackSet = new HashSet<>();
459     newStateEventMap = new HashMap<>();
460     readWriteFieldsMap = new HashMap<>();
461     // VOD graph
462     currChoiceValue = 0;
463     prevChoiceValue = -1;
464     vodGraphMap = new HashMap<>();
465     // Booleans
466     isEndOfExecution = false;
467   }
468
469   private void mapStateToEvent(int nextChoiceValue) {
470     // Update all states with this event/choice
471     // This means that all past states now see this transition
472     Set<Integer> stateSet = stateToEventMap.keySet();
473     for(Integer stateId : stateSet) {
474       HashSet<Integer> eventSet = stateToEventMap.get(stateId);
475       eventSet.add(nextChoiceValue);
476     }
477   }
478
479   private boolean terminateCurrentExecution() {
480     // We need to check all the states that have just been visited
481     // Often a transition (choice/event) can result into forwarding/backtracking to a number of states
482     for(Integer stateId : justVisitedStates) {
483       if (prevVisitedStates.contains(stateId) || containsCyclesWithAllEvents(stateId)) {
484         return true;
485       }
486     }
487     return false;
488   }
489
490   private void updateStateInfo(Search search) {
491     // Update the state variables
492     // Line 19 in the paper page 11 (see the heading note above)
493     int stateId = search.getStateId();
494     currVisitedStates.add(stateId);
495     // Insert state ID into the map if it is new
496     if (!stateToEventMap.containsKey(stateId)) {
497       HashSet<Integer> eventSet = new HashSet<>();
498       stateToEventMap.put(stateId, eventSet);
499     }
500     justVisitedStates.add(stateId);
501     // Update the VOD graph when there is a new state
502     updateVODGraph(search.getVM());
503   }
504
505   // --- Functions related to Read/Write access analysis on shared fields
506
507   private void addNewBacktrackPoint(int stateId, Integer[] newChoiceList) {
508     // Insert backtrack point to the right state ID
509     LinkedList<Integer[]> backtrackList;
510     if (backtrackMap.containsKey(stateId)) {
511       backtrackList = backtrackMap.get(stateId);
512     } else {
513       backtrackList = new LinkedList<>();
514       backtrackMap.put(stateId, backtrackList);
515     }
516     backtrackList.addFirst(newChoiceList);
517     // Add to priority queue
518     if (!backtrackStateQ.contains(stateId)) {
519       backtrackStateQ.add(stateId);
520     }
521   }
522
523   // Analyze Read/Write accesses that are directly invoked on fields
524   private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
525     // Do the analysis to get Read and Write accesses to fields
526     ReadWriteSet rwSet = getReadWriteSet(currentChoice);
527     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
528     // Record the field in the map
529     if (executedInsn instanceof WriteInstruction) {
530       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
531       for (String str : EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
532         if (fieldClass.startsWith(str)) {
533           return;
534         }
535       }
536       rwSet.addWriteField(fieldClass, objectId);
537     } else if (executedInsn instanceof ReadInstruction) {
538       rwSet.addReadField(fieldClass, objectId);
539     }
540   }
541
542   // Analyze Read accesses that are indirect (performed through iterators)
543   // These accesses are marked by certain bytecode instructions, e.g., INVOKEINTERFACE
544   private void analyzeReadWriteAccesses(Instruction instruction, ThreadInfo ti, int currentChoice) {
545     // Get method name
546     INVOKEINTERFACE insn = (INVOKEINTERFACE) instruction;
547     if (insn.toString().startsWith(GET_PROPERTY_METHOD) &&
548             insn.getMethodInfo().getName().equals(DO_CALL_METHOD)) {
549       // Extract info from the stack frame
550       StackFrame frame = ti.getTopFrame();
551       int[] frameSlots = frame.getSlots();
552       // Get the Groovy callsite library at index 0
553       ElementInfo eiCallsite = VM.getVM().getHeap().get(frameSlots[0]);
554       if (!eiCallsite.getClassInfo().getName().startsWith(GROOVY_CALLSITE_LIB)) {
555         return;
556       }
557       // Get the iterated object whose property is accessed
558       ElementInfo eiAccessObj = VM.getVM().getHeap().get(frameSlots[1]);
559       if (eiAccessObj == null) {
560         return;
561       }
562       // We exclude library classes (they start with java, org, etc.) and some more
563       String objClassName = eiAccessObj.getClassInfo().getName();
564       if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, objClassName) ||
565           excludeThisForItStartsWith(EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST, objClassName)) {
566         return;
567       }
568       // Extract fields from this object and put them into the read write
569       int numOfFields = eiAccessObj.getNumberOfFields();
570       for(int i=0; i<numOfFields; i++) {
571         FieldInfo fieldInfo = eiAccessObj.getFieldInfo(i);
572         if (fieldInfo.getType().equals(JAVA_STRING_LIB) || fieldInfo.getType().equals(JAVA_INTEGER)) {
573           String fieldClass = fieldInfo.getFullName();
574           ReadWriteSet rwSet = getReadWriteSet(currentChoice);
575           int objectId = fieldInfo.getClassInfo().getClassObjectRef();
576           // Record the field in the map
577           rwSet.addReadField(fieldClass, objectId);
578         }
579       }
580     }
581   }
582
583   private int checkAndAdjustChoice(int currentChoice, VM vm) {
584     // If current choice is not the same, then this is caused by the firing of IntIntervalGenerator
585     // for certain method calls in the infrastructure, e.g., eventSince()
586     int currChoiceInd = currentChoice % refChoices.length;
587     int currChoiceFromCG = currChoiceInd;
588     ChoiceGenerator<?> currentCG = vm.getChoiceGenerator();
589     // This is the main event CG
590     if (currentCG instanceof IntIntervalGenerator) {
591       // This is the interval CG used in device handlers
592       ChoiceGenerator<?> parentCG = ((IntIntervalGenerator) currentCG).getPreviousChoiceGenerator();
593       int actualEvtNum = ((IntChoiceFromSet) parentCG).getNextChoice();
594       // Find the index of the event/choice in refChoices
595       for (int i = 0; i<refChoices.length; i++) {
596         if (actualEvtNum == refChoices[i]) {
597           currChoiceFromCG = i;
598           break;
599         }
600       }
601     }
602     if (currChoiceInd != currChoiceFromCG) {
603       currentChoice = (currentChoice - currChoiceInd) + currChoiceFromCG;
604     }
605     return currentChoice;
606   }
607
608   private void createBacktrackingPoint(int currentChoice, int confEvtNum) {
609
610     // Create a new list of choices for backtrack based on the current choice and conflicting event number
611     // E.g. if we have a conflict between 1 and 3, then we create the list {3, 1, 0, 2}
612     // for the original set {0, 1, 2, 3}
613     Integer[] newChoiceList = new Integer[refChoices.length];
614     // Put the conflicting event numbers first and reverse the order
615     int actualCurrCho = currentChoice % refChoices.length;
616     // We use the actual choices here in case they have been modified/adjusted by the fair scheduling method
617     newChoiceList[0] = choices[actualCurrCho];
618     newChoiceList[1] = backtrackPointList.get(confEvtNum).getChoice();
619     // Put the rest of the event numbers into the array starting from the minimum to the upper bound
620     for (int i = 0, j = 2; i < refChoices.length; i++) {
621       if (refChoices[i] != newChoiceList[0] && refChoices[i] != newChoiceList[1]) {
622         newChoiceList[j] = refChoices[i];
623         j++;
624       }
625     }
626     // Get the backtrack CG for this backtrack point
627     int stateId = backtrackPointList.get(confEvtNum).getStateId();
628     // Check if this trace has been done starting from this state
629     if (isTraceAlreadyConstructed(newChoiceList, stateId)) {
630       return;
631     }
632     addNewBacktrackPoint(stateId, newChoiceList);
633   }
634
635   private boolean excludeThisForItContains(String[] excludedStrings, String className) {
636     for (String excludedField : excludedStrings) {
637       if (className.contains(excludedField)) {
638         return true;
639       }
640     }
641     return false;
642   }
643
644   private boolean excludeThisForItEndsWith(String[] excludedStrings, String className) {
645     for (String excludedField : excludedStrings) {
646       if (className.endsWith(excludedField)) {
647         return true;
648       }
649     }
650     return false;
651   }
652
653   private boolean excludeThisForItStartsWith(String[] excludedStrings, String className) {
654     for (String excludedField : excludedStrings) {
655       if (className.startsWith(excludedField)) {
656         return true;
657       }
658     }
659     return false;
660   }
661
662   private void exploreNextBacktrackPoints(VM vm, IntChoiceFromSet icsCG) {
663
664                 // Check if we are reaching the end of our execution: no more backtracking points to explore
665                 // cgMap, backtrackMap, backtrackStateQ are updated simultaneously (checking backtrackStateQ is enough)
666                 if (!backtrackStateQ.isEmpty()) {
667                         // Set done all the other backtrack points
668                         for (BacktrackPoint backtrackPoint : backtrackPointList) {
669                                 backtrackPoint.getBacktrackCG().setDone();
670                         }
671                         // Reset the next backtrack point with the latest state
672                         int hiStateId = backtrackStateQ.peek();
673                         // Restore the state first if necessary
674                         if (vm.getStateId() != hiStateId) {
675                                 RestorableVMState restorableState = restorableStateMap.get(hiStateId);
676                                 vm.restoreState(restorableState);
677                         }
678                         // Set the backtrack CG
679                         IntChoiceFromSet backtrackCG = (IntChoiceFromSet) vm.getChoiceGenerator();
680                         setBacktrackCG(hiStateId, backtrackCG);
681                 } else {
682                         // Set done this last CG (we save a few rounds)
683                         icsCG.setDone();
684                 }
685                 // Save all the visited states when starting a new execution of trace
686                 prevVisitedStates.addAll(currVisitedStates);
687                 currVisitedStates.clear();
688                 // This marks a transitional period to the new CG
689                 isEndOfExecution = true;
690   }
691
692   private ReadWriteSet getReadWriteSet(int currentChoice) {
693     // Do the analysis to get Read and Write accesses to fields
694     ReadWriteSet rwSet;
695     // We already have an entry
696     if (readWriteFieldsMap.containsKey(currentChoice)) {
697       rwSet = readWriteFieldsMap.get(currentChoice);
698     } else { // We need to create a new entry
699       rwSet = new ReadWriteSet();
700       readWriteFieldsMap.put(currentChoice, rwSet);
701     }
702     return rwSet;
703   }
704
705   private boolean isConflictFound(Instruction nextInsn, int eventCounter, int currentChoice, String fieldClass) {
706
707     int actualCurrCho = currentChoice % refChoices.length;
708     // Skip if this event does not have any Read/Write set or the two events are basically the same event (number)
709     if (!readWriteFieldsMap.containsKey(eventCounter) ||
710          choices[actualCurrCho] == backtrackPointList.get(eventCounter).getChoice()) {
711       return false;
712     }
713     ReadWriteSet rwSet = readWriteFieldsMap.get(eventCounter);
714     int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef();
715     // Check for conflicts with Write fields for both Read and Write instructions
716     if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) &&
717           rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) ||
718          (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) &&
719           rwSet.readFieldObjectId(fieldClass) == currObjId)) {
720       return true;
721     }
722     return false;
723   }
724
725   private boolean isFieldExcluded(String field) {
726     // Check against "starts-with", "ends-with", and "contains" list
727     if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, field) ||
728             excludeThisForItEndsWith(EXCLUDED_FIELDS_ENDS_WITH_LIST, field) ||
729             excludeThisForItContains(EXCLUDED_FIELDS_CONTAINS_LIST, field)) {
730       return true;
731     }
732
733     return false;
734   }
735
736   private boolean isNewConflict(int currentEvent, int eventNumber) {
737     HashSet<Integer> conflictSet;
738     if (!conflictPairMap.containsKey(currentEvent)) {
739       conflictSet = new HashSet<>();
740       conflictPairMap.put(currentEvent, conflictSet);
741     } else {
742       conflictSet = conflictPairMap.get(currentEvent);
743     }
744     // If this conflict has been recorded before, we return false because
745     // we don't want to save this backtrack point twice
746     if (conflictSet.contains(eventNumber)) {
747       return false;
748     }
749     // If it hasn't been recorded, then do otherwise
750     conflictSet.add(eventNumber);
751     return true;
752   }
753
754   private boolean isTraceAlreadyConstructed(Integer[] choiceList, int stateId) {
755     // Concatenate state ID and only the first event in the string, e.g., "1:1 for the trace 10234 at state 1"
756     // TODO: THIS IS AN OPTIMIZATION!
757     // This is the optimized version because after we execute, e.g., the trace 1:10234, we don't need to try
758     // another trace that starts with event 1 at state 1, e.g., the trace 1:13024
759     // The second time this event 1 is explored, it will generate the same state as the first one
760     StringBuilder sb = new StringBuilder();
761     sb.append(stateId);
762     sb.append(':');
763     sb.append(choiceList[0]);
764     // Check if the trace has been constructed as a backtrack point for this state
765     if (doneBacktrackSet.contains(sb.toString())) {
766       return true;
767     }
768     doneBacktrackSet.add(sb.toString());
769     return false;
770   }
771
772   private void resetStatesForNewExecution(IntChoiceFromSet icsCG, VM vm) {
773     if (choices == null || choices != icsCG.getAllChoices()) {
774       // Reset state variables
775       choiceCounter = 0;
776       choices = icsCG.getAllChoices();
777       refChoices = copyChoices(choices);
778       // Clearing data structures
779       conflictPairMap.clear();
780       readWriteFieldsMap.clear();
781       stateToEventMap.clear();
782       isEndOfExecution = false;
783       backtrackPointList.clear();
784     }
785   }
786
787   private void setBacktrackCG(int stateId, IntChoiceFromSet backtrackCG) {
788     // Set a backtrack CG based on a state ID
789     LinkedList<Integer[]> backtrackChoices = backtrackMap.get(stateId);
790     backtrackCG.setNewValues(backtrackChoices.removeLast());  // Get the last from the queue
791     backtrackCG.setStateId(stateId);
792     backtrackCG.reset();
793     // Remove from the queue if we don't have more backtrack points for that state
794     if (backtrackChoices.isEmpty()) {
795       backtrackMap.remove(stateId);
796       backtrackStateQ.remove(stateId);
797     }
798   }
799
800   // --- Functions related to the visible operation dependency graph implementation discussed in the SPIN paper
801
802   // This method checks whether a choice/event (transition) is reachable from the choice/event that produces
803   // the state right before this state in the VOD graph
804   // We use a BFS algorithm for this purpose
805   private boolean isReachableInVODGraph(int currentChoice, VM vm) {
806     // Current event
807     int choiceIndex = currentChoice % refChoices.length;
808     int currEvent = refChoices[choiceIndex];
809     // Previous event
810     int stateId = vm.getStateId();  // A state that has been seen
811     int prevEvent = newStateEventMap.get(stateId);
812     // Only start traversing the graph if prevEvent has an outgoing edge
813     if (vodGraphMap.containsKey(prevEvent)) {
814       // Record visited choices as we search in the graph
815       HashSet<Integer> visitedChoice = new HashSet<>();
816       visitedChoice.add(prevEvent);
817       // Get the first nodes to visit (the neighbors of prevEvent)
818       LinkedList<Integer> nodesToVisit = new LinkedList<>();
819       nodesToVisit.addAll(vodGraphMap.get(prevEvent));
820       // Traverse the graph using BFS
821       while (!nodesToVisit.isEmpty()) {
822         int choice = nodesToVisit.removeFirst();
823         if (choice == currEvent) {
824           return true;
825         }
826         if (visitedChoice.contains(choice)) { // If there is a loop then just continue the exploration
827           continue;
828         }
829         // Continue searching
830         visitedChoice.add(choice);
831         HashSet<Integer> choiceNextNodes = vodGraphMap.get(choice);
832         if (choiceNextNodes != null) {
833           // Add only if there is a mapping for next nodes
834           for (Integer nextNode : choiceNextNodes) {
835             // Skip cycles
836             if (nextNode == choice) {
837               continue;
838             }
839             nodesToVisit.addLast(nextNode);
840           }
841         }
842       }
843     }
844     return false;
845   }
846
847   private void updateVODGraph(VM vm) {
848     // Do this only if it is a new state
849     if (vm.isNewState()) {
850       // Update the graph when we have the current choice value
851       HashSet<Integer> choiceSet;
852       if (vodGraphMap.containsKey(prevChoiceValue)) {
853         // If the key already exists, just retrieve it
854         choiceSet = vodGraphMap.get(prevChoiceValue);
855       } else {
856         // Create a new entry
857         choiceSet = new HashSet<>();
858         vodGraphMap.put(prevChoiceValue, choiceSet);
859       }
860       choiceSet.add(currChoiceValue);
861       prevChoiceValue = currChoiceValue;
862       // Map this state ID to the event (transition) that produces it
863       newStateEventMap.put(vm.getStateId(), currChoiceValue);
864     }
865   }
866 }