view src/peers/gov/nasa/jpf/vm/JPF_java_lang_Thread.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children
line wrap: on
line source

/*
 * Copyright (C) 2014, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All rights reserved.
 *
 * The Java Pathfinder core (jpf-core) platform is licensed under the
 * Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 *        http://www.apache.org/licenses/LICENSE-2.0. 
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package gov.nasa.jpf.vm;

import gov.nasa.jpf.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, int groupRef, int runnableRef, int nameRef, long stackSize) {
    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, runnableRef, nameRef);
  }

  @MJI
  public boolean isAlive____Z (MJIEnv env, int objref) {
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    if (ti != null){
      return ti.isAlive();
    } else {
      return false; // not in ThreadList anymore
    }
  }

  @MJI
  public void setDaemon0__Z__V (MJIEnv env, int objref, boolean isDaemon) {
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    ti.setDaemon(isDaemon);
  }

  @MJI
  public void dumpStack____V (MJIEnv env, int clsObjRef){
    ThreadInfo ti = env.getThreadInfo();
    ti.printStackTrace(); // this is not correct, we should go through VM.print
  }

  @MJI
  public void setName0__Ljava_lang_String_2__V (MJIEnv env, int objref, int nameRef) {
    // it bails if you try to set a null name
    if (nameRef == MJIEnv.NULL) {
      env.throwException("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(nameRef));
  }

  @MJI
  public void setPriority0__I__V (MJIEnv env, int objref, int prio) {
    // again, we have to cache this in ThreadData for performance reasons
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    
    if (prio != ti.getPriority()){
      ti.setPriority(prio);
    
      // this could cause a context switch in a priority based scheduler
      if (ti.getScheduler().setsPriorityCG(ti)){
        env.repeatInvocation();
        return;
      }
    }
  }

  @MJI
  public int countStackFrames____I (MJIEnv env, int objref) {
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    return ti.countStackFrames();
  }

  @MJI
  public int currentThread____Ljava_lang_Thread_2 (MJIEnv env, int clsObjRef) {
    ThreadInfo ti = env.getThreadInfo();
    return ti.getThreadObjectRef();
  }

  @MJI
  public boolean holdsLock__Ljava_lang_Object_2__Z (MJIEnv env, int clsObjRef, int objref) {
    ThreadInfo  ti = env.getThreadInfo();
    ElementInfo ei = env.getElementInfo(objref);

    return ei.isLockedBy(ti);
  }

  @MJI
  public void interrupt____V (MJIEnv env, int objref) {
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiInterrupted = env.getThreadInfoForObjRef(objref);

    if (!tiCurrent.isFirstStepInsn()) {
      tiInterrupted.interrupt();
    }
    
    if (tiCurrent.getScheduler().setsInterruptCG(tiCurrent, tiInterrupted)) {
      env.repeatInvocation();
      return;
    }
  }

  // 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) {
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    return ti.isInterrupted(false);
  }

  @MJI
  public boolean interrupted____Z (MJIEnv env, int clsObjRef) {
    ThreadInfo ti = env.getThreadInfo();
    return ti.isInterrupted(true);
  }

  @MJI
  public void start____V (MJIEnv env, int objref) {
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiStarted = env.getThreadInfoForObjRef(objref);
    VM vm = tiCurrent.getVM();

    //--- actions that are only performed upon first execution
    if (!tiCurrent.isFirstStepInsn()){
      if (tiStarted.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 there is no handler invocation
        tiStarted.setTerminated();
        return;
      }

      if (!tiStarted.isNew()) {
        // alredy running, throw 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
        env.throwException("java.lang.IllegalThreadStateException");
        return;
      }

      int runnableRef = tiStarted.getRunnableRef();
      if (runnableRef == MJIEnv.NULL) {
        // note that we don't set the 'tiSuspended' field, since java.lang.Thread doesn't
        runnableRef = objref;
      }

      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(tiStarted);
      runFrame.setReferenceArgument(0, runnableRef, null);
            
      tiStarted.pushFrame(runFrame);
      tiStarted.setState(ThreadInfo.State.RUNNING);
      
      vm.notifyThreadStarted(tiStarted);
    }
    
    //--- scheduling point
    if (tiCurrent.getScheduler().setsStartCG(tiCurrent, tiStarted)){
      env.repeatInvocation();
    }
    // everything that would follow would be re-executed
  }

  @MJI
  public void yield____V (MJIEnv env, int clsObjRef) {
    ThreadInfo ti = env.getThreadInfo();
    if (ti.getScheduler().setsYieldCG(ti)){
      env.repeatInvocation();
    }
  }

  @MJI
  public void sleep__JI__V (MJIEnv env, int clsObjRef, long millis, int nanos) {
    ThreadInfo ti = env.getThreadInfo();

    // check scheduling point
    if (ti.getScheduler().setsSleepCG(ti, millis, nanos)){
      ti.setSleeping();
      env.repeatInvocation();
      return;
    }
    
    if (ti.isSleeping()){
      ti.setRunning();
    }
  }

  @MJI
  public void suspend____V (MJIEnv env, int threadObjRef) {
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiSuspended = env.getThreadInfoForObjRef(threadObjRef);

    if (tiSuspended.isTerminated()) {
      return;  // nothing to do, it's already gone
    }
    
    if (!tiCurrent.isFirstStepInsn()){ // do this just once
      tiSuspended.suspend();
    }
    
    // scheduling point
    if (tiCurrent.getScheduler().setsSuspendCG(tiCurrent, tiSuspended)){
      env.repeatInvocation();      
    }
  }

  @MJI
  public void resume____V (MJIEnv env, int threadObjRef) {
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiResumed = env.getThreadInfoForObjRef(threadObjRef);

    if (tiCurrent == tiResumed){
      return;  // no self resume prior to suspension
    }

    if (tiResumed.isTerminated()) {
      return;  // nothing to resume
    }

    if (!tiCurrent.isFirstStepInsn()) { // do this just once
      tiResumed.resume();
    }
    
    // check scheduling point
    if (tiCurrent.getScheduler().setsResumeCG(tiCurrent, tiResumed)){
      env.repeatInvocation();
      return;
    }
  }

  /*
   * the join() workhorse. We use lockfree waits instead of a simple wait from a synchronized block
   * to save states
   */
  protected void join0 (MJIEnv env, int joineeRef, long timeout){
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiJoinee = env.getThreadInfoForObjRef(joineeRef);
    ElementInfo eiJoinee = env.getModifiableElementInfo(joineeRef); // the thread object to wait on

    if (timeout < 0) {
      env.throwException("java.lang.IllegalArgumentException", "timeout value is negative");
      return;
    }
      
    if (tiCurrent.isInterrupted(true)){ // interrupt status is set, throw and bail      
      // since we use lock-free waits, we need to remove ourselves from the lock contender list
      eiJoinee.setMonitorWithoutLocked(tiCurrent);
      
      // note that we have to throw even if the thread to join to is not alive anymore
      env.throwInterrupt();
      return;
    }
  
    if (!tiCurrent.isFirstStepInsn()){ // to be executed only once    
      if (tiJoinee.isAlive()) {
        // block in first top half so that following transitions see this thread as not runnable
        eiJoinee.wait( tiCurrent, timeout, false);
      } else {
        return; // nothing to do
      }
    }    

    if (tiCurrent.getScheduler().setsJoinCG(tiCurrent, tiJoinee, timeout)) {
      env.repeatInvocation();
      return;
    }
    
    // unblock in bottom half
    switch (tiCurrent.getState()) {
      case WAITING:
      case TIMEOUT_WAITING:
        throw new JPFException("blocking join without transition break");        
      
      case UNBLOCKED:
        // Thread was owning the lock when it joined - we have to wait until
        // we can reacquire it
        eiJoinee.lockNotified(tiCurrent);
        break;

      case TIMEDOUT:
        eiJoinee.resumeNonlockedWaiter(tiCurrent);
        break;

      case RUNNING:
        if (tiJoinee.isAlive()) { // we still need to wait
          eiJoinee.wait(tiCurrent, timeout, false); // no need for a new CG
          env.repeatInvocation();
        }
        break;
    }
  }

  @MJI
  public void join____V (MJIEnv env, int objref){
    join0(env,objref,0);
  }

  @MJI
  public void join__J__V (MJIEnv env, int objref, long millis) {
    join0(env,objref,millis);

  }

  @MJI
  public void join__JI__V (MJIEnv env, int objref, long millis, int nanos) {
    join0(env,objref,millis); // <2do> we ignore nanos for now
  }

  @MJI
  public int getState0____I (MJIEnv env, int objref) {
    // 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) {
    ThreadInfo ti = env.getThreadInfoForObjRef(objref);
    return ti.getId();
  }
  
  @MJI
  public void stop____V (MJIEnv env, int threadRef) {
    stop__Ljava_lang_Throwable_2__V(env, threadRef, MJIEnv.NULL);
  }

  @MJI
  public void stop__Ljava_lang_Throwable_2__V (MJIEnv env, int threadRef, int throwableRef) {
    ThreadInfo tiCurrent = env.getThreadInfo();
    ThreadInfo tiStopped = env.getThreadInfoForObjRef(threadRef);

    if (tiStopped.isTerminated() || tiStopped.isStopped()) {
      return; // silently ignored
    }

    if (tiCurrent.getScheduler().setsStopCG(tiCurrent, tiStopped)){
      env.repeatInvocation();
      return;
    }

    // stop thread in bottom half
    tiStopped.setStopped(throwableRef);
  }
}