// // Copyright (C) 2010 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.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.BiFunction; import cmu.conditional.ChoiceFactory; import cmu.conditional.Conditional; import cmu.conditional.IChoice; import cmu.conditional.One; import cmu.utils.RuntimeConstants; import de.fosd.typechef.featureexpr.FeatureExpr; import de.fosd.typechef.featureexpr.FeatureExprFactory; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFNativePeerException; import gov.nasa.jpf.jvm.bytecode.EXCEPTION; import gov.nasa.jpf.util.JPFLogger; /** * 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; } 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 Conditional<Instruction> executeNative(final FeatureExpr ctx, ThreadInfo ti) { Object ret = null; Object[] args = null; MJIEnv env = ti.getMJIEnv(); NativeStackFrame nativeFrame = (NativeStackFrame) ti.getTopFrame(); env.setCallEnvironment(this); if (isUnsatisfiedLinkError(env)) { return new One<>(ti.createAndThrowException(ctx, "java.lang.UnsatisfiedLinkError", "cannot find native " + ci.getName() + '.' + getName())); } try { args = nativeFrame.getArguments(); if (RuntimeConstants.debug) { System.out.print("RUN " + name + " " + mth.toString()); for (Object a : args) { System.out.print(" " + a.toString()); } System.out.println(); } // this is the reflection call into the native peer boolean supportsConditional = false; for (Class<?> t : mth.getParameterTypes()) { if (t.equals(Conditional.class)) { supportsConditional = true; } } boolean handled = false; if (!supportsConditional) { int i = 0; for (Object a : args) { if (a instanceof One) { args[i++] = ((One<?>) a).getValue(); } else if (a instanceof IChoice) { // Entry for n-handler with conditional arguments Conditional<Object[]> unconditionalArgs = getUnconditionalArgs(args); ret = unconditionalArgs.mapf(ctx, new BiFunction<FeatureExpr, Object[], Conditional<Object>>() { @SuppressWarnings("unchecked") @Override public Conditional<Object> apply(FeatureExpr ctx, Object[] args) { try { Object returnValue = mth.invoke(peer, args); if (returnValue instanceof Conditional) { return (Conditional<Object>)returnValue; } return new One<>(returnValue); } catch (IllegalAccessException | InvocationTargetException e) { System.err.println(mth); for (Object a : args) { System.err.println(a.toString()); } throw new RuntimeException(e); } } }); handled = true; break; } else { args[i++] = a; } } } try { if (!handled) { if (mth.getParameterTypes()[1].isPrimitive()) { if (args[1] instanceof One) { args[1] = ((One<?>)args[1]).getValue(); } ret = mth.invoke(peer, args); } else { ret = mth.invoke(peer, args); } } } catch (IllegalAccessException e) { System.err.println(mth); for (Object a : args) { System.err.println(a.toString()); } throw e; } 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 new One<>(ti.throwException(ctx, env.popException())); } StackFrame top = ti.getTopFrame(); if (top == nativeFrame) { // no roundtrips, straight return if (env.isInvocationRepeated()) { // don't advance return new One<>(nativeFrame.getPC().getValue()); } 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 nativeFrame.setReturnValue(ret); nativeFrame.setReturnAttr(env.getReturnAttribute()); return nativeFrame.getPC().mapf(ctx, new BiFunction<FeatureExpr, Instruction, Conditional<Instruction>>() { @Override public Conditional<Instruction> apply(FeatureExpr f, Instruction y) { if (Conditional.isContradiction(f)) { return new One<>(y); } if (Conditional.isTautology(f)) { return new One<>(y.getNext()); } return ChoiceFactory.create(ctx, new One<>(y.getNext()), new One<>(y)); } }).simplify(); // return nativeFrame.getPC().getValue().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 new One<>(ti.createAndThrowException(ctx, "java.lang.IllegalArgumentException", "calling " + ci.getName() + '.' + getName())); } catch (IllegalAccessException ilax) { logger.warning(ilax.toString()); return new One<>(ti.createAndThrowException(ctx, "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 new One<>(new EXCEPTION(cie.getExceptionClass().toString(), 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()); } } /** * Transforms arguments with conditional values to unconditional values. */ public static Conditional<Object[]> getUnconditionalArgs(Object[] args) { List<Object[]> unconditionalArgs = new LinkedList<>(); Object[] initialObject = new Object[args.length]; initialObject[args.length - 1] = args[args.length - 1]; unconditionalArgs.add(initialObject); int index = 0; for (Object arg : args) { unconditionalArgs = insertArgs(unconditionalArgs, arg, index++); } return toChoice(unconditionalArgs); } /** * Inserts all entries of the probably conditional argument arg at the given index position. * */ private static List<Object[]> insertArgs(List<Object[]> unconditionalArgs, Object arg, int index) { final List<Object[]> newArgs = new LinkedList<>(); if (arg instanceof Conditional) { Map<?, FeatureExpr> map = ((Conditional<?>) arg).toMap(); for (Entry<?, FeatureExpr> e : map.entrySet()) { for (Object[] args : unconditionalArgs) { FeatureExpr ctx = ((FeatureExpr)args[args.length - 1]).and(e.getValue()); if (!Conditional.isContradiction(ctx)) { Object[] copy = copy(args); copy[index] = e.getKey(); copy[copy.length -1] = ctx; newArgs.add(copy); } } } } else if (arg instanceof FeatureExpr) { return unconditionalArgs; } else { for (Object[] args : unconditionalArgs) { Object[] copy = copy(args); copy[index] = arg; newArgs.add(copy); } } return newArgs; } /** * Transforms the list of arguments to a choice of unconditional arguments.<br> * The last argument is used as context. */ private static Conditional<Object[]> toChoice(final List<Object[]> unconditionalArgs) { Conditional<Object[]> result = null; for (Object[] args : unconditionalArgs) { if (result == null) { result = new One<>(args); } else { result = ChoiceFactory.create((FeatureExpr)args[args.length - 1], new One<>(args), result); } } return result; } private static Object[] copy(Object[] args) { Object[] copy = new Object[args.length]; System.arraycopy(args, 0, copy, 0, args.length); return copy; } 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(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = Boolean.valueOf(Types.intToBoolean(ival)); break; case Types.T_BYTE: ival = caller.peek(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = Byte.valueOf((byte) ival); break; case Types.T_CHAR: ival = caller.peek(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = Character.valueOf((char) ival); break; case Types.T_SHORT: ival = caller.peek(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = new Short((short) ival); break; case Types.T_INT: ival = caller.peek(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = new Integer(ival); break; case Types.T_LONG: lval = caller.peekLong(FeatureExprFactory.True(), stackOffset).getValue(); stackOffset++; // 2 stack words a[j] = new Long(lval); break; case Types.T_FLOAT: ival = caller.peek(FeatureExprFactory.True(), stackOffset).getValue(); a[j] = new Float(Types.intToFloat(ival)); break; case Types.T_DOUBLE: lval = caller.peekLong(FeatureExprFactory.True(), stackOffset).getValue(); stackOffset++; // 2 stack words a[j] = Double.valueOf(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(FeatureExprFactory.True(), stackOffset).getValue(); 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; } }