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.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;
34 import java.io.PrintWriter;
37 import java.util.List;
39 // TODO: Fix for Groovy's model-checking
40 // TODO: This is a setter to change the values of the ChoiceGenerator to implement POR
42 * simple tool to log state changes
44 public class StateReducer extends ListenerAdapter {
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;
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;
72 public StateReducer (Config config, JPF jpf) {
73 debugMode = config.getBoolean("debug_state_transition", false);
74 stateReductionMode = config.getBoolean("activate_state_reduction", true);
76 out = new PrintWriter(System.out, true);
84 isBooleanCGFlipped = false;
85 initializeStateReduction();
88 private void initializeStateReduction() {
89 if (stateReductionMode) {
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<>();
104 public void stateRestored(Search search) {
106 id = search.getStateId();
107 depth = search.getDepth();
108 transition = search.getTransition();
110 out.println("\n==> DEBUG: The state is restored to state with id: " + id + " -- Transition: " + transition +
111 " and depth: " + depth + "\n");
115 //--- the ones we are interested in
117 public void searchStarted(Search search) {
119 out.println("\n==> DEBUG: ----------------------------------- search started" + "\n");
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;
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;
145 icsCG.setNewValues(choices);
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]);
153 // Set done the subsequent CGs
154 // We only need n CGs (n is event numbers)
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()) {
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);
184 public void choiceGeneratorAdvanced (VM vm, ChoiceGenerator<?> currentCG) {
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;
192 initializeStateReduction();
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<>();
208 // Traverse the sub-graphs
209 if (isResetAfterAnalysis) {
210 // Advance choice counter for sub-graphs
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);
222 // Set done if this was the last backtrack list
227 // Update and reset the CG if needed (do this for the first time after the analysis)
228 if (!isResetAfterAnalysis && icsCG.getNextChoice() == -1) {
230 isResetAfterAnalysis = true;
237 public void stateAdvanced(Search search) {
239 id = search.getStateId();
240 depth = search.getDepth();
241 transition = search.getTransition();
242 if (search.isNewState()) {
248 if (search.isEndState()) {
249 out.println("\n==> DEBUG: This is the last state!\n");
252 out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth +
253 " which is " + detail + " Transition: " + transition + "\n");
258 public void stateBacktracked(Search search) {
260 id = search.getStateId();
261 depth = search.getDepth();
262 transition = search.getTransition();
265 out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition +
266 " and depth: " + depth + "\n");
271 public void searchFinished(Search search) {
273 out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
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;
284 public ReadWriteSet() {
285 readSet = new HashMap<>();
286 writeSet = new HashMap<>();
289 public void addReadField(String field, int objectId) {
290 readSet.put(field, objectId);
293 public void addWriteField(String field, int objectId) {
294 writeSet.put(field, objectId);
297 public boolean readFieldExists(String field) {
298 return readSet.containsKey(field);
301 public boolean writeFieldExists(String field) {
302 return writeSet.containsKey(field);
305 public int readFieldObjectId(String field) {
306 return readSet.get(field);
309 public int writeFieldObjectId(String field) {
310 return writeSet.get(field);
314 private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) {
315 // Do the analysis to get Read and Write accesses to fields
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);
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)) {
333 rwSet.addWriteField(fieldClass, objectId);
334 } else if (executedInsn instanceof ReadInstruction) {
335 rwSet.addReadField(fieldClass, objectId);
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);
345 conflictSet = conflictPairMap.get(currentEvent);
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)) {
352 // If it hasn't been recorded, then do otherwise
353 conflictSet.add(eventNumber);
357 private void createBacktrackChoiceList(int currentChoice, int conflictEventNumber) {
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);
366 backtrackChoiceLists = backtrackMap.get(conflictEventNumber);
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];
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];
398 // Put the conflicting events
399 newChoiceList[conflictEventNumber] = choices[currentChoice];
400 newChoiceList[conflictEventNumber + 1] = choices[conflictEventNumber];
402 for(int i = conflictEventNumber + 1, j = conflictEventNumber + 2; j < listLength - 1; i++) {
403 if (choices[i] != choices[currentChoice]) {
404 newChoiceList[j] = choices[i];
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);
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__",
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"};
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)) {
436 // Check against "ends-with" list
437 for(String str : EXCLUDED_FIELDS_ENDS_WITH_LIST) {
438 if (field.endsWith(str)) {
442 // Check against "contains" list
443 for(String str : EXCLUDED_FIELDS_CONTAINS_LIST) {
444 if (field.contains(str)) {
453 public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
454 if (stateReductionMode) {
456 if (choiceCounter > choices.length - 1) {
457 // We do not compute the conflicts for the choice '-1'
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);
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])) {
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!