/* * Copyright 2016 Google Inc. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Google designates this * particular file as subject to the "Classpath" exception as provided * by Google in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package java.lang.invoke; import dalvik.system.EmulatedStackFrame; import dalvik.system.EmulatedStackFrame.Range; import dalvik.system.EmulatedStackFrame.StackFrameAccessor; import dalvik.system.EmulatedStackFrame.StackFrameReader; import dalvik.system.EmulatedStackFrame.StackFrameWriter; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import sun.invoke.util.Wrapper; import sun.misc.Unsafe; import static dalvik.system.EmulatedStackFrame.StackFrameAccessor.copyNext; /** * @hide Public for testing only. */ public class Transformers { private Transformers() {} static { try { TRANSFORM_INTERNAL = MethodHandle.class.getDeclaredMethod("transformInternal", EmulatedStackFrame.class); } catch (NoSuchMethodException nsme) { throw new AssertionError(); } } /** * Method reference to the private {@code MethodHandle.transformInternal} method. This is * cached here because it's the point of entry for all transformers. */ private static final Method TRANSFORM_INTERNAL; /** @hide */ public static abstract class Transformer extends MethodHandle implements Cloneable { protected Transformer(MethodType type) { super(TRANSFORM_INTERNAL.getArtMethod(), MethodHandle.INVOKE_TRANSFORM, type); } protected Transformer(MethodType type, int invokeKind) { super(TRANSFORM_INTERNAL.getArtMethod(), invokeKind, type); } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } /** * A method handle that always throws an exception of a specified type. * * The handle declares a nominal return type, which is immaterial to the execution * of the handle because it never returns. * * @hide */ public static class AlwaysThrow extends Transformer { private final Class exceptionType; public AlwaysThrow(Class nominalReturnType, Class exType) { super(MethodType.methodType(nominalReturnType, exType)); this.exceptionType = exType; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { throw emulatedStackFrame.getReference(0, exceptionType); } } /** * Implements {@code MethodHandles.dropArguments}. */ public static class DropArguments extends Transformer { private final MethodHandle delegate; private final EmulatedStackFrame.Range range1; /** * Note that {@code range2} will be null if the arguments that are being dropped * are the last {@code n}. */ /* @Nullable */ private final EmulatedStackFrame.Range range2; public DropArguments(MethodType type, MethodHandle delegate, int startPos, int numDropped) { super(type); this.delegate = delegate; // We pre-calculate the ranges of values we have to copy through to the delegate // handle at the time of instantiation so that the actual invoke is performant. this.range1 = EmulatedStackFrame.Range.of(type, 0, startPos); final int numArgs = type.ptypes().length; if (startPos + numDropped < numArgs) { this.range2 = EmulatedStackFrame.Range.of(type, startPos + numDropped, numArgs); } else { this.range2 = null; } } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(delegate.type()); emulatedStackFrame.copyRangeTo(calleeFrame, range1, 0 /* referencesStart */, 0 /* stackFrameStart */); if (range2 != null) { final int referencesStart = range1.numReferences; final int stackFrameStart = range1.numBytes; emulatedStackFrame.copyRangeTo(calleeFrame, range2, referencesStart, stackFrameStart); } delegate.invoke(calleeFrame); calleeFrame.copyReturnValueTo(emulatedStackFrame); } } /** * Implements {@code MethodHandles.catchException}. */ public static class CatchException extends Transformer { private final MethodHandle target; private final MethodHandle handler; private final Class exType; private final EmulatedStackFrame.Range handlerArgsRange; public CatchException(MethodHandle target, MethodHandle handler, Class exType) { super(target.type()); this.target = target; this.handler = handler; this.exType = exType; // We only copy the first "count" args, dropping others if required. Note that // we subtract one because the first handler arg is the exception thrown by the // target. handlerArgsRange = EmulatedStackFrame.Range.of(target.type(), 0, (handler.type().parameterCount() - 1)); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { try { target.invoke(emulatedStackFrame); } catch (Throwable th) { if (th.getClass() == exType) { // We've gotten an exception of the appropriate type, so we need to call // the handler. Create a new frame of the appropriate size. EmulatedStackFrame fallback = EmulatedStackFrame.create(handler.type()); // The first argument to the handler is the actual exception. fallback.setReference(0, th); // We then copy other arguments that need to be passed through to the handler. // Note that we might drop arguments at the end, if needed. Note that // referencesStart == 1 because the first argument is the exception type. emulatedStackFrame.copyRangeTo(fallback, handlerArgsRange, 1 /* referencesStart */, 0 /* stackFrameStart */); // Perform the invoke and return the appropriate value. handler.invoke(fallback); fallback.copyReturnValueTo(emulatedStackFrame); } else { // The exception is not of the expected type, we throw it. throw th; } } } } /** * Implements {@code MethodHandles.GuardWithTest}. */ public static class GuardWithTest extends Transformer { private final MethodHandle test; private final MethodHandle target; private final MethodHandle fallback; private final EmulatedStackFrame.Range testArgsRange; public GuardWithTest(MethodHandle test, MethodHandle target, MethodHandle fallback) { super(target.type()); this.test = test; this.target = target; this.fallback = fallback; // The test method might have a subset of the arguments of the handle / target. testArgsRange = EmulatedStackFrame.Range.of(target.type(), 0, test.type().parameterCount()); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { EmulatedStackFrame testFrame = EmulatedStackFrame.create(test.type()); emulatedStackFrame.copyRangeTo(testFrame, testArgsRange, 0, 0); // We know that the return value for test is going to be boolean.class, so we don't have // to do the copyReturnValue dance. final boolean value = (boolean) test.invoke(testFrame); if (value) { target.invoke(emulatedStackFrame); } else { fallback.invoke(emulatedStackFrame); } } } /** * Implementation of MethodHandles.arrayElementGetter for reference types. */ public static class ReferenceArrayElementGetter extends Transformer { private final Class arrayClass; public ReferenceArrayElementGetter(Class arrayClass) { super(MethodType.methodType(arrayClass.getComponentType(), new Class[]{arrayClass, int.class})); this.arrayClass = arrayClass; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); // Read the array object and the index from the stack frame. final Object[] array = (Object[]) reader.nextReference(arrayClass); final int index = reader.nextInt(); // Write the array element back to the stack frame. final StackFrameWriter writer = new StackFrameWriter(); writer.attach(emulatedStackFrame); writer.makeReturnValueAccessor(); writer.putNextReference(array[index], arrayClass.getComponentType()); } } /** * Implementation of MethodHandles.arrayElementSetter for reference types. */ public static class ReferenceArrayElementSetter extends Transformer { private final Class arrayClass; public ReferenceArrayElementSetter(Class arrayClass) { super(MethodType.methodType(void.class, new Class[] { arrayClass, int.class, arrayClass.getComponentType() })); this.arrayClass = arrayClass; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); // Read the array object, index and the value to write from the stack frame. final Object[] array = (Object[]) reader.nextReference(arrayClass); final int index = reader.nextInt(); final Object value = reader.nextReference(arrayClass.getComponentType()); array[index] = value; } } /** * Implementation of MethodHandles.identity() for reference types. */ public static class ReferenceIdentity extends Transformer { private final Class type; public ReferenceIdentity(Class type) { super(MethodType.methodType(type, type)); this.type = type; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(emulatedStackFrame); writer.makeReturnValueAccessor(); writer.putNextReference(reader.nextReference(type), type); } } /** * Implementation of MethodHandles.constant. */ public static class Constant extends Transformer { private final Class type; // NOTE: This implementation turned out to be more awkward than expected becuase // of the type system. We could simplify this considerably at the cost of making // the emulated stack frame API uglier or by transitioning into JNI. // // We could consider implementing this in terms of bind() once that's implemented. // This would then just become : MethodHandles.identity(type).bind(value). private int asInt; private long asLong; private float asFloat; private double asDouble; private Object asReference; private char typeChar; public Constant(Class type, Object value) { super(MethodType.methodType(type)); this.type = type; if (!type.isPrimitive()) { asReference = value; typeChar = 'L'; } else if (type == int.class) { asInt = (int) value; typeChar = 'I'; } else if (type == char.class) { asInt = (int) (char) value; typeChar = 'C'; } else if (type == short.class) { asInt = (int) (short) value; typeChar = 'S'; } else if (type == byte.class) { asInt = (int) (byte) value; typeChar = 'B'; } else if (type == boolean.class) { asInt = ((boolean) value) ? 1 : 0; typeChar = 'Z'; } else if (type == long.class) { asLong = (long) value; typeChar = 'J'; } else if (type == float.class) { asFloat = (float) value; typeChar = 'F'; } else if (type == double.class) { asDouble = (double) value; typeChar = 'D'; } else { throw new AssertionError("unknown type: " + typeChar); } } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameWriter writer = new StackFrameWriter(); writer.attach(emulatedStackFrame); writer.makeReturnValueAccessor(); switch (typeChar) { case 'L' : { writer.putNextReference(asReference, type); break; } case 'I' : { writer.putNextInt(asInt); break; } case 'C' : { writer.putNextChar((char) asInt); break; } case 'S' : { writer.putNextShort((short) asInt); break; } case 'B' : { writer.putNextByte((byte) asInt); break; } case 'Z' : { writer.putNextBoolean(asInt == 1); break; } case 'J' : { writer.putNextLong(asLong); break; } case 'F' : { writer.putNextFloat(asFloat); break; } case 'D' : { writer.putNextDouble(asDouble); break; } default: throw new AssertionError("Unexpected typeChar: " + typeChar); } } } /*package*/ static class Construct extends Transformer { private final MethodHandle constructorHandle; private final EmulatedStackFrame.Range callerRange; /*package*/ Construct(MethodHandle constructorHandle, MethodType returnedType) { super(returnedType); this.constructorHandle = constructorHandle; this.callerRange = EmulatedStackFrame.Range.all(type()); } MethodHandle getConstructorHandle() { return constructorHandle; } private static boolean isAbstract(Class klass) { return (klass.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT; } private static void checkInstantiable(Class klass) throws InstantiationException { if (isAbstract(klass)) { String s = klass.isInterface() ? "interface " : "abstract class "; throw new InstantiationException("Can't instantiate " + s + klass); } } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final Class receiverType = type().rtype(); checkInstantiable(receiverType); // Allocate memory for receiver. Object receiver = Unsafe.getUnsafe().allocateInstance(receiverType); // The MethodHandle type for the caller has the form of // {rtype=T,ptypes=A1..An}. The constructor MethodHandle is of // the form {rtype=void,ptypes=T,A1...An}. So the frame for // the constructor needs to have a slot with the receiver // in position 0. EmulatedStackFrame constructorFrame = EmulatedStackFrame.create(constructorHandle.type()); constructorFrame.setReference(0, receiver); emulatedStackFrame.copyRangeTo(constructorFrame, callerRange, 1, 0); constructorHandle.invoke(constructorFrame); // Set return result for caller. emulatedStackFrame.setReturnValueTo(receiver); } } /** * Implements MethodHandle.bindTo. * * @hide */ public static class BindTo extends Transformer { private final MethodHandle delegate; private final Object receiver; private final EmulatedStackFrame.Range range; public BindTo(MethodHandle delegate, Object receiver) { super(delegate.type().dropParameterTypes(0, 1)); this.delegate = delegate; this.receiver = receiver; this.range = EmulatedStackFrame.Range.all(this.type()); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { // Create a new emulated stack frame with the full type (including the leading // receiver reference). EmulatedStackFrame stackFrame = EmulatedStackFrame.create(delegate.type()); // The first reference argument must be the receiver. stackFrame.setReference(0, receiver); // Copy all other arguments. emulatedStackFrame.copyRangeTo(stackFrame, range, 1 /* referencesStart */, 0 /* stackFrameStart */); // Perform the invoke. delegate.invoke(stackFrame); stackFrame.copyReturnValueTo(emulatedStackFrame); } } /** * Implements MethodHandle.filterReturnValue. */ public static class FilterReturnValue extends Transformer { private final MethodHandle target; private final MethodHandle filter; private final EmulatedStackFrame.Range allArgs; public FilterReturnValue(MethodHandle target, MethodHandle filter) { super(MethodType.methodType(filter.type().rtype(), target.type().ptypes())); this.target = target; this.filter = filter; allArgs = EmulatedStackFrame.Range.all(type()); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { // Create a new frame with the target's type and copy all arguments over. // This frame differs in return type with |emulatedStackFrame| but will have // the same parameter shapes. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); emulatedStackFrame.copyRangeTo(targetFrame, allArgs, 0, 0); target.invoke(targetFrame); // Perform the invoke. final StackFrameReader returnValueReader = new StackFrameReader(); returnValueReader.attach(targetFrame); returnValueReader.makeReturnValueAccessor(); // Create an emulated frame for the filter and copy all its arguments across. EmulatedStackFrame filterFrame = EmulatedStackFrame.create(filter.type()); final StackFrameWriter filterWriter = new StackFrameWriter(); filterWriter.attach(filterFrame); final Class returnType = target.type().rtype(); if (!returnType.isPrimitive()) { filterWriter.putNextReference(returnValueReader.nextReference(returnType), returnType); } else if (returnType == boolean.class) { filterWriter.putNextBoolean(returnValueReader.nextBoolean()); } else if (returnType == byte.class) { filterWriter.putNextByte(returnValueReader.nextByte()); } else if (returnType == char.class) { filterWriter.putNextChar(returnValueReader.nextChar()); } else if (returnType == short.class) { filterWriter.putNextShort(returnValueReader.nextShort()); } else if (returnType == int.class) { filterWriter.putNextInt(returnValueReader.nextInt()); } else if (returnType == long.class) { filterWriter.putNextLong(returnValueReader.nextLong()); } else if (returnType == float.class) { filterWriter.putNextFloat(returnValueReader.nextFloat()); } else if (returnType == double.class) { filterWriter.putNextDouble(returnValueReader.nextDouble()); } // Invoke the filter and copy its return value back to the original frame. filter.invoke(filterFrame); filterFrame.copyReturnValueTo(emulatedStackFrame); } } /* * Implements MethodHandles.permuteArguments. * * @hide */ public static class PermuteArguments extends Transformer { private final MethodHandle target; private final int[] reorder; public PermuteArguments(MethodType type, MethodHandle target, int[] reorder) { super(type); this.target = target; this.reorder = reorder; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); // In the interests of simplicity, we box / unbox arguments while performing // the permutation. We first iterate through the incoming stack frame and box // each argument. We then unbox and write out the argument to the target frame // according to the specified reordering. Object[] arguments = new Object[reorder.length]; final Class[] ptypes = type().ptypes(); for (int i = 0; i < ptypes.length; ++i) { final Class ptype = ptypes[i]; if (!ptype.isPrimitive()) { arguments[i] = reader.nextReference(ptype); } else if (ptype == boolean.class) { arguments[i] = reader.nextBoolean(); } else if (ptype == byte.class) { arguments[i] = reader.nextByte(); } else if (ptype == char.class) { arguments[i] = reader.nextChar(); } else if (ptype == short.class) { arguments[i] = reader.nextShort(); } else if (ptype == int.class) { arguments[i] = reader.nextInt(); } else if (ptype == long.class) { arguments[i] = reader.nextLong(); } else if (ptype == float.class) { arguments[i] = reader.nextFloat(); } else if (ptype == double.class) { arguments[i] = reader.nextDouble(); } else { throw new AssertionError("Unexpected type: " + ptype); } } EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(target.type()); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(calleeFrame); for (int i = 0; i < ptypes.length; ++i) { int idx = reorder[i]; final Class ptype = ptypes[idx]; final Object argument = arguments[idx]; if (!ptype.isPrimitive()) { writer.putNextReference(argument, ptype); } else if (ptype == boolean.class) { writer.putNextBoolean((boolean) argument); } else if (ptype == byte.class) { writer.putNextByte((byte) argument); } else if (ptype == char.class) { writer.putNextChar((char) argument); } else if (ptype == short.class) { writer.putNextShort((short) argument); } else if (ptype == int.class) { writer.putNextInt((int) argument); } else if (ptype == long.class) { writer.putNextLong((long) argument); } else if (ptype == float.class) { writer.putNextFloat((float) argument); } else if (ptype == double.class) { writer.putNextDouble((double) argument); } else { throw new AssertionError("Unexpected type: " + ptype); } } target.invoke(calleeFrame); calleeFrame.copyReturnValueTo(emulatedStackFrame); } } /** * Converts methods with a trailing array argument to variable arity * methods. So (A,B,C[])R can be invoked with any number of convertible * arguments after B, e.g. (A,B)R or (A, B, C0)R or (A, B, C0...Cn)R. * * @hide */ /*package*/ static class VarargsCollector extends Transformer { final MethodHandle target; /*package*/ VarargsCollector(MethodHandle target) { super(target.type(), MethodHandle.INVOKE_CALLSITE_TRANSFORM); if (!lastParameterTypeIsAnArray(target.type().ptypes())) { throw new IllegalArgumentException("target does not have array as last parameter"); } this.target = target; } private static boolean lastParameterTypeIsAnArray(Class[] parameterTypes) { if (parameterTypes.length == 0) return false; return parameterTypes[parameterTypes.length - 1].isArray(); } @Override public boolean isVarargsCollector() { return true; } @Override public MethodHandle asFixedArity() { return target; } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { MethodType callerFrameType = callerFrame.getMethodType(); Class[] callerPTypes = callerFrameType.ptypes(); Class[] targetPTypes = type().ptypes(); int lastTargetIndex = targetPTypes.length - 1; if (callerPTypes.length == targetPTypes.length && targetPTypes[lastTargetIndex].isAssignableFrom(callerPTypes[lastTargetIndex])) { // Caller frame matches target frame in the arity array parameter. Invoke // immediately, and let the invoke() dispatch perform any necessary conversions // on the other parameters present. target.invoke(callerFrame); return; } if (callerPTypes.length < targetPTypes.length - 1) { // Too few arguments to be compatible with variable arity invocation. throwWrongMethodTypeException(callerFrameType, type()); } if (!MethodType.canConvert(type().rtype(), callerFrameType.rtype())) { // Incompatible return type. throwWrongMethodTypeException(callerFrameType, type()); } Class elementType = targetPTypes[lastTargetIndex].getComponentType(); if (!arityArgumentsConvertible(callerPTypes, lastTargetIndex, elementType)) { // Wrong types to be compatible with variable arity invocation. throwWrongMethodTypeException(callerFrameType, type()); } // Allocate targetFrame. MethodType targetFrameType = makeTargetFrameType(callerFrameType, type()); EmulatedStackFrame targetFrame = EmulatedStackFrame.create(targetFrameType); prepareFrame(callerFrame, targetFrame); // Invoke target. target.invoke(targetFrame); // Copy return value to the caller's frame. targetFrame.copyReturnValueTo(callerFrame); } private static void throwWrongMethodTypeException(MethodType from, MethodType to) { throw new WrongMethodTypeException("Cannot convert " + from + " to " + to); } private static boolean arityArgumentsConvertible(Class[] ptypes, int arityStart, Class elementType) { if (ptypes.length - 1 == arityStart) { if (ptypes[arityStart].isArray() && ptypes[arityStart].getComponentType() == elementType) { // The last ptype is in the same position as the arity // array and has the same type. return true; } } for (int i = arityStart; i < ptypes.length; ++i) { if (!MethodType.canConvert(ptypes[i], elementType)) { return false; } } return true; } private static Object referenceArray(StackFrameReader reader, Class[] ptypes, Class elementType, int offset, int length) { Object arityArray = Array.newInstance(elementType, length); for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; Object o = null; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { o = reader.nextReference(argumentType); break; } case 'I': { o = reader.nextInt(); break; } case 'J': { o = reader.nextLong(); break; } case 'B': { o = reader.nextByte(); break; } case 'S': { o = reader.nextShort(); break; } case 'C': { o = reader.nextChar(); break; } case 'Z': { o = reader.nextBoolean(); break; } case 'F': { o = reader.nextFloat(); break; } case 'D': { o = reader.nextDouble(); break; } } Array.set(arityArray, i, elementType.cast(o)); } return arityArray; } private static Object intArray(StackFrameReader reader, Class ptypes[], int offset, int length) { int[] arityArray = new int[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'I': { arityArray[i] = reader.nextInt(); break; } case 'S': { arityArray[i] = reader.nextShort(); break; } case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Integer) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object longArray(StackFrameReader reader, Class ptypes[], int offset, int length) { long[] arityArray = new long[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'J': { arityArray[i] = reader.nextLong(); break; } case 'I': { arityArray[i] = reader.nextInt(); break; } case 'S': { arityArray[i] = reader.nextShort(); break; } case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Long) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object byteArray(StackFrameReader reader, Class ptypes[], int offset, int length) { byte[] arityArray = new byte[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Byte) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object shortArray(StackFrameReader reader, Class ptypes[], int offset, int length) { short[] arityArray = new short[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'S': { arityArray[i] = reader.nextShort(); break; } case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Short) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object charArray(StackFrameReader reader, Class ptypes[], int offset, int length) { char[] arityArray = new char[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'C': { arityArray[i] = reader.nextChar(); break; } default: { arityArray[i] = (Character) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object booleanArray(StackFrameReader reader, Class ptypes[], int offset, int length) { boolean[] arityArray = new boolean[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'Z': { arityArray[i] = reader.nextBoolean(); break; } default: arityArray[i] = (Boolean) reader.nextReference(argumentType); break; } } return arityArray; } private static Object floatArray(StackFrameReader reader, Class ptypes[], int offset, int length) { float[] arityArray = new float[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'F': { arityArray[i] = reader.nextFloat(); break; } case 'J': { arityArray[i] = reader.nextLong(); break; } case 'I': { arityArray[i] = reader.nextInt(); break; } case 'S': { arityArray[i] = reader.nextShort(); break; } case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Float) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object doubleArray(StackFrameReader reader, Class ptypes[], int offset, int length) { double[] arityArray = new double[length]; for (int i = 0; i < length; ++i) { Class argumentType = ptypes[i + offset]; switch (Wrapper.basicTypeChar(argumentType)) { case 'D': { arityArray[i] = reader.nextDouble(); break; } case 'F': { arityArray[i] = reader.nextFloat(); break; } case 'J': { arityArray[i] = reader.nextLong(); break; } case 'I': { arityArray[i] = reader.nextInt(); break; } case 'S': { arityArray[i] = reader.nextShort(); break; } case 'B': { arityArray[i] = reader.nextByte(); break; } default: { arityArray[i] = (Double) reader.nextReference(argumentType); break; } } } return arityArray; } private static Object makeArityArray(MethodType callerFrameType, StackFrameReader callerFrameReader, int indexOfArityArray, Class arityArrayType) { int arityArrayLength = callerFrameType.ptypes().length - indexOfArityArray; Class elementType = arityArrayType.getComponentType(); Class[] callerPTypes = callerFrameType.ptypes(); char elementBasicType = Wrapper.basicTypeChar(elementType); switch (elementBasicType) { case 'L': return referenceArray(callerFrameReader, callerPTypes, elementType, indexOfArityArray, arityArrayLength); case 'I': return intArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'J': return longArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'B': return byteArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'S': return shortArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'C': return charArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'Z': return booleanArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'F': return floatArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); case 'D': return doubleArray(callerFrameReader, callerPTypes, indexOfArityArray, arityArrayLength); } throw new InternalError("Unexpected type: " + elementType); } public static Object collectArguments(char basicComponentType, Class componentType, StackFrameReader reader, Class[] types, int startIdx, int length) { switch (basicComponentType) { case 'L': return referenceArray(reader, types, componentType, startIdx, length); case 'I': return intArray(reader, types, startIdx, length); case 'J': return longArray(reader, types, startIdx, length); case 'B': return byteArray(reader, types, startIdx, length); case 'S': return shortArray(reader, types, startIdx, length); case 'C': return charArray(reader, types, startIdx, length); case 'Z': return booleanArray(reader, types, startIdx, length); case 'F': return floatArray(reader, types, startIdx, length); case 'D': return doubleArray(reader, types, startIdx, length); } throw new InternalError("Unexpected type: " + basicComponentType); } private static void copyParameter(StackFrameReader reader, StackFrameWriter writer, Class ptype) { switch (Wrapper.basicTypeChar(ptype)) { case 'L': { writer.putNextReference(reader.nextReference(ptype), ptype); break; } case 'I': { writer.putNextInt(reader.nextInt()); break; } case 'J': { writer.putNextLong(reader.nextLong()); break; } case 'B': { writer.putNextByte(reader.nextByte()); break; } case 'S': { writer.putNextShort(reader.nextShort()); break; } case 'C': { writer.putNextChar(reader.nextChar()); break; } case 'Z': { writer.putNextBoolean(reader.nextBoolean()); break; } case 'F': { writer.putNextFloat(reader.nextFloat()); break; } case 'D': { writer.putNextDouble(reader.nextDouble()); break; } default: throw new InternalError("Unexpected type: " + ptype); } } private static void prepareFrame(EmulatedStackFrame callerFrame, EmulatedStackFrame targetFrame) { StackFrameWriter targetWriter = new StackFrameWriter(); targetWriter.attach(targetFrame); StackFrameReader callerReader = new StackFrameReader(); callerReader.attach(callerFrame); // Copy parameters from |callerFrame| to |targetFrame| leaving room for arity array. MethodType targetMethodType = targetFrame.getMethodType(); int indexOfArityArray = targetMethodType.ptypes().length - 1; for (int i = 0; i < indexOfArityArray; ++i) { Class ptype = targetMethodType.ptypes()[i]; copyParameter(callerReader, targetWriter, ptype); } // Add arity array as last parameter in |targetFrame|. Class arityArrayType = targetMethodType.ptypes()[indexOfArityArray]; Object arityArray = makeArityArray(callerFrame.getMethodType(), callerReader, indexOfArityArray, arityArrayType); targetWriter.putNextReference(arityArray, arityArrayType); } /** * Computes the frame type to invoke the target method handle with. This * is the same as the caller frame type, but with the trailing argument * being the array type that is the trailing argument in the target method * handle. * * Suppose the targetType is (T0, T1, T2[])RT and the callerType is (C0, C1, C2, C3)RC * then the constructed type is (C0, C1, T2[])RC. */ private static MethodType makeTargetFrameType(MethodType callerType, MethodType targetType) { final int ptypesLength = targetType.ptypes().length; final Class[] ptypes = new Class[ptypesLength]; // Copy types from caller types to new methodType. System.arraycopy(callerType.ptypes(), 0, ptypes, 0, ptypesLength - 1); // Set the last element in the type array to be the // varargs array of the target. ptypes[ptypesLength - 1] = targetType.ptypes()[ptypesLength - 1]; return MethodType.methodType(callerType.rtype(), ptypes); } } /** * Implements MethodHandles.invoker & MethodHandles.exactInvoker. */ static class Invoker extends Transformer { private final MethodType targetType; private final boolean isExactInvoker; private final EmulatedStackFrame.Range copyRange; Invoker(MethodType targetType, boolean isExactInvoker) { super(targetType.insertParameterTypes(0, MethodHandle.class)); this.targetType = targetType; this.isExactInvoker = isExactInvoker; copyRange = EmulatedStackFrame.Range.of(type(), 1, type().parameterCount()); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { // We need to artifically throw a WrongMethodTypeException here because we // can't call invokeExact on the target inside the transformer. if (isExactInvoker) { // TODO: We should do the comparison by hand if this new type creation // on every invoke proves too expensive. MethodType callType = emulatedStackFrame.getCallsiteType().dropParameterTypes(0, 1); if (!targetType.equals(callType)) { throw new WrongMethodTypeException("Wrong type, Expected: " + targetType + " was: " + callType); } } // The first argument to the stack frame is the handle that needs to be invoked. MethodHandle target = emulatedStackFrame.getReference(0, MethodHandle.class); // All other arguments must be copied to the target frame. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(targetType); emulatedStackFrame.copyRangeTo(targetFrame, copyRange, 0, 0); // Finally, invoke the handle and copy the return value. target.invoke(targetFrame); targetFrame.copyReturnValueTo(emulatedStackFrame); } } /** * Implements MethodHandle.asSpreader / MethodHandles.spreadInvoker. */ static class Spreader extends Transformer { /** The method handle we're delegating to. */ private final MethodHandle target; /** * The offset of the trailing array argument in the list of arguments to * this transformer. The array argument is always the last argument. */ private final int arrayOffset; /** * The type char of the component type of the array. */ private final char arrayTypeChar; /** * The number of input arguments that will be present in the array. In other words, * this is the expected array length. */ private final int numArrayArgs; /** * Range of arguments to copy verbatim from the input frame, This will cover all * arguments that aren't a part of the trailing array. */ private final Range copyRange; Spreader(MethodHandle target, MethodType spreaderType, int numArrayArgs) { super(spreaderType); this.target = target; // Copy all arguments except the last argument (which is the trailing array argument // that needs to be spread). arrayOffset = spreaderType.parameterCount() - 1; // Get and cache the component type of the input array. final Class componentType = spreaderType.ptypes()[arrayOffset].getComponentType(); if (componentType == null) { throw new AssertionError("Trailing argument must be an array."); } arrayTypeChar = Wrapper.basicTypeChar(componentType); this.numArrayArgs = numArrayArgs; // Copy all args except for the last argument. this.copyRange = EmulatedStackFrame.Range.of(spreaderType, 0, arrayOffset); } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { // Create a new stack frame for the callee. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); // Copy all arguments except for the trailing array argument. callerFrame.copyRangeTo(targetFrame, copyRange, 0, 0); // Attach the writer, prepare to spread the trailing array arguments into // the callee frame. StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, arrayOffset, copyRange.numReferences, copyRange.numBytes); // Get the array reference and check that its length is as expected. Object arrayObj = callerFrame.getReference( copyRange.numReferences, this.type().ptypes()[arrayOffset]); final int arrayLength = Array.getLength(arrayObj); if (arrayLength != numArrayArgs) { throw new IllegalArgumentException("Invalid array length: " + arrayLength); } final MethodType type = target.type(); switch (arrayTypeChar) { case 'L': spreadArray((Object[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'I': spreadArray((int[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'J': spreadArray((long[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'B': spreadArray((byte[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'S': spreadArray((short[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'C': spreadArray((char[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'Z': spreadArray((boolean[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'F': spreadArray((float[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; case 'D': spreadArray((double[]) arrayObj, writer, type, numArrayArgs, arrayOffset); break; } target.invoke(targetFrame); targetFrame.copyReturnValueTo(callerFrame); } public static void spreadArray(Object[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; Object o = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(o, argumentType); break; } case 'I': { writer.putNextInt((int) o); break; } case 'J': { writer.putNextLong((long) o); break; } case 'B': { writer.putNextByte((byte) o); break; } case 'S': { writer.putNextShort((short) o); break; } case 'C': { writer.putNextChar((char) o); break; } case 'Z': { writer.putNextBoolean((boolean) o); break; } case 'F': { writer.putNextFloat((float) o); break; } case 'D': { writer.putNextDouble((double) o); break; } } } } public static void spreadArray(int[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; int j = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(j, argumentType); break; } case 'I': { writer.putNextInt(j); break; } case 'J': { writer.putNextLong(j); break; } case 'F': { writer.putNextFloat(j); break; } case 'D': { writer.putNextDouble(j); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(long[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; long l = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(l, argumentType); break; } case 'J': { writer.putNextLong(l); break; } case 'F': { writer.putNextFloat((float) l); break; } case 'D': { writer.putNextDouble((double) l); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(byte[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; byte b = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(b, argumentType); break; } case 'I': { writer.putNextInt(b); break; } case 'J': { writer.putNextLong(b); break; } case 'B': { writer.putNextByte(b); break; } case 'S': { writer.putNextShort(b); break; } case 'F': { writer.putNextFloat(b); break; } case 'D': { writer.putNextDouble(b); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(short[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; short s = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(s, argumentType); break; } case 'I': { writer.putNextInt(s); break; } case 'J': { writer.putNextLong(s); break; } case 'S': { writer.putNextShort(s); break; } case 'F': { writer.putNextFloat(s); break; } case 'D': { writer.putNextDouble(s); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(char[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; char c = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(c, argumentType); break; } case 'I': { writer.putNextInt(c); break; } case 'J': { writer.putNextLong(c); break; } case 'C': { writer.putNextChar(c); break; } case 'F': { writer.putNextFloat(c); break; } case 'D': { writer.putNextDouble(c); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(boolean[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; boolean z = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(z, argumentType); break; } case 'Z': { writer.putNextBoolean(z); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(double[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; double d = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(d, argumentType); break; } case 'D': { writer.putNextDouble(d); break; } default : { throw new AssertionError(); } } } } public static void spreadArray(float[] array, StackFrameWriter writer, MethodType type, int numArgs, int offset) { final Class[] ptypes = type.ptypes(); for (int i = 0; i < numArgs; ++i) { Class argumentType = ptypes[i + offset]; float f = array[i]; switch (Wrapper.basicTypeChar(argumentType)) { case 'L': { writer.putNextReference(f, argumentType); break; } case 'D': { writer.putNextDouble((double) f); break; } case 'F': { writer.putNextFloat(f); break; } default : { throw new AssertionError(); } } } } } /** * Implements MethodHandle.asCollector. */ static class Collector extends Transformer { private final MethodHandle target; /** * The offset of the trailing array argument in the list of arguments to * this transformer. The array argument is always the last argument. */ private final int arrayOffset; /** * The number of input arguments that will be present in the array. In other words, * this is the expected array length. */ private final int numArrayArgs; /** * The type char of the component type of the array. */ private final char arrayTypeChar; /** * Range of arguments to copy verbatim from the input frame, This will cover all * arguments that aren't a part of the trailing array. */ private final Range copyRange; Collector(MethodHandle delegate, Class arrayType, int length) { super(delegate.type().asCollectorType(arrayType, length)); target = delegate; // Copy all arguments except the last argument (which is the trailing array argument // that needs to be spread). arrayOffset = delegate.type().parameterCount() - 1; arrayTypeChar = Wrapper.basicTypeChar(arrayType.getComponentType()); numArrayArgs = length; // Copy all args except for the last argument. copyRange = EmulatedStackFrame.Range.of(delegate.type(), 0, arrayOffset); } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { // Create a new stack frame for the callee. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); // Copy all arguments except for the trailing array argument. callerFrame.copyRangeTo(targetFrame, copyRange, 0, 0); // Attach the writer, prepare to spread the trailing array arguments into // the callee frame. final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, arrayOffset, copyRange.numReferences, copyRange.numBytes); final StackFrameReader reader = new StackFrameReader(); reader.attach(callerFrame, arrayOffset, copyRange.numReferences, copyRange.numBytes); switch (arrayTypeChar) { case 'L': { // Reference arrays are the only case where the component type of the // array we construct might differ from the type of the reference we read // from the stack frame. final Class targetType = target.type().ptypes()[arrayOffset]; final Class targetComponentType = targetType.getComponentType(); final Class adapterComponentType = type().lastParameterType(); Object[] arr = (Object[]) Array.newInstance(targetComponentType, numArrayArgs); for (int i = 0; i < numArrayArgs; ++i) { arr[i] = reader.nextReference(adapterComponentType); } writer.putNextReference(arr, targetType); break; } case 'I': { int[] array = new int[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextInt(); } writer.putNextReference(array, int[].class); break; } case 'J': { long[] array = new long[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextLong(); } writer.putNextReference(array, long[].class); break; } case 'B': { byte[] array = new byte[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextByte(); } writer.putNextReference(array, byte[].class); break; } case 'S': { short[] array = new short[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextShort(); } writer.putNextReference(array, short[].class); break; } case 'C': { char[] array = new char[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextChar(); } writer.putNextReference(array, char[].class); break; } case 'Z': { boolean[] array = new boolean[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextBoolean(); } writer.putNextReference(array, boolean[].class); break; } case 'F': { float[] array = new float[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextFloat(); } writer.putNextReference(array, float[].class); break; } case 'D': { double[] array = new double[numArrayArgs]; for (int i = 0; i < numArrayArgs; ++i) { array[i] = reader.nextDouble(); } writer.putNextReference(array, double[].class); break; } } target.invoke(targetFrame); targetFrame.copyReturnValueTo(callerFrame); } } /* * Implements MethodHandles.filterArguments. */ static class FilterArguments extends Transformer { /** The target handle. */ private final MethodHandle target; /** Index of the first argument to filter */ private final int pos; /** The list of filters to apply */ private final MethodHandle[] filters; FilterArguments(MethodHandle target, int pos, MethodHandle[] filters) { super(deriveType(target, pos, filters)); this.target = target; this.pos = pos; this.filters = filters; } private static MethodType deriveType(MethodHandle target, int pos, MethodHandle[] filters) { final Class[] filterArgs = new Class[filters.length]; for (int i = 0; i < filters.length; ++i) { filterArgs[i] = filters[i].type().parameterType(0); } return target.type().replaceParameterTypes(pos, pos + filters.length, filterArgs); } @Override public void transform(EmulatedStackFrame stackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(stackFrame); EmulatedStackFrame transformedFrame = EmulatedStackFrame.create(target.type()); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(transformedFrame); final Class[] ptypes = target.type().ptypes(); for (int i = 0; i < ptypes.length; ++i) { // Check whether the current argument has a filter associated with it. // If it has no filter, no further action need be taken. final Class ptype = ptypes[i]; final MethodHandle filter; if (i < pos) { filter = null; } else if (i >= pos + filters.length) { filter = null; } else { filter = filters[i - pos]; } if (filter != null) { // Note that filter.type() must be (ptype)ptype - this is checked before // this transformer is created. EmulatedStackFrame filterFrame = EmulatedStackFrame.create(filter.type()); // Copy the next argument from the stack frame to the filter frame. final StackFrameWriter filterWriter = new StackFrameWriter(); filterWriter.attach(filterFrame); copyNext(reader, filterWriter, filter.type().ptypes()[0]); filter.invoke(filterFrame); // Copy the argument back from the filter frame to the stack frame. final StackFrameReader filterReader = new StackFrameReader(); filterReader.attach(filterFrame); filterReader.makeReturnValueAccessor(); copyNext(filterReader, writer, ptype); } else { // There's no filter associated with this frame, just copy the next argument // over. copyNext(reader, writer, ptype); } } target.invoke(transformedFrame); transformedFrame.copyReturnValueTo(stackFrame); } } /** * Implements MethodHandles.collectArguments. */ static class CollectArguments extends Transformer { private final MethodHandle target; private final MethodHandle collector; private final int pos; /** The range of input arguments we copy to the collector. */ private final Range collectorRange; /** * The first range of arguments we copy to the target. These are arguments * in the range [0, pos). Note that arg[pos] is the return value of the filter. */ private final Range range1; /** * The second range of arguments we copy to the target. These are arguments in the range * (pos, N], where N is the number of target arguments. */ private final Range range2; private final int referencesOffset; private final int stackFrameOffset; CollectArguments(MethodHandle target, MethodHandle collector, int pos, MethodType adapterType) { super(adapterType); this.target = target; this.collector = collector; this.pos = pos; final int numFilterArgs = collector.type().parameterCount(); final int numAdapterArgs = type().parameterCount(); collectorRange = Range.of(type(), pos, pos + numFilterArgs); range1 = Range.of(type(), 0, pos); if (pos + numFilterArgs < numAdapterArgs) { this.range2 = Range.of(type(), pos + numFilterArgs, numAdapterArgs); } else { this.range2 = null; } // Calculate the number of primitive bytes (or references) we copy to the // target frame based on the return value of the combiner. final Class collectorRType = collector.type().rtype(); if (collectorRType == void.class) { stackFrameOffset = 0; referencesOffset = 0; } else if (collectorRType.isPrimitive()) { stackFrameOffset = EmulatedStackFrame.getSize(collectorRType); referencesOffset = 0; } else { stackFrameOffset = 0; referencesOffset = 1; } } @Override public void transform(EmulatedStackFrame stackFrame) throws Throwable { // First invoke the collector. EmulatedStackFrame filterFrame = EmulatedStackFrame.create(collector.type()); stackFrame.copyRangeTo(filterFrame, collectorRange, 0, 0); collector.invoke(filterFrame); // Start constructing the target frame. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); stackFrame.copyRangeTo(targetFrame, range1, 0, 0); // If one of these offsets is not zero, we have a return value to copy. if (referencesOffset != 0 || stackFrameOffset != 0) { final StackFrameReader reader = new StackFrameReader(); reader.attach(filterFrame).makeReturnValueAccessor(); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, pos, range1.numReferences, range1.numBytes); copyNext(reader, writer, target.type().ptypes()[0]); } if (range2 != null) { stackFrame.copyRangeTo(targetFrame, range2, range1.numReferences + referencesOffset, range2.numBytes + stackFrameOffset); } target.invoke(targetFrame); targetFrame.copyReturnValueTo(stackFrame); } } /** * Implements MethodHandles.foldArguments. */ static class FoldArguments extends Transformer { private final MethodHandle target; private final MethodHandle combiner; private final Range combinerArgs; private final Range targetArgs; private final int referencesOffset; private final int stackFrameOffset; FoldArguments(MethodHandle target, MethodHandle combiner) { super(deriveType(target, combiner)); this.target = target; this.combiner = combiner; combinerArgs = Range.all(combiner.type()); targetArgs = Range.all(type()); final Class combinerRType = combiner.type().rtype(); if (combinerRType == void.class) { stackFrameOffset = 0; referencesOffset = 0; } else if (combinerRType.isPrimitive()) { stackFrameOffset = EmulatedStackFrame.getSize(combinerRType); referencesOffset = 0; } else { stackFrameOffset = 0; referencesOffset = 1; } } @Override public void transform(EmulatedStackFrame stackFrame) throws Throwable { // First construct the combiner frame and invoke it. EmulatedStackFrame combinerFrame = EmulatedStackFrame.create(combiner.type()); stackFrame.copyRangeTo(combinerFrame, combinerArgs, 0, 0); combiner.invoke(combinerFrame); // Create the stack frame for the target. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); // If one of these offsets is not zero, we have a return value to copy. if (referencesOffset != 0 || stackFrameOffset != 0) { final StackFrameReader reader = new StackFrameReader(); reader.attach(combinerFrame).makeReturnValueAccessor(); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame); copyNext(reader, writer, target.type().ptypes()[0]); } stackFrame.copyRangeTo(targetFrame, targetArgs, referencesOffset, stackFrameOffset); target.invoke(targetFrame); targetFrame.copyReturnValueTo(stackFrame); } private static MethodType deriveType(MethodHandle target, MethodHandle combiner) { if (combiner.type().rtype() == void.class) { return target.type(); } return target.type().dropParameterTypes(0, 1); } } /** * Implements MethodHandles.insertArguments. */ static class InsertArguments extends Transformer { private final MethodHandle target; private final int pos; private final Object[] values; private final Range range1; private final Range range2; InsertArguments(MethodHandle target, int pos, Object[] values) { super(target.type().dropParameterTypes(pos, pos + values.length)); this.target = target; this.pos = pos; this.values = values; final MethodType type = type(); range1 = EmulatedStackFrame.Range.of(type, 0, pos); range2 = Range.of(type, pos, type.parameterCount()); } @Override public void transform(EmulatedStackFrame stackFrame) throws Throwable { EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(target.type()); // Copy all arguments before |pos|. stackFrame.copyRangeTo(calleeFrame, range1, 0, 0); // Attach a stack frame writer so that we can copy the next |values.length| // arguments. final StackFrameWriter writer = new StackFrameWriter(); writer.attach(calleeFrame, pos, range1.numReferences, range1.numBytes); // Copy all the arguments supplied in |values|. int referencesCopied = 0; int bytesCopied = 0; final Class[] ptypes = target.type().ptypes(); for (int i = 0; i < values.length; ++i) { final Class ptype = ptypes[i + pos]; if (ptype.isPrimitive()) { if (ptype == boolean.class) { writer.putNextBoolean((boolean) values[i]); } else if (ptype == byte.class) { writer.putNextByte((byte) values[i]); } else if (ptype == char.class) { writer.putNextChar((char) values[i]); } else if (ptype == short.class) { writer.putNextShort((short) values[i]); } else if (ptype == int.class) { writer.putNextInt((int) values[i]); } else if (ptype == long.class) { writer.putNextLong((long) values[i]); } else if (ptype == float.class) { writer.putNextFloat((float) values[i]); } else if (ptype == double.class) { writer.putNextDouble((double) values[i]); } bytesCopied += EmulatedStackFrame.getSize(ptype); } else { writer.putNextReference(values[i], ptype); referencesCopied++; } } // Copy all remaining arguments. if (range2 != null) { stackFrame.copyRangeTo(calleeFrame, range2, range1.numReferences + referencesCopied, range1.numBytes + bytesCopied); } target.invoke(calleeFrame); calleeFrame.copyReturnValueTo(stackFrame); } } /** * Implements {@link java.lang.invokeMethodHandles#explicitCastArguments()}. */ public static class ExplicitCastArguments extends Transformer { private final MethodHandle target; public ExplicitCastArguments(MethodHandle target, MethodType type) { super(type); this.target = target; } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { // Create a new stack frame for the target. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); explicitCastArguments(callerFrame, targetFrame); target.invoke(targetFrame); explicitCastReturnValue(callerFrame, targetFrame); } private void explicitCastArguments(final EmulatedStackFrame callerFrame, final EmulatedStackFrame targetFrame) { final StackFrameReader reader = new StackFrameReader(); reader.attach(callerFrame); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame); final Class[] fromTypes = type().ptypes(); final Class[] toTypes = target.type().ptypes(); for (int i = 0; i < fromTypes.length; ++i) { explicitCast(reader, fromTypes[i], writer, toTypes[i]); } } private void explicitCastReturnValue(final EmulatedStackFrame callerFrame, final EmulatedStackFrame targetFrame) { Class from = target.type().rtype(); Class to = type().rtype(); if (to != void.class) { final StackFrameWriter writer = new StackFrameWriter(); writer.attach(callerFrame); writer.makeReturnValueAccessor(); if (from == void.class) { if (to.isPrimitive()) { unboxNull(writer, to); } else { writer.putNextReference(null, to); } } else { final StackFrameReader reader = new StackFrameReader(); reader.attach(targetFrame); reader.makeReturnValueAccessor(); explicitCast(reader, target.type().rtype(), writer, type().rtype()); } } } private static void throwUnexpectedType(final Class unexpectedType) { throw new InternalError("Unexpected type: " + unexpectedType); } private static void explicitCastFromBoolean(boolean fromValue, final StackFrameWriter writer, final Class to) { int value = fromValue ? 1 : 0; if (to == byte.class) { writer.putNextByte((byte) value); } else if (to == char.class) { writer.putNextChar((char) value); } else if (to == short.class) { writer.putNextShort((short) value); } else if (to == int.class) { writer.putNextInt(value); } else if (to == long.class) { writer.putNextLong(value); } else if (to == float.class) { writer.putNextFloat(value); } else if (to == double.class) { writer.putNextDouble(value); } else { throwUnexpectedType(to); } } /** * Converts byte value to boolean according to * {@link java.lang.invoke.MethodHandles#explicitCast()} */ private static boolean toBoolean(byte value) { return (value & 1) == 1; } private static byte readPrimitiveAsByte(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (byte) reader.nextByte(); } else if (from == char.class) { return (byte) reader.nextChar(); } else if (from == short.class) { return (byte) reader.nextShort(); } else if (from == int.class) { return (byte) reader.nextInt(); } else if (from == long.class) { return (byte) reader.nextLong(); } else if (from == float.class) { return (byte) reader.nextFloat(); } else if (from == double.class) { return (byte) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static char readPrimitiveAsChar(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (char) reader.nextByte(); } else if (from == char.class) { return (char) reader.nextChar(); } else if (from == short.class) { return (char) reader.nextShort(); } else if (from == int.class) { return (char) reader.nextInt(); } else if (from == long.class) { return (char) reader.nextLong(); } else if (from == float.class) { return (char) reader.nextFloat(); } else if (from == double.class) { return (char) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static short readPrimitiveAsShort(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (short) reader.nextByte(); } else if (from == char.class) { return (short) reader.nextChar(); } else if (from == short.class) { return (short) reader.nextShort(); } else if (from == int.class) { return (short) reader.nextInt(); } else if (from == long.class) { return (short) reader.nextLong(); } else if (from == float.class) { return (short) reader.nextFloat(); } else if (from == double.class) { return (short) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static int readPrimitiveAsInt(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (int) reader.nextByte(); } else if (from == char.class) { return (int) reader.nextChar(); } else if (from == short.class) { return (int) reader.nextShort(); } else if (from == int.class) { return (int) reader.nextInt(); } else if (from == long.class) { return (int) reader.nextLong(); } else if (from == float.class) { return (int) reader.nextFloat(); } else if (from == double.class) { return (int) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static long readPrimitiveAsLong(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (long) reader.nextByte(); } else if (from == char.class) { return (long) reader.nextChar(); } else if (from == short.class) { return (long) reader.nextShort(); } else if (from == int.class) { return (long) reader.nextInt(); } else if (from == long.class) { return (long) reader.nextLong(); } else if (from == float.class) { return (long) reader.nextFloat(); } else if (from == double.class) { return (long) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static float readPrimitiveAsFloat(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (float) reader.nextByte(); } else if (from == char.class) { return (float) reader.nextChar(); } else if (from == short.class) { return (float) reader.nextShort(); } else if (from == int.class) { return (float) reader.nextInt(); } else if (from == long.class) { return (float) reader.nextLong(); } else if (from == float.class) { return (float) reader.nextFloat(); } else if (from == double.class) { return (float) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static double readPrimitiveAsDouble(final StackFrameReader reader, final Class from) { if (from == byte.class) { return (double) reader.nextByte(); } else if (from == char.class) { return (double) reader.nextChar(); } else if (from == short.class) { return (double) reader.nextShort(); } else if (from == int.class) { return (double) reader.nextInt(); } else if (from == long.class) { return (double) reader.nextLong(); } else if (from == float.class) { return (double) reader.nextFloat(); } else if (from == double.class) { return (double) reader.nextDouble(); } else { throwUnexpectedType(from); return 0; } } private static void explicitCastToBoolean(final StackFrameReader reader, final Class from, final StackFrameWriter writer) { byte byteValue = readPrimitiveAsByte(reader, from); writer.putNextBoolean(toBoolean(byteValue)); } private static void explicitCastPrimitives(final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { if (to == byte.class) { byte value = readPrimitiveAsByte(reader, from); writer.putNextByte(value); } else if (to == char.class) { char value = readPrimitiveAsChar(reader, from); writer.putNextChar(value); } else if (to == short.class) { short value = readPrimitiveAsShort(reader, from); writer.putNextShort(value); } else if (to == int.class) { int value = readPrimitiveAsInt(reader, from); writer.putNextInt(value); } else if (to == long.class) { long value = readPrimitiveAsLong(reader, from); writer.putNextLong(value); } else if (to == float.class) { float value = readPrimitiveAsFloat(reader, from); writer.putNextFloat(value); } else if (to == double.class) { double value = readPrimitiveAsDouble(reader, from); writer.putNextDouble(value); } else { throwUnexpectedType(to); } } private static void unboxNull(final StackFrameWriter writer, final Class to) { if (to == boolean.class) { writer.putNextBoolean(false); } else if (to == byte.class) { writer.putNextByte((byte) 0); } else if (to == char.class) { writer.putNextChar((char) 0); } else if (to == short.class) { writer.putNextShort((short) 0); } else if (to == int.class) { writer.putNextInt((int) 0); } else if (to == long.class) { writer.putNextLong((long) 0); } else if (to == float.class) { writer.putNextFloat((float) 0); } else if (to == double.class) { writer.putNextDouble((double) 0); } else { throwUnexpectedType(to); } } private static void unboxNonNull(final Object ref, final Class from, final StackFrameWriter writer, final Class to) { if (to == boolean.class) { if (from == Boolean.class) { writer.putNextBoolean((boolean) ref); } else if (from == Float.class || from == Double.class) { byte b = (byte) ((double) ref); writer.putNextBoolean(toBoolean(b)); } else { byte b = (byte) ((long) ref); writer.putNextBoolean(toBoolean(b)); } } else if (to == byte.class) { writer.putNextByte((byte) ref); } else if (to == char.class) { writer.putNextChar((char) ref); } else if (to == short.class) { writer.putNextShort((short) ref); } else if (to == int.class) { writer.putNextInt((int) ref); } else if (to == long.class) { writer.putNextLong((long) ref); } else if (to == float.class) { writer.putNextFloat((float) ref); } else if (to == double.class) { writer.putNextDouble((double) ref); } else { throwUnexpectedType(to); } } private static void unbox(final Object ref, final Class from, final StackFrameWriter writer, final Class to) { if (ref == null) { unboxNull(writer, to); } else { unboxNonNull(ref, from, writer, to); } } private static void box(final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { Object boxed = null; if (from == boolean.class) { boxed = Boolean.valueOf(reader.nextBoolean()); } else if (from == byte.class) { boxed = Byte.valueOf(reader.nextByte()); } else if (from == char.class) { boxed = Character.valueOf(reader.nextChar()); } else if (from == short.class) { boxed = Short.valueOf(reader.nextShort()); } else if (from == int.class) { boxed = Integer.valueOf(reader.nextInt()); } else if (from == long.class) { boxed = Long.valueOf(reader.nextLong()); } else if (from == float.class) { boxed = Float.valueOf(reader.nextFloat()); } else if (from == double.class) { boxed = Double.valueOf(reader.nextDouble()); } else { throwUnexpectedType(from); } writer.putNextReference(to.cast(boxed), to); } private static void explicitCast(final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { if (from.equals(to)) { StackFrameAccessor.copyNext(reader, writer, from); } else if (!from.isPrimitive()) { Object ref = reader.nextReference(from); if (to.isInterface()) { // Pass from without a cast according to description for // {@link java.lang.invoke.MethodHandles#explicitCastArguments()}. writer.putNextReference(ref, to); } else if (!to.isPrimitive()) { // |to| is a reference type, perform class cast check. writer.putNextReference(to.cast(ref), to); } else { // |from| is a reference type, |to| is a primitive type, unbox(ref, from, writer, to); } } else if (to.isPrimitive()) { // |from| and |to| are primitive types. if (from == boolean.class) { explicitCastFromBoolean(reader.nextBoolean(), writer, to); } else if (to == boolean.class) { explicitCastToBoolean(reader, from, writer); } else { explicitCastPrimitives(reader, from, writer, to); } } else { // |from| is a primitive type, |to| is a reference type. box(reader, from, writer, to); } } } }