Completing POR implementation with complete recursions.
[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.LocalVariableInstruction;
27 import gov.nasa.jpf.vm.bytecode.ReadInstruction;
28 import gov.nasa.jpf.vm.bytecode.StoreInstruction;
29 import gov.nasa.jpf.vm.bytecode.WriteInstruction;
30 import gov.nasa.jpf.vm.choice.IntChoiceFromSet;
31 import gov.nasa.jpf.vm.choice.IntIntervalGenerator;
32
33 import java.awt.*;
34 import java.io.PrintWriter;
35
36 import java.util.*;
37 import java.util.List;
38
39 // TODO: Fix for Groovy's model-checking
40 // TODO: This is a setter to change the values of the ChoiceGenerator to implement POR
41 /**
42  * simple tool to log state changes
43  */
44 public class StateReducer extends ListenerAdapter {
45
46   // Debug info fields
47   private boolean debugMode;
48   private boolean stateReductionMode;
49   private final PrintWriter out;
50   volatile private String detail;
51   volatile private int depth;
52   volatile private int id;
53   Transition transition;
54
55   // State reduction fields
56   private Integer[] choices;
57   private int choiceCounter;
58   private Integer choiceUpperBound;
59   private boolean isInitialized;
60   private boolean isResetAfterAnalysis;
61   private boolean isBooleanCGFlipped;
62   private HashMap<IntChoiceFromSet,Integer> cgMap;
63   // Record the mapping between event number and field accesses (Read and Write)
64   private HashMap<Integer,ReadWriteSet> readWriteFieldsMap;
65   // The following is the backtrack map (set) that stores all the backtrack information
66   // e.g., event number 1 can have two backtrack sequences: {3,1,2,4,...} and {2,1,3,4,...}
67   private HashMap<Integer,LinkedList<Integer[]>> backtrackMap;
68   private HashMap<Integer,HashSet<Integer>> conflictPairMap;
69   // Map choicelist with start index
70   private HashMap<Integer[],Integer> choiceListStartIndexMap;
71
72   public StateReducer (Config config, JPF jpf) {
73     debugMode = config.getBoolean("debug_state_transition", false);
74     stateReductionMode = config.getBoolean("activate_state_reduction", true);
75     if (debugMode) {
76       out = new PrintWriter(System.out, true);
77     } else {
78       out = null;
79     }
80     detail = null;
81     depth = 0;
82     id = 0;
83     transition = null;
84     isBooleanCGFlipped = false;
85     initializeStateReduction();
86   }
87
88   private void initializeStateReduction() {
89     if (stateReductionMode) {
90       choices = null;
91       choiceCounter = 0;
92       choiceUpperBound = 0;
93       isInitialized = false;
94       isResetAfterAnalysis = false;
95       cgMap = new HashMap<>();
96       readWriteFieldsMap = new HashMap<>();
97       backtrackMap = new HashMap<>();
98       conflictPairMap = new HashMap<>();
99       choiceListStartIndexMap = new HashMap<>();
100     }
101   }
102
103   @Override
104   public void stateRestored(Search search) {
105     if (debugMode) {
106       id = search.getStateId();
107       depth = search.getDepth();
108       transition = search.getTransition();
109       detail = null;
110       out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
111               " and depth: " + depth + "\n");
112     }
113   }
114
115   //--- the ones we are interested in
116   @Override
117   public void searchStarted(Search search) {
118     if (debugMode) {
119       out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
120     }
121   }
122
123   @Override
124   public void choiceGeneratorRegistered (VM vm, ChoiceGenerator<?> nextCG, ThreadInfo currentThread, Instruction executedInstruction) {
125     if (stateReductionMode) {
126       // Initialize with necessary information from the CG
127       if (nextCG instanceof IntChoiceFromSet) {
128         IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG;
129         // Check if CG has been initialized, otherwise initialize it
130         Integer[] cgChoices = icsCG.getAllChoices();
131         if (!isInitialized) {
132           // Get the upper bound from the last element of the choices
133           choiceUpperBound = (Integer) cgChoices[cgChoices.length - 1];
134           isInitialized = true;
135         }
136         // Record the subsequent Integer CGs only until we hit the upper bound
137         if (!isResetAfterAnalysis && choiceCounter <= choiceUpperBound && !cgMap.containsValue(choiceCounter)) {
138           // Update the choices of the first CG and add '-1'
139           if (choices == null) {
140             // All the choices are always the same so we only need to update it once
141             choices = new Integer[cgChoices.length + 1];
142             System.arraycopy(cgChoices, 0, choices, 0, cgChoices.length);
143             choices[choices.length - 1] = -1;
144           }
145           icsCG.setNewValues(choices);
146           icsCG.reset();
147           // Advance the current Integer CG
148           // This way we explore all the event numbers in the first pass
149           icsCG.advance(choices[choiceCounter]);
150           cgMap.put(icsCG, choices[choiceCounter]);
151           choiceCounter++;
152         } else {
153           // Set done the subsequent CGs
154           // We only need n CGs (n is event numbers)
155           icsCG.setDone();
156         }
157       }
158     }
159   }
160
161   private void resetAllCGs() {
162     // Extract the event numbers that have backtrack lists
163     Set<Integer> eventSet = backtrackMap.keySet();
164     // Return if there is no conflict at all (highly unlikely)
165     if (eventSet.isEmpty()) {
166       return;
167     }
168     // Reset every CG with the first backtrack lists
169     for(IntChoiceFromSet cg : cgMap.keySet()) {
170       int event = cgMap.get(cg);
171       LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
172       if (choiceLists != null && choiceLists.peekFirst() != null) {
173         Integer[] choiceList = choiceLists.removeFirst();
174         // Deploy the new choice list for this CG
175         cg.setNewValues(choiceList);
176         cg.reset();
177       } else {
178         cg.setDone();
179       }
180     }
181   }
182
183   @Override
184   public void choiceGeneratorAdvanced (VM vm, ChoiceGenerator<?> currentCG) {
185
186     if(stateReductionMode) {
187       // Check the boolean CG and if it is flipped, we are resetting the analysis
188       if (currentCG instanceof BooleanChoiceGenerator) {
189         if (!isBooleanCGFlipped) {
190           isBooleanCGFlipped = true;
191         } else {
192           initializeStateReduction();
193         }
194       }
195       // Check every choice generated and make sure that all the available choices
196       // are chosen first before repeating the same choice of value twice!
197       if (currentCG instanceof IntChoiceFromSet) {
198         IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG;
199         // Update the current pointer to the current set of choices
200         if (choices == null || choices != icsCG.getAllChoices()) {
201           choiceListStartIndexMap.remove(choices);
202           choices = icsCG.getAllChoices();
203           // Reset a few things for the sub-graph
204           conflictPairMap = new HashMap<>();
205           readWriteFieldsMap = new HashMap<>();
206           choiceCounter = 0;
207         }
208         // Traverse the sub-graphs
209         if (isResetAfterAnalysis) {
210           // Advance choice counter for sub-graphs
211           choiceCounter++;
212           // Do this for every CG after finishing each backtrack list
213           if (icsCG.getNextChoice() == -1) {
214             int event = cgMap.get(icsCG);
215             LinkedList<Integer[]> choiceLists = backtrackMap.get(event);
216             if (choiceLists != null && choiceLists.peekFirst() != null) {
217               Integer[] choiceList = choiceLists.removeFirst();
218               // Deploy the new choice list for this CG
219               icsCG.setNewValues(choiceList);
220               icsCG.reset();
221             } else {
222               // Set done if this was the last backtrack list
223               icsCG.setDone();
224             }
225           }
226         }
227         // Update and reset the CG if needed (do this for the first time after the analysis)
228         if (!isResetAfterAnalysis && icsCG.getNextChoice() == -1) {
229           resetAllCGs();
230           isResetAfterAnalysis = true;
231         }
232       }
233     }
234   }
235
236   @Override
237   public void stateAdvanced(Search search) {
238     if (debugMode) {
239       id = search.getStateId();
240       depth = search.getDepth();
241       transition = search.getTransition();
242       if (search.isNewState()) {
243         detail = "new";
244       } else {
245         detail = "visited";
246       }
247
248       if (search.isEndState()) {
249         out.println("\n==> DEBUG: This is the last state!\n");
250         detail += " end";
251       }
252       out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
253               " which is " + detail + " Transition: " + transition + "\n");
254     }
255   }
256
257   @Override
258   public void stateBacktracked(Search search) {
259     if (debugMode) {
260       id = search.getStateId();
261       depth = search.getDepth();
262       transition = search.getTransition();
263       detail = null;
264
265       out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
266               " and depth: " + depth + "\n");
267     }
268   }
269
270   @Override
271   public void searchFinished(Search search) {
272     if (debugMode) {
273       out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
274     }
275   }
276
277   // This class compactly stores Read and Write field sets
278   // We store the field name and its object ID
279   // Sharing the same field means the same field name and object ID
280   private class ReadWriteSet {
281     private HashMap<String,Integer> readSet;
282     private HashMap<String,Integer> writeSet;
283
284     public ReadWriteSet() {
285       readSet = new HashMap<>();
286       writeSet = new HashMap<>();
287     }
288
289     public void addReadField(String field, int objectId) {
290       readSet.put(field, objectId);
291     }
292
293     public void addWriteField(String field, int objectId) {
294       writeSet.put(field, objectId);
295     }
296
297     public boolean readFieldExists(String field) {
298       return readSet.containsKey(field);
299     }
300
301     public boolean writeFieldExists(String field) {
302       return writeSet.containsKey(field);
303     }
304
305     public int readFieldObjectId(String field) {
306       return readSet.get(field);
307     }
308
309     public int writeFieldObjectId(String field) {
310       return writeSet.get(field);
311     }
312   }
313
314   private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
315     // Do the analysis to get Read and Write accesses to fields
316     ReadWriteSet rwSet;
317     // We already have an entry
318     if (readWriteFieldsMap.containsKey(choices[currentChoice])) {
319       rwSet = readWriteFieldsMap.get(choices[currentChoice]);
320     } else { // We need to create a new entry
321       rwSet = new ReadWriteSet();
322       readWriteFieldsMap.put(choices[currentChoice], rwSet);
323     }
324     int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef();
325     // Record the field in the map
326     if (executedInsn instanceof WriteInstruction) {
327       // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes
328       for(String str : EXCLUDED_FIELDS_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) {
329         if (fieldClass.startsWith(str)) {
330           return;
331         }
332       }
333       rwSet.addWriteField(fieldClass, objectId);
334     } else if (executedInsn instanceof ReadInstruction) {
335       rwSet.addReadField(fieldClass, objectId);
336     }
337   }
338
339   private boolean recordConflictPair(int currentEvent, int eventNumber) {
340     HashSet<Integer> conflictSet;
341     if (!conflictPairMap.containsKey(currentEvent)) {
342       conflictSet = new HashSet<>();
343       conflictPairMap.put(currentEvent, conflictSet);
344     } else {
345       conflictSet = conflictPairMap.get(currentEvent);
346     }
347     // If this conflict has been recorded before, we return false because
348     // we don't want to service this backtrack point twice
349     if (conflictSet.contains(eventNumber)) {
350       return false;
351     }
352     // If it hasn't been recorded, then do otherwise
353     conflictSet.add(eventNumber);
354     return true;
355   }
356
357   private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
358
359     LinkedList<Integer[]> backtrackChoiceLists;
360     // Check if we have a list for this choice number
361     // If not we create a new one for it
362     if (!backtrackMap.containsKey(conflictEventNumber)) {
363       backtrackChoiceLists = new LinkedList<>();
364       backtrackMap.put(conflictEventNumber, backtrackChoiceLists);
365     } else {
366       backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
367     }
368     // Create a new list of choices for backtrack based on the current choice and conflicting event number
369     // If we have a conflict between 1 and 3, then we create the list {3, 1, 2, 4, 5} for backtrack
370     // The backtrack point is the CG for event number 1 and the list length is one less than the original list
371     // (originally of length 6) since we don't start from event number 0
372     if (!isResetAfterAnalysis) {
373       int maxListLength = choiceUpperBound + 1;
374       int listLength = maxListLength - conflictEventNumber;
375       Integer[] newChoiceList = new Integer[listLength + 1];
376       // Put the conflicting event numbers first and reverse the order
377       newChoiceList[0] = choices[currentChoice];
378       newChoiceList[1] = choices[conflictEventNumber];
379       // Put the rest of the event numbers into the array starting from the minimum to the upper bound
380       for (int i = conflictEventNumber + 1, j = 2; j < listLength; i++) {
381         if (choices[i] != choices[currentChoice]) {
382           newChoiceList[j] = choices[i];
383           j++;
384         }
385       }
386       // Set the last element to '-1' as the end of the sequence
387       newChoiceList[newChoiceList.length - 1] = -1;
388       backtrackChoiceLists.addLast(newChoiceList);
389       // The start index for the recursion is always 1 (from the main branch)
390       choiceListStartIndexMap.put(newChoiceList, 1);
391     } else { // This is a sub-graph
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 }