Change in the Conflict Tracker analysis
[jpf-core.git] / src / main / gov / nasa / jpf / listener / EfficientStateReducer.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
30 import java.io.PrintWriter;
31
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 // TODO: This is a more efficient implementation of StateReducer; it is using the Sleep Sets technique
37 /**
38  * simple tool to log state changes
39  */
40 public class EfficientStateReducer extends ListenerAdapter {
41
42   // Debug info fields
43   private boolean debugMode;
44   private boolean stateReductionMode;
45   private final PrintWriter out;
46   volatile private String detail;
47   volatile private int depth;
48   volatile private int id;
49   Transition transition;
50
51   // State reduction fields
52   private Integer[] choices;
53   private IntChoiceFromSet currCG;
54   private int choiceCounter;
55   private Integer choiceUpperBound;
56   private boolean isInitialized;
57   private boolean isResetAfterAnalysis;
58   private boolean isBooleanCGFlipped;
59   private HashMap<IntChoiceFromSet,Integer> cgMap = new HashMap<>();
60   // Record the mapping between event number and field accesses (Read and Write)
61   private HashMap<Integer,ReadWriteSet> readWriteFieldsMap = new HashMap<>();
62   // The following is the backtrack map (set) that stores all the backtrack information
63   // e.g., event number 1 can have two backtrack sequences: {3,1,2,4,...} and {2,1,3,4,...}
64   private HashMap<Integer,LinkedList<Integer[]>> backtrackMap = new HashMap<>();
65   private HashMap<Integer,HashSet<Integer>> conflictPairMap = new HashMap<>();
66   private HashMap<Integer,HashSet<Integer>> conflictPairReversedMap = new HashMap<>();
67   private HashMap<Integer,HashSet<Integer>> sleepSetMap = new HashMap<>();
68   // Map choicelist with start index
69   private HashMap<Integer[],Integer> choiceListStartIndexMap = new HashMap<>();
70
71   public EfficientStateReducer (Config config, JPF jpf) {
72     debugMode = config.getBoolean("debug_state_transition", false);
73     stateReductionMode = config.getBoolean("activate_state_reduction", true);
74     if (debugMode) {
75       out = new PrintWriter(System.out, true);
76     } else {
77       out = null;
78     }
79     detail = null;
80     depth = 0;
81     id = 0;
82     transition = null;
83     isBooleanCGFlipped = false;
84     initializeStateReduction();
85   }
86
87   private void initializeStateReduction() {
88     if (stateReductionMode) {
89       choices = null;
90       currCG = null;
91       choiceCounter = 0;
92       choiceUpperBound = 0;
93       isInitialized = false;
94       isResetAfterAnalysis = false;
95       cgMap.clear();
96       readWriteFieldsMap.clear();
97       backtrackMap.clear();
98       conflictPairMap.clear();
99       conflictPairReversedMap.clear();
100       sleepSetMap.clear();
101       choiceListStartIndexMap.clear();
102     }
103   }
104
105   @Override
106   public void stateRestored(Search search) {
107     if (debugMode) {
108       id = search.getStateId();
109       depth = search.getDepth();
110       transition = search.getTransition();
111       detail = null;
112       out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
113               " and depth: " + depth + "\n");
114     }
115   }
116
117   //--- the ones we are interested in
118   @Override
119   public void searchStarted(Search search) {
120     if (debugMode) {
121       out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
122     }
123   }
124
125   @Override
126   public void choiceGeneratorRegistered (VM vm, ChoiceGenerator<?> nextCG, ThreadInfo currentThread, Instruction executedInstruction) {
127     if (stateReductionMode) {
128       // Initialize with necessary information from the CG
129       if (nextCG instanceof IntChoiceFromSet) {
130         IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG;
131         // Check if CG has been initialized, otherwise initialize it
132         Integer[] cgChoices = icsCG.getAllChoices();
133         if (!isInitialized) {
134           // Get the upper bound from the last element of the choices
135           choiceUpperBound = cgChoices[cgChoices.length - 1];
136           isInitialized = true;
137         }
138         // Record the subsequent Integer CGs only until we hit the upper bound
139         if (!isResetAfterAnalysis && choiceCounter <= choiceUpperBound && !cgMap.containsValue(choiceCounter)) {
140           // Update the choices of the first CG and add '-1'
141           if (choices == null) {
142             // All the choices are always the same so we only need to update it once
143             choices = new Integer[cgChoices.length + 1];
144             System.arraycopy(cgChoices, 0, choices, 0, cgChoices.length);
145             choices[choices.length - 1] = -1;
146           }
147           icsCG.setNewValues(choices);
148           icsCG.reset();
149           // Advance the current Integer CG
150           // This way we explore all the event numbers in the first pass
151           icsCG.advance(choices[choiceCounter]);
152           cgMap.put(icsCG, choices[choiceCounter]);
153           choiceCounter++;
154         } else {
155           // Set done the subsequent CGs
156           // We only need n CGs (n is event numbers)
157           icsCG.setDone();
158         }
159       }
160     }
161   }
162
163   private void generateSleepSets() {
164
165     // Check and put the number into the sleep set if there is no conflict between the two
166     for (int i=0; i<=choiceUpperBound; i++) {
167       HashSet<Integer> sleepSet = new HashSet<>();
168       sleepSetMap.put(i,sleepSet);
169       for (int j=0; j<=choiceUpperBound; j++) {
170         if (i != j ) {
171           // Scour the data structure to find conflict
172           HashSet<Integer> conflictSet = new HashSet<>();
173           HashSet<Integer> reversedConflictSet = new HashSet<>();
174           if (conflictPairMap.containsKey(i)) {
175             conflictSet = conflictPairMap.get(i);
176           }
177           // Scour the reversed conflict mapper as well
178           if (conflictPairReversedMap.containsKey(i)) {
179             reversedConflictSet = conflictPairReversedMap.get(i);
180           }
181           if (!conflictSet.contains(j) && !reversedConflictSet.contains(j)) {
182             sleepSet.add(j);
183           }
184         }
185       }
186     }
187   }
188
189   private int getChoiceIndex(int event) {
190     for(int i=0; i<choices.length-1; i++) {
191       if (choices[i] == event) {
192         return i;
193       }
194     }
195     // Return -1 if not found
196     return -1;
197   }
198
199   private void generateBacktrackSets() {
200     int end = !isResetAfterAnalysis ? 0 : choiceListStartIndexMap.get(choices);
201     for(Integer currentChoice : conflictPairMap.keySet()) {
202       HashSet<Integer> conflictSet = conflictPairMap.get(currentChoice);
203       for(Integer eventNumber : conflictSet) {
204         // Both indices have to be greater than or equal to "end" index
205         // We don't want to repeat the same sequence
206         if (getChoiceIndex(currentChoice) >= end && getChoiceIndex(eventNumber) >= end) {
207           createBacktrackChoiceList(currentChoice, eventNumber);
208         }
209       }
210     }
211   }
212
213   private void resetAllCGs() {
214     // Extract the event numbers that have backtrack lists
215     Set<Integer> eventSet = backtrackMap.keySet();
216     // Return if there is no conflict at all (highly unlikely)
217     if (eventSet.isEmpty()) {
218       return;
219     }
220     // Reset every CG with the first backtrack lists
221     for(IntChoiceFromSet cg : cgMap.keySet()) {
222       int event = cgMap.get(cg);
223       LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
224       if (choiceLists != null && choiceLists.peekFirst() != null) {
225         Integer[] choiceList = choiceLists.removeFirst();
226         // Deploy the new choice list for this CG
227         cg.setNewValues(choiceList);
228         cg.reset();
229       } else {
230         cg.setDone();
231       }
232     }
233   }
234
235   @Override
236   public void choiceGeneratorAdvanced (VM vm, ChoiceGenerator<?> currentCG) {
237
238     if(stateReductionMode) {
239       // Check the boolean CG and if it is flipped, we are resetting the analysis
240       if (currentCG instanceof BooleanChoiceGenerator) {
241         if (!isBooleanCGFlipped) {
242           isBooleanCGFlipped = true;
243         } else {
244           initializeStateReduction();
245         }
246       }
247       // Check every choice generated and make sure that all the available choices
248       // are chosen first before repeating the same choice of value twice!
249       if (currentCG instanceof IntChoiceFromSet) {
250         IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG;
251         // Update the current pointer to the current set of choices
252         if (choices == null || choices != icsCG.getAllChoices()) {
253           choiceListStartIndexMap.remove(choices);
254           currCG = icsCG;
255           choices = icsCG.getAllChoices();
256         }
257         // Traverse the sub-graphs
258         if (isResetAfterAnalysis) {
259           // Do this for every CG after finishing each backtrack list
260           if (icsCG.getNextChoice() == -1) {
261             int event = cgMap.get(icsCG);
262             LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
263             if (choiceLists != null && choiceLists.peekFirst() != null) {
264               // Generate backtrack sets from this choiceList
265               generateBacktrackSets();
266               Integer[] choiceList = choiceLists.removeFirst();
267               // Deploy the new choice list for this CG
268               icsCG.setNewValues(choiceList);
269               icsCG.reset();
270             } else {
271               // Set done if this was the last backtrack list
272               icsCG.setDone();
273             }
274           }
275         }
276         // Update and reset the CG if needed (do this for the first time after the analysis)
277         if (!isResetAfterAnalysis && icsCG.getNextChoice() == -1) {
278           generateSleepSets();
279           generateBacktrackSets();
280           resetAllCGs();
281           isResetAfterAnalysis = true;
282         }
283       }
284     }
285   }
286
287   @Override
288   public void stateAdvanced(Search search) {
289     if (debugMode) {
290       id = search.getStateId();
291       depth = search.getDepth();
292       transition = search.getTransition();
293       if (search.isNewState()) {
294         detail = "new";
295       } else {
296         detail = "visited";
297       }
298
299       if (search.isEndState()) {
300         out.println("\n==> DEBUG: This is the last state!\n");
301         detail += " end";
302       }
303       out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
304               " which is " + detail + " Transition: " + transition + "\n");
305     }
306   }
307
308   @Override
309   public void stateBacktracked(Search search) {
310     if (debugMode) {
311       id = search.getStateId();
312       depth = search.getDepth();
313       transition = search.getTransition();
314       detail = null;
315
316       out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
317               " and depth: " + depth + "\n");
318     }
319   }
320
321   @Override
322   public void searchFinished(Search search) {
323     if (debugMode) {
324       out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
325     }
326   }
327
328   // This class compactly stores Read and Write field sets
329   // We store the field name and its object ID
330   // Sharing the same field means the same field name and object ID
331   private class ReadWriteSet {
332     private HashMap<String,Integer> readSet;
333     private HashMap<String,Integer> writeSet;
334
335     public ReadWriteSet() {
336       readSet = new HashMap<>();
337       writeSet = new HashMap<>();
338     }
339
340     public void addReadField(String field, int objectId) {
341       readSet.put(field, objectId);
342     }
343
344     public void addWriteField(String field, int objectId) {
345       writeSet.put(field, objectId);
346     }
347
348     public boolean readFieldExists(String field) {
349       return readSet.containsKey(field);
350     }
351
352     public boolean writeFieldExists(String field) {
353       return writeSet.containsKey(field);
354     }
355
356     public int readFieldObjectId(String field) {
357       return readSet.get(field);
358     }
359
360     public int writeFieldObjectId(String field) {
361       return writeSet.get(field);
362     }
363   }
364
365   private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
366     // Do the analysis to get Read and Write accesses to fields
367     ReadWriteSet rwSet;
368     // We already have an entry
369     if (readWriteFieldsMap.containsKey(choices[currentChoice])) {
370       rwSet = readWriteFieldsMap.get(choices[currentChoice]);
371     } else { // We need to create a new entry
372       rwSet = new ReadWriteSet();
373       readWriteFieldsMap.put(choices[currentChoice], rwSet);
374     }
375     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
376     // Record the field in the map
377     if (executedInsn instanceof WriteInstruction) {
378       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
379       for(String str : EXCLUDED_FIELDS_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
380         if (fieldClass.startsWith(str)) {
381           return;
382         }
383       }
384       rwSet.addWriteField(fieldClass, objectId);
385     } else if (executedInsn instanceof ReadInstruction) {
386       rwSet.addReadField(fieldClass, objectId);
387     }
388   }
389
390   private boolean recordConflictPair(HashMap<Integer,HashSet<Integer>> confPairMap, int currentEvent, int eventNumber) {
391     HashSet<Integer> conflictSet;
392     if (!confPairMap.containsKey(currentEvent)) {
393       conflictSet = new HashSet<>();
394       confPairMap.put(currentEvent, conflictSet);
395     } else {
396       conflictSet = confPairMap.get(currentEvent);
397     }
398     // If this conflict has been recorded before, we return false because
399     // we don't want to service this backtrack point twice
400     if (conflictSet.contains(eventNumber)) {
401       return false;
402     }
403     // If it hasn't been recorded, then do otherwise
404     conflictSet.add(eventNumber);
405     return true;
406   }
407
408   private Integer[] copyIntegerListToArray(List<Integer> list) {
409     Integer[] integerArray = new Integer[list.size()];
410     for (int i=0; i<list.size(); i++) {
411       integerArray[i] = list.get(i);
412     }
413     return integerArray;
414   }
415
416   private void completeBacktrackChoiceList(List<Integer> newChoiceList, int currentChoice, int conflictEventNumber,
417                                          LinkedList<Integer[]> backtrackChoiceLists) {
418     // Put the conflicting event numbers first and reverse the order
419     newChoiceList.add(choices[currentChoice]);
420     newChoiceList.add(choices[conflictEventNumber]);
421     // Put the rest of the event numbers into the array starting from the minimum to the upper bound
422     for (int i = conflictEventNumber + 1; i < choices.length - 1; i++) {
423       // Check the sleep sets for excepted events that do not conflict with the current one
424       int prevChoiceNum = newChoiceList.get(newChoiceList.size()-1);
425       HashSet<Integer> sleepSet = sleepSetMap.get(prevChoiceNum);
426       if (choices[i] != choices[currentChoice] && !sleepSet.contains(choices[i])) {
427         newChoiceList.add(choices[i]);
428       }
429     }
430     // Set the last element to '-1' as the end of the sequence
431     newChoiceList.add(-1);
432     Integer[] newChoiceArray = copyIntegerListToArray(newChoiceList);
433     backtrackChoiceLists.addLast(newChoiceArray);
434     if (!isResetAfterAnalysis) {
435       // The start index for the recursion is always 1 (from the main branch)
436       choiceListStartIndexMap.put(newChoiceArray, 1);
437     } else {
438       choiceListStartIndexMap.put(newChoiceArray, conflictEventNumber + 1);
439     }
440   }
441
442   private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
443
444     LinkedList<Integer[]> backtrackChoiceLists;
445     // Create a new list of choices for backtrack based on the current choice and conflicting event number
446     // If we have a conflict between 1 and 3, then we create the list {3, 1, 2, 4, 5} for backtrack
447     // The backtrack point is the CG for event number 1 and the list length is one less than the original list
448     // since we don't start from event number 0
449     if (!isResetAfterAnalysis) {
450       // Check if we have a list for this choice number
451       // If not we create a new one for it
452       if (!backtrackMap.containsKey(conflictEventNumber)) {
453         backtrackChoiceLists = new LinkedList<>();
454         backtrackMap.put(conflictEventNumber, backtrackChoiceLists);
455       } else {
456         backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
457       }
458       List<Integer> newChoiceList = new ArrayList<>();
459       completeBacktrackChoiceList(newChoiceList, currentChoice, conflictEventNumber, backtrackChoiceLists);
460     } else { // This is a sub-graph
461       int backtrackListIndex = cgMap.get(currCG);
462       backtrackChoiceLists = backtrackMap.get(backtrackListIndex);
463       List<Integer> newChoiceList = new ArrayList<>();
464       // Copy everything before the conflict number
465       int conflictEventCurrentNumber = getChoiceIndex(conflictEventNumber);
466       for(int i = 0; i < conflictEventCurrentNumber; i++) {
467         newChoiceList.add(choices[i]);
468       }
469       // Put the conflicting events
470       int currentChoiceCurrentNumber = getChoiceIndex(currentChoice);
471       completeBacktrackChoiceList(newChoiceList, currentChoiceCurrentNumber, conflictEventCurrentNumber,
472               backtrackChoiceLists);
473     }
474   }
475
476   // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure
477   private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST =
478           // Java and Groovy libraries
479           { "java", "org", "sun", "com", "gov", "groovy"};
480   private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST =
481           // Groovy library created fields
482           {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__",
483                   // Infrastructure
484                   "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList",
485                   "eventList", "valueList", "settings", "printToConsole", "app1", "app2"};
486   private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"};
487   private final static String[] EXCLUDED_FIELDS_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"};
488
489   private boolean isFieldExcluded(String field) {
490     // Check against "starts-with" list
491     for(String str : EXCLUDED_FIELDS_STARTS_WITH_LIST) {
492       if (field.startsWith(str)) {
493         return true;
494       }
495     }
496     // Check against "ends-with" list
497     for(String str : EXCLUDED_FIELDS_ENDS_WITH_LIST) {
498       if (field.endsWith(str)) {
499         return true;
500       }
501     }
502     // Check against "contains" list
503     for(String str : EXCLUDED_FIELDS_CONTAINS_LIST) {
504       if (field.contains(str)) {
505         return true;
506       }
507     }
508
509     return false;
510   }
511
512   @Override
513   public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
514     if (stateReductionMode) {
515       if (isInitialized && !isResetAfterAnalysis) {
516         if (choiceCounter > choices.length - 1) {
517           // We do not compute the conflicts for the choice '-1'
518           return;
519         }
520         int currentChoice = choiceCounter - 1;
521         // Record accesses from executed instructions
522         if (executedInsn instanceof JVMFieldInstruction) {
523           // Analyze only after being initialized
524           String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName();
525           // We don't care about libraries
526           if (!isFieldExcluded(fieldClass)) {
527             analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice);
528           }
529         }
530         // Analyze conflicts from next instructions
531         if (nextInsn instanceof JVMFieldInstruction) {
532           // The constructor is only called once when the object is initialized
533           // It does not have shared access with other objects
534           MethodInfo mi = nextInsn.getMethodInfo();
535           if (!mi.getName().equals("<init>")) {
536             String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName();
537             // We don't care about libraries
538             if (!isFieldExcluded(fieldClass)) {
539               // For the main graph we go down to 0, but for subgraph, we only go down to 1 since 0 contains
540               // the reversed event
541               // Check for conflict (go backward from currentChoice and get the first conflict)
542               // If the current event has conflicts with multiple events, then these will be detected
543               // one by one as this recursively checks backward when backtrack set is revisited and executed.
544               for (int eventNumber = currentChoice - 1; eventNumber >= 0; eventNumber--) {
545                 // Skip if this event number does not have any Read/Write set
546                 if (!readWriteFieldsMap.containsKey(choices[eventNumber])) {
547                   continue;
548                 }
549                 ReadWriteSet rwSet = readWriteFieldsMap.get(choices[eventNumber]);
550                 int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef();
551                 // 1) Check for conflicts with Write fields for both Read and Write instructions
552                 if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) &&
553                         rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) ||
554                         (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) &&
555                                 rwSet.readFieldObjectId(fieldClass) == currObjId)) {
556                   // We do not record and service the same backtrack pair/point twice!
557                   // If it has been serviced before, we just skip this
558                   if (recordConflictPair(conflictPairMap, currentChoice, eventNumber)) {
559                     recordConflictPair(conflictPairReversedMap, eventNumber, currentChoice);
560                     // Break if a conflict is found!
561                     break;
562                   }
563                 }
564               }
565             }
566           }
567         }
568       }
569     }
570   }
571 }