// // 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 java.io.File; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.function.BiConsumer; import java.util.logging.Level; import cmu.conditional.ChoiceFactory; import cmu.conditional.Conditional; import cmu.conditional.One; import cmu.utils.CoverageClass; import cmu.utils.RuntimeConstants; import cmu.utils.TraceComparator; 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.JPFException; import gov.nasa.jpf.SystemAttribute; import gov.nasa.jpf.jvm.bytecode.ATHROW; import gov.nasa.jpf.jvm.bytecode.EXCEPTION; import gov.nasa.jpf.jvm.bytecode.EXECUTENATIVE; import gov.nasa.jpf.jvm.bytecode.INVOKESTATIC; import gov.nasa.jpf.jvm.bytecode.InvokeInstruction; import gov.nasa.jpf.util.HashData; import gov.nasa.jpf.util.IntVector; import gov.nasa.jpf.util.JPFLogger; import gov.nasa.jpf.util.Predicate; import gov.nasa.jpf.util.StringSetMatcher; import gov.nasa.jpf.vm.choice.BreakGenerator; import gov.nasa.jpf.vm.choice.ThreadChoiceFromSet; /** * Represents a thread. It contains the state of the thread, static * information about the thread, and the stack frames. * Race detection and lock order also store some information * in this data structure. * <p/> * Note that we preserve identities according to their associated java.lang.Thread object * (oref). This esp. means along the same path, a ThreadInfo reference * is kept invariant * <p/> * <2do> remove EXECUTENATIVE,INVOKESTATIC .bytecode dependencies */ public class ThreadInfo extends InfoObject implements Iterable<StackFrame>, Comparable<ThreadInfo>, Restorable<ThreadInfo> { static JPFLogger log = JPF.getLogger("gov.nasa.jpf.vm.ThreadInfo"); static int[] emptyLockRefs = new int[0]; //--- our internal thread states public enum State { NEW, // means created but not yet started RUNNING, BLOCKED, // waiting to acquire a lock UNBLOCKED, // was BLOCKED but can acquire the lock now WAITING, // waiting to be notified TIMEOUT_WAITING, NOTIFIED, // was WAITING and got notified, but is still blocked INTERRUPTED, // was WAITING and got interrupted TIMEDOUT, // was TIMEOUT_WAITING and timed out TERMINATED, SLEEPING } ; static final int[] emptyRefArray = new int[0]; static final String MAIN_NAME = "main"; static ThreadInfo currentThread; protected class StackIterator implements Iterator<StackFrame> { StackFrame frame = top; @Override public boolean hasNext() { return frame != null; } @Override public StackFrame next() { if (frame != null) { StackFrame ret = frame; frame = frame.getPrevious(); return ret; } else { throw new NoSuchElementException(); } } @Override public void remove() { throw new UnsupportedOperationException("can't remove StackFrames"); } } protected class InvokedStackIterator extends StackIterator implements Iterator<StackFrame> { InvokedStackIterator() { frame = getLastInvokedStackFrame(); } @Override public StackFrame next() { if (frame != null) { StackFrame ret = frame; frame = null; for (StackFrame f = ret.getPrevious(); f != null; f = f.getPrevious()) { if (!f.isDirectCallFrame()) { frame = f; break; } } return ret; } else { throw new NoSuchElementException(); } } } //--- instance fields // transient, not state stored. This is reset when backtracking or starting a new transition protected ExceptionInfo pendingException; // state managed data that is copy-on-first-write protected ThreadData threadData; //<2do> Hmm, why are these not in ThreadData? // the top stack frame protected StackFrame top = null; // the current stack depth (number of frames) protected int stackDepth; // something that tells the ThreadList how to look this up efficiently (e.g. index) // note - this is for internal purposes only, there is no public accessor // (we don't want to expose/hardwire ThreadList implementation) // note also that the ThreadList is allowed to move this thread around, in which // case update is the ThreadLists responsibility protected int tlIdx; //--- the invariants // we need this mostly for getting our SystemClassLoader protected ApplicationContext appCtx; // search global id, which is the basis for canonical order of threads protected int id; protected int objRef; // the java.lang.Thread object reference protected ClassInfo ci; // the classinfo associated with the thread object protected int targetRef; // the associated java.lang.Runnable // which attributes are stored/restored static final int ATTR_STORE_MASK = 0x0000ffff; //--- the transient (un(re)stored) attributes static final int ATTR_DATA_CHANGED = 0x10000; static final int ATTR_STACK_CHANGED = 0x20000; static final int ATTR_ATTRIBUTE_CHANGED = 0x80000; //--- state stored/restored attributes // this is a typical "orthogonal" thread state we have to remember, but // that should not affect any locking, blocking, notifying or such static final int ATTR_STOPPED = 0x0001; //--- change sets static final int ATTR_ANY_CHANGED = (ATTR_DATA_CHANGED | ATTR_STACK_CHANGED | ATTR_ATTRIBUTE_CHANGED); static final int ATTR_SET_STOPPED = (ATTR_STOPPED | ATTR_ATTRIBUTE_CHANGED); protected int attributes; /** * counter for executed instructions along current transition */ protected int executedInstructions; /** * shall we skip the next insn */ protected boolean skipInstruction; /** * a listener or peer request for throwing an exception into the SUT, to be processed in executeInstruction */ protected SUTExceptionRequest pendingSUTExceptionRequest; /** * store the last executed insn in the path */ protected boolean logInstruction; /** * the last returned direct call frame */ protected DirectCallStackFrame returnedDirectCall; /** * the next insn to enter (null prior to execution) */ protected Conditional<Instruction> nextPc; /** * not so nice we cross-couple the NativePeers with ThreadInfo, * but to carry on with the JNI analogy, a MJIEnv is clearly * owned by a thread (even though we don't have much ThreadInfo * state dependency in here (yet), hence the simplistic init) */ MJIEnv env; /** * the VM we are running on. Bad backlink, but then again, we can't really * test a ThreadInfo outside a VM context anyways. * <2do> If we keep 'list' as a field, 'vm' might be moved over there * (all threads in the list share the same VM) */ VM vm; /** * !! this is a volatile object, i.e. it has to be re-computed explicitly * !! after each backtrack (we don't want to duplicate state storage) * list of lock objects currently held by this thread. * unfortunately, we cannot organize this as a stack, since it might get * restored (from the heap) in random order */ int[] lockedObjectReferences; /** * !! this is also volatile -> has to be reset after backtrack * the reference of the object if this thread is blocked or waiting for */ int lockRef = MJIEnv.NULL; Memento<ThreadInfo> cachedMemento; // cache for unchanged ThreadInfos static class TiMemento implements Memento<ThreadInfo> { // note that we don't have to store the invariants (id, oref, runnableRef, ciException) ThreadInfo ti; ThreadData threadData; StackFrame top; int stackDepth; int attributes; TiMemento(ThreadInfo ti) { this.ti = ti; threadData = ti.threadData; // no need to clone - it's copy on first write top = ti.top; // likewise stackDepth = ti.stackDepth; // we just copy this for efficiency reasons attributes = (ti.attributes & ATTR_STORE_MASK); ti.freeze(); ti.markUnchanged(); } @Override public ThreadInfo restore(ThreadInfo ignored) { ti.resetVolatiles(); ti.threadData = threadData; ti.top = top; ti.stackDepth = stackDepth; ti.attributes = attributes; ti.markUnchanged(); return ti; } } // the following parameters are configurable. Would be nice if we could keep // them on a per-instance basis, but there are a few locations // (e.g. ThreadList) where we loose the init context, and it's questionable // if we want to change this at runtime, or POR might make sense on a per-thread // basis /** * do we halt on each throw, i.e. don't look for an exception handler? * Useful to find empty handler blocks, or misusd exceptionHandlers */ static StringSetMatcher haltOnThrow; /** * do we delegate to Thread.UncaughtExceptionHandlers (in case there is any * other than the standard ThreadGroup) */ static boolean ignoreUncaughtHandlers; /** * do we go on if we return from an UncaughtExceptionHandler, or do we still * regard this as a NoUncaughtExceptionProperty violation */ static boolean passUncaughtHandler; /** * is on-the-fly partial order in effect? */ static boolean porInEffect; /** * do we treat access of fields referring to objects that are referenced from * different threads as transition breaks? */ static boolean porFieldBoundaries; /** * do we also break on reference field puts after assignment, i.e. potential * exposure of the referenced object */ static boolean porBreakOnExposure; /** * detect field synchronization (find locks which are used to synchronize * field access - if we have viable candidates, and we find the locks taken, * we don't treat access of the corresponding field as a boundary step */ static boolean porSyncDetection; /** * do we treat ctor calls as unshared. If set to true, field access in init methods * is not checked for shared access. While this is generally true since the object * is still in construction, it is possible to leak the object reference to another * thread before the ctor returns */ static boolean porUnsharedInit; /** * break the current transition after this number of instructions. * This is a safeguard against paths that won't break because potentially * shared fields are not yet accessed by a second thread (existence of such * paths is the downside of our access tracking). Note that we only break on * backjumps once this count gets exceeded, to give state matching a better * chance and avoid interference with the IdleLoop listener */ static int maxTransitionLength; /** * reset ThreadInfo statics (e.g. to reinitialize JPF) */ static boolean init(Config config) { currentThread = null; globalTids = new HashMap<Integer, Integer>(); String[] haltOnThrowSpecs = config.getStringArray("vm.halt_on_throw"); if (haltOnThrowSpecs != null) { haltOnThrow = new StringSetMatcher(haltOnThrowSpecs); } ignoreUncaughtHandlers = config.getBoolean("vm.ignore_uncaught_handler", true); passUncaughtHandler = config.getBoolean("vm.pass_uncaught_handler", true); //--- POR related configuration options porInEffect = config.getBoolean("vm.por"); porFieldBoundaries = porInEffect && config.getBoolean("vm.por.field_boundaries"); porBreakOnExposure = porFieldBoundaries && config.getBoolean("vm.por.break_on_exposure"); porSyncDetection = porInEffect && config.getBoolean("vm.por.sync_detection"); porUnsharedInit = porInEffect && config.getBoolean("vm.por.unshared_init", true); maxTransitionLength = Integer.MAX_VALUE; // because we do not model check // TODO could be done via configuration // maxTransitionLength = config.getInt("vm.max_transition_length", 5000); SharedObjectPolicy.init(config); return true; } //--- factory methods // <2do> this is going to be a configurable factory method /* * search global cache for dense ThreadInfo ids. We could just use oref since those are * guaranteed to be global, but not dense. The ids are search global, i.e. there is no * need to store/restore, but it needs to be (re)set during init() */ static Map<Integer, Integer> globalTids; // initialized by init protected int computeId(int objRef) { Integer id = globalTids.get(objRef); if (id == null) { id = globalTids.size(); addId(objRef, id); } return id; } static void addId(int objRef, int id) { globalTids.put(objRef, id); } public CoverageClass coverage = new CoverageClass(this); /** * mainThread ctor called by the VM. Note we don't have a thread object yet (hen-and-egg problem * since we can't allocate objects without a ThreadInfo) */ protected ThreadInfo (VM vm, int id, ApplicationContext appCtx) { this.id = id; this.appCtx = appCtx; init(vm); // we don't have the group yet, no Runnable or parent, and the name is fixed // the thread is also not yet in the ThreadList ci = appCtx.getSystemClassLoader().getThreadClassInfo(); targetRef = MJIEnv.NULL; threadData.name = MAIN_NAME.toCharArray(); } /** * the ctor for all explicitly (bytecode) created threads. At this point, there is at least * a mainThread and we have a corresponding java.lang.Thread object */ protected ThreadInfo(VM vm, int objRef, int groupRef, int runnableRef, int nameRef, ThreadInfo parent) { id = computeId(objRef); this.appCtx = parent.getApplicationContext(); init(vm); // this is only partial, we still have to link/set the references ElementInfo ei = vm.getElementInfo(objRef); this.ci = ei.getClassInfo(); this.objRef = objRef; this.targetRef = runnableRef; threadData.name = vm.getElementInfo(nameRef).asString().getValue().toCharArray(); // note the thread is not yet in the ThreadList, we have to register from the caller } protected void init(VM vm) { // 'gid' is set by the factory method // we can't set the 'id' field of the corresponding java.lang.Thread object until we have one this.vm = vm; threadData = new ThreadData(); threadData.state = State.NEW; threadData.priority = Thread.NORM_PRIORITY; threadData.isDaemon = false; threadData.lockCount = 0; threadData.suspendCount = 0; // threadData.name not yet known // 'priority', 'name', 'target' and 'group' are not taken // from the object, but set within the java.lang.Thread ctors top = null; stackDepth = 0; lockedObjectReferences = emptyLockRefs; markUnchanged(); attributes |= ATTR_DATA_CHANGED; env = new MJIEnv(this); } @Override public Memento<ThreadInfo> getMemento(MementoFactory factory) { return factory.getMemento(this); } public Memento<ThreadInfo> getMemento() { return new TiMemento(this); } void freeze() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { frame.freeze(); } } //--- cached mementos are only supposed to be accessed from the Restorer public Memento<ThreadInfo> getCachedMemento() { return cachedMemento; } public void setCachedMemento(Memento<ThreadInfo> memento) { cachedMemento = memento; } public static ThreadInfo getCurrentThread() { return currentThread; } public boolean isExecutingAtomically() { return vm.getSystemState().isAtomic(); } public boolean holdsLock(ElementInfo ei) { int objRef = ei.getObjectRef(); for (int i = 0; i < lockedObjectReferences.length; i++) { if (lockedObjectReferences[i] == objRef) { return true; } } return false; } public VM getVM() { return vm; } /** * answers if is this the first instruction within the current transition. * This is mostly used to tell the top- from the bottom-half of a native method * or Instruction.enter(), so that only one (the top half) registers the CG * (top = register CG and reschedule insn, bottom = re-enter insn and process choice * at beginning of new transition) * <p/> * This can be used in both pre- and post-exec notification listeners, * the executedInstructions number is incremented before notifying * instructionExecuted() * <p/> * this method depends on the sequence of operations in ThreadInfo.executeInstruction, * which is: * nextPc = null * notify executeInstruction * nextPc = insn.enter * increment executedInstructions * notify instructionExecuted */ public boolean isFirstStepInsn() { int nInsn = executedInstructions; if (nInsn == 0) { // that would be a break in enter() or instructionExecuted() return true; } else if (nInsn == 1 && nextPc != null) { // that is for setting the CG in executeInsn or enter, and then testing in // instructionExecuted. Note that nextPc is reset before pre-exec notification // and hence should only be non-null from insn.enter() up to the next // ThreadInfo.executeInstruction() return true; } return false; } /** * get the number of instructions executed in the current transition. This * gets incremented after calling Instruction.enter(), i.e. before * notifying instructionExecuted() listeners */ public int getExecutedInstructions() { return executedInstructions; } /** * to be used from methods called from listeners, to find out if we are in a * pre- or post-exec notification */ public boolean isPreExec() { return (nextPc == null); } public boolean usePor() { return porInEffect; } public boolean usePorFieldBoundaries() { return porFieldBoundaries; } public boolean useBreakOnExposure() { return porBreakOnExposure; } public boolean usePorSyncDetection() { return porSyncDetection; } public boolean useUnsharedInit() { return porUnsharedInit; } //--- various thread state related methods /** * Updates the status of the thread. */ public void setState(State newStatus) { State oldStatus = threadData.state; if (oldStatus != newStatus) { assert (oldStatus != State.TERMINATED) : "can't resurrect thread " + this + " to " + newStatus.name(); threadDataClone().state = newStatus; switch (newStatus) { case NEW: break; // Hmm, shall we report a thread object creation? case RUNNING: // nothing. the notifyThreadStarted has to happen from // Thread.start(), since the thread could have been blocked // at the time with a sync run() method // assert lockRef == MJIEnv.NULL; break; case TERMINATED: vm.notifyThreadTerminated(this); break; case BLOCKED: assert lockRef != MJIEnv.NULL; vm.notifyThreadBlocked(this); break; case UNBLOCKED: assert lockRef == MJIEnv.NULL; break; // nothing to notify case WAITING: assert lockRef != MJIEnv.NULL; vm.notifyThreadWaiting(this); break; case INTERRUPTED: vm.notifyThreadInterrupted(this); break; case NOTIFIED: assert lockRef != MJIEnv.NULL; vm.notifyThreadNotified(this); break; case SLEEPING: break; case TIMEDOUT: break; case TIMEOUT_WAITING: break; default: break; } if (log.isLoggable(Level.FINE)) { log.fine("setStatus of " + getName() + " from " + oldStatus.name() + " to " + newStatus.name()); } } } void setBlockedState(int objref) { State currentState = threadData.state; switch (currentState) { case NEW: case RUNNING: case UNBLOCKED: lockRef = objref; setState(State.BLOCKED); break; default: assert false : "thread " + this + "can't be blocked in state: " + currentState.name(); } } void setNotifiedState() { State currentState = threadData.state; switch (currentState) { case BLOCKED: case INTERRUPTED: // too late, we are already interrupted case NOTIFIED: // can happen in a Thread.join() break; case WAITING: case TIMEOUT_WAITING: setState(State.NOTIFIED); break; default: assert false : "thread " + this + "can't be notified in state: " + currentState.name(); } } /** * Returns the current status of the thread. */ public State getState() { return threadData.state; } /** * Returns true if this thread is either RUNNING or UNBLOCKED */ public boolean isRunnable() { if (threadData.suspendCount != 0) { return false; } switch (threadData.state) { case RUNNING: case UNBLOCKED: return true; case SLEEPING: return true; // that's arguable, but since we don't model time we treat it like runnable case TIMEDOUT: return true; // would have been set to blocked if it couldn't reacquire the lock default: return false; } } public boolean willBeRunnable() { if (threadData.suspendCount != 0) { return false; } switch (threadData.state) { case RUNNING: case UNBLOCKED: return true; case TIMEOUT_WAITING: // it's not yet, but it will be at the time it gets scheduled case SLEEPING: return true; default: return false; } } public boolean isNew() { return (threadData.state == State.NEW); } public boolean isTimeoutRunnable() { if (threadData.suspendCount != 0) { return false; } switch (threadData.state) { case RUNNING: case UNBLOCKED: case SLEEPING: return true; case TIMEOUT_WAITING: // depends on if we can re-acquire the lock //assert lockRef != MJIEnv.NULL : "timeout waiting but no blocked object"; if (lockRef != MJIEnv.NULL) { ElementInfo ei = vm.getElementInfo(lockRef); return ei.canLock(this); } else { return true; } default: return false; } } public boolean isTimedOut() { return (threadData.state == State.TIMEDOUT); } public boolean isTimeoutWaiting() { return (threadData.state == State.TIMEOUT_WAITING); } public void setTimedOut() { setState(State.TIMEDOUT); } public void setTerminated() { setState(State.TERMINATED); } public void resetTimedOut() { // should probably check for TIMEDOUT setState(State.TIMEOUT_WAITING); } public void setSleeping() { setState(State.SLEEPING); } public boolean isSleeping() { return (threadData.state == State.SLEEPING); } public void setRunning() { setState(State.RUNNING); } public void setStopped(FeatureExpr ctx, int throwableRef) { if (isTerminated()) { // no need to kill twice return; } attributes |= ATTR_SET_STOPPED; if (!hasBeenStarted()) { // that one is easy - just remember the state so that a subsequent start() // does nothing return; } // for all other cases, we need to have a proper stopping Throwable that does not // fall victim to GC, and that does not cause NoUncaughtExcceptionsProperty violations if (throwableRef == MJIEnv.NULL) { // if no throwable was provided (the normal case), throw a java.lang.ThreadDeath Error ClassInfo cix = ClassInfo.getInitializedSystemClassInfo(ctx, "java.lang.ThreadDeath", this); throwableRef = createException(ctx, cix, null, MJIEnv.NULL); } // now the tricky part - this thread is alive but might be blocked, notified // or waiting. In any case, exception action should not take place before // the thread becomes scheduled again, which // means we are not allowed to fiddle with its state in any way that changes // scheduling/locking behavior. On the other hand, if this is the currently // executing thread, take immediate action if (isCurrentThread()) { // we are suicidal if (isInNativeMethod()) { // remember the exception to be thrown when we return from the native method env.throwException(throwableRef); } else { Instruction nextPc = throwException(FeatureExprFactory.True(), throwableRef); setNextPC(nextPc); } } else { // this thread is not currently running, this is an external kill // remember there was a pending exception that has to be thrown the next // time this gets scheduled, and make sure the exception object does // not get GCed prematurely ElementInfo eit = getModifiableElementInfo(objRef); eit.setReferenceField(FeatureExprFactory.True(), "stopException", new One<>(throwableRef)); } } public boolean isCurrentThread() { return this == currentThread; } public boolean isInCurrentThreadList() { return vm.getThreadList().contains(this); } /** * An alive thread is anything but TERMINATED or NEW */ public boolean isAlive() { State state = threadData.state; return (state != State.TERMINATED && state != State.NEW); } public boolean isWaiting() { State state = threadData.state; return (state == State.WAITING) || (state == State.TIMEOUT_WAITING); } public boolean isNotified() { return (threadData.state == State.NOTIFIED); } public boolean isUnblocked() { State state = threadData.state; return (state == State.UNBLOCKED) || (state == State.TIMEDOUT); } public boolean isBlocked() { return (threadData.state == State.BLOCKED); } public boolean isTerminated() { return (threadData.state == State.TERMINATED); } MethodInfo getExitMethod() { MethodInfo mi = getClassInfo().getMethod("exit()V", true); return mi; } public boolean isBlockedOrNotified() { State state = threadData.state; return (state == State.BLOCKED) || (state == State.NOTIFIED); } // this is just a state attribute public boolean isStopped() { return (attributes & ATTR_STOPPED) != 0; } public boolean isInNativeMethod() { return top != null && top.isNative(); } public boolean hasBeenStarted() { return (threadData.state != State.NEW); } public String getStateName() { return threadData.getState().name(); } @Override public Iterator<StackFrame> iterator() { return new StackIterator(); } public Iterable<StackFrame> invokedStackFrames() { return new Iterable<StackFrame>() { @Override public Iterator<StackFrame> iterator() { return new InvokedStackIterator(); } }; } /** * this returns a copy of the StackFrames in reverse order. Note this is * redundant because the frames are linked explicitly * * @deprecated - use Iterable<StackFrame> */ @Deprecated public List<StackFrame> getStack() { ArrayList<StackFrame> list = new ArrayList<StackFrame>(stackDepth); for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { list.add(frame); } Collections.reverse(list); return list; } /** * returns StackFrames which have been entered through a corresponding * invoke instruction (in top first order) */ public List<StackFrame> getInvokedStackFrames() { ArrayList<StackFrame> list = new ArrayList<StackFrame>(stackDepth); // int i = stackDepth-1; for (StackFrame frame = top; frame != null; frame = frame.getPrevious()){ if (!frame.isDirectCallFrame()){ list.add( frame); } } Collections.reverse(list); return list; } public int getStackDepth() { return stackDepth; } public MethodInfo getEntryMethod() { return appCtx.getEntryMethod(); } public StackFrame getCallerStackFrame(int offset) { int n = offset; for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { if (n < 0) { break; } else if (n == 0) { return frame; } n--; } return null; } public StackFrame getLastInvokedStackFrame() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { if (!frame.isDirectCallFrame()) { return frame; } } return null; } public StackFrame getLastNonSyntheticStackFrame() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { if (!frame.isSynthetic()) { return frame; } } return null; } // this is ugly - it can modify deeper stack frames public StackFrame getModifiableLastNonSyntheticStackFrame() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { if (!frame.isSynthetic()) { if (frame.isFrozen()) { StackFrame newFrame = frame.clone(); if (frame == top) { frame = newFrame; top = newFrame; } else { // Ughh, now we have to clone all frozen frames above StackFrame fLast = null; for (StackFrame f = getModifiableTopFrame(); f != frame; f = f .getPrevious()) { if (f.isFrozen()) { f = f.clone(); if (fLast != null) { fLast.setPrevious(f); } } fLast = f; } if (fLast != null) { fLast.setPrevious(newFrame); } frame = newFrame; } } return frame; } } return null; } /** * Returns the this pointer of the callee from the stack. */ public int getCalleeThis(MethodInfo mi) { return top.getCalleeThis(FeatureExprFactory.True(), mi).getValue(); } /** * Returns the this pointer of the callee from the stack. */ public Conditional<Integer> getCalleeThis(FeatureExpr ctx, int size) { return top.getCalleeThis(ctx, size); } public ClassInfo getClassInfo(int objref) { return env.getClassInfo(objref); } public boolean isCalleeThis(ElementInfo r) { if (top == null || r == null) { return false; } Conditional<Instruction> pc = getPC(); if (pc == null || !(pc.getValue() instanceof InvokeInstruction) || pc.getValue() instanceof INVOKESTATIC) { return false; } InvokeInstruction call = (InvokeInstruction) pc.getValue(); return getCalleeThis(FeatureExprFactory.True(), Types.getArgumentsSize(call.getInvokedMethodSignature()) + 1).getValue().intValue() == r.getObjectRef(); } public ApplicationContext getApplicationContext() { return appCtx; } public SystemClassLoaderInfo getSystemClassLoaderInfo() { return appCtx.sysCl; } /** * Returns the class information. */ public ClassInfo getClassInfo() { return ci; } public MJIEnv getEnv() { return env; } public boolean isInterrupted(FeatureExpr ctx, boolean resetStatus) { ElementInfo ei = getElementInfo(getThreadObjectRef()); Conditional<Boolean> status = ei.getBooleanField("interrupted"); if (resetStatus && status.getValue()) { ei = ei.getModifiableInstance(); ei.setBooleanField(ctx, "interrupted", One.FALSE); } return status.getValue(); } /** * path local unique id for live threads. This is what we use for the * public java.lang.Thread.getId() that can be called from SUT code. It is * NOT what we use for our canonical root set */ public int getId() { return id; } /** * this is our internal, search global id that is used for the * canonical root set */ public int getGlobalId() { return id; } /** * record what this thread is being blocked on. */ void setLockRef(int objref) { /** assert ((lockRef == MJIEnv.NULL) || (lockRef == objref)) : "attempt to overwrite lockRef: " + vm.getHeap().get(lockRef) + " with: " + vm.getHeap().get(objref); **/ lockRef = objref; } /** * thread is not blocked anymore * needs to be public since we have to use it from INVOKECLINIT (during call skipping) */ public void resetLockRef() { lockRef = MJIEnv.NULL; } public int getLockRef() { return lockRef; } public ElementInfo getLockObject() { if (lockRef == MJIEnv.NULL) { return null; } else { return vm.getElementInfo(lockRef); } } /** * Returns the line number of the program counter of the top stack frame. */ public int getLine() { if (top == null) { return -1; } else { return top.getLine(); } } //--- suspend/resume modeling // modeling this with a count is an approximation. In reality it behaves // rather like a race that /sometimes/ causes the resume to fail, but its // Ok if we overapproximate on the safe side, since suspend/resume is such // an inherently unsafe thing. What we *do* want to preserve faithfully is // that locks held by the suspended thread are not released /** * set suspension status * * @return true if thread was not suspended */ public boolean suspend() { return threadDataClone().suspendCount++ == 0; } /** * unset suspension status * * @return true if thread was suspended */ public boolean resume() { return (threadData.suspendCount > 0) && (--threadDataClone().suspendCount == 0); } public boolean isSuspended() { return threadData.suspendCount > 0; } //--- locks /** * Sets the number of locks held at the time of a wait. */ public void setLockCount(int l) { if (threadData.lockCount != l) { threadDataClone().lockCount = l; } } /** * Returns the number of locks in the last wait. */ public int getLockCount() { return threadData.lockCount; } // avoid use in performance critical code public List<ElementInfo> getLockedObjects() { List<ElementInfo> lockedObjects = new LinkedList<ElementInfo>(); Heap heap = vm.getHeap(); for (int i = 0; i < lockedObjectReferences.length; i++) { ElementInfo ei = heap.get(lockedObjectReferences[i]); lockedObjects.add(ei); } return lockedObjects; } public boolean hasLockedObjects() { return lockedObjectReferences.length > 0; } public int[] getLockedObjectReferences() { return lockedObjectReferences; } /** * returns the current method in the top stack frame, which is always a * bytecode method (executed by JPF) */ public MethodInfo getTopFrameMethodInfo() { if (top != null) { return top.getMethodInfo(); } else { return null; } } /** * return the ClassInfo of the topmost stackframe that is not a direct call */ public ClassInfo getExecutingClassInfo() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { MethodInfo miExecuting = frame.getMethodInfo(); ClassInfo ciExecuting = miExecuting.getClassInfo(); if (ciExecuting != null) { return ciExecuting; } } return null; } public ClassInfo resolveReferencedClass(String clsName) { ClassInfo ciTop = top.getClassInfo(); return ciTop.resolveReferencedClass(null, clsName); //return ClassLoaderInfo.getCurrentClassLoader(this).getResolvedClassInfo(clsName); } public boolean isInCtor() { // <2do> - hmm, if we don't do this the whole stack, we miss factored // init funcs MethodInfo mi = getTopFrameMethodInfo(); if (mi != null) { return mi.isCtor(); } else { return false; } } public boolean isCtorOnStack(int objRef) { for (StackFrame f = top; f != null; f = f.getPrevious()) { if (f.getThis().getValue() == objRef && f.getMethodInfo().isCtor()) { return true; } } return false; } public boolean isClinitOnStack(ClassInfo ci) { for (StackFrame f = top; f != null; f = f.getPrevious()) { MethodInfo mi = f.getMethodInfo(); if (mi.isClinit(ci)) { return true; } } return false; } public String getName() { return new String(threadData.name); } /** * Returns the object reference. */ public int getThreadObjectRef() { return objRef; } public ElementInfo getThreadObject() { return getElementInfo(objRef); } public ElementInfo getModifiableThreadObject() { return getModifiableElementInfo(objRef); } /** * Sets the program counter of the top stack frame. */ public void setPC(Conditional<Instruction> pc) { getModifiableTopFrame().setPC(pc); } public void advancePC() { getModifiableTopFrame().advancePC(); } /** * Returns the program counter of the top stack frame. */ @SuppressWarnings("unchecked") public Conditional<Instruction> getPC() { if (top != null) { return top.getPC(); } else { return (Conditional<Instruction>) One.NULL; } } public Conditional<Instruction> getNextPC() { return nextPc; } /** * get the current stack trace of this thread * this is called during creation of a Throwable, hence we should skip * all throwable ctors in here * <2do> this is only a partial solution,since we don't catch exceptionHandlers * in Throwable ctors yet */ public String getStackTrace() { StringBuilder sb = new StringBuilder(256); for (StackFrame sf = top; sf != null; sf = sf.getPrevious()) { MethodInfo mi = sf.getMethodInfo(); if (mi.isCtor()) { ClassInfo ci = mi.getClassInfo(); if (ci.isInstanceOf("java.lang.Throwable")) { continue; } } sb.append("\tat "); sb.append(sf.getStackTraceInfo()); sb.append('\n'); } return sb.toString(); } /** * Returns the information necessary to store. * <p/> * <2do> pcm - not clear to me how lower stack frames can contribute to * a different threadinfo state hash - only the current one can be changed * by the executing method */ public void dumpStoringData(IntVector v) { // v = null; // Get rid of IDE warnings } /** * Returns the object reference of the target. */ public int getRunnableRef() { return targetRef; } /** * Returns the pointer to the object reference of the executing method */ public Conditional<Integer> getThis() { return top.getThis(); } public ElementInfo getThisElementInfo() { return getElementInfo(getThis().getValue()); } public boolean isThis(ElementInfo ei) { if (ei == null) { return false; } if (top == null) { return false; } if (getTopFrameMethodInfo().isStatic()) { return false; } else { int thisRef = top.getThis().getValue(); return ei.getObjectRef() == thisRef; } } public boolean atMethod(String mname) { return top != null && getTopFrameMethodInfo().getFullName().equals(mname); } public boolean atPosition(int position) { if (top == null) { return false; } else { Instruction pc = getPC().getValue(); return pc != null && pc.getPosition() == position; } } public boolean atReturn() { if (top == null) { return false; } else { Instruction pc = getPC().getValue(); return pc instanceof ReturnInstruction; } } /** * reset any information that has to be re-computed in a backtrack * (i.e. hasn't been stored explicitly) */ void resetVolatiles() { // resetting lock sets goes here lockedObjectReferences = emptyLockRefs; // the ref of the object we are blocked on or waiting for lockRef = MJIEnv.NULL; pendingException = null; } /** * this is used when restoring states */ void updateLockedObject(ElementInfo ei) { int n = lockedObjectReferences.length; int[] a = new int[n + 1]; System.arraycopy(lockedObjectReferences, 0, a, 0, n); a[n] = ei.getObjectRef(); lockedObjectReferences = a; // don't notify here, it's just a restore } void addLockedObject(ElementInfo ei) { int n = lockedObjectReferences.length; int[] a = new int[n + 1]; System.arraycopy(lockedObjectReferences, 0, a, 0, n); a[n] = ei.getObjectRef(); lockedObjectReferences = a; vm.notifyObjectLocked(this, ei); } void removeLockedObject(ElementInfo ei) { int objRef = ei.getObjectRef(); int n = lockedObjectReferences.length; if (n == 1) { assert lockedObjectReferences[0] == objRef; lockedObjectReferences = emptyLockRefs; } else { int[] a = new int[n - 1]; for (int i = 0, j = 0; i < n; i++) { if (lockedObjectReferences[i] != objRef) { a[j++] = lockedObjectReferences[i]; } } lockedObjectReferences = a; } vm.notifyObjectUnlocked(this, ei); } @Override public Object clone() { try { // threadData and top StackFrame are copy-on-write, so we should not have to clone them // lockedObjects are state-volatile and restored explicitly after a backtrack return super.clone(); } catch (CloneNotSupportedException cnsx) { return null; } } /** * Returns the number of stack frames. */ public int countStackFrames() { return stackDepth; } /** * get a stack snapshot that consists of an array of {mthId,pc} pairs. * strip stackframes that enter instance methods of the exception object */ public int[] getSnapshot(FeatureExpr ctx, int xObjRef) { StackFrame frame = top; int n = stackDepth; if (xObjRef != MJIEnv.NULL) { // filter out exception method frames for (; frame != null; frame = frame.getPrevious()) { if (frame.getThis().getValue() != xObjRef) { break; } n--; } } int j = 0; int[] snap = new int[n * 2]; for (; frame != null; frame = frame.getPrevious()) { snap[j++] = frame.getMethodInfo().getGlobalId(); snap[j++] = frame.getPC().simplify(ctx).getValue().getInstructionIndex(); } return snap; } /** * turn a snapshot into an JPF array of StackTraceElements, which means * a lot of objects. Do this only on demand */ public int createStackTraceElements(FeatureExpr ctx, int[] snapshot) { int n = snapshot.length / 2; int nVisible = 0; StackTraceElement[] list = new StackTraceElement[n]; for (int i = 0, j = 0; i < n; i++) { int methodId = snapshot[j++]; int pcOffset = snapshot[j++]; StackTraceElement ste = new StackTraceElement(methodId, pcOffset); if (!ste.ignore) { list[nVisible++] = ste; } } Heap heap = vm.getHeap(); ElementInfo eiArray = heap.newArray(ctx, "Ljava/lang/StackTraceElement;", nVisible, this); for (int i = 0; i < nVisible; i++) { int eref = list[i].createJPFStackTraceElement(ctx); eiArray.setReferenceElement(ctx, i, new One<>(eref)); } return eiArray.getObjectRef(); } void print(PrintWriter pw, String s) { if (pw != null) { pw.print(s); } else { vm.print(s); } } public void printStackTrace(FeatureExpr ctx, int objRef) { printStackTrace(ctx, null, objRef); } public void printPendingExceptionOn(PrintWriter pw) { if (pendingException != null) { printStackTrace(null, pw, pendingException.getExceptionReference()); } } /** * the reason why this is kind of duplicated (there is also a StackFrame.getPositionInfo) * is that this might be working off a StackTraceElement[] that is created when the exception * is created. At the time printStackTrace() is called, the StackFrames in question * are most likely already be unwinded */ public void printStackTrace(FeatureExpr ctx, final PrintWriter pw, int objRef) { // TODO revise output // 'env' usage is not ideal, since we don't know from what context we are called, and // hence the MJIEnv calling context might not be set (no Method or ClassInfo) // on the other hand, we don't want to re-implement all the MJIEnv accessor methods print(pw, "if " + Conditional.getCTXString(ctx) + ":\n"); print(pw, env.getClassInfo(objRef).getName()); Conditional<Integer> msgRef = env.getReferenceField(ctx, objRef, "detailMessage"); for (Entry<Integer, FeatureExpr> e : msgRef.toMap().entrySet()) { if (e.getKey() != MJIEnv.NULL) { print(pw, ": "); Conditional<String> message = env.getStringObjectNew(e.getValue().and(ctx), e.getKey()); if (message instanceof One) { print(pw, message.getValue()); } else { print(pw, message.toString()); } } print(pw, "\n"); } // try the 'stackTrace' field first, it might have been set explicitly int aRef = env.getReferenceField(ctx, objRef, "stackTrace").getValue(); // StackTrace[] if (aRef != MJIEnv.NULL) { int len = env.getArrayLength(ctx, aRef); for (int i = 0; i < len; i++) { int steRef = env.getReferenceArrayElement(aRef, i).getValue(); if (steRef != MJIEnv.NULL) { // might be ignored (e.g. direct call) StackTraceElement ste = new StackTraceElement(steRef); ste.printOn(pw); } } } else { // fall back to use the snapshot stored in the exception object Conditional<Integer> VAaRef = env.getReferenceField(ctx, objRef, "snapshot"); VAaRef.mapf(ctx, new BiConsumer<FeatureExpr, Integer>() { @Override public void accept(FeatureExpr ctx, Integer aRef) { int[] snapshot = env.getIntArrayObject(ctx, aRef); int len = snapshot.length / 2; for (int i = 0, j = 0; i < len; i++) { int methodId = snapshot[j++]; int pcOffset = snapshot[j++]; StackTraceElement ste = new StackTraceElement(methodId, pcOffset); ste.printOn(pw); } } }); } Conditional<Integer> causeRef = env.getReferenceField(ctx, objRef, "cause"); causeRef.map(ref -> { if ((ref != objRef) && (ref != MJIEnv.NULL)) { print(pw, "Caused by: "); printStackTrace(ctx, pw, ref); } return null; }); } class StackTraceElement { String clsName, mthName, fileName; int line; boolean ignore; StackTraceElement(int methodId, int pcOffset) { if (methodId == MethodInfo.DIRECT_CALL) { ignore = true; } else { MethodInfo mi = MethodInfo.getMethodInfo(methodId); if (mi != null) { clsName = mi.getClassName(); mthName = mi.getName(); fileName = mi.getStackTraceSource(); if (fileName != null) { fileName = fileName.substring(fileName.lastIndexOf('/') + 1); } if (pcOffset < 0) { // See ThreadStopTest.threadDeathWhileRunstart // <2do> remove when RUNSTART is gone pcOffset = 0; } line = mi.getLineNumber(mi.getInstruction(pcOffset)); } else { // this sounds like a bug clsName = "?"; mthName = "?"; fileName = "?"; line = -1; } } } @SuppressWarnings("deprecation") StackTraceElement (int sRef){ FeatureExpr ctx = FeatureExprFactory.True(); clsName = env.getStringObject(ctx, env.getReferenceField(ctx, sRef, "clsName").getValue()); mthName = env.getStringObject(ctx, env.getReferenceField(ctx, sRef, "mthName").getValue()); fileName = env.getStringObject(ctx, env.getReferenceField(ctx, sRef, "fileName").getValue()); line = env.getIntField(sRef, "line").getValue().intValue(); } int createJPFStackTraceElement(FeatureExpr ctx) { if (ignore) { return MJIEnv.NULL; } else { Heap heap = vm.getHeap(); ClassInfo ci = ClassLoaderInfo.getSystemResolvedClassInfo("java.lang.StackTraceElement"); ElementInfo ei = heap.newObject(ctx, ci, ThreadInfo.this); ei.setReferenceField(ctx, "clsName", new One<>(heap.newString(ctx, clsName, ThreadInfo.this).getObjectRef())); ei.setReferenceField(ctx, "mthName", new One<>(heap.newString(ctx, mthName, ThreadInfo.this).getObjectRef())); ei.setReferenceField(ctx, "fileName", new One<>(heap.newString(ctx, fileName, ThreadInfo.this).getObjectRef())); ei.setIntField(ctx, "line", new One<>(line)); return ei.getObjectRef(); } } void printOn(PrintWriter pw) { if (!ignore) { // the usual behavior is to print only the filename, strip the path if (fileName != null) { int idx = fileName.lastIndexOf(File.separatorChar); if (idx >= 0) { fileName = fileName.substring(idx + 1); } } print(pw, "\tat "); if (clsName != null) { print(pw, clsName); print(pw, "."); } else { // some synthetic methods don't belong to classes print(pw, "[no class] "); } print(pw, mthName); if (fileName != null) { print(pw, "("); print(pw, fileName); if (line >= 0) { print(pw, ":"); print(pw, Integer.toString(line)); } print(pw, ")"); } else { //print(pw, "<no source>"); } print(pw, "\n"); } } } /** * this is a helper class to store listener generated exception requests that are checked before and after * calling Instruction.execute(). This is a safe way to raise SUT exceptions from listener code without compromising * consistency of executes() that are not prepared to cut short by means of re-execution or host VM exceptions */ static class SUTExceptionRequest { String xClsName; String details; SUTExceptionRequest(String xClsName, String details) { this.xClsName = xClsName; this.details = details; } public String getExceptionClassName() { return xClsName; } public String getDetails() { return details; } } public void requestSUTException(String exceptionClsName, String details) { pendingSUTExceptionRequest = new SUTExceptionRequest(exceptionClsName, details); if (nextPc == null) { // this is pre-exec, skip the execute() skipInstruction = true; } } protected void processPendingSUTExceptionRequest() { if (pendingSUTExceptionRequest != null) { // <2do> we could do more specific checks for ClassNotFoundExceptions here nextPc = new One<>(createAndThrowException(FeatureExprFactory.True(), pendingSUTExceptionRequest.getExceptionClassName(), pendingSUTExceptionRequest.getDetails())); pendingSUTExceptionRequest = null; } } /** * <2do> pcm - this is only valid for java.* and our own Throwables that don't * need ctor execution since we only initialize the Throwable fields. This method * is here to avoid round trips in case of exceptions */ int createException(FeatureExpr ctx, ClassInfo ci, String details, int causeRef) { int[] snap = getSnapshot(ctx, MJIEnv.NULL); return vm.getHeap().newSystemThrowable(ctx, ci, details, snap, causeRef, this, 0).getObjectRef(); } /** * Creates and throws an exception. This is what is used if the exception is * thrown by the VM (or a listener) */ public Instruction createAndThrowException(FeatureExpr ctx, ClassInfo ci, String details) { if (!ci.isRegistered()) { ci.registerClass(ctx, this); } if (!ci.isInitialized()) { if (ci.initializeClass(ctx, this)) { return getPC().getValue(); } } int objref = createException(ctx, ci, details, MJIEnv.NULL); return throwException(ctx, objref); } /** * Creates an exception and throws it. */ public Instruction createAndThrowException(FeatureExpr ctx, String cname) { return createAndThrowException(ctx, cname, null); } public Instruction createAndThrowException(FeatureExpr ctx, String cname, String details) { try { ClassInfo ci = null; try { ci = ClassLoaderInfo.getCurrentResolvedClassInfo(cname); } catch (ClassInfoException cie) { // the non-system class loader couldn't find the class, if (cie.getExceptionClass().equals("java.lang.ClassNotFoundException") && !ClassLoaderInfo.getCurrentClassLoader().isSystemClassLoader()) { ci = ClassLoaderInfo.getSystemResolvedClassInfo(cname); } else { throw cie; } } return createAndThrowException(ctx, ci, details); } catch (ClassInfoException cie) { if (!cname.equals(cie.getExceptionClass())) { ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(cie.getExceptionClass()); return createAndThrowException(ctx, ci, cie.getMessage()); } else { throw cie; } } } /** * enter instructions until there is none left or somebody breaks * the transition (e.g. by registering a CG) * <p/> * this is the inner interpreter loop of JPF */ @SuppressWarnings("unchecked") protected void executeTransition(SystemState ss) throws JPFException { Conditional<Instruction> pc = getPC(); Conditional<Instruction> nextPc = (Conditional<Instruction>) One.NULL; currentThread = this; executedInstructions = 0; pendingException = null; if (isStopped()) { pc = new One<>(throwStopException()); setPC(pc); } if (RUN_SIMPLE) { while (executeInstruction() != One.NULL) { if (ss.breakTransition()) { break; } } return; } // this constitutes the main transition loop. It gobbles up // insns until someone registered a ChoiceGenerator, there are no insns left, // the transition was explicitly marked as ignored, or we have reached a // max insn count and preempt the thread upon the next available backjump while (pc != One.NULL) { nextPc = executeInstruction(); if (ss.breakTransition()) { break; } else { if (executedInstructions >= maxTransitionLength) { // try to preempt the current thread if (pc.getValue().isBackJump() && (pc.getValue() != nextPc.getValue()) && (top != null && !top.isNative())) { log.info("max transition length exceeded, breaking transition on ", nextPc); reschedule("maxTransitionLenth"); break; } } pc = nextPc; } } } private static int count = 0; private static long time = 0; public static boolean logtrace = false; public static boolean RUN_SIMPLE = false; /** * Execute next instruction. */ @SuppressWarnings("unchecked") public Conditional<Instruction> executeInstruction() { Conditional<Instruction> pc = getPC(); SystemState ss = vm.getSystemState(); // the default, might be changed by the insn depending on if it's the first // time we exec the insn, and whether it does its magic in the top (before break) // or bottom half (re-exec after break) of the exec logInstruction = true; skipInstruction = (pendingSUTExceptionRequest != null); nextPc = null; // note that we don't reset pendingSUTExceptionRequest since it could be set outside executeInstruction() if (log.isLoggable(Level.FINER)) { log.fine(pc.getValue().getMethodInfo().getFullName() + " " + pc.getValue().getPosition() + " : " + pc); } // this is the pre-execution notification, during which a listener can perform // on-the-fly instrumentation or even replace the instruction all together vm.notifyExecuteInstruction(this, pc.getValue(true));// TODO revise int popedFrames = 0; StackFrame oldStack = top; if (!skipInstruction) { // enter the next bytecode try { if (time == 0) { time = System.currentTimeMillis(); } count++; if (System.currentTimeMillis() - time > 10_000) { int instructions = count; count = 0; printSpeedLog(instructions / 10); if (checkGcNeeded(instructions)) { vm.getSystemState().gcIfNeeded(); } time = System.currentTimeMillis(); } Instruction i = null; FeatureExpr ctx = top.stack.getCtx(); if (pc.isOne()) { i = pc.getValue(); } else { final Map<Instruction, FeatureExpr> map = pc.toMap(); int minPos = Integer.MAX_VALUE; for (final Instruction ins : map.keySet()) { if (!(ins instanceof ReturnInstruction)){ if (ins.position < minPos) { minPos = ins.position; i = ins; } } else if (i == null) { i = ins; } } ctx = map.get(i).and(ctx); } if (RuntimeConstants.debug) { System.out.print(executedInstructions); System.out.print(" "); System.out.print(top.getDepth()); if (top.getDepth() < 10) { System.out.print(" "); } System.out.println(" " + i + " if " + ctx); } if (RuntimeConstants.tracing) { performTracing(i, ctx); } coverage.preExecuteInstruction(i); final int currentStackDepth = stackDepth; // the point where the instruction is executed final Conditional<Instruction> next = i.execute(ctx, this); coverage.postExecuteInstruction(i, ctx); final int poped = currentStackDepth - stackDepth; if (i instanceof InvokeInstruction) { nextPc = next.simplify(top.stack.getCtx()); if (stackDepth > RuntimeConstants.MAX_FRAMES) { nextPc = ChoiceFactory.create(ctx, new One<Instruction>(new EXCEPTION(StackOverflowError.class.getName(), "Too many frames (more than " + RuntimeConstants.MAX_FRAMES + ")")), next).simplify(top.stack.getCtx()); } } else if (i instanceof ReturnInstruction) { // instructions already joined at ReturnInstruction#getNext() if (top == null) { nextPc = next; } else { nextPc = next.simplify(top.stack.getCtx()); } } else if (i instanceof ATHROW || i instanceof EXCEPTION || (poped > 0 && stackTraceMember(oldStack, top))) { // some instruction (e.g., IDIV with div by zero) just pop frames but do not throw exceptions nextPc = ChoiceFactory.create(ctx, next, getPC()).simplify(top.stack.getCtx()); popedFrames = poped; int k = 0; StackFrame stackPointer = oldStack; // set the ctx of popped frames while (k < popedFrames) { FeatureExpr newCtx = stackPointer.stack.getCtx().andNot(ctx); stackPointer.stack.setCtx(newCtx); stackPointer.pc = stackPointer.pc.simplify(newCtx); stackPointer = stackPointer.prev; k++; } } else { nextPc = ChoiceFactory.create(ctx, next, pc).simplify(top.stack.getCtx()); } } catch (ClassInfoException cie) { nextPc = new One<>(this.createAndThrowException(FeatureExprFactory.True(), cie.getExceptionClass(), cie.getMessage())); } } // we also count the skipped ones executedInstructions++; if (logInstruction) { ss.recordExecutionStep(pc.getValue(true)); } // here we have our post exec bytecode exec observation point vm.notifyInstructionExecuted(this, pc.getValue(true), nextPc.getValue(true));// TODO replace true // since this is part of the inner execution loop, it is a convenient place to check for probes vm.getSearch().checkAndResetProbeRequest(); // clean up whatever might have been stored by enter pc.getValue(true).cleanupTransients(); if (pendingSUTExceptionRequest != null){ processPendingSUTExceptionRequest(); } // set+return the next insn to enter if we did not return from the last stack frame. // Note that 'nextPc' might have been set by a listener, and/or 'top' might have // been changed by executing an invoke, return or throw (handler), or by // pushing overlay calls on the stack if (top != null) { // <2do> this is where we would have to handle general insn repeat setPC(nextPc); if (popedFrames > 0) { // return to the current method after the chatch clause is set after an exception pushFrames(oldStack, popedFrames); } return getPC(); } else { return (Conditional<Instruction>) One.NULL; } } private static int countGc = 0; private final static long maxMemory = Runtime.getRuntime().maxMemory(); private static boolean checkGcNeeded(int instructions) { if (countGc++ >= 1) { countGc = 0; return true; } // VM is slow, maybe because heap is full if (instructions < 1_000) { return true; } final long total = Runtime.getRuntime().totalMemory(); if (total + (100<<20/*100MB*/) >= maxMemory) { final long free = Runtime.getRuntime().freeMemory(); final long usedMemory = total - free; if (usedMemory >> 2 > free) { return true; } } return false; } private void printSpeedLog(int instructions) { StringBuilder sb = new StringBuilder(); sb.append(insertDots(instructions)); sb.append(" instructions / s ("); sb.append(insertDots(executedInstructions)); sb.append(" @ "); sb.append((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())>>20); sb.append("MB)"); System.out.println(sb.toString()); } public static String insertDots(int instructions) { StringBuilder sb = new StringBuilder(); if (instructions >= 1_000_000_000) { sb.append(instructions / 1_000_000_000).append('.'); instructions = instructions % 1_000_000_000; sb.append(adjustValue(instructions / 1_000_000)).append('.'); } else { sb.append(instructions / 1_000_000).append('.'); } instructions = instructions % 1_000_000; sb.append(adjustValue(instructions / 1_000)).append('.'); instructions = instructions % 1_000; sb.append(adjustValue(instructions)); return sb.toString(); } private static String adjustValue(int value) { if (value < 10) { return "00" + value; } if (value < 100) { return "0" + value; } return String.valueOf(value); } /** * Log trace for trace comparison. * @param instruction The instruction to log. * @param ctx */ private void performTracing(Instruction instruction, FeatureExpr ctx) { if (logtrace) { if (!(instruction instanceof InvokeInstruction)) { MethodInfo mi = instruction.getMethodInfo(); if (mi == null || mi.getFullName().contains("clinit") || mi.getFullName().contains("java.lang.Class.desiredAssertionStatus")) { // ignore class initializations } else { TraceComparator.putInstruction(ctx, mi.getFullName() + " " + instruction.getMnemonic() + " " + instruction.getFileLocation()); } } } else if (JPF.traceMethod != null && instruction.getMethodInfo().getFullName().equals(JPF.traceMethod)) { logtrace = true; } } /** * Checks whether the the new top stack is part of the trace of the current stack. */ private boolean stackTraceMember(final StackFrame current, final StackFrame newTop) { if (current == newTop) { return true; } final StackFrame prev = current.prev; if (prev != null) { return stackTraceMember(prev, newTop); } return false; } /** * enter instruction hidden from any listeners, and do not * record it in the path */ public Instruction executeInstructionHidden () { Instruction pc = getPC().getValue(); // SystemState ss = vm.getSystemState(); // KernelState ks = vm.getKernelState(); nextPc = null; // reset in case pc.execute() blows (this could be behind an exception firewall) if (log.isLoggable(Level.FINE)) { log.fine(pc.getMethodInfo().getFullName() + " " + pc.getPosition() + " : " + pc); } try { nextPc = pc.execute(FeatureExprFactory.True(), this); } catch (ClassInfoException cie) { nextPc = new One<>(this.createAndThrowException(FeatureExprFactory.True(), cie.getExceptionClass(), cie.getMessage())); } // since this is part of the inner execution loop, it is a convenient place to check probe notifications vm.getSearch().checkAndResetProbeRequest(); // we did not return from the last frame stack if (top != null) { // <2do> should probably bomb otherwise setPC(nextPc); } return nextPc.getValue(); } /** * is this after calling Instruction.enter() * used by instructions and listeners */ public boolean isPostExec() { return (nextPc != null); } public void reExecuteInstruction() { nextPc = getPC(); } public boolean willReExecuteInstruction() { return (getPC() == nextPc); } /** * skip the next bytecode. To be used by listeners to on-the-fly replace * instructions */ public void skipInstruction(Instruction nextInsn) { skipInstruction = true; //assert nextInsn != null; nextPc = new One<>(nextInsn); } /** * skip and continue with the following instruction. This is deprecated because * callers should explicitly provide the next instruction (depending on the * skipped insn) */ @Deprecated public void skipInstruction() { skipInstruction(getPC().getValue().getNext()); } public boolean isInstructionSkipped() { return skipInstruction; } public void skipInstructionLogging() { logInstruction = false; } /** * explicitly set the next insn to enter. To be used by listeners that * replace bytecode exec (during 'executeInstruction' notification * <p/> * Note this is dangerous because you have to make sure the operand stack is * in a consistent state. This also will fail if someone already ordered * reexecution of the current instruction */ public boolean setNextPC(Instruction insn) { if (nextPc == null) { // this is pre-execution, if we don't skip the next insn.execute() is going // to override what we set here skipInstruction = true; nextPc = new One<>(insn); return true; } else { if (top != null && nextPc != top.getPC()) { // this needs to be re-executed nextPc = new One<>(insn); return true; } } return false; } /** * Executes a method call. Be aware that it executes the whole method as one atomic * step. Arguments have to be already on the provided stack * <p/> * This only works for non-native methods, and does not allow any choice points, * so you have to know very well what you are doing. * <p/> * Instructions executed by this method are still fully observable and stored in * the path */ public void executeMethodAtomic(StackFrame frame) { pushFrame(frame); int depth = countStackFrames(); // Instruction pc = frame.getPC().getValue(); SystemState ss = vm.getSystemState(); ss.incAtomic(); // to shut off avoidable context switches (MONITOR_ENTER and wait() can still block) while (depth <= countStackFrames()) { executeInstruction(); // Instruction nextPC = executeInstruction().getValue(); if (ss.getNextChoiceGenerator() != null) { // BANG - we can't have CG's here // should be rather an ordinary exception // createAndThrowException("java.lang.AssertionError", "choice point in sync executed method: " + frame); throw new JPFException("choice point in atomic method execution: " + frame); } else { // pc = nextPC; } } vm.getSystemState().decAtomic(); nextPc = null; // the frame was already removed by the RETURN insn of the frame's method } /** * enter method atomically, but also hide it from listeners and do NOT add * executed instructions to the path. * <p/> * this can be even more confusing than executeMethodAtomic(), since * nothing prevents such a method from changing the program state, and we * wouldn't know for what reason by looking at the trace * <p/> * this method should only be used if we have to enter test application code * like hashCode() or equals() from native code, e.g. to silently check property * violations * <p/> * executeMethodHidden also acts as an exception firewall, since we don't want * any silently executed code fall back into the visible path (for * no observable reason) */ public void executeMethodHidden(StackFrame frame) { pushFrame(frame); int depth = countStackFrames(); // this includes the DirectCallStackFrame Instruction pc = frame.getPC().getValue(); vm.getSystemState().incAtomic(); // to shut off avoidable context switches (MONITOR_ENTER and wait() can still block) while (depth <= countStackFrames()) { Instruction nextPC = executeInstructionHidden(); if (pendingException != null) { } else { if (nextPC == pc) { // BANG - we can't have CG's here // should be rather an ordinary exception // createAndThrowException("java.lang.AssertionError", "choice point in sync executed method: " + frame); throw new JPFException("choice point in hidden method execution: " + frame); } else { pc = nextPC; } } } vm.getSystemState().decAtomic(); nextPc = null; // the frame was already removed by the RETURN insn of the frame's method } public Heap getHeap() { return vm.getHeap(); } public ElementInfo getElementInfo(int objRef) { Heap heap = vm.getHeap(); return heap.get(objRef); } public ElementInfo getElementInfoWithUpdatedSharedness(int objRef) { Heap heap = vm.getHeap(); ElementInfo ei = heap.get(objRef); return ei.getInstanceWithUpdatedSharedness(this); } public ElementInfo getModifiableElementInfo(int ref) { Heap heap = vm.getHeap(); return heap.getModifiable(ref); } // terrible name public ElementInfo getModifiableElementInfoWithUpdatedSharedness(int objRef) { Heap heap = vm.getHeap(); ElementInfo ei = heap.getModifiable(objRef); return ei.getInstanceWithUpdatedSharedness(this); } public ElementInfo getBlockedObject(MethodInfo mi, boolean isBeforeCall, boolean isModifiable) { int objref; ElementInfo ei = null; if (mi.isSynchronized()) { if (mi.isStatic()) { objref = mi.getClassInfo().getClassObjectRef(); } else { // NOTE 'inMethod' doesn't work for natives, because getThis() pulls 'this' from the stack frame, // which we don't have (and don't need) for natives objref = isBeforeCall ? getCalleeThis(mi) : getThis().getValue(); } ei = (isModifiable) ? getModifiableElementInfo(objref) : getElementInfo(objref); assert (ei != null) : ("inconsistent stack, no object or class ref: " + mi.getFullName() + " (" + objref + ")"); } return ei; } //--- call processing /** * note - this assumes the stackframe of the method to enter is already initialized and on top (pushed) */ public void enter() { MethodInfo mi = top.getMethodInfo(); if (mi.isSynchronized()) { Conditional<Integer> oref = mi.isStatic() ? One.valueOf(mi.getClassInfo().getClassObjectRef()) : top.getThis(); final ThreadInfo ti = this; oref.map(oref1 -> { ElementInfo ei = getModifiableElementInfo(oref1); ei.lock(ti); if (mi.isClinit()) { mi.getClassInfo().setInitializing(ti); } return null; }); } vm.notifyMethodEntered(this, mi); } /** * note - this assumes the stackframe is still on top (not yet popped) */ public void leave() { MethodInfo mi = top.getMethodInfo(); // <2do> - that's not really enough, we might have suspicious bytecode that fails // to release locks acquired by monitor_enter (e.g. by not having a handler that // monitor_exits & re-throws). That's probably shifted into the bytecode verifier // in the future (i.e. outside JPF), but maybe we should add an explicit test here // and report an error if the code does asymmetric locking (according to the specs, // VMs are allowed to silently fix this, so it might run on some and fail on others) if (mi.isSynchronized()) { Conditional<Integer> oref = mi.isStatic() ? One.valueOf(mi.getClassInfo().getClassObjectRef()) : top.getThis(); oref.map(ref -> { ElementInfo ei = getElementInfo(ref); if (ei.isLocked()) { ei = ei.getModifiableInstance(); ei.unlock(this); } return null; }); if (mi.isClinit()) { // we just released the lock on the class object, returning from a clinit // now we can consider this class to be initialized. // NOTE this is still part of the RETURN insn of clinit, so ClassInfo.isInitialized // is protected mi.getClassInfo().setInitialized(); } } vm.notifyMethodExited(this, mi); } /** * this should only be called from the top half of the last DIRECTCALLRETURN of * a thread. * * @return true - if the thread is done, false - if instruction has to be re-executed */ public boolean exit(FeatureExpr ctx){ int objref = getThreadObjectRef(); ElementInfo ei = getModifiableElementInfo(objref); SystemState ss = vm.getSystemState(); // beware - this notifies all waiters for this thread (e.g. in a join()) // hence it has to be able to acquire the lock if (!ei.canLock(this)) { // block first, so that we don't get this thread in the list of CGs ei.block(this); // if we can't acquire the lock, it means there needs to be another thread alive, // but it might not be runnable (deadlock) and we don't want to mask that error ChoiceGenerator<ThreadInfo> cg = ss.getSchedulerFactory().createMonitorEnterCG(ei, this); ss.setMandatoryNextChoiceGenerator(cg, "blocking thread termination without CG: "); return false; // come back once we can obtain the lock to notify our waiters } else { // Ok, we can get the lock, time to die // if this is the last non-daemon and there are only daemons left (which // would be killed by our termination) we have to give them a chance to // run BEFORE we terminate, to catch errors in those daemons we might have // triggered in our last transition. Even if a daemon has a proper CG // on the trigger that would expose the error subsequently, it would not be // scheduled anymore but hard terminated. This is even true if the trigger // is the last operation in the daemon since a host VM might preempt // on every instruction, not just CG insns (see .test.mc.DaemonTest) if (vm.getThreadList().hasOnlyMatchingOtherThan(this, vm.getDaemonRunnablePredicate())) { if (yield()) { return false; } } //--- now we get into the termination spin // notify waiters on thread termination if (!holdsLock(ei)) { // we only need to increase the lockcount if we don't own the lock yet, // as is the case for synchronized run() in anonymous threads (otherwise // we have a lockcount > 1 and hence do not unlock upon return) ei.lock(this); } ei.notifiesAll(); // watch out, this might change the runnable count ei.unlock(this); setState(State.TERMINATED); // we don't unregister this thread yet, this happens when the corresponding // thread object is collected ss.clearAtomic(); cleanupThreadObject(ctx, ei); vm.activateGC(); // stack is gone, so reachability might change // give the thread tracking policy a chance to remove this thread from // object/class thread sets SharedObjectPolicy.getPolicy().cleanupThreadTermination(this); if (vm.getThreadList().hasAnyMatchingOtherThan(this, getRunnableNonDaemonPredicate())) { ChoiceGenerator<ThreadInfo> cg = ss.getSchedulerFactory().createThreadTerminateCG(this); ss.setMandatoryNextChoiceGenerator(cg, "thread terminated without CG: "); } popFrame(ctx); // we need to do this *after* setting the CG (so that we still have a CG insn) return true; } } Predicate<ThreadInfo> getRunnableNonDaemonPredicate() { return new Predicate<ThreadInfo>() { @Override public boolean isTrue(ThreadInfo ti) { return (ti.isRunnable() && !ti.isDaemon()); } }; } /** * this is called upon ThreadInfo.exit() and corresponds to the private Thread.exit() */ void cleanupThreadObject(FeatureExpr ctx, ElementInfo ei) { // ideally, this should be done by calling Thread.exit(), but this // does a ThreadGroup.remove(), which does a lot of sync stuff on the shared // ThreadGroup object, which might create lots of states. So we just nullify // the Thread fields and remove it from the ThreadGroup from here int grpRef = ei.getReferenceField("group").getValue(); cleanupThreadGroup(ctx, grpRef, ei.getObjectRef()); ei.setReferenceField(ctx, "group", One.MJIEnvNULL); ei.setReferenceField(ctx, "threadLocals", One.MJIEnvNULL); ei.setReferenceField(ctx, "inheritableThreadLocals", One.MJIEnvNULL); ei.setReferenceField(ctx, "uncaughtExceptionHandler", One.MJIEnvNULL); } /** * this is called upon ThreadInfo.exit() and corresponds to ThreadGroup.remove(t), which is called from Thread.exit() * <p/> * ideally this should be in the ThreadGroup peer, but we don't want to reference concrete peers from core (which is the * lowest layer). * Since we already depend on ThreadGroup fields during VM initialization we just keep all Thread/ThreadGroup * related methods here */ void cleanupThreadGroup(FeatureExpr ctx, int grpRef, int threadRef) { if (grpRef != MJIEnv.NULL) { ElementInfo eiGrp = getModifiableElementInfo(grpRef); int threadsRef = eiGrp.getReferenceField("threads").getValue(); if (threadsRef != MJIEnv.NULL) { ElementInfo eiThreads = getModifiableElementInfo(threadsRef); if (eiThreads.isArray()) { int nthreads = eiGrp.getIntField("nthreads").simplify(ctx).getValue(true); for (int i = 0; i < nthreads; i++) { Conditional<Integer> tref = eiThreads.getReferenceElement(i); if (tref.getValue() == threadRef) { // compact the threads array int n1 = nthreads - 1; for (int j = i; j < n1; j++) { eiThreads.setReferenceElement(ctx, j, eiThreads.getReferenceElement(j + 1)); } eiThreads.setReferenceElement(ctx, n1, One.MJIEnvNULL); eiGrp.setIntField(ctx, "nthreads", new One<>(n1)); if (n1 == 0) { eiGrp.lock(this); eiGrp.notifiesAll(); eiGrp.unlock(this); } // <2do> we should probably also check if we have to set it destroyed return; } } } } } } /** * this is called by the VM upon initialization of the main thread. At this point, we have a tiMain and a java.lang.Thread * object, but there is no ThreadGroup yet * <p/> * This method is here to keep all Thread/ThreadGroup field dependencies in one place. The downside of not keeping this in * VM is that we can't override in order to have specialized ThreadInfos, but there is no factory for them anyways */ protected void createMainThreadObject(FeatureExpr ctx, SystemClassLoaderInfo sysCl) { //--- now create & initialize all the related JPF objects Heap heap = getHeap(); ClassInfo ciThread = sysCl.threadClassInfo; ElementInfo eiThread = heap.newObject(ctx, ciThread, this); objRef = eiThread.getObjectRef(); ElementInfo eiName = heap.newArray(FeatureExprFactory.True(), "C", MAIN_NAME.length(), this); int i = 0; for (char c : MAIN_NAME.toCharArray()) { eiName.setCharElement(ctx, i++, One.valueOf(c)); } int nameRef = eiName.getObjectRef(); eiThread.setReferenceField(ctx, "name", new One<>(nameRef)); final ClassInfo ci = ClassLoaderInfo.getSystemResolvedClassInfo(Object.class.getName()); final ElementInfo blockerLock = heap.newObject(FeatureExprFactory.True(), ci, this); eiThread.setReferenceField(ctx, "blockerLock", new One<>(blockerLock.getObjectRef())); ElementInfo eiGroup = createMainThreadGroup(ctx, sysCl); eiThread.setReferenceField(ctx, "group", new One<>(eiGroup.getObjectRef())); eiThread.setIntField(ctx, "priority", new One<>(Thread.NORM_PRIORITY)); ClassInfo ciPermit = sysCl.getResolvedClassInfo(ctx, "java.lang.Thread$Permit"); ElementInfo eiPermit = heap.newObject(ctx, ciPermit, this); eiPermit.setBooleanField(ctx, "blockPark", One.TRUE); eiThread.setReferenceField(ctx, "permit", new One<>(eiPermit.getObjectRef())); addToThreadGroup(ctx, eiGroup); addId(objRef, id); //--- set the thread running setState(ThreadInfo.State.RUNNING); } /** * this creates and inits the main ThreadGroup object, which we have to do explicitly since * we can't execute bytecode yet */ protected ElementInfo createMainThreadGroup(FeatureExpr ctx, SystemClassLoaderInfo sysCl) { Heap heap = getHeap(); ClassInfo ciGroup = sysCl.getResolvedClassInfo(ctx, "java.lang.ThreadGroup"); ElementInfo eiThreadGrp = heap.newObject(ctx, ciGroup, this); ElementInfo eiGrpName = heap.newString(ctx, "main", this); eiThreadGrp.setReferenceField(ctx, "name", new One<>(eiGrpName.getObjectRef())); eiThreadGrp.setIntField(ctx, "maxPriority", new One<>(java.lang.Thread.MAX_PRIORITY)); // 'threads' and 'nthreads' will be set later from createMainThreadObject return eiThreadGrp; } /** * this is used for all thread objects, not just main */ protected void addToThreadGroup(FeatureExpr ctx, ElementInfo eiGroup) { FieldInfo finThreads = eiGroup.getFieldInfo("nthreads"); int nThreads = eiGroup.getIntField(finThreads).getValue(); if (eiGroup.getBooleanField("destroyed").getValue()) { env.throwException(ctx, "java.lang.IllegalThreadStateException"); } else { FieldInfo fiThreads = eiGroup.getFieldInfo("threads"); int threadsRef = eiGroup.getReferenceField(fiThreads).getValue(); if (threadsRef == MJIEnv.NULL) { threadsRef = env.newObjectArray("Ljava/lang/Thread;", 1); env.setReferenceArrayElement(ctx, threadsRef, 0, new One<>(objRef)); eiGroup.setReferenceField(ctx, fiThreads, new One<>(threadsRef)); } else { int newThreadsRef = env.newObjectArray("Ljava/lang/Thread;", nThreads + 1); ElementInfo eiNewThreads = env.getElementInfo(newThreadsRef); ElementInfo eiThreads = env.getElementInfo(threadsRef); for (int i = 0; i < nThreads; i++) { Conditional<Integer> tr = eiThreads.getReferenceElement(i); eiNewThreads.setReferenceElement(ctx, i, tr); } eiNewThreads.setReferenceElement(ctx, nThreads, new One<>(objRef)); eiGroup.setReferenceField(ctx, fiThreads, new One<>(newThreadsRef)); } eiGroup.setIntField(ctx, finThreads, new One<>(nThreads + 1)); /** <2do> we don't model this yet FieldInfo finUnstartedThreads = eiGroup.getFieldInfo("nUnstartedThreads"); int nUnstarted = eiGroup.getIntField(finUnstartedThreads); eiGroup.setIntField(finUnstartedThreads, nUnstarted-1); **/ } } public void hash(HashData hd) { threadData.hash(hd); for (StackFrame f = top; f != null; f = f.getPrevious()) { f.hash(hd); } } public void interrupt(FeatureExpr ctx) { ElementInfo eiThread = getModifiableElementInfo(getThreadObjectRef()); State status = getState(); switch (status) { case RUNNING: case BLOCKED: case UNBLOCKED: case NOTIFIED: case TIMEDOUT: // just set interrupt flag eiThread.setBooleanField(ctx, "interrupted", One.TRUE); break; case WAITING: case TIMEOUT_WAITING: eiThread.setBooleanField(ctx, "interrupted", One.TRUE); setState(State.INTERRUPTED); // since this is potentially called w/o owning the wait lock, we // have to check if the waiter goes directly to UNBLOCKED ElementInfo eiLock = getElementInfo(lockRef); if (eiLock.canLock(this)) { resetLockRef(); setState(State.UNBLOCKED); // we cannot yet remove this thread from the Monitor lock contender list // since it still has to re-acquire the lock before becoming runnable again // NOTE: this can lead to attempts to reenter the same thread to the // lock contender list if the interrupt handler of the interrupted thread // tries to wait/block/park again //eiLock.setMonitorWithoutLocked(this); } break; case NEW: case TERMINATED: // ignore break; default: } } /** * mark all objects during gc phase1 which are reachable from this threads * root set (Thread object, Runnable, stack) * * @aspects: gc */ void markRoots(Heap heap) { // 1. mark the Thread object itself heap.markThreadRoot(objRef, id); // 2. and its runnable if (targetRef != MJIEnv.NULL) { heap.markThreadRoot(targetRef, id); } // 3. if we have a pending exception that wasn't handled, make sure the exception object is not collected before we match states if (pendingException != null) { heap.markThreadRoot(pendingException.getExceptionReference(), id); } // 4. now all references on the stack for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { frame.markThreadRoots(heap, id); } } /** * replace the top frame - this is a dangerous method that should only * be used from Restoreres and to restore operators and locals in post-execution notifications * to their pre-execution contents */ public void setTopFrame(StackFrame frame) { top = frame; // since we have swapped the top frame, the stackDepth might have changed int n = 0; for (StackFrame f = frame; f != null; f = f.getPrevious()) { n++; } stackDepth = n; } /** * Pushes the top frames to the current stack. * * @param frame Number of frames to push. Only pushes stacks with satisfiable context. * @param nrFrames Number of frames to push. */ public void pushFrames(StackFrame frame, int nrFrames) { if (nrFrames < 1) { return; } pushFrames(frame.prev, nrFrames - 1); if (Conditional.isContradiction(frame.stack.getCtx())) { return; } pushFrame(frame); } /** * Adds a new stack frame for a new called method. */ public void pushFrame(StackFrame frame) { frame.setPrevious(top); top = frame; stackDepth++; // a new frame cannot have been stored yet, so we don't need to clone on the next mod // note this depends on not pushing a frame in the top half of a CG method markTfChanged(top); returnedDirectCall = null; } /** * Removes a stack frame */ public void popFrame(FeatureExpr ctx) { StackFrame frame = top; //--- do our housekeeping if (frame.hasAnyRef(ctx)) { vm.getSystemState().activateGC(); } // there always is one since we start all threads through directcalls top = frame.getPrevious(); stackDepth--; } public StackFrame popAndGetModifiableTopFrame(FeatureExpr ctx) { popFrame(ctx); if (top.isFrozen()) { top = top.clone(); } return top; } public StackFrame popAndGetTopFrame(FeatureExpr ctx) { popFrame(ctx); return top; } /** * removing DirectCallStackFrames is a bit different (only happens from * DIRECTCALLRETURN insns) */ public StackFrame popDirectCallFrame(FeatureExpr ctx) { //assert top instanceof DirectCallStackFrame; returnedDirectCall = (DirectCallStackFrame) top; if (top.hasFrameAttr(UncaughtHandlerAttr.class)) { return popUncaughtHandlerFrame(ctx); } top = top.getPrevious(); stackDepth--; return top; } public boolean hasReturnedFromDirectCall() { // this is reset each time we push a new frame return (returnedDirectCall != null); } public boolean hasReturnedFromDirectCall(String directCallId) { return (returnedDirectCall != null && returnedDirectCall.getMethodName().equals(directCallId)); } public DirectCallStackFrame getReturnedDirectCall() { return returnedDirectCall; } public String getStateDescription() { StringBuilder sb = new StringBuilder("thread "); sb.append(getThreadObjectClassInfo().getName()); sb.append(":{id:"); sb.append(id); sb.append(','); sb.append(threadData.getFieldValues()); sb.append('}'); return sb.toString(); } public ClassInfo getThreadObjectClassInfo() { return getThreadObject().getClassInfo(); } /** * Prints the content of the stack */ public void printStackContent() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { frame.printStackContent(); } } /** * Prints current stacktrace information */ public void printStackTrace() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { frame.printStackTrace(); } } boolean haltOnThrow(String exceptionClassName) { if ((haltOnThrow != null) && (haltOnThrow.matchesAny(exceptionClassName))) { return true; } return false; } Instruction throwStopException() { // <2do> maybe we should do a little sanity check first ElementInfo ei = getModifiableThreadObject(); int xRef = ei.getReferenceField("stopException").getValue(); ei.setReferenceField(FeatureExprFactory.True(), "stopException", new One<>(MJIEnv.NULL)); Instruction insn = getPC().getValue(); if (insn instanceof EXECUTENATIVE) { // we only get here if there was a CG in a native method and we might // have to reacquire a lock to go on // <2do> it would be better if we could avoid to enter the native method // since it might have side effects like overwriting the exception or // doing roundtrips in its bottom half, but we don't know which lock that // is (lockRef might be already reset) env.throwException(xRef); return insn; } return throwException(FeatureExprFactory.True(), xRef); } /** * this is basically a side-effect free version of throwException to determine if a given * exception will be handled. */ public HandlerContext getHandlerContextFor(ClassInfo ciException) { ExceptionHandler matchingHandler = null; // the matching handler we found (if any) for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { // that means we have to turn the exception into an InvocationTargetException if (frame.isReflection()) { ciException = ClassInfo.getInitializedSystemClassInfo(null, "java.lang.reflect.InvocationTargetException", this); } matchingHandler = frame.getHandlerFor(FeatureExprFactory.True(), ciException); if (matchingHandler != null) { return new HandlerContext(this, ciException, frame, matchingHandler); } } if (!ignoreUncaughtHandlers && !isUncaughtHandlerOnStack()) { int uchRef; if ((uchRef = getInstanceUncaughtHandler()) != MJIEnv.NULL) { return new HandlerContext(this, ciException, HandlerContext.UncaughtHandlerType.INSTANCE, uchRef); } int grpRef = getThreadGroupRef(); if ((uchRef = getThreadGroupUncaughtHandler(grpRef)) != MJIEnv.NULL) { return new HandlerContext(this, ciException, HandlerContext.UncaughtHandlerType.GROUP, uchRef); } if ((uchRef = getGlobalUncaughtHandler()) != MJIEnv.NULL) { return new HandlerContext(this, ciException, HandlerContext.UncaughtHandlerType.GLOBAL, uchRef); } } return null; } /** * unwind stack frames until we find a matching handler for the exception object */ public Instruction throwException(FeatureExpr ctx, int exceptionObjRef) { Heap heap = vm.getHeap(); ElementInfo eiException = heap.get(exceptionObjRef); ClassInfo ciException = eiException.getClassInfo(); String exceptionName = ciException.getName(); StackFrame handlerFrame = null; // the stackframe that has a matching handler (if any) ExceptionHandler matchingHandler = null; // the matching handler we found (if any) // first, give the VM a chance to intercept (we do this before changing anything) Instruction insn = vm.handleException(this, exceptionObjRef); if (insn != null) { return insn; } // we don't have to store the stacktrace explicitly anymore, since that is now // done in the Throwable ctor (more specifically the native fillInStackTrace) pendingException = new ExceptionInfo(ctx, this, eiException); vm.notifyExceptionThrown(this, eiException); if (haltOnThrow(exceptionName)) { // shortcut - we don't try to find a handler for this one but bail immediately //NoUncaughtExceptionsProperty.setExceptionInfo(pendingException); throw new UncaughtException(ctx, this, exceptionObjRef); } // check if we find a matching handler, and if we do store it. Leave the // stack untouched so that listeners can still inspect it for (StackFrame frame = top; (frame != null); frame = frame.getPrevious()) { // that means we have to turn the exception into an InvocationTargetException if (frame.isReflection()) { ciException = ClassInfo.getInitializedSystemClassInfo(ctx, "java.lang.reflect.InvocationTargetException", this); exceptionObjRef = createException(ctx, ciException, exceptionName, exceptionObjRef); exceptionName = ciException.getName(); eiException = heap.get(exceptionObjRef); pendingException = new ExceptionInfo(ctx, this, eiException); } matchingHandler = frame.getHandlerFor(ctx, ciException); if (matchingHandler != null) { handlerFrame = frame; break; } if (frame.isFirewall()) { // this method should not let exceptionHandlers pass into lower level stack frames // (e.g. for <clinit>, or hidden direct calls) // <2do> if this is a <clinit>, we should probably turn into an // ExceptionInInitializerError first unwindTo(frame, ctx); //NoUncaughtExceptionsProperty.setExceptionInfo(pendingException); throw new UncaughtException(ctx, this, exceptionObjRef); } } if (handlerFrame == null) { // we still have to check if there is a Thread.UncaughtExceptionHandler in effect, // and if we already enter within one, in which case we don't reenter it if (!ignoreUncaughtHandlers && !isUncaughtHandlerOnStack()) { // we use a direct call instead of exception handlers within the run()/main() // direct call methods because we want to preserve the whole stack in case // we treat returned (report-only) handlers as NoUncaughtExceptionProperty // violations (passUncaughtHandler=false) insn = callUncaughtHandler(ctx, pendingException); if (insn != null) { // we only do this if there is a UncaughtHandler other than the standard // ThreadGroup, hence we have to check for the return value. If there is // only ThreadGroup.uncaughtException(), we put the system out of its misery return insn; } } // there was no overridden uncaughtHandler, or we already executed it if ("java.lang.ThreadDeath".equals(exceptionName)) { // gracefully shut down unwindToFirstFrame(ctx); pendingException = null; return top.getPC().getValue().getNext(); // the final DIRECTCALLRETURN } else { // we have a NoUncaughtPropertyViolation //NoUncaughtExceptionsProperty.setExceptionInfo(pendingException); throw new UncaughtException(ctx, this, exceptionObjRef); } } else { // we found a matching handler unwindTo(handlerFrame, ctx); // according to the VM spec, before transferring control to the handler we have // to reset the operand stack to contain only the exception reference // (4.9.2 - "4. merge the state of the operand stack..") handlerFrame = getModifiableTopFrame(); handlerFrame.setExceptionReference(exceptionObjRef, ctx); // jump to the exception handler and set pc so that listeners can see it int handlerOffset = matchingHandler.getHandler(); insn = handlerFrame.getMethodInfo().getInstructionAt(handlerOffset); handlerFrame.setPC(ChoiceFactory.create(ctx, new One<>(insn), handlerFrame.getPC())); // notify before we reset the pendingException vm.notifyExceptionHandled(this); pendingException = null; // handled, no need to keep it return insn; } } /** * is there any UncaughHandler in effect for this thread? * NOTE - this doesn't check if we are already executing one (i.e. it would still handle an exception) * or if uncaughtHandlers are enabled within JPF */ public boolean hasUncaughtHandler() { if (getInstanceUncaughtHandler() != MJIEnv.NULL) { return true; } int grpRef = getThreadGroupRef(); if (getThreadGroupUncaughtHandler(grpRef) != MJIEnv.NULL) { return true; } if (getGlobalUncaughtHandler() != MJIEnv.NULL) { return true; } return false; } /** * this explicitly models the standard ThreadGroup.uncaughtException(), but we want * to save us a roundtrip if that's the only handler we got. If we would use * a handler block in the run()/main() direct call stubs that just delegate to * the standard ThreadGroup.uncaughtException(), we would have trouble mapping * this to NoUncaughtExceptionProperty violations (which is just a normal * printStackTrace() in there). */ protected Instruction callUncaughtHandler(FeatureExpr ctx, ExceptionInfo xi) { Instruction insn = null; // 1. check if this thread has its own uncaughtExceptionHandler set. If not, // hand it over to ThreadGroup.uncaughtException() int hRef = getInstanceUncaughtHandler(); if (hRef != MJIEnv.NULL) { insn = callUncaughtHandler(ctx, xi, hRef, "[threadUncaughtHandler]"); } else { // 2. check if any of the ThreadGroup chain has an overridden uncaughtException int grpRef = getThreadGroupRef(); hRef = getThreadGroupUncaughtHandler(grpRef); if (hRef != MJIEnv.NULL) { insn = callUncaughtHandler(ctx, xi, hRef, "[threadGroupUncaughtHandler]"); } else { // 3. as a last measure, check if there is a global handler hRef = getGlobalUncaughtHandler(); if (hRef != MJIEnv.NULL) { insn = callUncaughtHandler(ctx, xi, hRef, "[globalUncaughtHandler]"); } } } return insn; } protected boolean isUncaughtHandlerOnStack() { for (StackFrame frame = top; frame != null; frame = frame.getPrevious()) { if (frame.hasFrameAttr(UncaughtHandlerAttr.class)) { return true; } } return false; } protected int getInstanceUncaughtHandler() { ElementInfo ei = getElementInfo(objRef); int handlerRef = ei.getReferenceField("uncaughtExceptionHandler").getValue(); return handlerRef; } protected int getThreadGroupRef() { ElementInfo ei = getElementInfo(objRef); int groupRef = ei.getReferenceField("group").getValue(); return groupRef; } protected int getThreadGroupUncaughtHandler(int grpRef) { // get the first overridden uncaughtException() implementation in the group chain while (grpRef != MJIEnv.NULL) { ElementInfo eiGrp = getElementInfo(grpRef); ClassInfo ciGrp = eiGrp.getClassInfo(); MethodInfo miHandler = ciGrp.getMethod("uncaughtException(Ljava/lang/Thread;Ljava/lang/Throwable;)V", true); ClassInfo ciHandler = miHandler.getClassInfo(); if (!ciHandler.getName().equals("java.lang.ThreadGroup")) { return eiGrp.getObjectRef(); } grpRef = eiGrp.getReferenceField("parent").getValue(); } // no overridden uncaughtHandler found return MJIEnv.NULL; } protected int getGlobalUncaughtHandler() { ElementInfo ei = getElementInfo(objRef); ClassInfo ci = ei.getClassInfo(); FieldInfo fi = ci.getStaticField("defaultUncaughtExceptionHandler"); // the field is in our java.lang.Thread, but the concrete thread object class might differ ClassInfo fci = fi.getClassInfo(); return fci.getStaticElementInfo().getReferenceField(fi).getValue(); } /** * using an attribute to mark DirectCallStackFrames executing uncaughtHandlers is not * ideal, but the case is so exotic that we don't want another concrete StackFrame subclass that * would have to be created through the ClassInfo factory. Apart from retrieving the * ExceptionInfo this is just a normal DirectCallStackFrame. * We could directly use ExceptionInfo, but it seems more advisable to have a dedicated, * private type. This could also store execution state */ class UncaughtHandlerAttr implements SystemAttribute { ExceptionInfo xInfo; UncaughtHandlerAttr(ExceptionInfo xInfo) { this.xInfo = xInfo; } ExceptionInfo getExceptionInfo() { return xInfo; } } protected Instruction callUncaughtHandler(FeatureExpr ctx, ExceptionInfo xi, int handlerRef, String id) { ElementInfo eiHandler = getElementInfo(handlerRef); ClassInfo ciHandler = eiHandler.getClassInfo(); MethodInfo miHandler = ciHandler.getMethod("uncaughtException(Ljava/lang/Thread;Ljava/lang/Throwable;)V", true); // we have to clear this here in case there is a CG while executing the uncaughtHandler pendingException = null; DirectCallStackFrame frame = miHandler.createDirectCallStackFrame(ctx, this, 0); int argOffset = frame.setReferenceArgument(ctx, 0, handlerRef, null); argOffset = frame.setReferenceArgument(ctx, argOffset, objRef, null); frame.setReferenceArgument(ctx, argOffset, xi.getExceptionReference(), null); UncaughtHandlerAttr uchContext = new UncaughtHandlerAttr(xi); frame.setFrameAttr(uchContext); pushFrame(frame); return frame.getPC().getValue(); } protected StackFrame popUncaughtHandlerFrame(FeatureExpr fexpr) { // we return from an overridden uncaughtException() direct call, but // its debatable if this counts as 'handled'. For handlers that just do // reporting this is probably false and we want JPF to report the defect. // If this is a fail-safe handler that tries to clean up so that other threads can // take over, we want to be able to go on and properly shut down the // thread without property violation if (passUncaughtHandler) { // gracefully shutdown this thread unwindToFirstFrame(fexpr); // this will take care of notifying getModifiableTopFrame().advancePC(); assert top.getPC() instanceof ReturnInstruction : "topframe PC not a ReturnInstruction: " + top.getPC(); return top; } else { // treat this still as an NoUncaughtExceptionProperty violation UncaughtHandlerAttr ctx = returnedDirectCall.getFrameAttr(UncaughtHandlerAttr.class); pendingException = ctx.getExceptionInfo(); //NoUncaughtExceptionsProperty.setExceptionInfo(pendingException); throw new UncaughtException(fexpr, this, pendingException.getExceptionReference()); } } protected void unwindTo(StackFrame newTopFrame, FeatureExpr ctx) { for (StackFrame frame = top; (frame != null) && (frame != newTopFrame); frame = frame.getPrevious()) { leave(); // that takes care of releasing locks vm.notifyExceptionBailout(this); // notify before we pop the frame popFrame(ctx); } } protected StackFrame unwindToFirstFrame(FeatureExpr ctx) { StackFrame frame; for (frame = top; frame.getPrevious() != null; frame = frame.getPrevious()) { leave(); // that takes care of releasing locks vm.notifyExceptionBailout(this); // notify before we pop the frame popFrame(ctx); } return frame; } public ExceptionInfo getPendingException() { return pendingException; } /** * watch out - just clearing it might cause an infinite loop * if we don't drop frames and/or advance the pc */ public void clearPendingException() { //NoUncaughtExceptionsProperty.setExceptionInfo(null); pendingException = null; } /** * Returns a clone of the thread data. To be called every time we change some ThreadData field * (which unfortunately includes lock counts, hence this should be changed) */ protected ThreadData threadDataClone() { if ((attributes & ATTR_DATA_CHANGED) != 0) { // already cloned, so we don't have to clone } else { // reset, so that next storage request would recompute tdIndex markTdChanged(); vm.kernelStateChanged(); threadData = threadData.clone(); } return threadData; } public void restoreThreadData(ThreadData td) { threadData = td; } /** * this is a generic request to reschedule that is not based on instruction type * Note that we check for a mandatory CG, i.e. the policy has to return a CG to make sure * there is a transition break. We still go through the policy object for selection * of scheduling choices though */ public void reschedule(String reason) { SystemState ss = vm.getSystemState(); ChoiceGenerator<ThreadInfo> cg = ss.getSchedulerFactory().createBreakTransitionCG(reason, this); ss.setMandatoryNextChoiceGenerator(cg, "rescheduling request without CG: "); } /** * this is a version that unconditionally breaks the current transition * without really adding choices. It only goes on with the same thread * (to avoid state explosion). Since we require a break, and there is no * choice (current thread is supposed to continue), there is no point * going through the SchedulerFactory * <p/> * if the current transition is already marked as ignored, this method does nothing */ public void breakTransition(String reason) { SystemState ss = vm.getSystemState(); // no need to set a CG if this transition is already marked as ignored // (which will also break executeTransition) BreakGenerator cg = new BreakGenerator(reason, this, false); ss.setNextChoiceGenerator(cg); // this breaks the transition } public boolean mustYield() { SystemState ss = vm.getSystemState(); if (!ss.isIgnored()) { ChoiceGenerator<ThreadInfo> cg = vm.getSchedulerFactory().createThreadYieldCG(this); return ss.setNextChoiceGenerator(cg); } return false; } /** * this is like a reschedule request, but gives the scheduler an option to skip the CG */ public boolean yield() { SystemState ss = vm.getSystemState(); if (!ss.isIgnored()) { ThreadInfo[] choices = vm.getThreadList().getAllMatching(vm.getAppTimedoutRunnablePredicate()); ThreadChoiceFromSet cg = new ThreadChoiceFromSet("yield", choices, true); return ss.setNextChoiceGenerator(cg); // this breaks the transition } return false; } /** * this breaks the current transition with a CG that forces an end state (i.e. * has no choices) * this only takes effect if the current transition is not already marked * as ignored */ public void breakTransition(boolean isTerminator) { SystemState ss = vm.getSystemState(); if (!ss.isIgnored()) { BreakGenerator cg = new BreakGenerator("breakTransition", this, isTerminator); ss.setNextChoiceGenerator(cg); // this breaks the transition } } public boolean checkPorFieldBoundary() { return (executedInstructions == 0) && porFieldBoundaries && hasOtherRunnables(); } public boolean hasOtherRunnables() { return vm.getThreadList().hasAnyMatchingOtherThan(this, vm.getRunnablePredicate()); } protected void markUnchanged() { attributes &= ~ATTR_ANY_CHANGED; } protected void markTfChanged(StackFrame frame) { attributes |= ATTR_STACK_CHANGED; vm.kernelStateChanged(); } protected void markTdChanged() { attributes |= ATTR_DATA_CHANGED; vm.kernelStateChanged(); } public StackFrame getCallerStackFrame() { if (top != null) { return top.getPrevious(); } else { return null; } } public int mixinExecutionStateHash(int h) { for (StackFrame frame = top; frame != null; frame = frame.prev) { if (!frame.isNative()) { h = frame.mixinExecutionStateHash(h); } } return h; } public boolean hasDataChanged() { return (attributes & ATTR_DATA_CHANGED) != 0; } public boolean hasStackChanged() { return (attributes & ATTR_STACK_CHANGED) != 0; } public boolean hasChanged() { return (attributes & ATTR_ANY_CHANGED) != 0; } /** * Returns a clone of the top stack frame. */ public StackFrame getModifiableTopFrame() { if (top.isFrozen()) { top = top.clone(); markTfChanged(top); } return top; } /** * Returns the top stack frame. */ public StackFrame getTopFrame() { return top; } /** * this replaces all frames up from 'frame' to 'top' with modifiable ones. * <p/> * NOTE - you can't use this AFTER getModifiableTopFrame() since it changes top. Because * it is inherently unsafe, it should be used with care and you have to make sure nothing * else has stored top references, or respective references are updated after this call. * <p/> * NOTE also that we assume there is no frozen frame on top of an unfrozen one * <2do> that should probably be reported as an error since it is a stack consistency violation */ public StackFrame getModifiableFrame(StackFrame frame) { StackFrame newTop = null; StackFrame last = null; boolean done = false; for (StackFrame f = top; f != null; f = f.getPrevious()) { done = (f == frame); if (f.isFrozen()) { f = f.clone(); if (newTop == null) { newTop = f; } else { last.setPrevious(f); } last = f; } if (done) { // done if (newTop != null) { top = newTop; markTfChanged(top); } return f; } } return null; // it wasn't on the callstack } /** * note that we don't provide a modifiable version of this. All modifications * should only happen in the executing (top) frame */ public StackFrame getStackFrameExecuting(Instruction insn, int offset) { int n = offset; StackFrame frame = top; for (; (n > 0) && frame != null; frame = frame.getPrevious()) { n--; } for (; frame != null; frame = frame.getPrevious()) { if (frame.getPC().getValue() == insn) { return frame; } } return null; } @Override public String toString() { return "ThreadInfo [name=" + getName() + ",id=" + id + ",state=" + getStateName() + ']'; } void setDaemon(boolean isDaemon) { threadDataClone().isDaemon = isDaemon; } public boolean isDaemon() { return threadData.isDaemon; } public MJIEnv getMJIEnv() { return env; } void setName(String newName) { threadDataClone().name = newName.toCharArray(); // see 'setPriority()', only that it's more serious here, because the // java.lang.Thread name is stored as a char[] } public void setPriority(int newPrio) { if (threadData.priority != newPrio) { threadDataClone().priority = newPrio; // note that we don't update the java.lang.Thread object, but // use our threadData value (which works because the object // values are just used directly from the Thread ctors (from where we pull // it out in our ThreadInfo ctor), and henceforth only via our intercepted // native getters } } public int getPriority() { return threadData.priority; } /** * Comparison for sorting based on index. */ @Override public int compareTo(ThreadInfo that) { return this.id - that.id; } /** * only for debugging purposes */ public void checkConsistency(boolean isStore) { checkAssertion(threadData != null, "no thread data"); // if the thread is runnable, it can't be blocked if (isRunnable()) { checkAssertion(lockRef == MJIEnv.NULL, "runnable thread with non-null lockRef: " + lockRef); } // every thread that has been started and is not terminated has to have a stackframe with a next pc if (!isTerminated() && !isNew()) { checkAssertion(stackDepth > 0, "empty stack " + getState()); checkAssertion(top != null, "no top frame"); checkAssertion(top.getPC() != null, "no top PC"); } // if we are timedout, the top pc has to be either on a native Object.wait() or Unsafe.park() if (isTimedOut()) { Instruction insn = top.getPC().getValue(); checkAssertion(insn instanceof EXECUTENATIVE, "thread timedout outside of native method"); // this is a bit dangerous in case we introduce new timeout points MethodInfo mi = ((EXECUTENATIVE) insn).getExecutedMethod(); String mname = mi.getUniqueName(); checkAssertion(mname.equals("wait(J") || mname.equals("park(ZJ"), "timedout thread outside timeout method: " + mi.getFullName()); } List<ElementInfo> lockedObjects = getLockedObjects(); if (lockRef != MJIEnv.NULL) { // object we are blocked on has to exist ElementInfo ei = this.getElementInfo(lockRef); checkAssertion(ei != null, "thread locked on non-existing object: " + lockRef); // we have to be in the lockedThreads list of that objects monitor checkAssertion(ei.isLocking(this), "thread blocked on non-locking object: " + ei); // can't be blocked on a lock we own (but could be in waiting before giving it up) if (!isWaiting() && lockedObjectReferences.length > 0) { for (ElementInfo lei : lockedObjects) { checkAssertion(lei.getObjectRef() != lockRef, "non-waiting thread blocked on owned lock: " + lei); } } // the state has to be BLOCKED, NOTIFIED, WAITING or TIMEOUT_WAITING checkAssertion(isWaiting() || isBlockedOrNotified(), "locked thread not waiting, blocked or notified"); } else { // no lockRef set checkAssertion(!isWaiting() && !isBlockedOrNotified(), "non-locked thread is waiting, blocked or notified"); } // if we have locked objects, we have to be the locking thread of each of them if (lockedObjects != null && !lockedObjects.isEmpty()) { for (ElementInfo ei : lockedObjects) { ThreadInfo lti = ei.getLockingThread(); if (lti != null) { checkAssertion(lti == this, "not the locking thread for locked object: " + ei + " owned by " + lti); } else { // can happen for a nested monitor lockout } } } } protected void checkAssertion(boolean cond, String failMsg) { if (!cond) { System.out.println("!!!!!! failed thread consistency: " + this + ": " + failMsg); vm.dumpThreadStates(); assert false; } } public boolean isSystemThread() { return false; } }