From: rtrimana Date: Wed, 1 Apr 2020 23:44:57 +0000 (-0700) Subject: Reimplementing DPOR Phase 1: First trace execution, cycle detection, R/W field access... X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;ds=sidebyside;h=cbebe94f5164064cb48640d6025de04fc47d637f;p=jpf-core.git Reimplementing DPOR Phase 1: First trace execution, cycle detection, R/W field access analysis. --- diff --git a/main.jpf b/main.jpf index 0948c38..0665ff2 100644 --- a/main.jpf +++ b/main.jpf @@ -46,7 +46,7 @@ apps=App1,App2 #debug_mode=true # Debug mode for StateReducer -#printout_state_transition=true +printout_state_transition=true #activate_state_reduction=true # Timeout in minutes (default is 0 which means no timeout) diff --git a/src/main/gov/nasa/jpf/listener/DPORStateReducer.java b/src/main/gov/nasa/jpf/listener/DPORStateReducer.java index 746adfe..a688754 100644 --- a/src/main/gov/nasa/jpf/listener/DPORStateReducer.java +++ b/src/main/gov/nasa/jpf/listener/DPORStateReducer.java @@ -48,7 +48,7 @@ import java.util.*; */ public class DPORStateReducer extends ListenerAdapter { - // Debug info fields + // Information printout fields for verbose mode private boolean verboseMode; private boolean stateReductionMode; private final PrintWriter out; @@ -57,6 +57,25 @@ public class DPORStateReducer extends ListenerAdapter { private int id; private Transition transition; + // DPOR-related fields + private Integer[] choices; + private Integer[] refChoices; + private int choiceCounter; + private int maxEventChoice; + // Record CGs for backtracking points + private List cgList; + // Data structure to track the events seen by each state to track cycles (containing all events) for termination + private HashMap> stateToEventMap; + private HashSet justVisitedStates; // States just visited in the previous choice/event + private HashSet prevVisitedStates; // States visited in the previous execution + private HashSet currVisitedStates; // States being visited in the current execution + // Data structure to analyze field Read/Write accesses + private HashMap readWriteFieldsMap; + private HashMap> conflictPairMap; + + // Boolean states + private boolean isBooleanCGFlipped; + public DPORStateReducer(Config config, JPF jpf) { verboseMode = config.getBoolean("printout_state_transition", false); stateReductionMode = config.getBoolean("activate_state_reduction", true); @@ -65,6 +84,20 @@ public class DPORStateReducer extends ListenerAdapter { } else { out = null; } + // DPOR-related + choices = null; + refChoices = null; + choiceCounter = 0; + maxEventChoice = 0; + cgList = new ArrayList<>(); + stateToEventMap = new HashMap<>(); + justVisitedStates = new HashSet<>(); + prevVisitedStates = new HashSet<>(); + currVisitedStates = new HashSet<>(); + readWriteFieldsMap = new HashMap<>(); + conflictPairMap = new HashMap<>(); + // Booleans + isBooleanCGFlipped = false; } @Override @@ -105,6 +138,9 @@ public class DPORStateReducer extends ListenerAdapter { out.println("\n==> DEBUG: The state is forwarded to state with id: " + id + " with depth: " + depth + " which is " + detail + " Transition: " + transition + "\n"); } + if (stateReductionMode) { + updateStateInfo(search); + } } @Override @@ -118,6 +154,9 @@ public class DPORStateReducer extends ListenerAdapter { out.println("\n==> DEBUG: The state is backtracked to state with id: " + id + " -- Transition: " + transition + " and depth: " + depth + "\n"); } + if (stateReductionMode) { + updateStateInfo(search); + } } @Override @@ -126,4 +165,402 @@ public class DPORStateReducer extends ListenerAdapter { out.println("\n==> DEBUG: ----------------------------------- search finished" + "\n"); } } + + @Override + public void choiceGeneratorRegistered(VM vm, ChoiceGenerator nextCG, ThreadInfo currentThread, Instruction executedInstruction) { + if (stateReductionMode) { + // Initialize with necessary information from the CG + if (nextCG instanceof IntChoiceFromSet) { + IntChoiceFromSet icsCG = (IntChoiceFromSet) nextCG; + // Check if CG has been initialized, otherwise initialize it + Integer[] cgChoices = icsCG.getAllChoices(); + // Record the events (from choices) + if (choices == null) { + choices = cgChoices; + // Make a copy of choices as reference + refChoices = copyChoices(choices); + // Record the max event choice (the last element of the choice array) + maxEventChoice = choices[choices.length - 1]; + } + // Use a modulo since choiceCounter is going to keep increasing + int choiceIndex = choiceCounter % choices.length; + icsCG.advance(choices[choiceIndex]); + // Index the ChoiceGenerator to set backtracking points + cgList.add(icsCG); + } + } + } + + @Override + public void choiceGeneratorAdvanced(VM vm, ChoiceGenerator currentCG) { + + if (stateReductionMode) { + // Check the boolean CG and if it is flipped, we are resetting the analysis +// if (currentCG instanceof BooleanChoiceGenerator) { +// if (!isBooleanCGFlipped) { +// isBooleanCGFlipped = true; +// } else { +// initializeStateReduction(); +// } +// } + // Check every choice generated and ensure fair scheduling! + if (currentCG instanceof IntChoiceFromSet) { + IntChoiceFromSet icsCG = (IntChoiceFromSet) currentCG; + // If we don't see a fair scheduling of events/choices then we have to enforce it + checkAndEnforceFairScheduling(icsCG); + // Map state to event + mapStateToEvent(icsCG.getNextChoice()); + // Check if we have seen this state or this state contains cycles that involve all events + if (terminateCurrentExecution()) { + exploreNextBacktrackSets(icsCG); + } + justVisitedStates.clear(); + choiceCounter++; + } + } + } + + @Override + public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) { + if (stateReductionMode) { + // Has to be initialized and a integer CG + ChoiceGenerator cg = vm.getChoiceGenerator(); + if (cg instanceof IntChoiceFromSet || cg instanceof IntIntervalGenerator) { + int currentChoice = choiceCounter - 1; // Accumulative choice w.r.t the current trace + //if (getCurrentChoice(vm) < 0) { // If choice is -1 then skip + if (currentChoice < 0) { // If choice is -1 then skip + return; + } + // Record accesses from executed instructions + if (executedInsn instanceof JVMFieldInstruction) { + // Analyze only after being initialized + String fieldClass = ((JVMFieldInstruction) executedInsn).getFieldInfo().getFullName(); + // We don't care about libraries + if (!isFieldExcluded(fieldClass)) { + analyzeReadWriteAccesses(executedInsn, fieldClass, currentChoice); + } + } else if (executedInsn instanceof INVOKEINTERFACE) { + // Handle the read/write accesses that occur through iterators + analyzeReadWriteAccesses(executedInsn, ti, currentChoice); + } + // Analyze conflicts from next instructions + if (nextInsn instanceof JVMFieldInstruction) { + // Skip the constructor because it is called once and does not have shared access with other objects + if (!nextInsn.getMethodInfo().getName().equals("")) { + String fieldClass = ((JVMFieldInstruction) nextInsn).getFieldInfo().getFullName(); + if (!isFieldExcluded(fieldClass)) { + // Check for conflict (go backward from current choice and get the first conflict) + for (int evtCntr = currentChoice - 1; evtCntr >= 0; evtCntr--) { + if (!readWriteFieldsMap.containsKey(evtCntr)) { // Skip if this event does not have any Read/Write set + continue; + } + ReadWriteSet rwSet = readWriteFieldsMap.get(evtCntr); + int currObjId = ((JVMFieldInstruction) nextInsn).getFieldInfo().getClassInfo().getClassObjectRef(); + // Check for conflicts with Write fields for both Read and Write instructions + if (((nextInsn instanceof WriteInstruction || nextInsn instanceof ReadInstruction) && + rwSet.writeFieldExists(fieldClass) && rwSet.writeFieldObjectId(fieldClass) == currObjId) || + (nextInsn instanceof WriteInstruction && rwSet.readFieldExists(fieldClass) && + rwSet.readFieldObjectId(fieldClass) == currObjId)) { + // Check and record a backtrack set for just once! + if (successfullyRecordConflictPair(currentChoice, evtCntr)) { + // Lines 4-8 of the algorithm in the paper page 11 (see the heading note above) +// if (vm.isNewState() || isReachableInVODGraph(refChoices[currentChoice], refChoices[currentChoice-1])) { +// createBacktrackChoiceList(currentChoice, eventNumber); +// } + } + } + } + } + } + } + } + } + } + + + // == HELPERS + // -- INNER CLASS + // This class compactly stores Read and Write field sets + // We store the field name and its object ID + // Sharing the same field means the same field name and object ID + private class ReadWriteSet { + private HashMap readSet; + private HashMap writeSet; + + public ReadWriteSet() { + readSet = new HashMap<>(); + writeSet = new HashMap<>(); + } + + public void addReadField(String field, int objectId) { + readSet.put(field, objectId); + } + + public void addWriteField(String field, int objectId) { + writeSet.put(field, objectId); + } + + public boolean readFieldExists(String field) { + return readSet.containsKey(field); + } + + public boolean writeFieldExists(String field) { + return writeSet.containsKey(field); + } + + public int readFieldObjectId(String field) { + return readSet.get(field); + } + + public int writeFieldObjectId(String field) { + return writeSet.get(field); + } + } + + // -- CONSTANTS + private final static String DO_CALL_METHOD = "doCall"; + // We exclude fields that come from libraries (Java and Groovy), and also the infrastructure + private final static String[] EXCLUDED_FIELDS_CONTAINS_LIST = {"_closure"}; + private final static String[] EXCLUDED_FIELDS_ENDS_WITH_LIST = + // Groovy library created fields + {"stMC", "callSiteArray", "metaClass", "staticClassInfo", "__constructor__", + // Infrastructure + "sendEvent", "Object", "reference", "location", "app", "state", "log", "functionList", "objectList", + "eventList", "valueList", "settings", "printToConsole", "app1", "app2"}; + private final static String[] EXCLUDED_FIELDS_STARTS_WITH_LIST = + // Java and Groovy libraries + { "java", "org", "sun", "com", "gov", "groovy"}; + private final static String[] EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST = {"Event"}; + private final static String GET_PROPERTY_METHOD = + "invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.callGetProperty"; + private final static String GROOVY_CALLSITE_LIB = "org.codehaus.groovy.runtime.callsite"; + private final static String JAVA_INTEGER = "int"; + private final static String JAVA_STRING_LIB = "java.lang.String"; + + // -- FUNCTIONS + private void checkAndEnforceFairScheduling(IntChoiceFromSet icsCG) { + // Check the next choice and if the value is not the same as the expected then force the expected value + int choiceIndex = choiceCounter % refChoices.length; + int nextChoice = icsCG.getNextChoice(); + if (refChoices[choiceIndex] != nextChoice) { + int expectedChoice = refChoices[choiceIndex]; + int currCGIndex = icsCG.getNextChoiceIndex(); + if ((currCGIndex >= 0) && (currCGIndex < refChoices.length)) { + icsCG.setChoice(currCGIndex, expectedChoice); + } + } + } + + private Integer[] copyChoices(Integer[] choicesToCopy) { + + Integer[] copyOfChoices = new Integer[choicesToCopy.length]; + System.arraycopy(choicesToCopy, 0, copyOfChoices, 0, choicesToCopy.length); + return copyOfChoices; + } + + // --- Functions related to cycle detection + + // Detect cycles in the current execution/trace + // We terminate the execution iff: + // (1) the state has been visited in the current execution + // (2) the state has one or more cycles that involve all the events + // With simple approach we only need to check for a re-visited state. + // Basically, we have to check that we have executed all events between two occurrences of such state. + private boolean containsCyclesWithAllEvents(int stId) { + + // False if the state ID hasn't been recorded + if (!stateToEventMap.containsKey(stId)) { + return false; + } + HashSet visitedEvents = stateToEventMap.get(stId); + // Check if this set contains all the event choices + // If not then this is not the terminating condition + for(int i=0; i<=maxEventChoice; i++) { + if (!visitedEvents.contains(i)) { + return false; + } + } + return true; + } + + private void mapStateToEvent(int nextChoiceValue) { + // Update all states with this event/choice + // This means that all past states now see this transition + Set stateSet = stateToEventMap.keySet(); + for(Integer stateId : stateSet) { + HashSet eventSet = stateToEventMap.get(stateId); + eventSet.add(nextChoiceValue); + } + } + + 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 + for(Integer stateId : justVisitedStates) { + if (prevVisitedStates.contains(stateId) || containsCyclesWithAllEvents(stateId)) { + return true; + } + } + return false; + } + + private void updateStateInfo(Search search) { + // Update the state variables + // Line 19 in the paper page 11 (see the heading note above) + int stateId = search.getStateId(); + currVisitedStates.add(stateId); + // Insert state ID into the map if it is new + if (!stateToEventMap.containsKey(stateId)) { + HashSet eventSet = new HashSet<>(); + stateToEventMap.put(stateId, eventSet); + } + justVisitedStates.add(stateId); + } + + // --- Functions related to Read/Write access analysis on shared fields + + // Analyze Read/Write accesses that are directly invoked on fields + private void analyzeReadWriteAccesses(Instruction executedInsn, String fieldClass, int currentChoice) { + // Do the analysis to get Read and Write accesses to fields + ReadWriteSet rwSet = getReadWriteSet(currentChoice); + int objectId = ((JVMFieldInstruction) executedInsn).getFieldInfo().getClassInfo().getClassObjectRef(); + // Record the field in the map + if (executedInsn instanceof WriteInstruction) { + // Exclude certain field writes because of infrastructure needs, e.g., Event class field writes + for (String str : EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST) { + if (fieldClass.startsWith(str)) { + return; + } + } + rwSet.addWriteField(fieldClass, objectId); + } else if (executedInsn instanceof ReadInstruction) { + rwSet.addReadField(fieldClass, objectId); + } + } + + // Analyze Read accesses that are indirect (performed through iterators) + // These accesses are marked by certain bytecode instructions, e.g., INVOKEINTERFACE + private void analyzeReadWriteAccesses(Instruction instruction, ThreadInfo ti, int currentChoice) { + // Get method name + INVOKEINTERFACE insn = (INVOKEINTERFACE) instruction; + if (insn.toString().startsWith(GET_PROPERTY_METHOD) && + insn.getMethodInfo().getName().equals(DO_CALL_METHOD)) { + // Extract info from the stack frame + StackFrame frame = ti.getTopFrame(); + int[] frameSlots = frame.getSlots(); + // Get the Groovy callsite library at index 0 + ElementInfo eiCallsite = VM.getVM().getHeap().get(frameSlots[0]); + if (!eiCallsite.getClassInfo().getName().startsWith(GROOVY_CALLSITE_LIB)) { + return; + } + // Get the iterated object whose property is accessed + ElementInfo eiAccessObj = VM.getVM().getHeap().get(frameSlots[1]); + // We exclude library classes (they start with java, org, etc.) and some more + String objClassName = eiAccessObj.getClassInfo().getName(); + if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, objClassName) || + excludeThisForItStartsWith(EXCLUDED_FIELDS_READ_WRITE_INSTRUCTIONS_STARTS_WITH_LIST, objClassName)) { + return; + } + // Extract fields from this object and put them into the read write + int numOfFields = eiAccessObj.getNumberOfFields(); + for(int i=0; i currentCG = vm.getChoiceGenerator(); + // This is the main event CG + if (currentCG instanceof IntChoiceFromSet) { + return ((IntChoiceFromSet) currentCG).getNextChoiceIndex(); + } else { + // This is the interval CG used in device handlers + ChoiceGenerator parentCG = ((IntIntervalGenerator) currentCG).getPreviousChoiceGenerator(); + return ((IntChoiceFromSet) parentCG).getNextChoiceIndex(); + } + } + + private ReadWriteSet getReadWriteSet(int currentChoice) { + // Do the analysis to get Read and Write accesses to fields + ReadWriteSet rwSet; + // We already have an entry + if (readWriteFieldsMap.containsKey(currentChoice)) { + rwSet = readWriteFieldsMap.get(currentChoice); + } else { // We need to create a new entry + rwSet = new ReadWriteSet(); + readWriteFieldsMap.put(currentChoice, rwSet); + } + return rwSet; + } + + private boolean isFieldExcluded(String field) { + // Check against "starts-with", "ends-with", and "contains" list + if (excludeThisForItStartsWith(EXCLUDED_FIELDS_STARTS_WITH_LIST, field) || + excludeThisForItEndsWith(EXCLUDED_FIELDS_ENDS_WITH_LIST, field) || + excludeThisForItContains(EXCLUDED_FIELDS_CONTAINS_LIST, field)) { + return true; + } + + return false; + } + + private boolean successfullyRecordConflictPair(int currentEvent, int eventNumber) { + HashSet conflictSet; + if (!conflictPairMap.containsKey(currentEvent)) { + conflictSet = new HashSet<>(); + conflictPairMap.put(currentEvent, conflictSet); + } else { + conflictSet = conflictPairMap.get(currentEvent); + } + // If this conflict has been recorded before, we return false because + // we don't want to service this backtrack point twice + if (conflictSet.contains(eventNumber)) { + return false; + } + // If it hasn't been recorded, then do otherwise + conflictSet.add(eventNumber); + return true; + } } diff --git a/src/main/gov/nasa/jpf/listener/StateReducer.java b/src/main/gov/nasa/jpf/listener/StateReducer.java index a1a01c2..b17b29a 100644 --- a/src/main/gov/nasa/jpf/listener/StateReducer.java +++ b/src/main/gov/nasa/jpf/listener/StateReducer.java @@ -421,9 +421,6 @@ public class StateReducer extends ListenerAdapter { public void choiceGeneratorAdvanced(VM vm, ChoiceGenerator currentCG) { if (stateReductionMode) { - if (vm.getNextChoiceGenerator() instanceof BooleanChoiceGenerator) { - System.out.println("Next CG is a booleanCG"); - } // Check the boolean CG and if it is flipped, we are resetting the analysis if (currentCG instanceof BooleanChoiceGenerator) { if (!isBooleanCGFlipped) {