// // Copyright (C) 2007 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.listener; import java.util.Random; import de.fosd.typechef.featureexpr.FeatureExpr; import de.fosd.typechef.featureexpr.FeatureExprFactory; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.ListenerAdapter; import gov.nasa.jpf.annotation.JPFOption; import gov.nasa.jpf.annotation.JPFOptions; import gov.nasa.jpf.jvm.bytecode.InvokeInstruction; import gov.nasa.jpf.search.Search; import gov.nasa.jpf.util.StringSetMatcher; import gov.nasa.jpf.vm.ChoiceGenerator; import gov.nasa.jpf.vm.ChoicePoint; import gov.nasa.jpf.vm.Instruction; import gov.nasa.jpf.vm.ThreadInfo; import gov.nasa.jpf.vm.VM; /** * this is a listener that only executes single choices until it detects * that it should start to search. If nothing is specified, this is pretty * much a simulator that randomly picks choices. Otherwise the user can * give it any combination of * - a set of thread names * - a set of method names * - a start search depth * to turn on the search. If more than one condition is given, all have to be * satisfied */ @JPFOptions({ @JPFOption(type = "Int", key = "choice.seed", defaultValue= "42", comment = ""), @JPFOption(type = "StringArray", key = "choice.threads", defaultValue = "", comment="start search, when all threads in the set are active"), @JPFOption(type = "StringArray", key = "choice.calls", defaultValue = "", comment = "start search, when any of the methods is called"), @JPFOption(type = "Int", key = "choice.depth", defaultValue = "-1", comment = "start search, when reaching this depth"), @JPFOption(type = "String", key = "choice.use_trace", defaultValue ="", comment = ""), @JPFOption(type = "Boolean", key = "choice.search_after_trace", defaultValue = "true", comment="start search, when reaching the end of the stored trace") }) public class ChoiceSelector extends ListenerAdapter { Random random; boolean singleChoice = true; // those are our singleChoice end conditions (i.e. where we start the search) StringSetMatcher threadSet; // we start when all threads in the set are active boolean threadsAlive = true;; StringSetMatcher calls; // .. when any of the methods is called boolean callSeen = true; int startDepth; // .. when reaching this depth boolean depthReached = true; // set if we replay a trace ChoicePoint trace; // start the search when reaching the end of the stored trace. If not set, // the listener will just randomly select single choices once the trace // got processed boolean searchAfterTrace; public ChoiceSelector (Config config, JPF jpf) { random = new Random( config.getInt("choice.seed", 42)); threadSet = StringSetMatcher.getNonEmpty(config.getStringArray("choice.threads")); if (threadSet != null) { threadsAlive = false; } calls = StringSetMatcher.getNonEmpty(config.getStringArray("choice.calls")); callSeen = false; startDepth = config.getInt("choice.depth", -1); if (startDepth != -1) { depthReached = false; } // if nothing was specified, we just do single choice (simulation) if ((threadSet == null) && (calls == null) && (startDepth == -1)) { threadsAlive = false; callSeen = false; depthReached = false; } VM vm = jpf.getVM(); trace = ChoicePoint.readTrace(config.getString("choice.use_trace"), vm.getSUTName()); searchAfterTrace = config.getBoolean("choice.search_after_trace", true); vm.setTraceReplay(trace != null); } void checkSingleChoiceCond() { singleChoice = !(depthReached && callSeen && threadsAlive); } @Override public void choiceGeneratorAdvanced (VM vm, ChoiceGenerator<?> currentCG) { int n = currentCG.getTotalNumberOfChoices(); if (trace != null) { // this is a replay // <2do> maybe that should just be a warning, and then a single choice assert currentCG.getClass().getName().equals(trace.getCgClassName()) : "wrong choice generator class, expecting: " + trace.getCgClassName() + ", read: " + currentCG.getClass().getName(); currentCG.select(trace.getChoice()); } else { if (singleChoice) { if (n > 1) { int r = random.nextInt(n); currentCG.select(r); // sets it done, so we never backtrack into it } } } } @Override public void threadStarted(VM vm, ThreadInfo ti) { if (singleChoice && (threadSet != null)) { String tname = ti.getName(); if (threadSet.matchesAny( tname)){ threadsAlive = true; checkSingleChoiceCond(); } } } @Override public void executeInstruction(FeatureExpr ctx, VM vm, ThreadInfo ti, Instruction insnToExecute) { if (singleChoice && !callSeen && (calls != null)) { if (insnToExecute instanceof InvokeInstruction) { String mthName = ((InvokeInstruction)insnToExecute).getInvokedMethod(FeatureExprFactory.True(), ti).getBaseName(); if (calls.matchesAny(mthName)){ callSeen = true; checkSingleChoiceCond(); } } } } @Override public void stateAdvanced(Search search) { if (trace != null) { // there is no backtracking or restoring as long as we replay trace = trace.getNext(); if (trace == null){ search.getVM().setTraceReplay(false); if (searchAfterTrace){ singleChoice = false; } } } else { if (singleChoice && !depthReached && (startDepth >= 0)) { if (search.getDepth() == startDepth) { depthReached = true; checkSingleChoiceCond(); } } } } }