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