2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
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
10 * http://www.apache.org/licenses/LICENSE-2.0.
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.
18 package gov.nasa.jpf.listener;
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;
30 import java.io.PrintWriter;
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
38 * simple tool to log state changes
40 public class EfficientStateReducer extends ListenerAdapter {
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;
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<>();
71 public EfficientStateReducer (Config config, JPF jpf) {
72 debugMode = config.getBoolean("debug_state_transition", false);
73 stateReductionMode = config.getBoolean("activate_state_reduction", true);
75 out = new PrintWriter(System.out, true);
83 isBooleanCGFlipped = false;
84 initializeStateReduction();
87 private void initializeStateReduction() {
88 if (stateReductionMode) {
93 isInitialized = false;
94 isResetAfterAnalysis = false;
96 readWriteFieldsMap.clear();
98 conflictPairMap.clear();
99 conflictPairReversedMap.clear();
101 choiceListStartIndexMap.clear();
106 public void stateRestored(Search search) {
108 id = search.getStateId();
109 depth = search.getDepth();
110 transition = search.getTransition();
112 out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
113 " and depth: " + depth + "\n");
117 //--- the ones we are interested in
119 public void searchStarted(Search search) {
121 out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
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;
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;
147 icsCG.setNewValues(choices);
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]);
155 // Set done the subsequent CGs
156 // We only need n CGs (n is event numbers)
163 private void generateSleepSets() {
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++) {
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);
177 // Scour the reversed conflict mapper as well
178 if (conflictPairReversedMap.containsKey(i)) {
179 reversedConflictSet = conflictPairReversedMap.get(i);
181 if (!conflictSet.contains(j) && !reversedConflictSet.contains(j)) {
189 private int getChoiceIndex(int event) {
190 for(int i=0; i<choices.length-1; i++) {
191 if (choices[i] == event) {
195 // Return -1 if not found
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);
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()) {
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);
236 public void choiceGeneratorAdvanced (VM vm, ChoiceGenerator<?> currentCG) {
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;
244 initializeStateReduction();
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);
255 choices = icsCG.getAllChoices();
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);
271 // Set done if this was the last backtrack list
276 // Update and reset the CG if needed (do this for the first time after the analysis)
277 if (!isResetAfterAnalysis && icsCG.getNextChoice() == -1) {
279 generateBacktrackSets();
281 isResetAfterAnalysis = true;
288 public void stateAdvanced(Search search) {
290 id = search.getStateId();
291 depth = search.getDepth();
292 transition = search.getTransition();
293 if (search.isNewState()) {
299 if (search.isEndState()) {
300 out.println("\n==> DEBUG: This is the last state!\n");
303 out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
304 " which is " + detail + " Transition: " + transition + "\n");
309 public void stateBacktracked(Search search) {
311 id = search.getStateId();
312 depth = search.getDepth();
313 transition = search.getTransition();
316 out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
317 " and depth: " + depth + "\n");
322 public void searchFinished(Search search) {
324 out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
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;
335 public ReadWriteSet() {
336 readSet = new HashMap<>();
337 writeSet = new HashMap<>();
340 public void addReadField(String field, int objectId) {
341 readSet.put(field, objectId);
344 public void addWriteField(String field, int objectId) {
345 writeSet.put(field, objectId);
348 public boolean readFieldExists(String field) {
349 return readSet.containsKey(field);
352 public boolean writeFieldExists(String field) {
353 return writeSet.containsKey(field);
356 public int readFieldObjectId(String field) {
357 return readSet.get(field);
360 public int writeFieldObjectId(String field) {
361 return writeSet.get(field);
365 private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
366 // Do the analysis to get Read and Write accesses to fields
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);
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)) {
384 rwSet.addWriteField(fieldClass, objectId);
385 } else if (executedInsn instanceof ReadInstruction) {
386 rwSet.addReadField(fieldClass, objectId);
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);
396 conflictSet = confPairMap.get(currentEvent);
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)) {
403 // If it hasn't been recorded, then do otherwise
404 conflictSet.add(eventNumber);
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);
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]);
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);
438 choiceListStartIndexMap.put(newChoiceArray, conflictEventNumber + 1);
442 private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
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);
456 backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
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]);
469 // Put the conflicting events
470 int currentChoiceCurrentNumber = getChoiceIndex(currentChoice);
471 completeBacktrackChoiceList(newChoiceList, currentChoiceCurrentNumber, conflictEventCurrentNumber,
472 backtrackChoiceLists);
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__",
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"};
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)) {
496 // Check against "ends-with" list
497 for(String str : EXCLUDED_FIELDS_ENDS_WITH_LIST) {
498 if (field.endsWith(str)) {
502 // Check against "contains" list
503 for(String str : EXCLUDED_FIELDS_CONTAINS_LIST) {
504 if (field.contains(str)) {
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'
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);
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])) {
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!