view src/main/gov/nasa/jpf/vm/NativeMethodInfo.java @ 2:b920e6b1be83

second part of the jpf-statechart motivated event interface overhaul, providing dynamic (context specific) expansion of EventTrees from within EventChoiceGenerators. This adds a EventContext mechanism that can replace events on-the-fly during advance() (e.g. expand wildcard patterns) this also included the refined 'vm.extend.transitions' property, which is now a list of TypeSpecs (glob notation plus bounds) for CG types that should be subject to transition extension. We also support CheckExtendTransition attrs for CGs, which can be used to dynamically mark CGs. Note that each matching CG is still tested for non-rescheduling single choices small Type/FeatureSpec extension to make it applicable to java.lang.Class instances. There is no reason why we can't make use of this for native types
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Sat, 24 Jan 2015 18:19:08 -0800
parents 61d41facf527
children e15b03204dc7
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.JPFNativePeerException;
import gov.nasa.jpf.util.JPFLogger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * a MethodInfo for a native peer executed method
 */
public class NativeMethodInfo extends MethodInfo {

  static JPFLogger logger = JPF.getLogger("gov.nasa.jpf.vm.NativePeer");

  static final int  MAX_NARGS = 6;
  static Object[][]  argCache;

  static {
    argCache = new Object[MAX_NARGS][];

    for (int i = 0; i < MAX_NARGS; i++) {
      argCache[i] = new Object[i];
    }
  }

  protected Method mth; // the native method to enter in lieu
  protected NativePeer peer;

  public NativeMethodInfo (MethodInfo mi, Method mth, NativePeer peer){
    super(mi);  // <2do> do we want any operands or locals?

    this.peer = peer;
    this.mth = mth;

    ci.setNativeCallCode(this);
  }

  public void replace( MethodInfo mi){
    mthTable.set(mi.globalId, this);
    mi.ci.putDeclaredMethod(this);
  }
  
  @Override
  public boolean isUnresolvedNativeMethod() {
    // we are already a NativeMethodInfo
    return false;
  }

  @Override
  public boolean isMJI () {
    return true;
  }

  @Override
  public boolean hasEmptyBody (){
    // how would we know
    return false;
  }

  public NativePeer getNativePeer() {
    return peer;
  }

  public Method getMethod() {
    return mth;
  }

  @Override
  public String getStackTraceSource() {
    if (peer != null){
      return peer.getPeerClassName();
    } else {
      return "no peer";
    }
  }

  @Override
  public int getLineNumber (Instruction pc) {
    return -1; // we have no line numbers
  }

  public Instruction executeNative (ThreadInfo ti) {
    Object   ret = null;
    Object[] args = null;
    MJIEnv   env = ti.getMJIEnv();
        
    NativeStackFrame nativeFrame = (NativeStackFrame)ti.getTopFrame();

    env.setCallEnvironment(this);

    if (isUnsatisfiedLinkError(env)) {
      return ti.createAndThrowException("java.lang.UnsatisfiedLinkError",
                                        "cannot find native " + ci.getName() + '.' + getName());
    }

    try {
      args = nativeFrame.getArguments();

      // this is the reflection call into the native peer
      ret = mth.invoke(peer, args);

      if (env.hasException()) {
        // even though we should prefer throwing normal exceptionHandlers,
        // sometimes it might be better/required to explicitly throw
        // something that's not wrapped into a InvocationTargetException
        // (e.g. InterruptedException), which is why there still is a
        // MJIEnv.throwException()
        return ti.throwException( env.popException());
      }

      StackFrame top = ti.getTopFrame();
//      if (top == nativeFrame){ // no roundtrips, straight return
      if (top.originatesFrom(nativeFrame)){ // could have changed attributes
        NativeStackFrame ntop = (NativeStackFrame)top;

        if (env.isInvocationRepeated()){
          // don't advance
          return ntop.getPC();

        } else {
          // we don't have to do a ti.topClone() because the last insn left
          // is NATIVERETURN. Even if a listener creates a CG on it, it won't
          // modify its StackFrame, which is then popped anyways

          ntop.setReturnValue(ret);
          ntop.setReturnAttr(env.getReturnAttribute());

          return ntop.getPC().getNext(); // that should be the NATIVERETURN
        }

      } else {
        // direct calls from within the native method, i.e. nativeFrame is not
        // on top anymore, but its current instruction (invoke) will be reexecuted
        // because DirectCallStackFrames don't advance the pc of the new top top upon return
        return top.getPC();
      }

    } catch (IllegalArgumentException iax) {
      logger.warning(iax.toString());
      return ti.createAndThrowException("java.lang.IllegalArgumentException",
                                        "calling " + ci.getName() + '.' + getName());
    } catch (IllegalAccessException ilax) {
      logger.warning(ilax.toString());
      return ti.createAndThrowException("java.lang.IllegalAccessException",
                                        "calling " + ci.getName() + '.' + getName());
    } catch (InvocationTargetException itx) {

      // if loading a class throws an exception
      if(itx.getTargetException() instanceof ClassInfoException) {
        ClassInfoException cie = (ClassInfoException) itx.getTargetException();
        return ti.createAndThrowException(cie.getExceptionClass(), cie.getMessage());
      }

      if (itx.getTargetException() instanceof UncaughtException) {  // Native methods could 
        throw (UncaughtException) itx.getTargetException();
      } 
       
      // this will catch all exceptionHandlers thrown by the native method execution
      // we don't try to hand them back to the application
      throw new JPFNativePeerException("exception in native method "
          + ci.getName() + '.' + getName(), itx.getTargetException());
    }
  }

