Initial import
[jpf-core.git] / src / main / gov / nasa / jpf / vm / VM.java
diff --git a/src/main/gov/nasa/jpf/vm/VM.java b/src/main/gov/nasa/jpf/vm/VM.java
new file mode 100644 (file)
index 0000000..560a979
--- /dev/null
@@ -0,0 +1,2053 @@
+/*
+ * Copyright (C) 2014, United States Government, as represented by the
+ * Administrator of the National Aeronautics and Space Administration.
+ * All rights reserved.
+ *
+ * The Java Pathfinder core (jpf-core) platform is licensed under the
+ * Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0. 
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+package gov.nasa.jpf.vm;
+
+import gov.nasa.jpf.Config;
+import gov.nasa.jpf.JPF;
+import gov.nasa.jpf.JPFConfigException;
+import gov.nasa.jpf.JPFException;
+import gov.nasa.jpf.JPFListenerException;
+import gov.nasa.jpf.jvm.ClassFile;
+import gov.nasa.jpf.vm.FinalizerThreadInfo;
+import gov.nasa.jpf.search.Search;
+import gov.nasa.jpf.util.IntTable;
+import gov.nasa.jpf.util.JPFLogger;
+import gov.nasa.jpf.util.Misc;
+import gov.nasa.jpf.util.Predicate;
+
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * This class represents the virtual machine. The virtual machine is able to
+ * move backward and forward one transition at a time.
+ */
+public abstract class VM {
+
+  /**
+   * this is a debugging aid to control compilation of expensive consistency checks
+   * (we don't control these with class-wise assertion enabling since we do use
+   * unconditional assertions for mandatory consistency checks)
+   */
+  public static final boolean CHECK_CONSISTENCY = false;
+  
+  protected static final String[] EMPTY_ARGS = new String[0];
+  
+  protected static JPFLogger log = JPF.getLogger("vm");
+
+  /**
+   * our execution context
+   */
+  protected JPF jpf;
+
+  /**
+   * The number of errors saved so far.
+   * Used to generate the name of the error trail file.
+   */
+  protected static int error_id;
+
+  /**
+   * <2do> - this is a hack to be removed once there are no static references
+   * anymore
+   */
+  protected static VM vm;
+
+  static {
+    initStaticFields();
+  }
+
+  protected SystemState ss;
+  
+  protected FunctionObjectFactory funcObjFactory = new FunctionObjectFactory();
+
+  // <2do> - if you are confused about the various pieces of state and its
+  // storage/backtrack structures, I'm with you. It's mainly an attempt to
+  // separate non-policy VM state (objects), policy VM state (Scheduler)
+  // and general JPF execution state, with special support for stack oriented
+  // state restoration (backtracking).
+  // this needs to be cleaned up and the principle reinstated
+
+
+  protected Path path;  /** execution path to current state */
+  protected StringBuilder out;  /** buffer to store output along path execution */
+
+  /**
+   * various caches for VMListener state acquisition. NOTE - these are only
+   * valid during notification
+   *
+   * <2do> get rid of the 'lasts' in favor of queries on the insn, the executing
+   * thread, and the VM. This is superfluous work to for every notification
+   * (even if there are no listeners using it) that can easily lead to inconsistencies
+   */
+  protected Transition      lastTrailInfo;
+
+  protected boolean isTraceReplay; // can be set by listeners to indicate this is a replay
+
+  /** the repository we use to find out if we already have seen a state */
+  protected StateSet stateSet;
+
+  /** this was the last stateId - note this is also used for stateless model checking */
+  protected int newStateId;
+
+  /** the structure responsible for storing and restoring backtrack info */
+  protected Backtracker backtracker;
+
+  /** optional serializer/restorer to support backtracker */
+  protected StateRestorer<?> restorer;
+
+  /** optional serializer to support stateSet */
+  protected StateSerializer serializer;
+
+  /** potential execution listeners. We keep them in a simple array to avoid
+   creating objects on each notification */
+  protected VMListener[] listeners = new VMListener[0];
+
+  /** did we get a new transition */
+  protected boolean transitionOccurred;
+
+  /** how we model execution time */
+  protected TimeModel timeModel;
+  
+  /** ThreadChoiceGenerator management related to data races and shared objects */
+  protected Scheduler scheduler;
+  
+  
+  protected Config config; // that's for the options we use only once
+
+  // VM options we use frequently
+  protected boolean runGc;
+  protected boolean treeOutput;
+  protected boolean pathOutput;
+  protected boolean indentOutput;
+  protected boolean processFinalizers;
+  
+  // <2do> there are probably many places where this should be used
+  protected boolean isBigEndian;
+
+  protected boolean initialized;
+
+  //thread filters
+  protected Predicate<ThreadInfo> userliveNonDaemonPredicate;
+  protected Predicate<ThreadInfo> timedoutRunnablePredicate;
+  protected Predicate<ThreadInfo> alivePredicate;
+  protected Predicate<ThreadInfo> userTimedoutRunnablePredicate;
+
+  // a list of actions to be run post GC. This is a bit redundant to VMListener,
+  // but in addition to avoid the per-instruction execution overhead of a VMListener
+  // we want a (internal) mechanism that is on-demand only, i.e. processed
+  // actions are removed from the list
+  protected ArrayList<Runnable> postGcActions = new ArrayList<Runnable>();
+  
+  /**
+   * be prepared this might throw JPFConfigExceptions
+   */
+  public VM (JPF jpf, Config conf) {
+    this.jpf = jpf; // so that we know who instantiated us
+
+    // <2do> that's really a bad hack and should be removed once we
+    // have cleaned up the reference chains
+    vm = this;
+
+    config = conf;
+
+    runGc = config.getBoolean("vm.gc", true);
+
+    treeOutput = config.getBoolean("vm.tree_output", true);
+    // we have to defer setting pathOutput until we have a reporter registered
+    indentOutput = config.getBoolean("vm.indent_output",false);
+
+    processFinalizers = config.getBoolean("vm.process_finalizers", false);
+    
+    isBigEndian = getPlatformEndianness(config);
+    initialized = false;
+    
+    initTimeModel(config);
+
+    initSubsystems(config);
+    initFields(config);
+    
+    // set predicates used to query from threadlist
+    userliveNonDaemonPredicate = new Predicate<ThreadInfo>() {
+      @Override
+       public boolean isTrue (ThreadInfo ti) {
+        return (!ti.isDaemon() && !ti.isTerminated() && !ti.isSystemThread());
+      }
+    };
+
+    timedoutRunnablePredicate = new Predicate<ThreadInfo>() {
+      @Override
+       public boolean isTrue (ThreadInfo ti) {
+        return (ti.isTimeoutRunnable());
+      }
+    };
+    
+    userTimedoutRunnablePredicate = new Predicate<ThreadInfo>() {
+      @Override
+       public boolean isTrue (ThreadInfo ti) {
+        return (ti.isTimeoutRunnable() && !ti.isSystemThread());
+      }
+    };
+    
+    alivePredicate = new Predicate<ThreadInfo>() {
+      @Override
+       public boolean isTrue (ThreadInfo ti) {
+        return (ti.isAlive());
+      }
+    };
+  }
+
+  /**
+   * just here for unit test mockups, don't use as implicit base ctor in
+   * VM derived classes
+   */
+  protected VM (){}
+
+  public JPF getJPF() {
+    return jpf;
+  }
+
+  public void initFields (Config config) {
+    path = new Path("fix-this!");
+    out = null;
+
+    ss = new SystemState(config, this);
+
+    stateSet = config.getInstance("vm.storage.class", StateSet.class);
+    if (stateSet != null) stateSet.attach(this);
+    backtracker = config.getEssentialInstance("vm.backtracker.class", Backtracker.class);
+    backtracker.attach(this);
+
+    scheduler = config.getEssentialInstance("vm.scheduler.class", Scheduler.class);
+    
+    newStateId = -1;
+  }
+
+  protected void initSubsystems (Config config) {
+    ClassLoaderInfo.init(config);
+    ClassInfo.init(config);
+    ThreadInfo.init(config);
+    ElementInfo.init(config);
+    MethodInfo.init(config);
+    NativePeer.init(config);
+    ChoiceGeneratorBase.init(config);
+    
+    // peer classes get initialized upon NativePeer creation
+  }
+
+  protected void initTimeModel (Config config){
+    Class<?>[] argTypes = { VM.class, Config.class };
+    Object[] args = { this, config };
+    timeModel = config.getEssentialInstance("vm.time.class", TimeModel.class, argTypes, args);
+  }
+  
+  /**
+   * called after the JPF run is finished. Shouldn't be public, but is called by JPF
+   */
+  public void cleanUp(){
+    // nothing yet
+  }
+  
+  protected boolean getPlatformEndianness (Config config){
+    String endianness = config.getString("vm.endian");
+    if (endianness == null) {
+      return ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+    } else if (endianness.equalsIgnoreCase("big")) {
+      return true;
+    } else if (endianness.equalsIgnoreCase("little")) {
+      return false;
+    } else {
+      config.exception("illegal vm.endian value: " + endianness);
+      return false; // doesn't matter
+    }
+  }
+  
+  public boolean isBigEndianPlatform(){
+    return isBigEndian;
+  }
+
+  public boolean finalizersEnabled() {
+    return processFinalizers;
+  }
+  
+  public boolean isInitialized() {
+    return initialized;
+  }
+  
+  public boolean isSingleProcess() {
+    return true;
+  }
+
+  /**
+   * do we see our model classes? Some of them cannot be used from the standard CLASSPATH, because they
+   * are tightly coupled with the JPF core (e.g. java.lang.Class, java.lang.Thread,
+   * java.lang.StackTraceElement etc.)
+   * Our strategy here is kind of lame - we just look into java.lang.Class if we find the 'uniqueId' field
+   * (that's a true '42')
+   */
+  static boolean checkSystemClassCompatibility (SystemClassLoaderInfo systemLoader) {
+    ClassInfo ci = systemLoader.getClassClassInfo();
+    return ci.checkIfValidClassClassInfo();
+  }
+
+  static boolean isValidClassName (String clsName) {
+    if ( !clsName.matches("[a-zA-Z_$][a-zA-Z_$0-9.]*")) {
+      return false;
+    }
+
+    // well, those two could be part of valid class names, but
+    // in all likeliness somebody specified a filename instead of
+    // a classname
+    if (clsName.endsWith(".java")) {
+      return false;
+    }
+    if (clsName.endsWith(".class")) {
+      return false;
+    }
+
+    return true;
+  }
+
+  //--- ThreadInfo factory methods
+  protected ThreadInfo createMainThreadInfo (int id, ApplicationContext appCtx){
+    ThreadInfo tiMain = new ThreadInfo( this, id, appCtx);
+    ThreadInfo.currentThread = tiMain; // we still need this for listeners that process startup class loading events
+    registerThread(tiMain);
+    
+    return tiMain;
+  }
+  
+  protected ThreadInfo createThreadInfo (int objRef, int groupRef, int runnableRef, int nameRef){
+    ThreadInfo tiCurrent = ThreadInfo.getCurrentThread();
+    ThreadInfo tiNew = new ThreadInfo( this, objRef, groupRef, runnableRef, nameRef, tiCurrent);
+
+    // note that we have to register here so that subsequent native peer calls can use the objRef
+    // to lookup the ThreadInfo. This is a bit premature since the thread is not runnable yet,
+    // but chances are it will be started soon, so we don't waste another data structure to do the mapping
+    registerThread( tiNew);
+    
+    return tiNew;
+  }
+
+  // created if the option "vm.process_finalizers" is set to true
+  protected ThreadInfo createFinalizerThreadInfo (int id, ApplicationContext appCtx){
+    FinalizerThreadInfo finalizerTi = new FinalizerThreadInfo( this, appCtx, id);
+    registerThread(finalizerTi);
+    
+    return finalizerTi;
+  }
+  
+  /**
+   * the minimal set of system classes we need for initialization
+   */
+  protected List<String> getStartupSystemClassNames() {
+    ArrayList<String> startupClasses = new ArrayList<String>(64);
+
+    // bare essentials
+    startupClasses.add("java.lang.Object");
+    startupClasses.add("java.lang.Class");
+    startupClasses.add("java.lang.ClassLoader");
+
+    // the builtin types (and their arrays)
+    startupClasses.add("boolean");
+    startupClasses.add("[Z");
+    startupClasses.add("byte");
+    startupClasses.add("[B");
+    startupClasses.add("char");
+    startupClasses.add("[C");
+    startupClasses.add("short");
+    startupClasses.add("[S");
+    startupClasses.add("int");
+    startupClasses.add("[I");
+    startupClasses.add("long");
+    startupClasses.add("[J");
+    startupClasses.add("float");
+    startupClasses.add("[F");
+    startupClasses.add("double");
+    startupClasses.add("[D");
+    startupClasses.add("void");
+
+    // the box types
+    startupClasses.add("java.lang.Boolean");
+    startupClasses.add("java.lang.Character");
+    startupClasses.add("java.lang.Short");
+    startupClasses.add("java.lang.Integer");
+    startupClasses.add("java.lang.Long");
+    startupClasses.add("java.lang.Float");
+    startupClasses.add("java.lang.Double");
+    startupClasses.add("java.lang.Byte");
+
+    // the cache for box types
+    startupClasses.add("gov.nasa.jpf.BoxObjectCaches");
+
+    // standard system classes
+    startupClasses.add("java.lang.String");
+    startupClasses.add("java.lang.Thread");
+    startupClasses.add("java.lang.ThreadGroup");
+    startupClasses.add("java.lang.Thread$State");
+    startupClasses.add("java.lang.Thread$Permit");
+    startupClasses.add("java.io.PrintStream");
+    startupClasses.add("java.io.InputStream");
+    startupClasses.add("java.lang.System");
+    startupClasses.add("java.lang.ref.Reference");
+    startupClasses.add("java.lang.ref.WeakReference");
+    startupClasses.add("java.lang.Enum");
+    startupClasses.add("gov.nasa.jpf.FinalizerThread");
+
+    // we could be more fancy and use wildcard patterns and the current classpath
+    // to specify extra classes, but this could be VERY expensive. Projected use
+    // is mostly to avoid static init of single classes during the search
+    String[] extraStartupClasses = config.getStringArray("vm.extra_startup_classes");
+    if (extraStartupClasses != null) {      
+      for (String extraCls : extraStartupClasses) {
+        startupClasses.add(extraCls);
+      }
+    }
+
+    // the main class has to be handled separately since it might be VM specific
+
+    return startupClasses;
+  }
+
+  /**
+   * return a list of ClassInfos for essential system types
+   * 
+   * If system classes are not found, or are not valid JPF model classes, we throw
+   * a JPFConfigException and exit
+   * 
+   * returned ClassInfos are not yet registered in Statics and don't have class objects
+   */
+  protected List<ClassInfo> getStartupSystemClassInfos (SystemClassLoaderInfo sysCl, ThreadInfo tiMain){
+    LinkedList<ClassInfo> list = new LinkedList<ClassInfo>();
+    
+    try {
+      for (String clsName : getStartupSystemClassNames()) {
+        ClassInfo ci = sysCl.getResolvedClassInfo(clsName);
+        ci.registerStartupClass( tiMain, list); // takes care of superclasses and interfaces
+      }
+    } catch (ClassInfoException e){
+      e.printStackTrace();
+      throw new JPFConfigException("cannot load system class " + e.getFailedClass());
+    } 
+    
+    return list;
+  }
+  
+  /**
+   * this adds the application main class and its supers to the list of startup classes 
+   */
+  protected ClassInfo getMainClassInfo (SystemClassLoaderInfo sysCl, String mainClassName, ThreadInfo tiMain, List<ClassInfo> list){
+    try {
+      ClassInfo ciMain = sysCl.getResolvedClassInfo(mainClassName);
+      ciMain.registerStartupClass(tiMain, list); // this might add a couple more
+      
+      return ciMain;
+      
+    } catch (ClassInfoException e){
+      throw new JPFConfigException("cannot load application class " + e.getFailedClass());
+    }
+  }
+  
+  /*
+   * these are called when creating ApplicationContexts and can be overridden by concrete VM types 
+   */
+  protected SystemClassLoaderInfo createSystemClassLoaderInfo (int appId) {
+    Class<?>[] argTypes = { VM.class, int.class };
+   
+    Object[] args = { this, Integer.valueOf(appId)};
+    SystemClassLoaderInfo sysCli = config.getEssentialInstance("vm.classloader.class", SystemClassLoaderInfo.class, argTypes, args);
+    return sysCli;
+  }
+  
+  protected void createSystemClassLoaderObject (SystemClassLoaderInfo sysCl, ThreadInfo tiMain) {
+    Heap heap = getHeap();
+
+    // create ClassLoader object for the ClassLoader type defined by this SystemClassLoaderInfo
+    // NOTE - this requires the SystemClassLoaderInfo cache to be initialized
+    ClassInfo ciCl = sysCl.getClassLoaderClassInfo();
+    ElementInfo ei = heap.newObject( ciCl, tiMain);
+    //ei.setReferenceField("parent", MJIEnv.NULL);
+    heap.registerPinDown(ei.getObjectRef());
+
+    sysCl.setClassLoaderObject(ei);
+  }  
+  
+  protected void pushMainEntryArgs (MethodInfo miMain, String[] args, ThreadInfo tiMain, DirectCallStackFrame frame){
+    String sig = miMain.getSignature();
+    Heap heap = getHeap();
+    
+    if (sig.contains("([Ljava/lang/String;)")){
+      ElementInfo eiArgs = heap.newArray("Ljava/lang/String;", args.length, tiMain);
+      for (int i = 0; i < args.length; i++) {
+        ElementInfo eiArg = heap.newString(args[i], tiMain);
+        eiArgs.setReferenceElement(i, eiArg.getObjectRef());
+      }
+      frame.setReferenceArgument( 0, eiArgs.getObjectRef(), null);
+
+    } else if (sig.contains("(Ljava/lang/String;)")){
+      if (args.length > 1){
+        ElementInfo eiArg = heap.newString(args[0], tiMain);
+        frame.setReferenceArgument( 0, eiArg.getObjectRef(), null);
+      } else {
+        frame.setReferenceArgument( 0, MJIEnv.NULL, null);
+      }
+      
+    } else if (!sig.contains("()")){
+      throw new JPFException("unsupported main entry signature: " + miMain.getFullName());
+    }
+  }
+  
+  protected void pushMainEntry (MethodInfo miMain, String[] args, ThreadInfo tiMain) {
+    DirectCallStackFrame frame = miMain.createDirectCallStackFrame(tiMain, 0);
+    pushMainEntryArgs( miMain, args, tiMain, frame);    
+    tiMain.pushFrame(frame);
+  }
+
+  protected MethodInfo getMainEntryMethodInfo (String mthName, ClassInfo ciMain) {
+    MethodInfo miMain = ciMain.getMethod(mthName, true);
+
+    //--- do some sanity checks if this is a valid entry method
+    if (miMain == null || !miMain.isStatic()) {
+      throw new JPFConfigException("no static entry method: " + ciMain.getName() + '.' + mthName);
+    }
+    
+    return miMain;
+  }
+  
+  protected void pushClinits (List<ClassInfo> startupClasses, ThreadInfo tiMain) {
+    for (ClassInfo ci : startupClasses){
+      MethodInfo mi = ci.getMethod("<clinit>()V", false);
+      if (mi != null) {
+        DirectCallStackFrame frame = mi.createDirectCallStackFrame(tiMain, 0);
+        tiMain.pushFrame(frame);
+      } else {
+        ci.setInitialized();
+      }      
+    }
+  }
+  
+  /**
+   * this is the main initialization point that sets up startup objects threads and callstacks.
+   * If this returns false VM initialization cannot proceed and JPF will terminate
+   */
+  public abstract boolean initialize ();
+  
+  /**
+   * create and initialize the main thread for the given ApplicationContext.
+   * This is called from VM.initialize() implementations, the caller has to handle exceptions that should be reported
+   * differently (JPFConfigException, ClassInfoException)
+   */
+  protected ThreadInfo initializeMainThread (ApplicationContext appCtx, int tid){
+    SystemClassLoaderInfo sysCl = appCtx.sysCl;
+    
+    ThreadInfo tiMain = createMainThreadInfo(tid, appCtx);
+    List<ClassInfo> startupClasses = getStartupSystemClassInfos(sysCl, tiMain);
+    ClassInfo ciMain = getMainClassInfo(sysCl, appCtx.mainClassName, tiMain, startupClasses);
+
+    if (!checkSystemClassCompatibility( sysCl)){
+      throw new JPFConfigException("non-JPF system classes, check classpath");
+    }
+    
+    // create essential objects (we can't call ctors yet)
+    createSystemClassLoaderObject(sysCl, tiMain);
+    for (ClassInfo ci : startupClasses) {
+      ci.createAndLinkStartupClassObject(tiMain);
+    }
+    tiMain.createMainThreadObject(sysCl);
+    registerThread(tiMain);
+    
+    // note that StackFrames have to be pushed in reverse order
+    MethodInfo miMain = getMainEntryMethodInfo(appCtx.mainEntry, ciMain);
+    appCtx.setEntryMethod(miMain);
+    pushMainEntry(miMain, appCtx.args, tiMain);
+    Collections.reverse(startupClasses);
+    pushClinits(startupClasses, tiMain);
+
+    registerThreadListCleanup(sysCl.getThreadClassInfo());
+
+    return tiMain;
+  }
+  
+  protected void initializeFinalizerThread (ApplicationContext appCtx, int tid) {
+    if(processFinalizers) {
+      ApplicationContext app = getCurrentApplicationContext();
+      FinalizerThreadInfo finalizerTi = app.getFinalizerThread();
+    
+      finalizerTi = (FinalizerThreadInfo) createFinalizerThreadInfo(tid, app);
+      finalizerTi.createFinalizerThreadObject(app.getSystemClassLoader());
+    
+      appCtx.setFinalizerThread(finalizerTi);
+    }
+  }
+  
+  protected void registerThreadListCleanup (ClassInfo ciThread){
+    assert ciThread != null : "java.lang.Thread not loaded yet";
+    
+    ciThread.addReleaseAction( new ReleaseAction(){
+      @Override
+       public void release (ElementInfo ei) {
+        ThreadList tl = getThreadList();
+        int objRef = ei.getObjectRef();
+        ThreadInfo ti = tl.getThreadInfoForObjRef(objRef);
+        if (tl.remove(ti)){        
+          vm.getKernelState().changed();    
+        }
+      }
+    });
+  }
+  
+
+  /**
+   * override this if the concrete VM needs a special root CG
+   */
+  protected void setRootCG(){
+    scheduler.setRootCG();
+  }
+  
+  protected void initSystemState (ThreadInfo mainThread){
+    ss.setStartThread(mainThread);
+
+    ss.recordSteps(hasToRecordSteps());
+
+    if (!pathOutput) { // don't override if explicitly requested
+      pathOutput = hasToRecordPathOutput();
+    }
+
+    setRootCG(); // this has to be guaranteed to register a CG
+    if (!hasNextChoiceGenerator()){
+      throw new JPFException("scheduler failed to set ROOT choice generator: " + scheduler);
+    }
+    
+    transitionOccurred = true;
+  }
+  
+  public void addPostGcAction (Runnable r){
+    postGcActions.add(r);
+  }
+  
+  /**
+   * to be called from the Heap after GC is completed (i.e. only live objects remain)
+   */
+  public void processPostGcActions(){
+    if (!postGcActions.isEmpty()){
+      for (Runnable r : postGcActions){
+        r.run();
+      }
+      
+      postGcActions.clear();
+    }
+  }
+  
+  public void addListener (VMListener newListener) {
+    log.info("VMListener added: ", newListener);
+    listeners = Misc.appendElement(listeners, newListener);
+  }
+
+  public boolean hasListenerOfType (Class<?> listenerCls) {
+    return Misc.hasElementOfType(listeners, listenerCls);
+  }
+
+  public <T> T getNextListenerOfType(Class<T> type, T prev){
+    return Misc.getNextElementOfType(listeners, type, prev);
+  }
+  
+  public void removeListener (VMListener removeListener) {
+    listeners = Misc.removeElement(listeners, removeListener);
+  }
+
+  public void setTraceReplay (boolean isReplay) {
+    isTraceReplay = isReplay;
+  }
+
+  public boolean isTraceReplay() {
+    return isTraceReplay;
+  }
+
+  public boolean hasToRecordSteps() {
+    // we have to record if there either is a reporter that has
+    // a 'trace' topic, or there is an explicit request
+    return jpf.getReporter().hasToReportTrace()
+             || config.getBoolean("vm.store_steps");
+  }
+
+  public void recordSteps( boolean cond) {
+    // <2do> not ideal - it might be already too late when this is called
+
+    config.setProperty("vm.store_steps", cond ? "true" : "false");
+
+    if (ss != null){
+      ss.recordSteps(cond);
+    }
+  }
+
+  public boolean hasToRecordPathOutput() {
+    if (config.getBoolean("vm.path_output")){ // explicitly requested
+      return true;
+    } else {
+      return jpf.getReporter().hasToReportOutput(); // implicilty required
+    }
+  }
+  
+  //--- VM listener notifications
+  
+  /*
+   * while some of these can be called from various places, the calls that happen from within Instruction.execute() should
+   * happen right before return since listeners might do things such as ThreadInfo.createAndThrowException(..), i.e. cause
+   * side effects that would violate consistency requirements of successive operations (e.g. by assuming we are still executing
+   * in the same StackFrame - after throwing an exception)
+   */
+  
+  protected void notifyVMInitialized () {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].vmInitialized(this);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during vmInitialized() notification", t);
+    }    
+  }
+  
+  protected void notifyChoiceGeneratorRegistered (ChoiceGenerator<?>cg, ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].choiceGeneratorRegistered(this, cg, ti, ti.getPC());
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during choiceGeneratorRegistered() notification", t);
+    }
+  }
+
+  protected void notifyChoiceGeneratorSet (ChoiceGenerator<?>cg) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].choiceGeneratorSet(this, cg);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during choiceGeneratorSet() notification", t);
+    }
+  }
+
+  protected void notifyChoiceGeneratorAdvanced (ChoiceGenerator<?>cg) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].choiceGeneratorAdvanced(this, cg);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during choiceGeneratorAdvanced() notification", t);
+    }
+  }
+
+  protected void notifyChoiceGeneratorProcessed (ChoiceGenerator<?>cg) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].choiceGeneratorProcessed(this, cg);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during choiceGeneratorProcessed() notification", t);
+    }
+  }
+
+  protected void notifyExecuteInstruction (ThreadInfo ti, Instruction insn) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].executeInstruction(this, ti, insn);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during executeInstruction() notification", t);
+    }
+  }
+
+  protected void notifyInstructionExecuted (ThreadInfo ti, Instruction insn, Instruction nextInsn) {
+    try {
+      //listener.instructionExecuted(this);
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].instructionExecuted(this, ti, nextInsn, insn);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during instructionExecuted() notification", t);
+    }
+  }
+
+  protected void notifyThreadStarted (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadStarted(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadStarted() notification", t);
+    }
+  }
+
+  // NOTE: the supplied ThreadInfo does NOT have to be the running thread, as this
+  // notification can occur as a result of a lock operation in the current thread
+  protected void notifyThreadBlocked (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadBlocked(this, ti, ti.getLockObject());
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadBlocked() notification", t);
+    }
+  }
+
+  protected void notifyThreadWaiting (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadWaiting(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadWaiting() notification", t);
+    }
+  }
+
+  protected void notifyThreadNotified (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadNotified(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadNotified() notification", t);
+    }
+  }
+
+  protected void notifyThreadInterrupted (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadInterrupted(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadInterrupted() notification", t);
+    }
+  }
+
+  protected void notifyThreadTerminated (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadTerminated(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadTerminated() notification", t);
+    }
+  }
+
+  protected void notifyThreadScheduled (ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].threadScheduled(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during threadScheduled() notification", t);
+    }
+  }
+  
+  protected void notifyLoadClass (ClassFile cf){
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].loadClass(this, cf);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during classLoaded() notification", t);
+    }    
+  }
+
+  protected void notifyClassLoaded(ClassInfo ci) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].classLoaded(this, ci);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during classLoaded() notification", t);
+    }
+  }
+
+  protected void notifyObjectCreated(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectCreated(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectCreated() notification", t);
+    }
+  }
+
+  protected void notifyObjectReleased(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectReleased(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectReleased() notification", t);
+    }
+  }
+
+  protected void notifyObjectLocked(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectLocked(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectLocked() notification", t);
+    }
+  }
+
+  protected void notifyObjectUnlocked(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectUnlocked(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectUnlocked() notification", t);
+    }
+  }
+
+  protected void notifyObjectWait(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectWait(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectWait() notification", t);
+    }
+  }
+
+   protected void notifyObjectExposed(ThreadInfo ti, ElementInfo eiShared, ElementInfo eiExposed) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectExposed(this, ti, eiShared, eiExposed);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectExposed() notification", t);
+    }
+  }
+
+   protected void notifyObjectShared(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectShared(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectShared() notification", t);
+    }
+  }
+  
+  protected void notifyObjectNotifies(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectNotify(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectNotifies() notification", t);
+    }
+  }
+
+  protected void notifyObjectNotifiesAll(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].objectNotifyAll(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during objectNotifiesAll() notification", t);
+    }
+  }
+
+  protected void notifyGCBegin() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].gcBegin(this);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during gcBegin() notification", t);
+    }
+  }
+
+  protected void notifyGCEnd() {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].gcEnd(this);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during gcEnd() notification", t);
+    }
+  }
+
+  protected void notifyExceptionThrown(ThreadInfo ti, ElementInfo ei) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].exceptionThrown(this, ti, ei);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during exceptionThrown() notification", t);
+    }
+  }
+
+  protected void notifyExceptionBailout(ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].exceptionBailout(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during exceptionBailout() notification", t);
+    }
+  }
+
+  protected void notifyExceptionHandled(ThreadInfo ti) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].exceptionHandled(this, ti);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during exceptionHandled() notification", t);
+    }
+  }
+
+  protected void notifyMethodEntered(ThreadInfo ti, MethodInfo mi) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].methodEntered(this, ti, mi);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during methodEntered() notification", t);
+    }
+  }
+
+  protected void notifyMethodExited(ThreadInfo ti, MethodInfo mi) {
+    try {
+      for (int i = 0; i < listeners.length; i++) {
+        listeners[i].methodExited(this, ti, mi);
+      }
+    } catch (UncaughtException x) {
+      throw x;
+    } catch (JPF.ExitException x) {
+      throw x;
+    } catch (Throwable t) {
+      throw new JPFListenerException("exception during methodExited() notification", t);
+    }
+  }
+
+  // VMListener acquisition
+  public String getThreadName () {
+    ThreadInfo ti = ThreadInfo.getCurrentThread();
+
+    return ti.getName();
+  }
+
+  // VMListener acquisition
+  public Instruction getInstruction () {
+    ThreadInfo ti = ThreadInfo.getCurrentThread();
+    return ti.getPC();
+  }
+
+  /**
+   * note this is gone after backtracking or starting the next exception
+   */
+  public ExceptionInfo getPendingException () {
+    ThreadInfo ti = ThreadInfo.getCurrentThread();
+
+    if (ti != null){
+      return ti.getPendingException();
+    } else {
+      return null;
+    }
+  }
+
+  public Step getLastStep () {
+    Transition trail = ss.getTrail();
+    if (trail != null) {
+      return trail.getLastStep();
+    }
+
+    return null;
+  }
+
+  public Transition getLastTransition () {
+    if (path.size() == 0) {
+      return null;
+    }
+    return path.get(path.size() - 1);
+  }
+
+  public ClassInfo getClassInfo (int objref) {
+    if (objref != MJIEnv.NULL) {
+      return getElementInfo(objref).getClassInfo();
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * NOTE: only use this locally, since the path is getting modified by the VM
+   *
+   * The path only contains all states when queried from a stateAdvanced() notification.
+   * If this is called from an instructionExecuted() (or other VMListener), and you need
+   * the ongoing transition in it, you have to call updatePath() first
+   */
+  public Path getPath () {
+    return path;
+  }
+
+  /**
+   * this is the ongoing transition. Note that it is not yet stored in the path
+   * if this is called from a VMListener notification
+   */
+  public Transition getCurrentTransition() {
+    return ss.getTrail();
+  }
+
+  /**
+   * use that one if you have to store the path for subsequent use
+   *
+   * NOTE: without a prior call to updatePath(), this does NOT contain the
+   * ongoing transition. See getPath() for usage from a VMListener
+   */
+  public Path getClonedPath () {
+    return path.clone();
+  }
+
+  public int getPathLength () {
+    return path.size();
+  }
+
+  public ThreadList getThreadList () {
+    return getKernelState().getThreadList();
+  }
+  
+  public ClassLoaderList getClassLoaderList() {
+    return getKernelState().getClassLoaderList();
+  }
+
+  
+  /**
+   * Bundles up the state of the system for export
+   */
+  public RestorableVMState getRestorableState () {
+    return new RestorableVMState(this);
+  }
+
+  /**
+   * Gets the system state.
+   */
+  public SystemState getSystemState () {
+    return ss;
+  }
+
+  public KernelState getKernelState () {
+    return ss.getKernelState();
+  }
+
+  public void kernelStateChanged(){
+    ss.getKernelState().changed();
+  }
+  
+  public Config getConfig() {
+    return config;
+  }
+
+  public Backtracker getBacktracker() {
+    return backtracker;
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T> StateRestorer<T> getRestorer() {
+    if (restorer == null) {
+      if (serializer instanceof StateRestorer) {
+        restorer = (StateRestorer<?>) serializer;
+      } else if (stateSet instanceof StateRestorer) {
+        restorer = (StateRestorer<?>) stateSet;
+      } else {
+        // config read only if serializer is not also a restorer
+        restorer = config.getInstance("vm.restorer.class", StateRestorer.class);
+      }
+      restorer.attach(this);
+    }
+
+    return (StateRestorer<T>) restorer;
+  }
+
+  public StateSerializer getSerializer() {
+    if (serializer == null) {
+      serializer = config.getEssentialInstance("vm.serializer.class",
+                                      StateSerializer.class);
+      serializer.attach(this);
+    }
+    return serializer;
+  }
+
+  public void setSerializer (StateSerializer newSerializer){
+    serializer = newSerializer;
+    serializer.attach(this);
+  }
+  
+  /**
+   * Returns the stateSet if states are being matched.
+   */
+  public StateSet getStateSet() {
+    return stateSet;
+  }
+
+  public Scheduler getScheduler(){
+    return scheduler;
+  }
+  
+  public FunctionObjectFactory getFunctionObjectFacotry() {
+    return funcObjFactory;
+  }
+  
+  /**
+   * return the last registered SystemState's ChoiceGenerator object
+   * NOTE: there might be more than one ChoiceGenerator associated with the
+   * current transition (ChoiceGenerators can be cascaded)
+   */
+  public ChoiceGenerator<?> getChoiceGenerator () {
+    return ss.getChoiceGenerator();
+  }
+
+  public ChoiceGenerator<?> getNextChoiceGenerator() {
+    return ss.getNextChoiceGenerator();
+  }
+  
+  public boolean hasNextChoiceGenerator(){
+    return (ss.getNextChoiceGenerator() != null);
+  }
+
+  public boolean setNextChoiceGenerator (ChoiceGenerator<?> cg){
+    return ss.setNextChoiceGenerator(cg);
+  }
+  
+  public void setMandatoryNextChoiceGenerator (ChoiceGenerator<?> cg, String failMsg){
+    ss.setMandatoryNextChoiceGenerator(cg, failMsg);
+  }
+  
+  /**
+   * return the latest registered ChoiceGenerator used in this transition
+   * that matches the provided 'id' and is of 'cgType'.
+   * 
+   * This should be the main getter for clients that are cascade aware
+   */
+  public <T extends ChoiceGenerator<?>> T getCurrentChoiceGenerator (String id, Class<T> cgType) {
+    return ss.getCurrentChoiceGenerator(id,cgType);
+  }
+
+  /**
+   * returns all ChoiceGenerators in current path
+   */
+  public ChoiceGenerator<?>[] getChoiceGenerators() {
+    return ss.getChoiceGenerators();
+  }
+
+  public <T extends ChoiceGenerator<?>> T[] getChoiceGeneratorsOfType (Class<T> cgType) {
+    return ss.getChoiceGeneratorsOfType(cgType);
+  }
+
+  public <T extends ChoiceGenerator<?>> T getLastChoiceGeneratorOfType (Class<T> cgType){
+    return ss.getLastChoiceGeneratorOfType(cgType);
+  }
+
+  public ChoiceGenerator<?> getLastChoiceGeneratorInThread (ThreadInfo ti){
+    return ss.getLastChoiceGeneratorInThread(ti);
+  }
+  
+  public void print (String s) {
+    if (treeOutput) {
+      System.out.print(s);
+    }
+
+    if (pathOutput) {
+      appendOutput(s);
+    }
+  }
+
+  public void println (String s) {
+    if (treeOutput) {
+      if (indentOutput){
+        StringBuilder indent = new StringBuilder();
+        int i;
+        for (i = 0;i<=path.size();i++) {
+          indent.append('|').append(i);
+        }
+        indent.append("|").append(s);
+        System.out.println(indent);
+      }
+      else {
+        System.out.println(s);
+      }
+    }
+
+    if (pathOutput) {
+      appendOutput(s);
+      appendOutput('\n');
+    }
+  }
+
+  public void print (boolean b) {
+    if (treeOutput) {
+      System.out.print(b);
+    }
+
+    if (pathOutput) {
+      appendOutput(Boolean.toString(b));
+    }
+  }
+
+  public void print (char c) {
+    if (treeOutput) {
+      System.out.print(c);
+    }
+
+    if (pathOutput) {
+      appendOutput(c);
+    }
+  }
+
+  public void print (int i) {
+    if (treeOutput) {
+      System.out.print(i);
+    }
+
+    if (pathOutput) {
+      appendOutput(Integer.toString(i));
+    }
+  }
+
+  public void print (long l) {
+    if (treeOutput) {
+      System.out.print(l);
+    }
+
+    if (pathOutput) {
+      appendOutput(Long.toString(l));
+    }
+  }
+
+  public void print (double d) {
+    if (treeOutput) {
+      System.out.print(d);
+    }
+
+    if (pathOutput) {
+      appendOutput(Double.toString(d));
+    }
+  }
+
+  public void print (float f) {
+    if (treeOutput) {
+      System.out.print(f);
+    }
+
+    if (pathOutput) {
+      appendOutput(Float.toString(f));
+    }
+  }
+
+  public void println () {
+    if (treeOutput) {
+      System.out.println();
+    }
+
+    if (pathOutput) {
+      appendOutput('\n');
+    }
+  }
+
+
+  void appendOutput (String s) {
+    if (out == null) {
+      out = new StringBuilder();
+    }
+    out.append(s);
+  }
+
+  void appendOutput (char c) {
+    if (out == null) {
+      out = new StringBuilder();
+    }
+    out.append(c);
+  }
+
+  /**
+   * get the pending output (not yet stored in the path)
+   */
+  public String getPendingOutput() {
+    if (out != null && out.length() > 0){
+      return out.toString();
+    } else {
+      return null;
+    }
+  }
+  
+  /**
+   * this is here so that we can intercept it in subclassed VMs
+   */
+  public Instruction handleException (ThreadInfo ti, int xObjRef){
+    ti = null;        // Get rid of IDE warning
+    xObjRef = 0;
+    return null;
+  }
+
+  public void storeTrace (String fileName, String comment, boolean verbose) {
+    ChoicePoint.storeTrace(fileName, getSUTName(), comment,
+                           ss.getChoiceGenerators(), verbose);
+  }
+
+  public void storePathOutput () {
+    pathOutput = true;
+  }
+
+  private void printCG (ChoiceGenerator<?> cg, int n){
+    ChoiceGenerator cgPrev = cg.getPreviousChoiceGenerator();
+    if (cgPrev != null){
+      printCG( cgPrev, --n);
+    }
+    
+    System.out.printf("[%d] ", n);
+    System.out.println(cg);
+  } 
+  
+  // for debugging purposes
+  public void printChoiceGeneratorStack(){
+    ChoiceGenerator<?> cg = getChoiceGenerator();
+    if (cg != null){
+      int n = cg.getNumberOfParents();
+      printCG(cg, n);
+    }
+  }
+  
+  public ThreadInfo[] getLiveThreads () {
+    return getThreadList().getThreads();
+  }
+  
+  /**
+   * print call stacks of all live threads
+   * this is also used for debugging purposes, so we can't move it to the Reporter system
+   * (it's also using a bit too many internals for that)
+   */
+  public void printLiveThreadStatus (PrintWriter pw) {
+    int nThreads = ss.getThreadCount();
+    ThreadInfo[] threads = getThreadList().getThreads();
+    int n=0;
+
+    for (int i = 0; i < nThreads; i++) {
+      ThreadInfo ti = threads[i];
+
+      if (ti.getStackDepth() > 0){
+        n++;
+        //pw.print("Thread: ");
+        //pw.print(tiMain.getName());
+        pw.println(ti.getStateDescription());
+
+        List<ElementInfo> locks = ti.getLockedObjects();
+        if (!locks.isEmpty()) {
+          pw.print("  owned locks:");
+          boolean first = true;
+          for (ElementInfo e : locks) {
+            if (first) {
+              first = false;
+            } else {
+              pw.print(",");
+            }
+            pw.print(e);
+          }
+          pw.println();
+        }
+
+        ElementInfo ei = ti.getLockObject();
+        if (ei != null) {
+          if (ti.getState() == ThreadInfo.State.WAITING) {
+            pw.print( "  waiting on: ");
+          } else {
+            pw.print( "  blocked on: ");
+          }
+          pw.println(ei);
+        }
+
+        pw.println("  call stack:");
+        for (StackFrame frame : ti){
+          if (!frame.isDirectCallFrame()) {
+            pw.print("\tat ");
+            pw.println(frame.getStackTraceInfo());
+          }
+        }
+
+        pw.println();
+      }
+    }
+
+    if (n==0) {
+      pw.println("no live threads");
+    }
+  }
+
+  // just a debugging aid
+  public void dumpThreadStates () {
+    java.io.PrintWriter pw = new java.io.PrintWriter(System.out, true);
+    printLiveThreadStatus(pw);
+    pw.flush();
+  }
+
+  /**
+   * Moves one step backward. This method and forward() are the main methods
+   * used by the search object.
+   * Note this is called with the state that caused the backtrack still being on
+   * the stack, so we have to remove that one first (i.e. popping two states
+   * and restoring the second one)
+   */
+  public boolean backtrack () {
+    transitionOccurred = false;
+
+    boolean success = backtracker.backtrack();
+    if (success) {
+      if (CHECK_CONSISTENCY) checkConsistency(false);
+      
+      // restore the path
+      path.removeLast();
+      lastTrailInfo = path.getLast();
+
+      return true;
+      
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * store the current SystemState's Trail in our path, after updating it
+   * with whatever annotations the VM wants to add.
+   * This is supposed to be called after each transition we want to keep
+   */
+  public void updatePath () {
+    Transition t = ss.getTrail();
+    Transition tLast = path.getLast();
+
+    // NOTE: don't add the transition twice, this is public and might get called
+    // from listeners, so the transition object might get changed
+
+    if (tLast != t) {
+      // <2do> we should probably store the output directly in the TrailInfo,
+      // but this might not be our only annotation in the future
+
+      // did we have output during the last transition? If yes, add it
+      if ((out != null) && (out.length() > 0)) {
+        t.setOutput( out.toString());
+        out.setLength(0);
+      }
+
+      path.add(t);
+    }
+  }
+
+  /**
+   * advance the program state
+   *
+   * forward() and backtrack() are the two primary interfaces towards the Search
+   * driver. note that the caller still has to check if there is a next state,
+   * and if the executed instruction sequence led into a new or already visited state
+   *
+   * @return 'true' if there was an un-executed sequence out of the current state,
+   * 'false' if it was completely explored
+   *
+   */
+  public boolean forward () {
+
+    // the reason we split up CG initialization and transition execution
+    // is that program state storage is not required if the CG initialization
+    // does not produce a new choice since we have to backtrack in that case
+    // anyways. This can be caused by complete enumeration of CGs and/or by
+    // CG listener intervention (i.e. not just after backtracking). For a large
+    // number of matched or end states and ignored transitions this can be a
+    // huge saving.
+    // The downside is that CG notifications are NOT allowed anymore to change the
+    // KernelState (modify fields or thread states) since those changes would
+    // happen before storing the KernelState, and hence would make backtracking
+    // inconsistent. This is advisable anyways since all program state changes
+    // should take place during transitions, but the real snag is that this
+    // cannot be easily enforced.
+
+    // actually, it hasn't occurred yet, but will
+    transitionOccurred = ss.initializeNextTransition(this);
+    
+    if (transitionOccurred){
+      if (CHECK_CONSISTENCY) {
+        checkConsistency(true); // don't push an inconsistent state
+      }
+
+      backtracker.pushKernelState();
+
+      // cache this before we enter (and increment) the next insn(s)
+      lastTrailInfo = path.getLast();
+
+      try {
+        ss.executeNextTransition(vm);
+
+      } catch (UncaughtException e) {
+        // we don't pass this up since it means there were insns executed and we are
+        // in a consistent state
+      } // every other exception goes upwards
+
+      backtracker.pushSystemState();
+      updatePath();
+
+      if (!isIgnoredState()) {
+        // if this is ignored we are going to backtrack anyways
+        // matching states out of ignored transitions is also not a good idea
+        // because this transition is usually incomplete
+
+        if (runGc && !hasPendingException()) {
+          if(ss.gcIfNeeded()) {
+            processFinalizers();
+          }
+        }
+
+        if (stateSet != null) {
+          newStateId = stateSet.size();
+          int id = stateSet.addCurrent();
+          ss.setId(id);
+
+        } else { // this is 'state-less' model checking, i.e. we don't match states
+          ss.setId(++newStateId); // but we still should have states numbered in case listeners use the id
+        }
+      }
+      
+      return true;
+
+    } else {
+
+      return false;  // no transition occurred
+    }
+  }
+
+  /**
+   * Prints the current stack trace. Just for debugging purposes
+   */
+  public void printCurrentStackTrace () {
+    ThreadInfo th = ThreadInfo.getCurrentThread();
+
+    if (th != null) {
+      th.printStackTrace();
+    }
+  }
+
+
+  public void restoreState (RestorableVMState state) {
+    if (state.path == null) {
+      throw new JPFException("tried to restore partial VMState: " + state);
+    }
+    backtracker.restoreState(state.getBkState());
+    path = state.path.clone();
+  }
+
+  public void activateGC () {
+    ss.activateGC();
+  }
+
+
+  //--- various state attribute getters and setters (mostly forwarding to SystemState)
+
+  public void retainStateAttributes (boolean isRetained){
+    ss.retainAttributes(isRetained);
+  }
+
+  public void forceState () {
+    ss.setForced(true);
+  }
+
+  /**
+   * override the state matching - ignore this state, no matter if we changed
+   * the heap or stacks.
+   * use this with care, since it prunes whole search subtrees
+   */
+  public void ignoreState (boolean cond) {
+    ss.setIgnored(cond);
+  }
+
+  public void ignoreState(){
+    ignoreState(true);
+  }
+
+  /**
+   * imperatively break the transition to enable state matching
+   */
+  public void breakTransition (String reason) {
+    ThreadInfo ti = ThreadInfo.getCurrentThread();
+    ti.breakTransition(reason);
+  }
+
+  public boolean transitionOccurred(){
+    return transitionOccurred;
+  }
+
+  /**
+   * answers if the current state already has been visited. This is mainly
+   * used by the searches (to control backtracking), but could also be useful
+   * for observers to build up search graphs (based on the state ids)
+   *
+   * this returns true if no state has been produced yet, and false if
+   * no transition occurred after a forward call
+   */
+  public boolean isNewState() {
+
+    if (!transitionOccurred){
+      return false;
+    }
+
+    if (stateSet != null) {
+      if (ss.isForced()){
+        return true;
+      } else if (ss.isIgnored()){
+        return false;
+      } else {
+        return (newStateId == ss.getId());
+      }
+
+    } else { // stateless model checking - each transition leads to a new state
+      return true;
+    }
+  }
+
+  /**
+   * We made this to be overriden by Single/MultiprcessesVM implementations,
+   * since for MultiprcessesVM one can decide when to terminate (after the
+   * the termination of all processes or only one process).
+   * todo - that needs to be specified through the properties file
+   */
+  public abstract boolean isEndState ();
+
+  public boolean isVisitedState(){
+    return !isNewState();
+  }
+
+  public boolean isIgnoredState(){
+    return ss.isIgnored();
+  }
+
+  public boolean isInterestingState () {
+    return ss.isInteresting();
+  }
+
+  public boolean isBoringState () {
+    return ss.isBoring();
+  }
+
+  public boolean hasPendingException () {
+    return (getPendingException() != null);
+  }
+
+  public abstract boolean isDeadlocked ();
+  
+  public Exception getException () {
+    return ss.getUncaughtException();
+  }
+
+
+
+  /**
+   * get the numeric id for the current state
+   * Note: this can be called several times (by the search and observers) for
+   * every forward()/backtrack(), so we want to cache things a bit
+   */
+  public int getStateId() {
+    return ss.getId();
+  }
+
+  public int getStateCount() {
+    return newStateId;
+  }
+
+
+  /**
+   * <2do> this is a band aid to bundle all these legacy reference chains
+   * from JPFs past. The goal is to replace them with proper accessors (normally
+   * through ThreadInfo, MJIEnv or VM, which all act as facades) wherever possible,
+   * and use VM.getVM() where there is no access to such a facade. Once this
+   * has been completed, we can start refactoring the users of VM.getVM() to
+   * get access to a suitable facade. 
+   */
+  public static VM getVM () {
+    return vm;
+  }
+
+  /**
+   * not ideal to have this here since it is kind of a backlink, but it's not
+   * any better if listeners have to dig this out from JPF
+   * Note - this isn't set during initialization, since the VM object is created first
+   */
+  public Search getSearch() {
+    return jpf.getSearch();
+  }
+  
+  /**
+   * pushClinit all our static fields. Called from <clinit> and reset
+   */
+  static void initStaticFields () {
+    error_id = 0;
+  }
+
+  /**
+   *  given an object reference, it returns the ApplicationContext of the process to which
+   *  this object belongs
+   */
+  public abstract ApplicationContext getCurrentApplicationContext();
+  public abstract ApplicationContext getApplicationContext(int objRef);
+  public abstract ApplicationContext[] getApplicationContexts();
+  public abstract String getSUTName();
+  public abstract String getSUTDescription();
+
+  public abstract int getNumberOfApplications();
+  
+  public Heap getHeap() {
+    return ss.getHeap();
+  }
+
+  public ElementInfo getElementInfo(int objref){
+    return ss.getHeap().get(objref);
+  }
+
+  public ElementInfo getModifiableElementInfo(int objref){
+    return ss.getHeap().getModifiable(objref);
+  }
+
+  
+  public ThreadInfo getCurrentThread () {
+    return ThreadInfo.currentThread;
+  }
+  
+  public void registerClassLoader(ClassLoaderInfo cl) {
+    this.getKernelState().addClassLoader(cl);
+  }
+
+  public int registerThread (ThreadInfo ti){
+    getKernelState().changed();
+    return getThreadList().add(ti);    
+  }
+
+  /**
+   * Returns the ClassLoader with the given globalId
+   */
+  protected ClassLoaderInfo getClassLoader(int gid) {
+    return ss.ks.getClassLoader(gid);
+  }
+
+  /**
+   * <2do> this is where we will hook in a better time model
+   */
+  public long currentTimeMillis () {
+    return timeModel.currentTimeMillis();
+  }
+
+  /**
+   * <2do> this is where we will hook in a better time model
+   */
+  public long nanoTime() {
+    return timeModel.nanoTime();
+  }
+
+  public void resetNextCG() {
+    if (ss.nextCg != null) {
+      ss.nextCg.reset();
+    }
+  }
+  
+  /**
+   * only for debugging, this is expensive
+   *
+   * If this is a store (forward) this is called before the state is stored.
+   *
+   * If this is a restore (visited forward or backtrack), this is called after
+   * the state got restored
+   */
+  public void checkConsistency(boolean isStateStore) {
+    getThreadList().checkConsistency( isStateStore);
+    getHeap().checkConsistency( isStateStore);
+  }
+  
+  public abstract void terminateProcess (ThreadInfo ti);
+  
+  // this is invoked by the heap (see GenericHeap.newInternString()) upon creating
+  // the very first intern string
+  public abstract Map<Integer,IntTable<String>> getInitialInternStringsMap();
+  
+  // ---------- Predicates used to query threads from ThreadList ---------- //
+  
+  public abstract Predicate<ThreadInfo> getRunnablePredicate();
+  
+  public abstract Predicate<ThreadInfo> getDaemonRunnablePredicate();
+  
+  public abstract Predicate<ThreadInfo> getAppTimedoutRunnablePredicate();
+  
+  public Predicate<ThreadInfo> getUserTimedoutRunnablePredicate () {
+    return userTimedoutRunnablePredicate;
+  }
+  
+  public Predicate<ThreadInfo> getUserLiveNonDaemonPredicate() {
+    return userliveNonDaemonPredicate;
+  }
+  
+  public Predicate<ThreadInfo> getTimedoutRunnablePredicate () {
+    return timedoutRunnablePredicate;
+  }
+  
+  public Predicate<ThreadInfo> getAlivePredicate () {
+    return alivePredicate;
+  }
+  
+  
+  // ---------- Methods for handling finalizers ---------- //
+    
+  public FinalizerThreadInfo getFinalizerThread() {
+    return getCurrentApplicationContext().getFinalizerThread();
+  }
+  
+  abstract void updateFinalizerQueues();
+  
+  public void processFinalizers() {
+    if(processFinalizers) {
+      updateFinalizerQueues();
+      ChoiceGenerator<?> cg = getNextChoiceGenerator();
+      if(cg==null || (cg.isSchedulingPoint() && !cg.isCascaded())) {
+        getFinalizerThread().scheduleFinalizer();
+      }
+    }
+  }
+}