//
// Copyright (C) 2006 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.vm;
import cmu.conditional.Conditional;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPFException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
/**
* the class that encapsulates not only the current execution state of the VM
* (the KernelState), but also the part of it's history that is required
* by VM to backtrack, plus some potential annotations that can be used to
* control the search (i.e. forward/backtrack calls)
*/
@SuppressWarnings("unchecked")
public class SystemState {
/**
* instances of this class are used to store the SystemState parts which are
* subject to backtracking/state resetting. At some point, we might have
* stripped SystemState down enough to just store the SystemState itself
* (so far, we don't change it's identity, there is only one)
* the KernelState is still stored separately (which seems to be another
* anachronism)
*
* NOTE: this gets stored at the end of a transition, i.e. if we need a value
* to be restored to it's transition entry state (like atomicLevel), we have
* to do that explicitly. Alternatively we could create the Memento before
* we start to enter the step, but then we have to update the nextCg in the
* snapshot, since it's only set at the transition end (required for
* restore(), i.e. HeuristicSearches)
*
* NOTE: the plain Memento doesn't deep copy the CGs, which means it can
* only be used for depth first search, where the parent CG states are always
* current if we encounter an error. If general state restoration is
* required (where the parent CGs might have been changed at the time we
* restore), we have to use a RestorableMemento
* <2do> this separation is error prone and fragile. It depends on correct
* ChoiceGenerator deepCopy() implementations and a separate state acquisition
* for restorable states. Currently, the gate for this is VM.getRestorableState(),
* but this could be bypassed.
*/
static class Memento {
ChoiceGenerator<?> curCg; // the ChoiceGenerator for the current transition
ChoiceGenerator<?> nextCg;
int atomicLevel;
ChoicePoint trace;
ThreadInfo execThread;
int id; // the state id
LinkedHashMap<Object,ClosedMemento> restorers;
static protected ChoiceGenerator<?> cloneCG( ChoiceGenerator<?> cg){
if (cg != null){
try {
return cg.deepClone();
} catch (CloneNotSupportedException cnsx){
throw new JPFException("clone failed: " + cg);
}
} else {
return null;
}
}
Memento (SystemState ss) {
nextCg = ss.nextCg;
curCg = ss.curCg;
atomicLevel = ss.entryAtomicLevel; // store the value we had when we started the transition
id = ss.id;
execThread = ss.execThread;
// we can just copy the reference since it is re-created in each transition
restorers = ss.restorers;
}
/**
* this one is used to restore to a state which will re-enter with the next choice
* of the same CG, i.e. nextCG is reset
*/
void backtrack (SystemState ss) {
ss.nextCg = null; // this is important - the nextCG will be set by the next Transition
ss.curCg = curCg;
ss.atomicLevel = atomicLevel;
ss.id = id;
ss.execThread = execThread;
if (restorers != null){
for (ClosedMemento r : restorers.values()){
r.restore();
}
}
}
void restore (SystemState ss) {
throw new JPFException("can't restore a SystemState.Memento that was created for backtracking");
/**
ss.nextCg = nextCg;
ss.curCg = curCg;
ss.atomicLevel = atomicLevel;
ss.id = id;
ss.execThread = execThread;
**/
}
}
/**
* a Memento that can be restored, not just backtracked to. Be aware this can
* be a lot more expensive since it has to deep copy CGs so that we have
* the state of the parent CGs restored properly
*/
static class RestorableMemento extends Memento {
RestorableMemento (SystemState ss){
super(ss);
nextCg = cloneCG(nextCg);
curCg = cloneCG( curCg);
}
void backtrack (SystemState ss){
super.backtrack(ss);
ss.curCg = cloneCG(curCg);
}
/**
* this one is used if we restore and then advance, i.e. it might change the CG on
* the next advance (if nextCg was set)
*/
void restore (SystemState ss) {
// if we don't clone them on restore, it means we can only restore this memento once
ss.nextCg = cloneCG(nextCg);
ss.curCg = cloneCG(curCg);
ss.atomicLevel = atomicLevel;
ss.id = id;
ss.execThread = execThread;
if (restorers != null){
for (ClosedMemento r : restorers.values()){
r.restore();
}
}
}
}
int id; /** the state id */
ChoiceGenerator<?> nextCg; // the ChoiceGenerator for the next transition
ChoiceGenerator<?> curCg; // the ChoiceGenerator used in the current transition
ThreadInfo execThread; // currently executing thread, reset by ThreadChoiceGenerators
// on-demand list of optional restorers that run if we backtrack to this state
// this is reset before each transition
LinkedHashMap<Object,ClosedMemento> restorers;
/** current execution state of the VM (stored separately by VM) */
public KernelState ks;
public Transition trail; /** trace information */
//--- attributes that can be explicitly set for a state
boolean retainAttributes; // as long as this is set, we don't reset attributes
//--- ignored and isNewState are imperative
boolean isIgnored; // treat this as a matched state, i.e. backtrack
boolean isForced; // treat this as a new state
//--- those are hints (e.g. for HeuristicSearches)
boolean isInteresting;
boolean isBoring;
boolean isBlockedInAtomicSection;
/** uncaught exception in current transition */
public UncaughtException uncaughtException;
/** set to true if garbage collection is necessary */
boolean GCNeeded = false;
// this is an optimization - long transitions can cause a lot of short-living
// garbage, which in turn can slow down the system considerably (heap size)
// by setting 'nAllocGCThreshold', we can do sync. on-the-fly gc when the
// number of new allocs within a single transition exceeds this value
int maxAllocGC;
int nAlloc;
/** NOTE: this has changed its meaning again. Now it once more is an
* optimization that can be used by applications calling Verify.begin/endAtomic(),
* but be aware of that it now reports a deadlock property violation in
* case of a blocking op inside an atomic section
* Data CGs however are now allowed to be inside atomic sections
*
* BEWARE - It is in the nature of atomic sections that they might loose paths that
* are relevant. This is esp. true for Thread.start() within AS if the starter
* runs to completion without further scheduling points (DiningPhil problem).
*/
int atomicLevel;
int entryAtomicLevel;
/** the policy object used to create scheduling related ChoiceGenerators */
SchedulerFactory schedulerFactory;
/** do we want executed insns to be recorded */
boolean recordSteps;
/**
* Creates a new system state.
*/
public SystemState (Config config, VM vm) {
ks = new KernelState(config);
id = StateSet.UNKNOWN_ID;
Class<?>[] argTypes = { Config.class, VM.class, SystemState.class };
Object[] args = { config, vm, this };
schedulerFactory = config.getEssentialInstance("vm.scheduler_factory.class",
SchedulerFactory.class,
argTypes, args);
// we can't yet initialize the trail until we have the start thread
maxAllocGC = config.getInt("vm.max_alloc_gc", Integer.MAX_VALUE);
if (maxAllocGC <= 0){
maxAllocGC = Integer.MAX_VALUE;
}
// recordSteps is set later by VM, first we need a reporter (which requires the VM)
}
protected SystemState() {
// just for unit test mockups
}
public void setStartThread (ThreadInfo ti) {
execThread = ti;
trail = new Transition(nextCg, execThread);
}
public int getId () {
return id;
}
public void setId (int newId) {
id = newId;
trail.setStateId(newId);
}
public void recordSteps (boolean cond) {
recordSteps = cond;
}
/**
* use those with extreme care, it overrides scheduling choices
*/
public void incAtomic () {
atomicLevel++;
}
public void decAtomic () {
if (atomicLevel > 0) {
atomicLevel--;
}
}
public void clearAtomic() {
atomicLevel = 0;
}
public boolean isAtomic () {
return (atomicLevel > 0);
}
public boolean isBlockedInAtomicSection() {
return isBlockedInAtomicSection;
}
public void setBlockedInAtomicSection() {
isBlockedInAtomicSection = true;
}
public Transition getTrail() {
return trail;
}
public SchedulerFactory getSchedulerFactory () {
return schedulerFactory;
}
public KernelState getKernelState() {
return ks;
}
public Heap getHeap() {
return ks.getHeap();
}
//--- these are the various choice generator retrievers
/**
* answer the ChoiceGenerator that is used in the current transition
*/
public ChoiceGenerator<?> getChoiceGenerator () {
return curCg;
}
public ChoiceGenerator<?> getChoiceGenerator (String id) {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getPreviousChoiceGenerator()){
if (id.equals(cg.getId())){
return cg;
}
}
return null;
}
/**
* return the whole stack of CGs of the current path
*/
public ChoiceGenerator<?>[] getChoiceGenerators () {
if (curCg != null){
return curCg.getAll();
} else {
return null;
}
}
public <T extends ChoiceGenerator<?>> T[] getChoiceGeneratorsOfType (Class<T> cgType) {
if (curCg != null){
return curCg.getAllOfType(cgType);
} else {
return null;
}
}
public <T extends ChoiceGenerator<?>> T getLastChoiceGeneratorOfType (Class<T> cgType) {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getPreviousChoiceGenerator()){
if (cgType.isAssignableFrom(cg.getClass())) {
return (T)cg;
}
}
return null;
}
public <T extends ChoiceGenerator<?>> T getCurrentChoiceGeneratorOfType (Class<T> cgType) {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getCascadedParent()){
if (cgType.isAssignableFrom(cg.getClass())){
return (T)cg;
}
}
return null;
}
public <T extends ChoiceGenerator<?>> T getCurrentChoiceGenerator (String id, Class<T> cgType) {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getCascadedParent()){
if (id.equals(cg.getId()) && cgType.isAssignableFrom(cg.getClass())){
return (T)cg;
}
}
return null;
}
public ChoiceGenerator<?> getCurrentChoiceGenerator (String id) {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getCascadedParent()){
if (id.equals(cg.getId())){
return cg;
}
}
return null;
}
public ChoiceGenerator<?> getCurrentChoiceGenerator (ChoiceGenerator<?> cgPrev) {
if (cgPrev == null){
return curCg;
} else {
return cgPrev.getCascadedParent();
}
}
/**
* this returns the most recently registered ThreadChoiceGenerator that is
* also a scheduling point, or 'null' if there is none in the list of current CGs
*/
public ThreadChoiceGenerator getCurrentSchedulingPoint () {
for (ChoiceGenerator<?> cg = curCg; cg != null; cg = cg.getCascadedParent()){
if (cg instanceof ThreadChoiceGenerator){
ThreadChoiceGenerator tcg = (ThreadChoiceGenerator)cg;
if (tcg.isSchedulingPoint()){
return tcg;
}
}
}
return null;
}
public ChoiceGenerator<?>[] getCurrentChoiceGenerators () {
return curCg.getCascade();
}
public <T extends ChoiceGenerator<?>> T getInsnChoiceGeneratorOfType (Class<T> cgType, Conditional<Instruction> insn, ChoiceGenerator<?> cgPrev){
ChoiceGenerator<?> cg = cgPrev != null ? cgPrev.getPreviousChoiceGenerator() : curCg;
if (cg != null && cg.getInsn() == insn && cgType.isAssignableFrom(cg.getClass())){
return (T)cg;
}
return null;
}
public ChoiceGenerator<?> getNextChoiceGenerator () {
return nextCg;
}
/**
* set the ChoiceGenerator to be used in the next transition
* @return true if there is a nextCg set after registration and listener notification
*/
public boolean setNextChoiceGenerator (ChoiceGenerator<?> cg) {
if (isIgnored){
// if this transition is already marked as ignored, we are not allowed
// to set nextCg because 'isIgnored' results in a shortcut backtrack that
// is not handed back to the Search (its solely in VM forward)
return false;
}
if (cg != null){
// first, check if we have to randomize it. Note this might change the CG
// instance since some algorithmic CG types need to be transformed into
// explicit choice lists
if (ChoiceGeneratorBase.useRandomization()) {
cg = cg.randomize();
}
// set its context (thread and insn)
cg.setContext(execThread);
// do we already have a nextCG, which means this one is a cascaded CG
if (nextCg != null) {
cg.setPreviousChoiceGenerator(nextCg);
nextCg.setCascaded(); // note the last registered CG is NOT set cascaded
} else {
cg.setPreviousChoiceGenerator(curCg);
}
nextCg = cg;
execThread.getVM().notifyChoiceGeneratorRegistered(cg, execThread); // <2do> we need a better way to get the vm
}
// a choiceGeneratorRegistered listener might have removed this CG
return (nextCg != null);
}
public void setMandatoryNextChoiceGenerator (ChoiceGenerator<?> cg, String failMsg){
if (!setNextChoiceGenerator(cg)){
throw new JPFException(failMsg);
}
}
/**
* remove the current 'nextCg'
* Note this has to be called in a loop if all cascaded CGs have to be removed
*/
public void removeNextChoiceGenerator (){
if (nextCg != null){
nextCg = nextCg.getPreviousChoiceGenerator();
}
}
/**
* remove the whole chain of currently registered nextCGs
*/
public void removeAllNextChoiceGenerators(){
while (nextCg != null){
nextCg = nextCg.getPreviousChoiceGenerator();
}
}
public Object getBacktrackData () {
return new Memento(this);
}
public void backtrackTo (Object backtrackData) {
((Memento) backtrackData).backtrack( this);
}
public Object getRestoreData(){
return new RestorableMemento(this);
}
public void restoreTo (Object backtrackData) {
((Memento) backtrackData).restore( this);
}
public void retainAttributes (boolean b){
retainAttributes = b;
}
public boolean getRetainAttributes() {
return retainAttributes;
}
/**
* this can be called anywhere from within a transition, to revert it and
* go on with the next choice. This is mostly used explicitly in the app
* via Verify.ignoreIf(..)
*
* calling setIgnored() also breaks the current transition, i.e. no further
* instructions are executed within this step
*/
public void setIgnored (boolean b) {
isIgnored = b;
if (b){
isForced = false; // mutually exclusive
}
}
public boolean isIgnored () {
return isIgnored;
}
public void setForced (boolean b){
isForced = b;
if (b){
isIgnored = false; // mutually exclusive
}
}
public boolean isForced () {
return isForced;
}
public void setInteresting (boolean b) {
isInteresting = b;
if (b){
isBoring = false;
}
}
public boolean isInteresting () {
return isInteresting;
}
public void setBoring (boolean b) {
isBoring = b;
if (b){
isInteresting = false;
}
}
public boolean isBoring () {
return isBoring;
}
public boolean isInitState () {
return (id == StateSet.UNKNOWN_ID);
}
public int getThreadCount () {
return ks.threads.length();
}
public UncaughtException getUncaughtException () {
return uncaughtException;
}
public void activateGC () {
GCNeeded = true;
}
public boolean hasRestorer (Object key){
if (restorers != null){
return restorers.containsKey(key);
}
return false;
}
public ClosedMemento getRestorer( Object key){
if (restorers != null){
return restorers.get(key);
}
return null;
}
/**
* call the provided restorer each time we get back to this state
*
* @param key usually the object this restorer encapsulates
* @param restorer the ClosedMemento that restores the state of the object
* it encapsulates once we backtrack/restore this program state
*
* Note that restorers are called in the order of registration, but in
* general it is not a good idea to depend on order since restorers can
* be set from different locations (listeners, peers, instructions)
*/
public void putRestorer (Object key, ClosedMemento restorer){
if (restorers == null){
restorers = new LinkedHashMap<Object,ClosedMemento>();
}
// we only support one restorer per target for now
restorers.put(key,restorer);
}
public boolean gcIfNeeded () {
boolean needed = false;
if (GCNeeded) {
ks.gc();
GCNeeded = false;
needed = true;
}
nAlloc = 0;
return needed;
}
/**
* check if number of allocations since last GC exceed the maxAllocGC
* threshold, perform on-the-fly GC if yes. This is aimed at avoiding a lot
* of short-living garbage in long transitions, which slows down the heap
* exponentially
*/
public void checkGC () {
if (nAlloc++ > maxAllocGC){
gcIfNeeded();
}
}
void dumpThreadCG (ThreadChoiceGenerator cg) {
PrintWriter pw = new PrintWriter(System.out, true);
cg.printOn(pw);
pw.flush();
}
/**
* reset the SystemState and initialize the next CG. This gets called
* *before* the restorer computes the KernelState snapshot, i.e. it is *not*
* allowed to change anything in the program state. The reason for splitting
* CG initialization from transition execution is to avoid KernelState storage
* in case the initialization does not produce a next choice and we have to
* backtrack.
*
* @see VM.forward()
*
* @return 'true' if there is a next choice, i.e. a next transition to enter.
* 'false' if there is no next choice and the system has to backtrack
*/
public boolean initializeNextTransition(VM vm) {
// set this before any choiceGeneratorSet or choiceGeneratorAdvanced
// notification (which can override it)
if (!retainAttributes){
isIgnored = false;
isForced = false;
isInteresting = false;
isBoring = false;
}
restorers = null;
// 'nextCg' got set at the end of the previous transition (or a preceding
// choiceGeneratorSet() notification).
// Be aware of that 'nextCg' is only the *last* CG that was registered, i.e.
// there can be any number of CGs between the previous 'curCg' and 'nextCg'
// that were registered for the same insn.
while (nextCg != null) {
curCg = nextCg;
nextCg = null;
// Hmm, that's a bit late (could be in setNextCG), but we keep it here
// for the sake of locality, and it's more consistent if it just refers
// to curCg, i.e. the CG that is actually going to be used
notifyChoiceGeneratorSet(vm, curCg);
}
assert (curCg != null) : "transition without choice generator";
return advanceCurCg(vm);
}
/**
* enter all instructions that constitute the next transition.
*
* Note this gets called *after* storing the KernelState, i.e. is allowed to
* modify thread states and fields
*
* @see VM.forward()
*/
public void executeNextTransition (VM vm){
// do we have a thread context switch? (this sets execThread)
setExecThread( vm);
assert execThread.isRunnable() : "next transition thread not runnable: " + execThread.getStateDescription();
trail = new Transition(curCg, execThread);
entryAtomicLevel = atomicLevel; // store before we start to enter
execThread.executeTransition(this);
}
protected void setExecThread( VM vm){
ThreadChoiceGenerator tcg = getCurrentSchedulingPoint();
if (tcg != null){
ThreadInfo tiNext = tcg.getNextChoice();
if (tiNext != execThread) {
vm.notifyThreadScheduled(tiNext);
execThread = tiNext;
}
}
if (execThread.isTimeoutWaiting()) {
execThread.setTimedOut();
}
}
// the number of advanced choice generators in this step
protected int nAdvancedCGs;
protected void advance( VM vm, ChoiceGenerator<?> cg){
while (true) {
if (cg.hasMoreChoices()){
cg.advance();
isIgnored = false;
vm.notifyChoiceGeneratorAdvanced(cg);
if (!isIgnored){
nAdvancedCGs++;
break;
}
} else {
vm.notifyChoiceGeneratorProcessed(cg);
break;
}
}
}
protected void advanceAllCascadedParents( VM vm, ChoiceGenerator<?> cg){
ChoiceGenerator<?> parent = cg.getCascadedParent();
if (parent != null){
advanceAllCascadedParents(vm, parent);
}
advance(vm, cg);
}
protected boolean advanceCascadedParent (VM vm, ChoiceGenerator<?> cg){
if (cg.hasMoreChoices()){
advance(vm,cg);
return true;
} else {
vm.notifyChoiceGeneratorProcessed(cg);
ChoiceGenerator<?> parent = cg.getCascadedParent();
if (parent != null){
if (advanceCascadedParent(vm,parent)){
cg.reset();
advance(vm,cg);
return true;
}
}
return false;
}
}
protected boolean advanceCurCg (VM vm){
nAdvancedCGs = 0;
ChoiceGenerator<?> cg = curCg;
ChoiceGenerator<?> parent = cg.getCascadedParent();
if (cg.hasMoreChoices()){
// check if this is the first time, for which we also have to advance our parents
if (parent != null && parent.getProcessedNumberOfChoices() == 0){
advanceAllCascadedParents(vm,parent);
}
advance(vm, cg);
} else { // this one is done, but how about our parents
vm.notifyChoiceGeneratorProcessed(cg);
if (parent != null){
if (advanceCascadedParent(vm,parent)){
cg.reset();
advance(vm,cg);
}
}
}
return (nAdvancedCGs > 0);
}
protected void notifyChoiceGeneratorSet (VM vm, ChoiceGenerator<?> cg){
ChoiceGenerator<?> parent = cg.getCascadedParent();
if (parent != null) {
notifyChoiceGeneratorSet(vm, parent);
}
vm.notifyChoiceGeneratorSet(cg); // notify top down
}
// this is called on every executeInstruction from the running thread
public boolean breakTransition () {
return ((nextCg != null) || isIgnored);
}
void recordExecutionStep (Instruction pc) {
// this can require a lot of memory, so we should only store
// executed insns if we have to
if (recordSteps) {
Step step = new Step(pc);
trail.addStep( step);
} else {
trail.incStepCount();
}
}
// the three primitive ops used from within VM.forward()
}