  protected boolean isUnsatisfiedLinkError(MJIEnv env){
    return(mth == null);
  }

  /**
   * Get and convert the native method parameters off the ThreadInfo stack.
   * Use the MethodInfo parameter type info for this (not the reflect.Method
   * type array), or otherwise we won't have any type check
   */
  protected Object[] getArguments (ThreadInfo ti) {
    // these are just local refs to speed up
    int      nArgs = getNumberOfArguments();
    byte[]   argTypes = getArgumentTypes();

    //Object[] a = getArgArray(nArgs + 2);
    Object[] a = new Object[nArgs+2];

    int      stackOffset;
    int      i, j, k;
    int      ival;
    long     lval;
    StackFrame caller = ti.getTopFrame();


    for (i = 0, stackOffset = 0, j = nArgs + 1, k = nArgs - 1;
         i < nArgs;
         i++, j--, k--) {
      switch (argTypes[k]) {
      case Types.T_BOOLEAN:
        ival = caller.peek(stackOffset);
        a[j] = Boolean.valueOf(Types.intToBoolean(ival));

        break;

      case Types.T_BYTE:
        ival = caller.peek(stackOffset);
        a[j] = Byte.valueOf((byte) ival);

        break;

      case Types.T_CHAR:
        ival = caller.peek(stackOffset);
        a[j] = Character.valueOf((char) ival);

        break;

      case Types.T_SHORT:
        ival = caller.peek(stackOffset);
        a[j] = new Short((short) ival);

        break;

      case Types.T_INT:
        ival = caller.peek(stackOffset);
        a[j] = new Integer(ival);

        break;

      case Types.T_LONG:
        lval = caller.peekLong(stackOffset);
        stackOffset++; // 2 stack words
        a[j] = new Long(lval);

        break;

      case Types.T_FLOAT:
        ival = caller.peek(stackOffset);
        a[j] = new Float(Types.intToFloat(ival));

        break;

      case Types.T_DOUBLE:
        lval = caller.peekLong(stackOffset);
        stackOffset++; // 2 stack words
        a[j] = new Double(Types.longToDouble(lval));

        break;

      default:
        // NOTE - we have to store T_REFERENCE as an Integer, because
        // it shows up in our native method as an 'int'
        ival = caller.peek(stackOffset);
        a[j] = new Integer(ival);
      }

      stackOffset++;
    }

    //--- set  our standard MJI header arguments
    if (isStatic()) {
      a[1] = new Integer(ci.getClassObjectRef());
    } else {
      a[1] = new Integer(ti.getCalleeThis(this));
    }

    a[0] = ti.getMJIEnv();

    return a;
  }
}