import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
+import gov.nasa.jpf.jvm.bytecode.INVOKEINTERFACE;
+import gov.nasa.jpf.jvm.bytecode.JVMFieldInstruction;
import gov.nasa.jpf.search.Search;
-import gov.nasa.jpf.jvm.bytecode.*;
import gov.nasa.jpf.vm.*;
import gov.nasa.jpf.vm.bytecode.ReadInstruction;
import gov.nasa.jpf.vm.bytecode.WriteInstruction;
import gov.nasa.jpf.vm.choice.IntIntervalGenerator;
import java.io.FileWriter;
+import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.Field;
import java.util.*;
import java.util.logging.Logger;
-import java.io.IOException;
/**
* This a DPOR implementation for event-driven applications with loops that create cycles of state matching
private int choiceCounter;
private int maxEventChoice;
// Data structure to track the events seen by each state to track cycles (containing all events) for termination
- private HashSet<Integer> currVisitedStates; // States being visited in the current execution
+ private HashMap<Integer,Integer> currVisitedStates; // States visited in the current execution (maps to frequency)
private HashSet<Integer> justVisitedStates; // States just visited in the previous choice/event
private HashSet<Integer> prevVisitedStates; // States visited in the previous execution
private HashSet<ClassInfo> nonRelevantClasses;// Class info objects of non-relevant classes
private HashMap<Integer, LinkedList<BacktrackExecution>> backtrackMap; // Track created backtracking points
private PriorityQueue<Integer> backtrackStateQ; // Heap that returns the latest state
private Execution currentExecution; // Holds the information about the current execution
- private HashSet<String> doneBacktrackSet; // Record state ID and trace already constructed
+ private HashMap<Integer, HashSet<Integer>> doneBacktrackMap; // Record state ID and trace already constructed
private HashMap<Integer, RestorableVMState> restorableStateMap; // Maps state IDs to the restorable state object
private RGraph rGraph; // R-Graph for past executions
private boolean isEndOfExecution;
// Statistics
- private int numOfConflicts;
private int numOfTransitions;
-
+
public DPORStateReducer(Config config, JPF jpf) {
verboseMode = config.getBoolean("printout_state_transition", false);
stateReductionMode = config.getBoolean("activate_state_reduction", true);
}
}
isBooleanCGFlipped = false;
- numOfConflicts = 0;
numOfTransitions = 0;
nonRelevantClasses = new HashSet<>();
nonRelevantFields = new HashSet<>();
@Override
public void searchFinished(Search search) {
- if (stateReductionMode) {
- // Number of conflicts = first trace + subsequent backtrack points
- numOfConflicts += 1 + doneBacktrackSet.size();
- }
if (verboseMode) {
out.println("\n==> DEBUG: ----------------------------------- search finished");
out.println("\n==> DEBUG: State reduction mode : " + stateReductionMode);
- out.println("\n==> DEBUG: Number of conflicts : " + numOfConflicts);
out.println("\n==> DEBUG: Number of transitions : " + numOfTransitions);
out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n");
fileWriter.println("==> DEBUG: State reduction mode : " + stateReductionMode);
- fileWriter.println("==> DEBUG: Number of conflicts : " + numOfConflicts);
fileWriter.println("==> DEBUG: Number of transitions : " + numOfTransitions);
fileWriter.println();
fileWriter.close();
// Initialize with necessary information from the CG
if (nextCG instanceof IntChoiceFromSet) {
IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG;
+ // Tell JPF that we are performing DPOR
+ icsCG.setDpor();
if (!isEndOfExecution) {
// Check if CG has been initialized, otherwise initialize it
Integer[] cgChoices = icsCG.getAllChoices();
if (!isBooleanCGFlipped) {
isBooleanCGFlipped = true;
} else {
- // Number of conflicts = first trace + subsequent backtrack points
- numOfConflicts = 1 + doneBacktrackSet.size();
// Allocate new objects for data structure when the boolean is flipped from "false" to "true"
initializeStatesVariables();
}
private ArrayList<TransitionEvent> executionTrace; // The BacktrackPoint objects of this execution
private boolean isNew; // Track if this is the first time it is accessed
private HashMap<Integer, ReadWriteSet> readWriteFieldsMap; // Record fields that are accessed
- private HashMap<Integer, TransitionEvent> stateToTransitionMap; // For O(1) access to backtrack point
public Execution() {
cgToChoiceMap = new HashMap<>();
executionTrace = new ArrayList<>();
isNew = true;
readWriteFieldsMap = new HashMap<>();
- stateToTransitionMap = new HashMap<>();
}
public void addTransition(TransitionEvent newBacktrackPoint) {
return executionTrace.get(0);
}
- public HashMap<Integer, ReadWriteSet> getReadWriteFieldsMap() {
- return readWriteFieldsMap;
+ public TransitionEvent getLastTransition() {
+ return executionTrace.get(executionTrace.size() - 1);
}
- public TransitionEvent getTransitionFromState(int stateId) {
- if (stateToTransitionMap.containsKey(stateId)) {
- return stateToTransitionMap.get(stateId);
- }
- // Return the latest transition for unseen states (that have just been encountered in this transition)
- return executionTrace.get(executionTrace.size() - 1);
+ public HashMap<Integer, ReadWriteSet> getReadWriteFieldsMap() {
+ return readWriteFieldsMap;
}
public boolean isNew() {
public void mapCGToChoice(IntChoiceFromSet icsCG, int choice) {
cgToChoiceMap.put(icsCG, choice);
}
-
- public void mapStateToTransition(int stateId, TransitionEvent transition) {
- stateToTransitionMap.put(stateId, transition);
- }
}
// This class compactly stores a predecessor
}
public HashSet<TransitionEvent> getReachableTransitionsAtState(int stateId) {
+ if (!graph.containsKey(stateId)) {
+ // This is a loop from a transition to itself, so just return the current transition
+ HashSet<TransitionEvent> transitionSet = new HashSet<>();
+ transitionSet.add(currentExecution.getLastTransition());
+ return transitionSet;
+ }
return graph.get(stateId);
}
HashSet<TransitionEvent> reachableTransitions = new HashSet<>();
// All transitions from states higher than the given state ID (until the highest state ID) are reachable
for(int stId = stateId; stId <= hiStateId; stId++) {
- reachableTransitions.addAll(graph.get(stId));
+ // We might encounter state IDs from the first round of Boolean CG
+ // The second round of Boolean CG should consider these new states
+ if (graph.containsKey(stId)) {
+ reachableTransitions.addAll(graph.get(stId));
+ }
}
return reachableTransitions;
}
private int choiceCounter; // Choice counter at this transition
private Execution execution; // The execution where this transition belongs
private HashSet<Predecessor> predecessors; // Maps incoming events/transitions (execution and choice)
+ private HashMap<Execution, HashSet<Integer>> recordedPredecessors;
+ // Memorize event and choice number to not record them twice
private int stateId; // State at this transition
private IntChoiceFromSet transitionCG; // CG at this transition
choiceCounter = 0;
execution = null;
predecessors = new HashSet<>();
+ recordedPredecessors = new HashMap<>();
stateId = 0;
transitionCG = null;
}
public IntChoiceFromSet getTransitionCG() { return transitionCG; }
+ private boolean isRecordedPredecessor(Execution execution, int choice) {
+ // See if we have recorded this predecessor earlier
+ HashSet<Integer> recordedChoices;
+ if (recordedPredecessors.containsKey(execution)) {
+ recordedChoices = recordedPredecessors.get(execution);
+ if (recordedChoices.contains(choice)) {
+ return true;
+ }
+ } else {
+ recordedChoices = new HashSet<>();
+ recordedPredecessors.put(execution, recordedChoices);
+ }
+ // Record the choice if we haven't seen it
+ recordedChoices.add(choice);
+
+ return false;
+ }
+
public void recordPredecessor(Execution execution, int choice) {
- predecessors.add(new Predecessor(choice, execution));
+ if (!isRecordedPredecessor(execution, choice)) {
+ predecessors.add(new Predecessor(choice, execution));
+ }
}
public void setChoice(int cho) {
// Add new transition to the current execution and map it in R-Graph
for (Integer stId : justVisitedStates) { // Map this transition to all the previously passed states
rGraph.addReachableTransition(stId, transition);
- currentExecution.mapStateToTransition(stId, transition);
}
currentExecution.mapCGToChoice(icsCG, choiceCounter);
// Store restorable state object for this state (always store the latest)
- RestorableVMState restorableState = vm.getRestorableState();
- restorableStateMap.put(stateId, restorableState);
+ if (!restorableStateMap.containsKey(stateId)) {
+ RestorableVMState restorableState = vm.getRestorableState();
+ restorableStateMap.put(stateId, restorableState);
+ }
}
private TransitionEvent setupTransition(IntChoiceFromSet icsCG, int stateId, int choiceIndex) {
choiceCounter = 0;
maxEventChoice = 0;
// Cycle tracking
- currVisitedStates = new HashSet<>();
+ currVisitedStates = new HashMap<>();
justVisitedStates = new HashSet<>();
prevVisitedStates = new HashSet<>();
stateToEventMap = new HashMap<>();
backtrackStateQ = new PriorityQueue<>(Collections.reverseOrder());
currentExecution = new Execution();
currentExecution.addTransition(new TransitionEvent()); // Always start with 1 backtrack point
- doneBacktrackSet = new HashSet<>();
+ doneBacktrackMap = new HashMap<>();
rGraph = new RGraph();
// Booleans
isEndOfExecution = false;
private boolean terminateCurrentExecution() {
// We need to check all the states that have just been visited
// Often a transition (choice/event) can result into forwarding/backtracking to a number of states
+ boolean terminate = false;
for(Integer stateId : justVisitedStates) {
- if (prevVisitedStates.contains(stateId) || completeFullCycle(stateId)) {
- return true;
+ // We only flip the value of terminate once ...
+ if (!terminate && prevVisitedStates.contains(stateId) || completeFullCycle(stateId)) {
+ updateBacktrackSetsFromGraph(stateId);
+ terminate = true;
+ }
+ // If frequency > 1 then this means we have visited this stateId more than once
+ if (currVisitedStates.containsKey(stateId) && currVisitedStates.get(stateId) > 1) {
+ updateBacktrackSetsFromGraph(stateId);
}
}
- return false;
+ return terminate;
}
private void updateStateInfo(Search search) {
HashSet<Integer> eventSet = new HashSet<>();
stateToEventMap.put(stateId, eventSet);
}
- analyzeReachabilityAndCreateBacktrackPoints(search.getVM(), stateId);
+ addPredecessorToRevisitedState(search.getVM(), stateId);
justVisitedStates.add(stateId);
if (!prevVisitedStates.contains(stateId)) {
// It is a currently visited states if the state has not been seen in previous executions
- currVisitedStates.add(stateId);
+ int frequency = 0;
+ if (currVisitedStates.containsKey(stateId)) {
+ frequency = currVisitedStates.get(stateId);
+ }
+ currVisitedStates.put(stateId, frequency + 1); // Increment frequency counter
}
}
// Create a new list of choices for backtrack based on the current choice and conflicting event number
// E.g. if we have a conflict between 1 and 3, then we create the list {3, 1, 0, 2}
// for the original set {0, 1, 2, 3}
+
+ // execution/currentChoice represent the event/transaction that will be put into the backtracking set of
+ // conflictExecution/conflictChoice
Integer[] newChoiceList = new Integer[refChoices.length];
ArrayList<TransitionEvent> currentTrace = execution.getExecutionTrace();
ArrayList<TransitionEvent> conflictTrace = conflictExecution.getExecutionTrace();
icsCG.setDone();
}
// Save all the visited states when starting a new execution of trace
- prevVisitedStates.addAll(currVisitedStates);
+ prevVisitedStates.addAll(currVisitedStates.keySet());
// This marks a transitional period to the new CG
isEndOfExecution = true;
}
private boolean isConflictFound(Execution execution, int reachableChoice, Execution conflictExecution, int conflictChoice,
ReadWriteSet currRWSet) {
+ // conflictExecution/conflictChoice represent a predecessor event/transaction that can potentially have a conflict
ArrayList<TransitionEvent> executionTrace = execution.getExecutionTrace();
ArrayList<TransitionEvent> conflictTrace = conflictExecution.getExecutionTrace();
HashMap<Integer, ReadWriteSet> confRWFieldsMap = conflictExecution.getReadWriteFieldsMap();
// Check if this trace is already constructed
private boolean isTraceAlreadyConstructed(int firstChoice, int stateId) {
// Concatenate state ID and only the first event in the string, e.g., "1:1 for the trace 10234 at state 1"
- // TODO: THIS IS AN OPTIMIZATION!
- // This is the optimized version because after we execute, e.g., the trace 1:10234, we don't need to try
- // another trace that starts with event 1 at state 1, e.g., the trace 1:13024
- // The second time this event 1 is explored, it will generate the same state as the first one
- StringBuilder sb = new StringBuilder();
- sb.append(stateId);
- sb.append(':');
- sb.append(firstChoice);
// Check if the trace has been constructed as a backtrack point for this state
- if (doneBacktrackSet.contains(sb.toString())) {
- return true;
+ // TODO: THIS IS AN OPTIMIZATION!
+ HashSet<Integer> choiceSet;
+ if (doneBacktrackMap.containsKey(stateId)) {
+ choiceSet = doneBacktrackMap.get(stateId);
+ if (choiceSet.contains(firstChoice)) {
+ return true;
+ }
+ } else {
+ choiceSet = new HashSet<>();
+ doneBacktrackMap.put(stateId, choiceSet);
}
- doneBacktrackSet.add(sb.toString());
+ choiceSet.add(firstChoice);
+
return false;
}
choices = icsCG.getAllChoices();
refChoices = copyChoices(choices);
// Clear data structures
- currVisitedStates = new HashSet<>();
+ currVisitedStates = new HashMap<>();
stateToEventMap = new HashMap<>();
isEndOfExecution = false;
}
// Memorize visited TransitionEvent object while performing backward DFS to avoid getting caught up in a cycle
HashSet<TransitionEvent> visited = new HashSet<>();
// Update backtrack set recursively
- // TODO: The following is the call to the original version of the method
-// updateBacktrackSetRecursive(execution, currentChoice, execution, currentChoice, currRWSet, visited);
- // TODO: The following is the call to the version of the method with pushing up happens-before transitions
- updateBacktrackSetRecursive(execution, currentChoice, execution, currentChoice, execution, currentChoice, currRWSet, visited);
+ updateBacktrackSetRecursive(execution, currentChoice, execution, currentChoice, currRWSet, visited);
}
-// TODO: This is the original version of the recursive method
-// private void updateBacktrackSetRecursive(Execution execution, int currentChoice,
-// Execution conflictExecution, int conflictChoice,
-// ReadWriteSet currRWSet, HashSet<TransitionEvent> visited) {
-// // Halt when we have found the first read/write conflicts for all memory locations
-// if (currRWSet.isEmpty()) {
-// return;
-// }
-// TransitionEvent confTrans = conflictExecution.getExecutionTrace().get(conflictChoice);
-// // Halt when we have visited this transition (in a cycle)
-// if (visited.contains(confTrans)) {
-// return;
-// }
-// visited.add(confTrans);
-// // Explore all predecessors
-// for (Predecessor predecessor : confTrans.getPredecessors()) {
-// // Get the predecessor (previous conflict choice)
-// conflictChoice = predecessor.getChoice();
-// conflictExecution = predecessor.getExecution();
-// // Check if a conflict is found
-// if (isConflictFound(execution, currentChoice, conflictExecution, conflictChoice, currRWSet)) {
-// createBacktrackingPoint(execution, currentChoice, conflictExecution, conflictChoice);
-// }
-// // Continue performing DFS if conflict is not found
-// updateBacktrackSetRecursive(execution, currentChoice, conflictExecution, conflictChoice, currRWSet, visited);
-// }
-// }
-
- // TODO: This is the version of the method with pushing up happens-before transitions
private void updateBacktrackSetRecursive(Execution execution, int currentChoice,
Execution conflictExecution, int conflictChoice,
- Execution hbExecution, int hbChoice,
ReadWriteSet currRWSet, HashSet<TransitionEvent> visited) {
// Halt when we have found the first read/write conflicts for all memory locations
if (currRWSet.isEmpty()) {
return;
}
- TransitionEvent confTrans = conflictExecution.getExecutionTrace().get(conflictChoice);
+ TransitionEvent currTrans = execution.getExecutionTrace().get(currentChoice);
// Halt when we have visited this transition (in a cycle)
- if (visited.contains(confTrans)) {
+ if (visited.contains(currTrans)) {
return;
}
- visited.add(confTrans);
+ visited.add(currTrans);
// Explore all predecessors
- for (Predecessor predecessor : confTrans.getPredecessors()) {
+ for (Predecessor predecessor : currTrans.getPredecessors()) {
// Get the predecessor (previous conflict choice)
- conflictChoice = predecessor.getChoice();
- conflictExecution = predecessor.getExecution();
+ int predecessorChoice = predecessor.getChoice();
+ Execution predecessorExecution = predecessor.getExecution();
// Push up one happens-before transition
- int pushedChoice = hbChoice;
- Execution pushedExecution = hbExecution;
+ int newConflictChoice = conflictChoice;
+ Execution newConflictExecution = conflictExecution;
// Check if a conflict is found
- if (isConflictFound(execution, currentChoice, conflictExecution, conflictChoice, currRWSet)) {
- createBacktrackingPoint(pushedExecution, pushedChoice, conflictExecution, conflictChoice);
- pushedChoice = conflictChoice;
- pushedExecution = conflictExecution;
+ if (isConflictFound(conflictExecution, conflictChoice, predecessorExecution, predecessorChoice, currRWSet)) {
+ createBacktrackingPoint(conflictExecution, conflictChoice, predecessorExecution, predecessorChoice);
+ newConflictChoice = predecessorChoice;
+ newConflictExecution = predecessorExecution;
}
// Continue performing DFS if conflict is not found
- updateBacktrackSetRecursive(execution, currentChoice, conflictExecution, conflictChoice,
- pushedExecution, pushedChoice, currRWSet, visited);
+ updateBacktrackSetRecursive(predecessorExecution, predecessorChoice, newConflictExecution, newConflictChoice,
+ currRWSet, visited);
}
}
// --- Functions related to the reachability analysis when there is a state match
- private void analyzeReachabilityAndCreateBacktrackPoints(VM vm, int stateId) {
+ private void addPredecessorToRevisitedState(VM vm, int stateId) {
// Perform this analysis only when:
// 1) this is not during a switch to a new execution,
// 2) at least 2 choices/events have been explored (choiceCounter > 1),
// 3) state > 0 (state 0 is for boolean CG)
if (!isEndOfExecution && choiceCounter > 1 && stateId > 0) {
- if (currVisitedStates.contains(stateId)) {
- // Get the backtrack point from the current execution
- TransitionEvent transition = currentExecution.getTransitionFromState(stateId);
- transition.recordPredecessor(currentExecution, choiceCounter - 1);
- updateBacktrackSetsFromPreviousExecution(stateId);
- } else if (prevVisitedStates.contains(stateId)) { // We visit a state in a previous execution
- // Update past executions with a predecessor
+ if ((currVisitedStates.containsKey(stateId) && currVisitedStates.get(stateId) > 1) ||
+ prevVisitedStates.contains(stateId)) {
+ // Update reachable transitions in the graph with a predecessor
HashSet<TransitionEvent> reachableTransitions = rGraph.getReachableTransitionsAtState(stateId);
- for(TransitionEvent transition : reachableTransitions) {
+ for (TransitionEvent transition : reachableTransitions) {
transition.recordPredecessor(currentExecution, choiceCounter - 1);
}
- updateBacktrackSetsFromPreviousExecution(stateId);
}
}
}
// Update the backtrack sets from previous executions
- private void updateBacktrackSetsFromPreviousExecution(int stateId) {
+ private void updateBacktrackSetsFromGraph(int stateId) {
// Collect all the reachable transitions from R-Graph
HashSet<TransitionEvent> reachableTransitions = rGraph.getReachableTransitions(stateId);
for(TransitionEvent transition : reachableTransitions) {