3152 lines
128 KiB
Java
3152 lines
128 KiB
Java
/*
|
|
* 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<? extends Throwable> exceptionType;
|
|
|
|
AlwaysThrow(Class<?> nominalReturnType, Class<? extends Throwable> 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.
|
|
*
|
|
* <p>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<Class<?>> 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 <method-type:stack-frame> 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<Class<?>> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|