/* * Copyright (C) 2016 The Android Open Source Project * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * 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. The Android Open Source * Project designates this particular file as subject to the "Classpath" * exception as provided by The Android Open Source Project 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 static dalvik.system.EmulatedStackFrame.StackFrameAccessor.copyNext; import dalvik.system.EmulatedStackFrame; import dalvik.system.EmulatedStackFrame.Range; import dalvik.system.EmulatedStackFrame.RandomOrderStackFrameReader; import dalvik.system.EmulatedStackFrame.StackFrameAccessor; import dalvik.system.EmulatedStackFrame.StackFrameReader; import dalvik.system.EmulatedStackFrame.StackFrameWriter; import sun.invoke.util.Wrapper; import sun.misc.Unsafe; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; /** @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 abstract static 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(); } /** * Performs a MethodHandle.invoke() call with arguments held in an * EmulatedStackFrame. * @param target the method handle to invoke * @param stackFrame the stack frame containing arguments for the invocation */ protected static void invokeFromTransform(MethodHandle target, EmulatedStackFrame stackFrame) throws Throwable { if (target instanceof Transformer) { ((Transformer) target).transform(stackFrame); } else { final MethodHandle adaptedTarget = target.asType(stackFrame.getMethodType()); adaptedTarget.invokeExactWithFrame(stackFrame); } } /** * Performs a MethodHandle.invokeExact() call with arguments held in an * EmulatedStackFrame. * @param target the method handle to invoke * @param stackFrame the stack frame containing arguments for the invocation */ protected void invokeExactFromTransform(MethodHandle target, EmulatedStackFrame stackFrame) throws Throwable { if (target instanceof Transformer) { ((Transformer) target).transform(stackFrame); } else { target.invokeExactWithFrame(stackFrame); } } } /** Implements {@code MethodHandles.throwException}. */ static class AlwaysThrow extends Transformer { private final Class exceptionType; 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}. */ static class DropArguments extends Transformer { private final MethodHandle delegate; private final EmulatedStackFrame.Range range1; private final EmulatedStackFrame.Range range2; 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); this.range2 = EmulatedStackFrame.Range.from(type, startPos + numDropped); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(delegate.type()); emulatedStackFrame.copyRangeTo( calleeFrame, range1, 0 /* referencesStart */, 0 /* stackFrameStart */); emulatedStackFrame.copyRangeTo( calleeFrame, range2, range1.numReferences, range1.numBytes); invokeFromTransform(delegate, calleeFrame); calleeFrame.copyReturnValueTo(emulatedStackFrame); } } /** Implements {@code MethodHandles.catchException}. */ static class CatchException extends Transformer { private final MethodHandle target; private final MethodHandle handler; private final Class exType; private final EmulatedStackFrame.Range handlerArgsRange; 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 { invokeFromTransform(target, 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. invokeFromTransform(handler, fallback); fallback.copyReturnValueTo(emulatedStackFrame); } else { // The exception is not of the expected type, we throw it. throw th; } } } } /** Implements {@code MethodHandles.tryFinally}. */ static class TryFinally extends Transformer { /** The target handle to try. */ private final MethodHandle target; /** The cleanup handle to invoke after the target. */ private final MethodHandle cleanup; TryFinally(MethodHandle target, MethodHandle cleanup) { super(target.type()); this.target = target; this.cleanup = cleanup; } @Override protected void transform(EmulatedStackFrame callerFrame) throws Throwable { Throwable throwable = null; try { invokeExactFromTransform(target, callerFrame); } catch (Throwable t) { throwable = t; throw t; } finally { final EmulatedStackFrame cleanupFrame = prepareCleanupFrame(callerFrame, throwable); invokeExactFromTransform(cleanup, cleanupFrame); if (cleanup.type().returnType() != void.class) { cleanupFrame.copyReturnValueTo(callerFrame); } } } /** Prepares the frame used to invoke the cleanup handle. */ private EmulatedStackFrame prepareCleanupFrame(final EmulatedStackFrame callerFrame, final Throwable throwable) { final EmulatedStackFrame cleanupFrame = EmulatedStackFrame.create(cleanup.type()); final StackFrameWriter cleanupWriter = new StackFrameWriter(); cleanupWriter.attach(cleanupFrame); // The first argument to `cleanup` is (any) pending exception kind. cleanupWriter.putNextReference(throwable, Throwable.class); int added = 1; // The second argument to `cleanup` is the result from `target` (if not void). Class targetReturnType = target.type().returnType(); StackFrameReader targetReader = new StackFrameReader(); targetReader.attach(callerFrame); if (targetReturnType != void.class) { targetReader.makeReturnValueAccessor(); copyNext(targetReader, cleanupWriter, targetReturnType); added += 1; // Reset `targetReader` to reference the arguments in `callerFrame`. targetReader.attach(callerFrame); } // The final arguments from the invocation of target. As many are copied as the cleanup // handle expects (it may be fewer than the arguments provided to target). Class [] cleanupTypes = cleanup.type().parameterArray(); for (; added != cleanupTypes.length; ++added) { copyNext(targetReader, cleanupWriter, cleanupTypes[added]); } return cleanupFrame; } } /** Implements {@code MethodHandles.GuardWithTest}. */ static class GuardWithTest extends Transformer { private final MethodHandle test; private final MethodHandle target; private final MethodHandle fallback; private final EmulatedStackFrame.Range testArgsRange; 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. StackFrameReader reader = new StackFrameReader(); reader.attach(testFrame); reader.makeReturnValueAccessor(); invokeFromTransform(test, testFrame); final boolean testResult = (boolean) reader.nextBoolean(); if (testResult) { invokeFromTransform(target, emulatedStackFrame); } else { invokeFromTransform(fallback, emulatedStackFrame); } } } /** Implements {@code MethodHandles.arrayElementGetter}. */ static class ReferenceArrayElementGetter extends Transformer { private final Class arrayClass; 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()); } } /** Implements {@code MethodHandles.arrayElementSetter}. */ static class ReferenceArrayElementSetter extends Transformer { private final Class arrayClass; 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; } } /** Implements {@code MethodHandles.identity}. */ static class ReferenceIdentity extends Transformer { private final Class type; 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); } } /** Implements {@code MethodHandles.makeZero}. */ static class ZeroValue extends Transformer { public ZeroValue(Class type) { super(MethodType.methodType(type)); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { // Return-value is zero-initialized in emulatedStackFrame. } } /** Implements {@code MethodHandles.arrayConstructor}. */ static class ArrayConstructor extends Transformer { private final Class componentType; ArrayConstructor(Class arrayType) { super(MethodType.methodType(arrayType, int.class)); componentType = arrayType.getComponentType(); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); final int length = reader.nextInt(); final Object array = Array.newInstance(componentType, length); emulatedStackFrame.setReturnValueTo(array); } } /** Implements {@code MethodHandles.arrayLength}. */ static class ArrayLength extends Transformer { private final Class arrayType; ArrayLength(Class arrayType) { super(MethodType.methodType(int.class, arrayType)); this.arrayType = arrayType; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final StackFrameReader reader = new StackFrameReader(); reader.attach(emulatedStackFrame); final Object arrayObject = reader.nextReference(arrayType); int length; switch (Wrapper.basicTypeChar(arrayType.getComponentType())) { case 'L': length = ((Object[]) arrayObject).length; break; case 'Z': length = ((boolean[]) arrayObject).length; break; case 'B': length = ((byte[]) arrayObject).length; break; case 'C': length = ((char[]) arrayObject).length; break; case 'S': length = ((short[]) arrayObject).length; break; case 'I': length = ((int[]) arrayObject).length; break; case 'J': length = ((long[]) arrayObject).length; break; case 'F': length = ((float[]) arrayObject).length; break; case 'D': length = ((double[]) arrayObject).length; break; default: throw new IllegalStateException("Unsupported type: " + arrayType); } final StackFrameWriter writer = new StackFrameWriter(); writer.attach(emulatedStackFrame).makeReturnValueAccessor(); writer.putNextInt(length); } } /** Implements {@code MethodHandles.createMethodHandleForConstructor}. */ static class Construct extends Transformer { private final MethodHandle constructorHandle; private final EmulatedStackFrame.Range callerRange; 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 = constructorHandle.type().parameterType(0); 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); invokeExactFromTransform(constructorHandle, constructorFrame); // Set return result for caller. emulatedStackFrame.setReturnValueTo(receiver); } } /** Implements {@code MethodHandle.bindTo}. */ static class BindTo extends Transformer { private final MethodHandle delegate; private final Object receiver; private final EmulatedStackFrame.Range range; 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. invokeFromTransform(delegate, stackFrame); stackFrame.copyReturnValueTo(emulatedStackFrame); } } /** Implements {@code MethodHandle.filterReturnValue}. */ static class FilterReturnValue extends Transformer { private final MethodHandle target; private final MethodHandle filter; private final EmulatedStackFrame.Range allArgs; 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); invokeFromTransform(target, targetFrame); // Create an emulated frame for the filter and move the return value from // target to the argument of the filter. final EmulatedStackFrame filterFrame = EmulatedStackFrame.create(filter.type()); final Class filterArgumentType = target.type().rtype(); if (filterArgumentType != void.class) { final StackFrameReader returnValueReader = new StackFrameReader(); returnValueReader.attach(targetFrame).makeReturnValueAccessor(); final StackFrameWriter filterWriter = new StackFrameWriter(); filterWriter.attach(filterFrame); StackFrameAccessor.copyNext(returnValueReader, filterWriter, filterArgumentType); } // Invoke the filter and copy its return value back to the original frame. invokeExactFromTransform(filter, filterFrame); filterFrame.copyReturnValueTo(emulatedStackFrame); } } /** Implements {@code MethodHandles.permuteArguments}. */ static class PermuteArguments extends Transformer { private final MethodHandle target; private final int[] reorder; PermuteArguments(MethodType type, MethodHandle target, int[] reorder) { super(type); this.target = target; this.reorder = reorder; } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { final RandomOrderStackFrameReader reader = new RandomOrderStackFrameReader(); reader.attach(emulatedStackFrame); final EmulatedStackFrame calleeFrame = EmulatedStackFrame.create(target.type()); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(calleeFrame); final Class [] ptypes = emulatedStackFrame.getMethodType().parameterArray(); for (int i = 0; i < reorder.length; ++i) { final int readerIndex = reorder[i]; reader.moveTo(readerIndex); StackFrameAccessor.copyNext(reader, writer, ptypes[readerIndex]); } invokeFromTransform(target, calleeFrame); calleeFrame.copyReturnValueTo(emulatedStackFrame); } } /** Implements {@code MethodHandle.asVarargsCollector}. */ static class VarargsCollector extends Transformer { final MethodHandle target; private final Class arrayType; VarargsCollector(MethodHandle target) { super(target.type()); Class[] parameterTypes = target.type().ptypes(); if (!lastParameterTypeIsAnArray(parameterTypes)) { throw new IllegalArgumentException("target does not have array as last parameter"); } this.target = target; this.arrayType = parameterTypes[parameterTypes.length - 1]; } 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 MethodHandle asTypeUncached(MethodType newType) { // asType() behavior is specialized per: // // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/MethodHandle.html#asVarargsCollector(java.lang.Class) // // "The behavior of asType is also specialized for variable arity adapters, to maintain // the invariant that plain, inexact invoke is always equivalent to an asType call to // adjust the target type, followed by invokeExact. Therefore, a variable arity // adapter responds to an asType request by building a fixed arity collector, if and // only if the adapter and requested type differ either in arity or trailing argument // type. The resulting fixed arity collector has its type further adjusted // (if necessary) to the requested type by pairwise conversion, as if by another // application of asType." final MethodType currentType = type(); final MethodHandle currentFixedArity = asFixedArity(); if (currentType.parameterCount() == newType.parameterCount() && currentType .lastParameterType() .isAssignableFrom(newType.lastParameterType())) { return asTypeCache = currentFixedArity.asType(newType); } final int arrayLength = newType.parameterCount() - currentType.parameterCount() + 1; if (arrayLength < 0) { // arrayType is definitely array per VarargsCollector constructor. throwWrongMethodTypeException(currentType, newType); } MethodHandle collector = null; try { collector = currentFixedArity.asCollector(arrayType, arrayLength).asType(newType); } catch (IllegalArgumentException ex) { throwWrongMethodTypeException(currentType, newType); } return asTypeCache = collector; } @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. invokeFromTransform(target, 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. invokeExactFromTransform(target, targetFrame); // Copy return value to the caller's frame. targetFrame.copyReturnValueTo(callerFrame); } @Override public MethodHandle withVarargs(boolean makeVarargs) { return makeVarargs ? this : target; } 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 {@code MethodHandles.invoker} and {@code 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.from(type(), 1); } @Override public void transform(EmulatedStackFrame emulatedStackFrame) throws Throwable { // 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. if (isExactInvoker) { invokeExactFromTransform(target, targetFrame); } else { invokeFromTransform(target, targetFrame); } targetFrame.copyReturnValueTo(emulatedStackFrame); } /** * Checks whether two method types are compatible as an exact match. The exact match is * based on the erased form (all reference types treated as Object). * * @param callsiteType the MethodType associated with the invocation. * @param targetType the MethodType of the MethodHandle being invoked. * @return true if {@code callsiteType} and {@code targetType} are an exact match. */ private static boolean exactMatch(MethodType callsiteType, MethodType targetType) { final int parameterCount = callsiteType.parameterCount(); if (callsiteType.parameterCount() != targetType.parameterCount()) { return false; } for (int i = 0; i < parameterCount; ++i) { Class argumentType = callsiteType.parameterType(i); Class parameterType = targetType.parameterType(i); if (!exactMatch(argumentType, parameterType)) { return false; } } // Check return type, noting it's always okay to discard the return value. return callsiteType.returnType() == Void.TYPE || exactMatch(callsiteType.returnType(), targetType.returnType()); } /** * Checks whether two types are an exact match. The exact match is based on the erased types * so any two reference types match, but primitive types must be the same. * * @param lhs first class to compare. * @param rhs second class to compare. * @return true if both classes satisfy the exact match criteria. */ private static boolean exactMatch(Class lhs, Class rhs) { if (lhs.isPrimitive() || rhs.isPrimitive()) { return lhs == rhs; } return true; // Both types are references, compatibility is checked at the point of use. } } /** Implements {@code MethodHandle.asSpreader}. */ 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 component type of the array. */ private final Class componentType; /** * 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 leadingRange; private final Range trailingRange; Spreader(MethodHandle target, MethodType spreaderType, int spreadArgPos, int numArrayArgs) { super(spreaderType); this.target = target; arrayOffset = spreadArgPos; componentType = spreaderType.ptypes()[arrayOffset].getComponentType(); if (componentType == null) { throw new AssertionError("Argument " + spreadArgPos + " must be an array."); } this.numArrayArgs = numArrayArgs; // Copy all args except the spreader array. leadingRange = EmulatedStackFrame.Range.of(spreaderType, 0, arrayOffset); trailingRange = EmulatedStackFrame.Range.from(spreaderType, arrayOffset + 1); } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { // Get the array reference and check that its length is as expected. final Class arrayType = type().parameterType(arrayOffset); final Object arrayObj = callerFrame.getReference(arrayOffset, arrayType); // The incoming array may be null if the expected number of array arguments is zero. final int arrayLength = (numArrayArgs == 0 && arrayObj == null) ? 0 : Array.getLength(arrayObj); if (arrayLength != numArrayArgs) { throw new IllegalArgumentException( "Invalid array length " + arrayLength + " expected " + numArrayArgs); } // Create a new stack frame for the callee. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); // Copy ranges not affected by the spreading. callerFrame.copyRangeTo(targetFrame, leadingRange, 0, 0); if (componentType.isPrimitive()) { final int elementBytes = EmulatedStackFrame.getSize(componentType); final int spreadBytes = elementBytes * arrayLength; callerFrame.copyRangeTo(targetFrame, trailingRange, leadingRange.numReferences, leadingRange.numBytes + spreadBytes); } else { callerFrame.copyRangeTo(targetFrame, trailingRange, leadingRange.numReferences + numArrayArgs, leadingRange.numBytes); } if (arrayLength != 0) { StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, arrayOffset, leadingRange.numReferences, leadingRange.numBytes); spreadArray(arrayType, arrayObj, writer); } invokeExactFromTransform(target, targetFrame); targetFrame.copyReturnValueTo(callerFrame); } private void spreadArray(Class arrayType, Object arrayObj, StackFrameWriter writer) { final Class componentType = arrayType.getComponentType(); switch (Wrapper.basicTypeChar(componentType)) { case 'L': { final Object[] array = (Object[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextReference(array[i], componentType); } break; } case 'I': { final int[] array = (int[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextInt(array[i]); } break; } case 'J': { final long[] array = (long[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextLong(array[i]); } break; } case 'B': { final byte[] array = (byte[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextByte(array[i]); } break; } case 'S': { final short[] array = (short[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextShort(array[i]); } break; } case 'C': { final char[] array = (char[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextChar(array[i]); } break; } case 'Z': { final boolean[] array = (boolean[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextBoolean(array[i]); } break; } case 'F': { final float[] array = (float[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextFloat(array[i]); } break; } case 'D': { final double[] array = (double[]) arrayObj; for (int i = 0; i < array.length; ++i) { writer.putNextDouble(array[i]); } break; } } } } /** Implements {@code MethodHandle.asCollector}. */ static class Collector extends Transformer { private final MethodHandle target; /** * The array start is the position in the target outputs of the array collecting arguments * and the position in the source inputs where collection starts. */ private final int arrayOffset; /** * The array length is the number of arguments to be collected. */ private final int arrayLength; /** The type of the array. */ private final Class arrayType; /** * Range of arguments to copy verbatim from the start of the input frame. */ private final Range leadingRange; /** * Range of arguments to copy verbatim from the end of the input frame. */ private final Range trailingRange; Collector(MethodHandle delegate, Class arrayType, int start, int length) { super(delegate.type().asCollectorType(arrayType, start, length)); this.target = delegate; this.arrayOffset = start; this.arrayLength = length; this.arrayType = arrayType; // Build ranges of arguments to be copied. leadingRange = EmulatedStackFrame.Range.of(type(), 0, arrayOffset); trailingRange = EmulatedStackFrame.Range.from(type(), arrayOffset + arrayLength); } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { // Create a new stack frame for the callee. final EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); // Copy arguments before the collector array. callerFrame.copyRangeTo(targetFrame, leadingRange, 0, 0); // Copy arguments after the collector array. callerFrame.copyRangeTo(targetFrame, trailingRange, leadingRange.numReferences + 1, leadingRange.numBytes); // Collect arguments between arrayOffset and arrayOffset + arrayLength. final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, arrayOffset, leadingRange.numReferences, leadingRange.numBytes); final StackFrameReader reader = new StackFrameReader(); reader.attach(callerFrame, arrayOffset, leadingRange.numReferences, leadingRange.numBytes); final char arrayTypeChar = Wrapper.basicTypeChar(arrayType.getComponentType()); 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 arrayComponentType = arrayType.getComponentType(); Object[] arr = (Object[]) Array.newInstance(arrayComponentType, arrayLength); for (int i = 0; i < arrayLength; ++i) { arr[i] = reader.nextReference(arrayComponentType); } writer.putNextReference(arr, targetType); break; } case 'I': { int[] array = new int[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextInt(); } writer.putNextReference(array, int[].class); break; } case 'J': { long[] array = new long[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextLong(); } writer.putNextReference(array, long[].class); break; } case 'B': { byte[] array = new byte[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextByte(); } writer.putNextReference(array, byte[].class); break; } case 'S': { short[] array = new short[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextShort(); } writer.putNextReference(array, short[].class); break; } case 'C': { char[] array = new char[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextChar(); } writer.putNextReference(array, char[].class); break; } case 'Z': { boolean[] array = new boolean[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextBoolean(); } writer.putNextReference(array, boolean[].class); break; } case 'F': { float[] array = new float[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextFloat(); } writer.putNextReference(array, float[].class); break; } case 'D': { double[] array = new double[arrayLength]; for (int i = 0; i < arrayLength; ++i) { array[i] = reader.nextDouble(); } writer.putNextReference(array, double[].class); break; } } invokeFromTransform(target, targetFrame); targetFrame.copyReturnValueTo(callerFrame); } } /** Implements {@code 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) { MethodHandle filter = filters[i]; if (filter != null) { filterArgs[i] = filter.type().parameterType(0); } else { // null filters are treated as identity functions. filterArgs[i] = target.type().parameterType(i); } } 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]); invokeFromTransform(filter, 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); } } invokeFromTransform(target, transformedFrame); transformedFrame.copyReturnValueTo(stackFrame); } } /** Implements {@code 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(); collectorRange = Range.of(type(), pos, pos + numFilterArgs); range1 = Range.of(type(), 0, pos); this.range2 = Range.from(type(), pos + numFilterArgs); // 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); invokeFromTransform(collector, 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()[pos]); } stackFrame.copyRangeTo( targetFrame, range2, range1.numReferences + referencesOffset, range1.numBytes + stackFrameOffset); invokeFromTransform(target, targetFrame); targetFrame.copyReturnValueTo(stackFrame); } } /** Implements {@code MethodHandles.foldArguments}. */ static class FoldArguments extends Transformer { private final MethodHandle target; private final MethodHandle combiner; private final int position; /** The range of arguments in our frame passed to the combiner. */ private final Range combinerArgs; /** The range of arguments in our frame copied to the start of the target frame. */ private final Range leadingArgs; /** The range of arguments in our frame copied to the end of the target frame. */ private final Range trailingArgs; private final int referencesOffset; private final int stackFrameOffset; FoldArguments(MethodHandle target, int position, MethodHandle combiner) { super(deriveType(target, position, combiner)); this.target = target; this.combiner = combiner; this.position = position; this.combinerArgs = Range.of(type(), position, position + combiner.type().parameterCount()); this.leadingArgs = Range.of(type(), 0, position); this.trailingArgs = Range.from(type(), position); 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 { // combinerRType is a reference. stackFrameOffset = 0; referencesOffset = 1; } } @Override public void transform(EmulatedStackFrame stackFrame) throws Throwable { // First construct the combiner frame and invoke the combiner. EmulatedStackFrame combinerFrame = EmulatedStackFrame.create(combiner.type()); stackFrame.copyRangeTo(combinerFrame, combinerArgs, 0, 0); invokeExactFromTransform(combiner, combinerFrame); // Create the stack frame for the target and copy leading arguments to it. EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); stackFrame.copyRangeTo(targetFrame, leadingArgs, 0, 0); // If one of these offsets is not zero, we have to slot the return value from the // combiner into the target frame. if (referencesOffset != 0 || stackFrameOffset != 0) { final StackFrameReader reader = new StackFrameReader(); reader.attach(combinerFrame).makeReturnValueAccessor(); final StackFrameWriter writer = new StackFrameWriter(); writer.attach(targetFrame, position, leadingArgs.numReferences, leadingArgs.numBytes); copyNext(reader, writer, target.type().ptypes()[position]); } // Copy the arguments provided to the combiner to the tail of the target frame. stackFrame.copyRangeTo( targetFrame, trailingArgs, leadingArgs.numReferences + referencesOffset, leadingArgs.numBytes + stackFrameOffset); // Call the target and propagate return value. invokeExactFromTransform(target, targetFrame); targetFrame.copyReturnValueTo(stackFrame); } private static MethodType deriveType(MethodHandle target, int position, MethodHandle combiner) { if (combiner.type().rtype() == void.class) { return target.type(); } return target.type().dropParameterTypes(position, position + 1); } } /** Implements {@code 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]; final char typeChar = Wrapper.basicTypeChar(ptype); if (typeChar == 'L') { writer.putNextReference(values[i], ptype); referencesCopied++; } else { switch (typeChar) { case 'Z': writer.putNextBoolean((boolean) values[i]); break; case 'B': writer.putNextByte((byte) values[i]); break; case 'C': writer.putNextChar((char) values[i]); break; case 'S': writer.putNextShort((short) values[i]); break; case 'I': writer.putNextInt((int) values[i]); break; case 'J': writer.putNextLong((long) values[i]); break; case 'F': writer.putNextFloat((float) values[i]); break; case 'D': writer.putNextDouble((double) values[i]); break; } bytesCopied += EmulatedStackFrame.getSize(ptype); } } // Copy all remaining arguments. if (range2 != null) { stackFrame.copyRangeTo( calleeFrame, range2, range1.numReferences + referencesCopied, range1.numBytes + bytesCopied); } invokeFromTransform(target, calleeFrame); calleeFrame.copyReturnValueTo(stackFrame); } } /** Implements {@code MethodHandle.asType}. */ static class AsTypeAdapter extends Transformer { private final MethodHandle target; AsTypeAdapter(MethodHandle target, MethodType type) { super(type); this.target = target; } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { final EmulatedStackFrame targetFrame = EmulatedStackFrame.create(target.type()); final StackFrameReader reader = new StackFrameReader(); final StackFrameWriter writer = new StackFrameWriter(); // Adapt arguments reader.attach(callerFrame); writer.attach(targetFrame); adaptArguments(reader, writer); // Invoke target invokeFromTransform(target, targetFrame); if (callerFrame.getMethodType().rtype() != void.class) { // Adapt return value reader.attach(targetFrame).makeReturnValueAccessor(); writer.attach(callerFrame).makeReturnValueAccessor(); adaptReturnValue(reader, writer); } } private void adaptArguments(final StackFrameReader reader, final StackFrameWriter writer) { final Class[] fromTypes = type().ptypes(); final Class[] toTypes = target.type().ptypes(); for (int i = 0; i < fromTypes.length; ++i) { adaptArgument(reader, fromTypes[i], writer, toTypes[i]); } } private void adaptReturnValue( final StackFrameReader reader, final StackFrameWriter writer) { final Class fromType = target.type().rtype(); final Class toType = type().rtype(); adaptArgument(reader, fromType, writer, toType); } private void throwWrongMethodTypeException() throws WrongMethodTypeException { throw new WrongMethodTypeException( "Cannot convert from " + type() + " to " + target.type()); } private static void throwClassCastException(Class from, Class to) throws ClassCastException { throw new ClassCastException("Cannot cast from " + from + " to " + to); } private void writePrimitiveByteAs(final StackFrameWriter writer, char baseType, byte value) throws WrongMethodTypeException { switch (baseType) { case 'B': writer.putNextByte(value); return; case 'S': writer.putNextShort((short) value); return; case 'I': writer.putNextInt((int) value); return; case 'J': writer.putNextLong((long) value); return; case 'F': writer.putNextFloat((float) value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveShortAs( final StackFrameWriter writer, char baseType, short value) throws WrongMethodTypeException { switch (baseType) { case 'S': writer.putNextShort(value); return; case 'I': writer.putNextInt((int) value); return; case 'J': writer.putNextLong((long) value); return; case 'F': writer.putNextFloat((float) value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveCharAs(final StackFrameWriter writer, char baseType, char value) throws WrongMethodTypeException { switch (baseType) { case 'C': writer.putNextChar(value); return; case 'I': writer.putNextInt((int) value); return; case 'J': writer.putNextLong((long) value); return; case 'F': writer.putNextFloat((float) value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveIntAs(final StackFrameWriter writer, char baseType, int value) throws WrongMethodTypeException { switch (baseType) { case 'I': writer.putNextInt(value); return; case 'J': writer.putNextLong((long) value); return; case 'F': writer.putNextFloat((float) value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } throwWrongMethodTypeException(); } private void writePrimitiveLongAs(final StackFrameWriter writer, char baseType, long value) throws WrongMethodTypeException { switch (baseType) { case 'J': writer.putNextLong(value); return; case 'F': writer.putNextFloat((float) value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveFloatAs( final StackFrameWriter writer, char baseType, float value) throws WrongMethodTypeException { switch (baseType) { case 'F': writer.putNextFloat(value); return; case 'D': writer.putNextDouble((double) value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveDoubleAs( final StackFrameWriter writer, char baseType, double value) throws WrongMethodTypeException { switch (baseType) { case 'D': writer.putNextDouble(value); return; default: throwWrongMethodTypeException(); } } private void writePrimitiveVoidAs(final StackFrameWriter writer, char baseType) { switch (baseType) { case 'Z': writer.putNextBoolean(false); return; case 'B': writer.putNextByte((byte) 0); return; case 'S': writer.putNextShort((short) 0); return; case 'C': writer.putNextChar((char) 0); return; case 'I': writer.putNextInt(0); return; case 'J': writer.putNextLong(0L); return; case 'F': writer.putNextFloat(0.0f); return; case 'D': writer.putNextDouble(0.0); return; default: throwWrongMethodTypeException(); } } private static Class getBoxedPrimitiveClass(char baseType) { switch (baseType) { case 'Z': return Boolean.class; case 'B': return Byte.class; case 'S': return Short.class; case 'C': return Character.class; case 'I': return Integer.class; case 'J': return Long.class; case 'F': return Float.class; case 'D': return Double.class; default: return null; } } private void adaptArgument( final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { if (from.equals(to)) { StackFrameAccessor.copyNext(reader, writer, from); return; } if (to.isPrimitive()) { if (from.isPrimitive()) { final char fromBaseType = Wrapper.basicTypeChar(from); final char toBaseType = Wrapper.basicTypeChar(to); switch (fromBaseType) { case 'B': writePrimitiveByteAs(writer, toBaseType, reader.nextByte()); return; case 'S': writePrimitiveShortAs(writer, toBaseType, reader.nextShort()); return; case 'C': writePrimitiveCharAs(writer, toBaseType, reader.nextChar()); return; case 'I': writePrimitiveIntAs(writer, toBaseType, reader.nextInt()); return; case 'J': writePrimitiveLongAs(writer, toBaseType, reader.nextLong()); return; case 'F': writePrimitiveFloatAs(writer, toBaseType, reader.nextFloat()); return; case 'V': writePrimitiveVoidAs(writer, toBaseType); return; default: throwWrongMethodTypeException(); } } else { final Object value = reader.nextReference(Object.class); if (to == void.class) { return; } if (value == null) { throw new NullPointerException(); } if (!Wrapper.isWrapperType(value.getClass())) { throwClassCastException(value.getClass(), to); } final Wrapper fromWrapper = Wrapper.forWrapperType(value.getClass()); final Wrapper toWrapper = Wrapper.forPrimitiveType(to); if (!toWrapper.isConvertibleFrom(fromWrapper)) { throwClassCastException(from, to); } final char toChar = toWrapper.basicTypeChar(); switch (fromWrapper.basicTypeChar()) { case 'Z': writer.putNextBoolean(((Boolean) value).booleanValue()); return; case 'B': writePrimitiveByteAs(writer, toChar, ((Byte) value).byteValue()); return; case 'S': writePrimitiveShortAs(writer, toChar, ((Short) value).shortValue()); return; case 'C': writePrimitiveCharAs(writer, toChar, ((Character) value).charValue()); return; case 'I': writePrimitiveIntAs(writer, toChar, ((Integer) value).intValue()); return; case 'J': writePrimitiveLongAs(writer, toChar, ((Long) value).longValue()); return; case 'F': writePrimitiveFloatAs(writer, toChar, ((Float) value).floatValue()); return; case 'D': writePrimitiveDoubleAs(writer, toChar, ((Double) value).doubleValue()); return; default: throw new IllegalStateException(); } } } else { if (from.isPrimitive()) { // Boxing conversion final char fromBaseType = Wrapper.basicTypeChar(from); final Class fromBoxed = getBoxedPrimitiveClass(fromBaseType); // 'to' maybe a super class of the boxed `from` type, e.g. Number. if (fromBoxed != null && !to.isAssignableFrom(fromBoxed)) { throwWrongMethodTypeException(); } Object boxed; switch (fromBaseType) { case 'Z': boxed = Boolean.valueOf(reader.nextBoolean()); break; case 'B': boxed = Byte.valueOf(reader.nextByte()); break; case 'S': boxed = Short.valueOf(reader.nextShort()); break; case 'C': boxed = Character.valueOf(reader.nextChar()); break; case 'I': boxed = Integer.valueOf(reader.nextInt()); break; case 'J': boxed = Long.valueOf(reader.nextLong()); break; case 'F': boxed = Float.valueOf(reader.nextFloat()); break; case 'D': boxed = Double.valueOf(reader.nextDouble()); break; case 'V': boxed = null; break; default: throw new IllegalStateException(); } writer.putNextReference(boxed, to); return; } else { // Cast Object value = reader.nextReference(Object.class); if (value != null && !to.isAssignableFrom(value.getClass())) { throwClassCastException(value.getClass(), to); } writer.putNextReference(value, to); } } } } /** Implements {@code MethodHandles.explicitCastArguments}. */ static class ExplicitCastArguments extends Transformer { private final MethodHandle target; 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); invokeFromTransform(target, 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); } @SuppressWarnings("unchecked") private static void badCast(final Class from, final Class to) { throw new ClassCastException("Cannot cast " + from.getName() + " to " + to.getName()); } /** * 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) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (byte) reader.nextByte(); case 'C': return (byte) reader.nextChar(); case 'S': return (byte) reader.nextShort(); case 'I': return (byte) reader.nextInt(); case 'J': return (byte) reader.nextLong(); case 'F': return (byte) reader.nextFloat(); case 'D': return (byte) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? (byte) 1 : (byte) 0; default: throwUnexpectedType(from); return 0; } } private static char readPrimitiveAsChar( final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (char) reader.nextByte(); case 'C': return (char) reader.nextChar(); case 'S': return (char) reader.nextShort(); case 'I': return (char) reader.nextInt(); case 'J': return (char) reader.nextLong(); case 'F': return (char) reader.nextFloat(); case 'D': return (char) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? (char) 1 : (char) 0; default: throwUnexpectedType(from); return 0; } } private static short readPrimitiveAsShort( final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (short) reader.nextByte(); case 'C': return (short) reader.nextChar(); case 'S': return (short) reader.nextShort(); case 'I': return (short) reader.nextInt(); case 'J': return (short) reader.nextLong(); case 'F': return (short) reader.nextFloat(); case 'D': return (short) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? (short) 1 : (short) 0; default: throwUnexpectedType(from); return 0; } } private static int readPrimitiveAsInt(final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (int) reader.nextByte(); case 'C': return (int) reader.nextChar(); case 'S': return (int) reader.nextShort(); case 'I': return (int) reader.nextInt(); case 'J': return (int) reader.nextLong(); case 'F': return (int) reader.nextFloat(); case 'D': return (int) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? 1 : 0; default: throwUnexpectedType(from); return 0; } } private static long readPrimitiveAsLong( final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (long) reader.nextByte(); case 'C': return (long) reader.nextChar(); case 'S': return (long) reader.nextShort(); case 'I': return (long) reader.nextInt(); case 'J': return (long) reader.nextLong(); case 'F': return (long) reader.nextFloat(); case 'D': return (long) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? 1L : 0L; default: throwUnexpectedType(from); return 0; } } private static float readPrimitiveAsFloat( final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (float) reader.nextByte(); case 'C': return (float) reader.nextChar(); case 'S': return (float) reader.nextShort(); case 'I': return (float) reader.nextInt(); case 'J': return (float) reader.nextLong(); case 'F': return (float) reader.nextFloat(); case 'D': return (float) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? 1.0f : 0.0f; default: throwUnexpectedType(from); return 0; } } private static double readPrimitiveAsDouble( final StackFrameReader reader, final Class from) { switch (Wrapper.basicTypeChar(from)) { case 'B': return (double) reader.nextByte(); case 'C': return (double) reader.nextChar(); case 'S': return (double) reader.nextShort(); case 'I': return (double) reader.nextInt(); case 'J': return (double) reader.nextLong(); case 'F': return (double) reader.nextFloat(); case 'D': return (double) reader.nextDouble(); case 'Z': return reader.nextBoolean() ? 1.0 : 0.0; default: throwUnexpectedType(from); return 0; } } private static void explicitCastPrimitives( final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { switch (Wrapper.basicTypeChar(to)) { case 'B': writer.putNextByte(readPrimitiveAsByte(reader, from)); break; case 'C': writer.putNextChar(readPrimitiveAsChar(reader, from)); break; case 'S': writer.putNextShort(readPrimitiveAsShort(reader, from)); break; case 'I': writer.putNextInt(readPrimitiveAsInt(reader, from)); break; case 'J': writer.putNextLong(readPrimitiveAsLong(reader, from)); break; case 'F': writer.putNextFloat(readPrimitiveAsFloat(reader, from)); break; case 'D': writer.putNextDouble(readPrimitiveAsDouble(reader, from)); break; case 'Z': writer.putNextBoolean(toBoolean(readPrimitiveAsByte(reader, from))); break; default: throwUnexpectedType(to); break; } } private static void unboxNull(final StackFrameWriter writer, final Class to) { switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean(false); break; case 'B': writer.putNextByte((byte) 0); break; case 'C': writer.putNextChar((char) 0); break; case 'S': writer.putNextShort((short) 0); break; case 'I': writer.putNextInt((int) 0); break; case 'J': writer.putNextLong((long) 0); break; case 'F': writer.putNextFloat((float) 0); break; case 'D': writer.putNextDouble((double) 0); break; default: throwUnexpectedType(to); break; } } private static void unboxNonNull( final Object ref, final StackFrameWriter writer, final Class to) { final Class from = ref.getClass(); final Class unboxedFromType = Wrapper.asPrimitiveType(from); switch (Wrapper.basicTypeChar(unboxedFromType)) { case 'Z': boolean z = (boolean) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean(z); break; case 'B': writer.putNextByte(z ? (byte) 1 : (byte) 0); break; case 'S': writer.putNextShort(z ? (short) 1 : (short) 0); break; case 'C': writer.putNextChar(z ? (char) 1 : (char) 0); break; case 'I': writer.putNextInt(z ? 1 : 0); break; case 'J': writer.putNextLong(z ? 1l : 0l); break; case 'F': writer.putNextFloat(z ? 1.0f : 0.0f); break; case 'D': writer.putNextDouble(z ? 1.0 : 0.0); break; default: badCast(from, to); break; } break; case 'B': byte b = (byte) ref; switch (Wrapper.basicTypeChar(to)) { case 'B': writer.putNextByte(b); break; case 'Z': writer.putNextBoolean(toBoolean(b)); break; case 'S': writer.putNextShort((short) b); break; case 'C': writer.putNextChar((char) b); break; case 'I': writer.putNextInt((int) b); break; case 'J': writer.putNextLong((long) b); break; case 'F': writer.putNextFloat((float) b); break; case 'D': writer.putNextDouble((double) b); break; default: badCast(from, to); break; } break; case 'S': short s = (short) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean((s & 1) == 1); break; case 'B': writer.putNextByte((byte) s); break; case 'S': writer.putNextShort(s); break; case 'C': writer.putNextChar((char) s); break; case 'I': writer.putNextInt((int) s); break; case 'J': writer.putNextLong((long) s); break; case 'F': writer.putNextFloat((float) s); break; case 'D': writer.putNextDouble((double) s); break; default: badCast(from, to); break; } break; case 'C': char c = (char) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean((c & (char) 1) == (char) 1); break; case 'B': writer.putNextByte((byte) c); break; case 'S': writer.putNextShort((short) c); break; case 'C': writer.putNextChar(c); break; case 'I': writer.putNextInt((int) c); break; case 'J': writer.putNextLong((long) c); break; case 'F': writer.putNextFloat((float) c); break; case 'D': writer.putNextDouble((double) c); break; default: badCast(from, to); break; } break; case 'I': int i = (int) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean((i & 1) == 1); break; case 'B': writer.putNextByte((byte) i); break; case 'S': writer.putNextShort((short) i); break; case 'C': writer.putNextChar((char) i); break; case 'I': writer.putNextInt(i); break; case 'J': writer.putNextLong((long) i); break; case 'F': writer.putNextFloat((float) i); break; case 'D': writer.putNextDouble((double) i); break; default: badCast(from, to); } break; case 'J': long j = (long) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean((j & 1l) == 1l); break; case 'B': writer.putNextByte((byte) j); break; case 'S': writer.putNextShort((short) j); break; case 'C': writer.putNextChar((char) j); break; case 'I': writer.putNextInt((int) j); break; case 'J': writer.putNextLong(j); break; case 'F': writer.putNextFloat((float) j); break; case 'D': writer.putNextDouble((double) j); break; default: badCast(from, to); break; } break; case 'F': float f = (float) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean(((byte) f & 1) != 0); break; case 'B': writer.putNextByte((byte) f); break; case 'S': writer.putNextShort((short) f); break; case 'C': writer.putNextChar((char) f); break; case 'I': writer.putNextInt((int) f); break; case 'J': writer.putNextLong((long) f); break; case 'F': writer.putNextFloat(f); break; case 'D': writer.putNextDouble((double) f); break; default: badCast(from, to); break; } break; case 'D': double d = (double) ref; switch (Wrapper.basicTypeChar(to)) { case 'Z': writer.putNextBoolean(((byte) d & 1) != 0); break; case 'B': writer.putNextByte((byte) d); break; case 'S': writer.putNextShort((short) d); break; case 'C': writer.putNextChar((char) d); break; case 'I': writer.putNextInt((int) d); break; case 'J': writer.putNextLong((long) d); break; case 'F': writer.putNextFloat((float) d); break; case 'D': writer.putNextDouble(d); break; default: badCast(from, to); break; } break; default: badCast(from, to); break; } } private static void unbox( final Object ref, final StackFrameWriter writer, final Class to) { if (ref == null) { unboxNull(writer, to); } else { unboxNonNull(ref, writer, to); } } private static void box( final StackFrameReader reader, final Class from, final StackFrameWriter writer, final Class to) { Object boxed = null; switch (Wrapper.basicTypeChar(from)) { case 'Z': boxed = Boolean.valueOf(reader.nextBoolean()); break; case 'B': boxed = Byte.valueOf(reader.nextByte()); break; case 'C': boxed = Character.valueOf(reader.nextChar()); break; case 'S': boxed = Short.valueOf(reader.nextShort()); break; case 'I': boxed = Integer.valueOf(reader.nextInt()); break; case 'J': boxed = Long.valueOf(reader.nextLong()); break; case 'F': boxed = Float.valueOf(reader.nextFloat()); break; case 'D': boxed = Double.valueOf(reader.nextDouble()); break; default: throwUnexpectedType(from); break; } 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); return; } if (from.isPrimitive()) { if (to.isPrimitive()) { // |from| and |to| are primitive types. explicitCastPrimitives(reader, from, writer, to); } else { // |from| is a primitive type, |to| is a reference type. box(reader, from, writer, to); } } else { // |from| is a reference type. Object ref = reader.nextReference(from); if (to.isPrimitive()) { // |from| is a reference type, |to| is a primitive type, unbox(ref, writer, to); } else if (to.isInterface()) { // Pass from without a cast according to description for // {@link java.lang.invoke.MethodHandles#explicitCastArguments()}. writer.putNextReference(ref, to); } else { // |to| and from |from| are reference types, perform class cast check. writer.putNextReference(to.cast(ref), to); } } } } /** Implements {@code MethodHandles.loop}. */ static class Loop extends Transformer { /** Loop variable initialization methods. */ final MethodHandle[] inits; /** Loop variable step methods. */ final MethodHandle[] steps; /** Loop variable predicate methods. */ final MethodHandle[] preds; /** Loop return value calculating methods. */ final MethodHandle[] finis; /** Synthetic method type for frame used to hold loop variables. */ final MethodType loopVarsType; /** Range of loop variables in the frame used for loop variables. */ final Range loopVarsRange; /** Range of suffix variables in the caller frame. */ final Range suffixRange; public Loop(Class loopReturnType, List> commonSuffix, MethodHandle[] finit, MethodHandle[] fstep, MethodHandle[] fpred, MethodHandle[] ffini) { super(MethodType.methodType(loopReturnType, commonSuffix)); inits = finit; steps = fstep; preds = fpred; finis = ffini; loopVarsType = deduceLoopVarsType(finit); loopVarsRange = EmulatedStackFrame.Range.all(loopVarsType); suffixRange = EmulatedStackFrame.Range.all(type()); } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { final EmulatedStackFrame loopVarsFrame = EmulatedStackFrame.create(loopVarsType); final StackFrameWriter loopVarsWriter = new StackFrameWriter(); init(callerFrame, loopVarsFrame, loopVarsWriter); for (;;) { loopVarsWriter.attach(loopVarsFrame); for (int i = 0; i < steps.length; ++i) { // Future optimization opportunity: there is a good deal of StackFrame // allocation here, one is allocated per MH invocation. Consider caching // frames and passing the cache on the stack. doStep(steps[i], callerFrame, loopVarsFrame, loopVarsWriter); boolean keepGoing = doPredicate(preds[i], callerFrame, loopVarsFrame); if (!keepGoing) { doFinish(finis[i], callerFrame, loopVarsFrame); return; } } } } private static MethodType deduceLoopVarsType(final MethodHandle[] inits) { List> loopVarTypes = new ArrayList(inits.length); for (MethodHandle init : inits) { Class returnType = init.type().returnType(); if (returnType != void.class) { loopVarTypes.add(returnType); } } return MethodType.methodType(void.class, loopVarTypes); } private void init(final EmulatedStackFrame callerFrame, final EmulatedStackFrame loopVarsFrame, final StackFrameWriter loopVarsWriter) throws Throwable { loopVarsWriter.attach(loopVarsFrame); for (MethodHandle init : inits) { EmulatedStackFrame initFrame = EmulatedStackFrame.create(init.type()); callerFrame.copyRangeTo(initFrame, suffixRange, 0, 0); invokeExactFromTransform(init, initFrame); final Class loopVarType = init.type().returnType(); if (loopVarType != void.class) { StackFrameReader initReader = new StackFrameReader(); initReader.attach(initFrame).makeReturnValueAccessor(); copyNext(initReader, loopVarsWriter, loopVarType); } } } /** * Creates a frame for invoking a method of specified type. * * The frame arguments are the loop variables followed by the arguments provided to the * loop MethodHandle. * * @param mt the type of the method to be invoked. * @param callerFrame the frame invoking the loop MethodHandle. * @param loopVarsFrame the frame holding loop variables. * @return an EmulatedStackFrame initialized with the required arguments. */ private EmulatedStackFrame prepareFrame(final MethodType mt, final EmulatedStackFrame callerFrame, final EmulatedStackFrame loopVarsFrame) { EmulatedStackFrame frame = EmulatedStackFrame.create(mt); // Copy loop variables. loopVarsFrame.copyRangeTo(frame, loopVarsRange, 0, 0); // Copy arguments provided in the loop invoke(). callerFrame.copyRangeTo(frame, suffixRange, loopVarsRange.numReferences, loopVarsRange.numBytes); return frame; } private void doStep(final MethodHandle step, final EmulatedStackFrame callerFrame, final EmulatedStackFrame loopVarsFrame, final StackFrameWriter loopVarsWriter) throws Throwable { final EmulatedStackFrame stepFrame = prepareFrame(step.type(), callerFrame, loopVarsFrame); invokeExactFromTransform(step, stepFrame); final Class loopVarType = step.type().returnType(); if (loopVarType != void.class) { final StackFrameReader stepReader = new StackFrameReader(); stepReader.attach(stepFrame).makeReturnValueAccessor(); copyNext(stepReader, loopVarsWriter, loopVarType); } } private boolean doPredicate(final MethodHandle pred, final EmulatedStackFrame callerFrame, final EmulatedStackFrame loopVarsFrame) throws Throwable { final EmulatedStackFrame predFrame = prepareFrame(pred.type(), callerFrame, loopVarsFrame); invokeExactFromTransform(pred, predFrame); final StackFrameReader predReader = new StackFrameReader(); predReader.attach(predFrame).makeReturnValueAccessor(); return predReader.nextBoolean(); } private void doFinish(final MethodHandle fini, final EmulatedStackFrame callerFrame, final EmulatedStackFrame loopVarsFrame) throws Throwable { final EmulatedStackFrame finiFrame = prepareFrame(fini.type(), callerFrame, loopVarsFrame); invokeExactFromTransform(fini, finiFrame); finiFrame.copyReturnValueTo(callerFrame); } } /** Implements {@code MethodHandles.tableSwitch}. */ static class TableSwitch extends Transformer { private final MethodHandle fallback; private final MethodHandle[] targets; TableSwitch(MethodType type, MethodHandle fallback, MethodHandle[] targets) { super(type); this.fallback = fallback; this.targets = targets; } @Override public void transform(EmulatedStackFrame callerFrame) throws Throwable { final MethodHandle selected = selectMethodHandle(callerFrame); invokeExactFromTransform(selected, callerFrame); } private MethodHandle selectMethodHandle(EmulatedStackFrame callerFrame) { StackFrameReader reader = new StackFrameReader(); reader.attach(callerFrame); final int index = reader.nextInt(); if (index >= 0 && index < targets.length) { return targets[index]; } else { return fallback; } } } }