// // Copyright (C) 2006 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.vm; import cmu.conditional.Conditional; import de.fosd.typechef.featureexpr.FeatureExpr; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFException; import gov.nasa.jpf.annotation.MJI; import gov.nasa.jpf.util.JPFLogger; /** * MJI NativePeer class for java.lang.Thread library abstraction * * NOTE - this implementation depends on all live thread objects being * in ThreadList */ public class JPF_java_lang_Thread extends NativePeer { static JPFLogger log = JPF.getLogger("gov.nasa.jpf.vm.ThreadInfo"); /** * This method is the common initializer for all Thread ctors, and the only * single location where we can init our ThreadInfo, but it is PRIVATE */ @MJI public void init0__Ljava_lang_ThreadGroup_2Ljava_lang_Runnable_2Ljava_lang_String_2J__V (MJIEnv env, int objRef, Conditional<Integer> groupRef, Conditional<Integer> runnableRef, Conditional<Integer> nameRef, Conditional<Long> stackSize, FeatureExpr ctx) { VM vm = env.getVM(); // we only need to create the ThreadInfo - its initialization will take care // of proper linkage to the java.lang.Thread object (objRef) vm.createThreadInfo( objRef, groupRef.getValue(), runnableRef.getValue(), nameRef.getValue()); } @MJI public boolean isAlive____Z (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfoForObjRef(objref); return ti.isAlive(); } @MJI public void setDaemon0__Z__V (MJIEnv env, int objref, boolean isDaemon, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfoForObjRef(objref); ti.setDaemon(isDaemon); } @MJI public void dumpStack____V (MJIEnv env, int clsObjRef, FeatureExpr ctx){ ThreadInfo ti = env.getThreadInfo(); ti.printStackTrace(); // this is not correct, we should go through VM.print } @SuppressWarnings("deprecation") @MJI public void setName0__Ljava_lang_String_2__V (MJIEnv env, int objref, int nameRef, FeatureExpr ctx) { // it bails if you try to set a null name if (nameRef == MJIEnv.NULL) { env.throwException(ctx, "java.lang.IllegalArgumentException"); return; } // we have to intercept this to cache the name as a Java object // (to be stored in ThreadData) // luckily enough, it's copied into the java.lang.Thread object // as a char[], i.e. does not have to preserve identity // Note the nastiness in here - the java.lang.Thread object is only used // to get the initial values into ThreadData, and gets inconsistent // if this method is called (just works because the 'name' field is only // directly accessed from within the Thread ctors) ThreadInfo ti = env.getThreadInfoForObjRef(objref); ti.setName(env.getStringObject(ctx, nameRef)); } @MJI public void setPriority0__I__V (MJIEnv env, int objref, int prio, FeatureExpr ctx) { // again, we have to cache this in ThreadData for performance reasons ThreadInfo ti = env.getThreadInfoForObjRef(objref); ti.setPriority(prio); } @MJI public int countStackFrames____I (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfoForObjRef(objref); return ti.countStackFrames(); } @MJI public int currentThread____Ljava_lang_Thread_2 (MJIEnv env, int clsObjRef, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); return ti.getThreadObjectRef(); } @MJI public boolean holdsLock__Ljava_lang_Object_2__Z (MJIEnv env, int clsObjRef, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); ElementInfo ei = env.getElementInfo(objref); return ei.isLockedBy(ti); } @MJI public void interrupt0____V (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); SystemState ss = env.getSystemState(); ThreadInfo interruptedThread = env.getThreadInfoForObjRef(objref); if (!ti.isFirstStepInsn()) { interruptedThread.interrupt(ctx); // the interrupted thread can re-acquire the lock and therefore is runnable again // hence we should give it a chance to do so (Thread.interrupt() does not require // holding a lock) if (interruptedThread.isUnblocked()){ ChoiceGenerator<?> cg = ss.getSchedulerFactory().createInterruptCG(interruptedThread); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); } } } } // these could be in the model, but we keep it symmetric, which also saves // us the effort of avoiding unwanted shared object field access CGs @MJI public boolean isInterrupted____Z (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfoForObjRef(objref); return ti.isInterrupted(ctx, false); } @MJI public boolean interrupted____Z (MJIEnv env, int clsObjRef, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); return ti.isInterrupted(ctx, true); } @MJI public void start____V (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo tiCurrent = env.getThreadInfo(); SystemState ss = env.getSystemState(); VM vm = tiCurrent.getVM(); ThreadInfo tiStartee = env.getThreadInfoForObjRef(objref); if (!tiCurrent.isFirstStepInsn()) { // first time we see this (may be the only time) if (tiStartee.isStopped()) { // don't do anything but set it terminated - it hasn't acquired any resources yet. // note that apparently host VMs don't schedule this thread, so it never // gets a handler in its miRun() invoked tiStartee.setTerminated(); return; } // check if this thread was already started. If it is still running, this // is a IllegalThreadStateException. If it already terminated, it just gets // silently ignored in Java 1.4, but the 1.5 spec explicitly marks this // as illegal, so we adopt this by throwing an IllegalThreadState, too if (! tiStartee.isNew()) { env.throwException(ctx, "java.lang.IllegalThreadStateException"); return; } int runnableRef = tiStartee.getRunnableRef(); if (runnableRef == MJIEnv.NULL) { // note that we don't set the 'target' field, since java.lang.Thread doesn't runnableRef = objref; } // Note: already did in the init0 method //vm.registerThread(tiStartee); // we don't do this during thread creation because the thread isn't in // the GC root set before it actually starts to enter. Until then, // it's just an ordinary object vm.notifyThreadStarted(tiStartee); ElementInfo eiTarget = env.getElementInfo(runnableRef); ClassInfo ci = eiTarget.getClassInfo(); MethodInfo miRun = ci.getMethod("run()V", true); // we do direct call run() invocation so that we have a well defined // exit point (DIRECTCALLRETURN) in case the thread is stopped or there is // a fail-safe UncaughtExceptionHandler set DirectCallStackFrame runFrame = miRun.createRunStartStackFrame(ctx, tiStartee); runFrame.setReferenceArgument(ctx, 0, runnableRef, null); tiStartee.pushFrame(runFrame); tiStartee.setState(ThreadInfo.State.RUNNING); // now we have a new thread, create a CG for scheduling it ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadStartCG(tiStartee); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); } else { Instruction insn = tiCurrent.getPC().getValue(); log.info(tiStartee.getName(), " start not a scheduling point in ", insn.getMethodInfo().getFullName()); } } else { // nothing to do in the bottom half } } @MJI public void yield____V (MJIEnv env, int clsObjRef, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); // System.out.println(ti.getName() + " trying to yield"); SystemState ss = env.getSystemState(); if (!ti.isFirstStepInsn()) { // first time we see this (may be the only time) ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadYieldCG( ti); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); } } else { // nothing to do, this was just a forced reschedule } } @MJI public void sleep__JI__V (MJIEnv env, int clsObjRef, long millis, int nanos, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfo(); SystemState ss = env.getSystemState(); if (!ti.isFirstStepInsn()) { // first time we see this (may be the only time) ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadSleepCG( ti, millis, nanos); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); ti.setSleeping(); } } else { // this seems asymmetric (since we only set it to sleeping when we register // a CG) but it isn't. This is only the firstStepInsn if we had a CG ti.setRunning(); } } @MJI public void suspend____ (MJIEnv env, int threadObjRef, FeatureExpr ctx) { ThreadInfo currentThread = env.getThreadInfo(); ThreadInfo target = env.getThreadInfoForObjRef(threadObjRef); SystemState ss = env.getSystemState(); if (target.isTerminated()) { return; } if (!currentThread.isFirstStepInsn()) { if (target.suspend()){ ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadSuspendCG(currentThread); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); return; } } } } @MJI public void resume____ (MJIEnv env, int threadObjRef, FeatureExpr ctx) { ThreadInfo currentThread = env.getThreadInfo(); ThreadInfo target = env.getThreadInfoForObjRef(threadObjRef); SystemState ss = env.getSystemState(); if (currentThread == target){ // no self resume prior to suspension return; } if (target.isTerminated()) { return; } if (!currentThread.isFirstStepInsn()) { if (target.resume()){ ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadResumeCG(currentThread); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); return; } } } } /** * this is here so that we don't have to break the transition on a synchronized call */ @SuppressWarnings("incomplete-switch") static void join0 (MJIEnv env, int joineeRef, long timeout, FeatureExpr ctx){ ThreadInfo tiJoiner = env.getThreadInfo(); // this is the CURRENT thread (joiner) ThreadInfo tiJoinee = env.getThreadInfoForObjRef(joineeRef); boolean isAlive = tiJoinee.isAlive(); SystemState ss = env.getSystemState(); ElementInfo ei = env.getModifiableElementInfo(joineeRef); // the thread object to wait on if (tiJoiner.isInterrupted(ctx, true)){ // interrupt status is set, throw and bail // since we use lock-free joins, we need to remove ourselves from the // lock contender list ei.setMonitorWithoutLocked(tiJoiner); // note that we have to throw even if the thread to join to is not alive anymore env.throwInterrupt(ctx); return; } //--- the join if (tiJoiner.isFirstStepInsn()) { // re-execution, we already have a CG switch (tiJoiner.getState()){ case UNBLOCKED: // Thread was owning the lock when it joined - we have to wait until // we can reacquire it ei.lockNotified(tiJoiner); break; case TIMEDOUT: ei.resumeNonlockedWaiter(tiJoiner); break; case RUNNING: if (isAlive) { // we still need to wait ei.wait(tiJoiner, timeout, false); // no need for a new CG env.repeatInvocation(); } break; } } else { // first time exec, create a CG if the thread is still alive if (timeout < 0){ env.throwException(ctx, "java.lang.IllegalArgumentException", "timeout value is negative"); return; } if (isAlive) { ei.wait(tiJoiner, timeout, false); ChoiceGenerator<ThreadInfo> cg = ss.getSchedulerFactory().createWaitCG(ei, tiJoiner, timeout); env.setMandatoryNextChoiceGenerator(cg, "no CG for blocking join()"); env.repeatInvocation(); } else { // nothing to do, thread is already terminated } } } // the old generic version that was based on a synchronized method, which // is bad because it leads to superfluous transitions /* public static void join__ (MJIEnv env, int objref) { ThreadInfo tiStop = getThreadInfo(env,objref); if (tiStop.isAlive()) { env.wait(objref); } } */ @MJI public void join____V (MJIEnv env, int objref, FeatureExpr ctx){ join0(env,objref,0, ctx); } @MJI public void join__J__V (MJIEnv env, int objref, long millis, FeatureExpr ctx) { join0(env,objref,millis, ctx); } @MJI public void join__JI__V (MJIEnv env, int objref, long millis, int nanos, FeatureExpr ctx) { join0(env,objref,millis, ctx); // <2do> we ignore nanos for now } @MJI public int getState0____I (MJIEnv env, int objref, FeatureExpr ctx) { // return the state index with respect to one of the public Thread.States ThreadInfo ti = env.getThreadInfoForObjRef(objref); switch (ti.getState()) { case NEW: return 1; case RUNNING: return 2; case BLOCKED: return 0; case UNBLOCKED: return 2; case WAITING: return 5; case TIMEOUT_WAITING: return 4; case SLEEPING: return 4; case NOTIFIED: return 0; case INTERRUPTED: return 0; case TIMEDOUT: return 2; case TERMINATED: return 3; default: throw new JPFException("illegal thread state: " + ti.getState()); } } @MJI public long getId____J (MJIEnv env, int objref, FeatureExpr ctx) { ThreadInfo ti = env.getThreadInfoForObjRef(objref); return ti.getId(); } @MJI public void stop____V (MJIEnv env, int threadRef, FeatureExpr ctx) { stop__Ljava_lang_Throwable_2__V(env, threadRef, MJIEnv.NULL, ctx); } @MJI public void stop__Ljava_lang_Throwable_2__V(MJIEnv env, int threadRef, int throwableRef, FeatureExpr ctx) { ThreadInfo tiStop = env.getThreadInfoForObjRef(threadRef); // the thread to stop ThreadInfo tiCurrent = env.getThreadInfo(); // the currently executing thread if (tiStop.isTerminated() || tiStop.isStopped()) { // no need to kill it twice return; } if (!tiCurrent.isFirstStepInsn()) { // since this is usually not caught (it shouldn't, at least not without // rethrowing), we might turn this into a right mover since it terminates tiStop SystemState ss = env.getSystemState(); ChoiceGenerator<?> cg = ss.getSchedulerFactory().createThreadStopCG(tiCurrent); if (ss.setNextChoiceGenerator(cg)) { env.repeatInvocation(); return; } } tiStop.setStopped(ctx, throwableRef); } }