/* * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. * 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang.invoke; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import sun.invoke.empty.Empty; import sun.invoke.util.ValueConversions; import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; /** * Trusted implementation code for MethodHandle. * @author jrose */ /*non-public*/ abstract class MethodHandleImpl { /// Factory methods to create method handles: static void initStatics() { // Trigger selected static initializations. MemberName.Factory.INSTANCE.getClass(); } static MethodHandle makeArrayElementAccessor(Class arrayClass, boolean isSetter) { if (!arrayClass.isArray()) throw newIllegalArgumentException("not an array: "+arrayClass); MethodHandle accessor = ArrayAccessor.getAccessor(arrayClass, isSetter); MethodType srcType = accessor.type().erase(); MethodType lambdaType = srcType.invokerType(); Name[] names = arguments(1, lambdaType); Name[] args = Arrays.copyOfRange(names, 1, 1 + srcType.parameterCount()); names[names.length - 1] = new Name(accessor.asType(srcType), (Object[]) args); LambdaForm form = new LambdaForm("getElement", lambdaType.parameterCount(), names); MethodHandle mh = SimpleMethodHandle.make(srcType, form); if (ArrayAccessor.needCast(arrayClass)) { mh = mh.bindTo(arrayClass); } mh = mh.asType(ArrayAccessor.correctType(arrayClass, isSetter)); return mh; } static final class ArrayAccessor { /// Support for array element access static final HashMap, MethodHandle> GETTER_CACHE = new HashMap<>(); // TODO use it static final HashMap, MethodHandle> SETTER_CACHE = new HashMap<>(); // TODO use it static int getElementI(int[] a, int i) { return a[i]; } static long getElementJ(long[] a, int i) { return a[i]; } static float getElementF(float[] a, int i) { return a[i]; } static double getElementD(double[] a, int i) { return a[i]; } static boolean getElementZ(boolean[] a, int i) { return a[i]; } static byte getElementB(byte[] a, int i) { return a[i]; } static short getElementS(short[] a, int i) { return a[i]; } static char getElementC(char[] a, int i) { return a[i]; } static Object getElementL(Object[] a, int i) { return a[i]; } static void setElementI(int[] a, int i, int x) { a[i] = x; } static void setElementJ(long[] a, int i, long x) { a[i] = x; } static void setElementF(float[] a, int i, float x) { a[i] = x; } static void setElementD(double[] a, int i, double x) { a[i] = x; } static void setElementZ(boolean[] a, int i, boolean x) { a[i] = x; } static void setElementB(byte[] a, int i, byte x) { a[i] = x; } static void setElementS(short[] a, int i, short x) { a[i] = x; } static void setElementC(char[] a, int i, char x) { a[i] = x; } static void setElementL(Object[] a, int i, Object x) { a[i] = x; } static Object getElementL(Class arrayClass, Object[] a, int i) { arrayClass.cast(a); return a[i]; } static void setElementL(Class arrayClass, Object[] a, int i, Object x) { arrayClass.cast(a); a[i] = x; } // Weakly typed wrappers of Object[] accessors: static Object getElementL(Object a, int i) { return getElementL((Object[])a, i); } static void setElementL(Object a, int i, Object x) { setElementL((Object[]) a, i, x); } static Object getElementL(Object arrayClass, Object a, int i) { return getElementL((Class) arrayClass, (Object[])a, i); } static void setElementL(Object arrayClass, Object a, int i, Object x) { setElementL((Class) arrayClass, (Object[])a, i, x); } static boolean needCast(Class arrayClass) { Class elemClass = arrayClass.getComponentType(); return !elemClass.isPrimitive() && elemClass != Object.class; } static String name(Class arrayClass, boolean isSetter) { Class elemClass = arrayClass.getComponentType(); if (elemClass == null) throw new IllegalArgumentException(); return (!isSetter ? "getElement" : "setElement") + Wrapper.basicTypeChar(elemClass); } static final boolean USE_WEAKLY_TYPED_ARRAY_ACCESSORS = false; // FIXME: decide static MethodType type(Class arrayClass, boolean isSetter) { Class elemClass = arrayClass.getComponentType(); Class arrayArgClass = arrayClass; if (!elemClass.isPrimitive()) { arrayArgClass = Object[].class; if (USE_WEAKLY_TYPED_ARRAY_ACCESSORS) arrayArgClass = Object.class; } if (!needCast(arrayClass)) { return !isSetter ? MethodType.methodType(elemClass, arrayArgClass, int.class) : MethodType.methodType(void.class, arrayArgClass, int.class, elemClass); } else { Class classArgClass = Class.class; if (USE_WEAKLY_TYPED_ARRAY_ACCESSORS) classArgClass = Object.class; return !isSetter ? MethodType.methodType(Object.class, classArgClass, arrayArgClass, int.class) : MethodType.methodType(void.class, classArgClass, arrayArgClass, int.class, Object.class); } } static MethodType correctType(Class arrayClass, boolean isSetter) { Class elemClass = arrayClass.getComponentType(); return !isSetter ? MethodType.methodType(elemClass, arrayClass, int.class) : MethodType.methodType(void.class, arrayClass, int.class, elemClass); } static MethodHandle getAccessor(Class arrayClass, boolean isSetter) { String name = name(arrayClass, isSetter); MethodType type = type(arrayClass, isSetter); try { return IMPL_LOOKUP.findStatic(ArrayAccessor.class, name, type); } catch (ReflectiveOperationException ex) { throw uncaughtException(ex); } } } /** * Create a JVM-level adapter method handle to conform the given method * handle to the similar newType, using only pairwise argument conversions. * For each argument, convert incoming argument to the exact type needed. * The argument conversions allowed are casting, boxing and unboxing, * integral widening or narrowing, and floating point widening or narrowing. * @param srcType required call type * @param target original method handle * @param level which strength of conversion is allowed * @return an adapter to the original handle with the desired new type, * or the original target if the types are already identical * or null if the adaptation cannot be made */ static MethodHandle makePairwiseConvert(MethodHandle target, MethodType srcType, int level) { assert(level >= 0 && level <= 2); MethodType dstType = target.type(); assert(dstType.parameterCount() == target.type().parameterCount()); if (srcType == dstType) return target; // Calculate extra arguments (temporaries) required in the names array. // FIXME: Use an ArrayList. Some arguments require more than one conversion step. final int INARG_COUNT = srcType.parameterCount(); int conversions = 0; boolean[] needConv = new boolean[1+INARG_COUNT]; for (int i = 0; i <= INARG_COUNT; i++) { Class src = (i == INARG_COUNT) ? dstType.returnType() : srcType.parameterType(i); Class dst = (i == INARG_COUNT) ? srcType.returnType() : dstType.parameterType(i); if (!VerifyType.isNullConversion(src, dst) || level <= 1 && dst.isInterface() && !dst.isAssignableFrom(src)) { needConv[i] = true; conversions++; } } boolean retConv = needConv[INARG_COUNT]; final int IN_MH = 0; final int INARG_BASE = 1; final int INARG_LIMIT = INARG_BASE + INARG_COUNT; final int NAME_LIMIT = INARG_LIMIT + conversions + 1; final int RETURN_CONV = (!retConv ? -1 : NAME_LIMIT - 1); final int OUT_CALL = (!retConv ? NAME_LIMIT : RETURN_CONV) - 1; // Now build a LambdaForm. MethodType lambdaType = srcType.basicType().invokerType(); Name[] names = arguments(NAME_LIMIT - INARG_LIMIT, lambdaType); // Collect the arguments to the outgoing call, maybe with conversions: final int OUTARG_BASE = 0; // target MH is Name.function, name Name.arguments[0] Object[] outArgs = new Object[OUTARG_BASE + INARG_COUNT]; int nameCursor = INARG_LIMIT; for (int i = 0; i < INARG_COUNT; i++) { Class src = srcType.parameterType(i); Class dst = dstType.parameterType(i); if (!needConv[i]) { // do nothing: difference is trivial outArgs[OUTARG_BASE + i] = names[INARG_BASE + i]; continue; } // Tricky case analysis follows. MethodHandle fn = null; if (src.isPrimitive()) { if (dst.isPrimitive()) { fn = ValueConversions.convertPrimitive(src, dst); } else { Wrapper w = Wrapper.forPrimitiveType(src); MethodHandle boxMethod = ValueConversions.box(w); if (dst == w.wrapperType()) fn = boxMethod; else fn = boxMethod.asType(MethodType.methodType(dst, src)); } } else { if (dst.isPrimitive()) { // Caller has boxed a primitive. Unbox it for the target. Wrapper w = Wrapper.forPrimitiveType(dst); if (level == 0 || VerifyType.isNullConversion(src, w.wrapperType())) { fn = ValueConversions.unbox(dst); } else if (src == Object.class || !Wrapper.isWrapperType(src)) { // Examples: Object->int, Number->int, Comparable->int; Byte->int, Character->int // must include additional conversions // src must be examined at runtime, to detect Byte, Character, etc. MethodHandle unboxMethod = (level == 1 ? ValueConversions.unbox(dst) : ValueConversions.unboxCast(dst)); fn = unboxMethod; } else { // Example: Byte->int // Do this by reformulating the problem to Byte->byte. Class srcPrim = Wrapper.forWrapperType(src).primitiveType(); MethodHandle unbox = ValueConversions.unbox(srcPrim); // Compose the two conversions. FIXME: should make two Names for this job fn = unbox.asType(MethodType.methodType(dst, src)); } } else { // Simple reference conversion. // Note: Do not check for a class hierarchy relation // between src and dst. In all cases a 'null' argument // will pass the cast conversion. fn = ValueConversions.cast(dst); } } Name conv = new Name(fn, names[INARG_BASE + i]); assert(names[nameCursor] == null); names[nameCursor++] = conv; assert(outArgs[OUTARG_BASE + i] == null); outArgs[OUTARG_BASE + i] = conv; } // Build argument array for the call. assert(nameCursor == OUT_CALL); names[OUT_CALL] = new Name(target, outArgs); if (RETURN_CONV < 0) { assert(OUT_CALL == names.length-1); } else { Class needReturn = srcType.returnType(); Class haveReturn = dstType.returnType(); MethodHandle fn; Object[] arg = { names[OUT_CALL] }; if (haveReturn == void.class) { // synthesize a zero value for the given void Object zero = Wrapper.forBasicType(needReturn).zero(); fn = MethodHandles.constant(needReturn, zero); arg = new Object[0]; // don't pass names[OUT_CALL] to conversion } else { MethodHandle identity = MethodHandles.identity(needReturn); MethodType needConversion = identity.type().changeParameterType(0, haveReturn); fn = makePairwiseConvert(identity, needConversion, level); } assert(names[RETURN_CONV] == null); names[RETURN_CONV] = new Name(fn, arg); assert(RETURN_CONV == names.length-1); } LambdaForm form = new LambdaForm("convert", lambdaType.parameterCount(), names); return SimpleMethodHandle.make(srcType, form); } static MethodHandle makeReferenceIdentity(Class refType) { MethodType lambdaType = MethodType.genericMethodType(1).invokerType(); Name[] names = arguments(1, lambdaType); names[names.length - 1] = new Name(ValueConversions.identity(), names[1]); LambdaForm form = new LambdaForm("identity", lambdaType.parameterCount(), names); return SimpleMethodHandle.make(MethodType.methodType(refType, refType), form); } static MethodHandle makeVarargsCollector(MethodHandle target, Class arrayType) { MethodType type = target.type(); int last = type.parameterCount() - 1; if (type.parameterType(last) != arrayType) target = target.asType(type.changeParameterType(last, arrayType)); target = target.asFixedArity(); // make sure this attribute is turned off return new AsVarargsCollector(target, target.type(), arrayType); } static class AsVarargsCollector extends MethodHandle { private final MethodHandle target; private final Class arrayType; private /*@Stable*/ MethodHandle asCollectorCache; AsVarargsCollector(MethodHandle target, MethodType type, Class arrayType) { super(type, reinvokerForm(target)); this.target = target; this.arrayType = arrayType; this.asCollectorCache = target.asCollector(arrayType, 0); } @Override MethodHandle reinvokerTarget() { return target; } @Override public boolean isVarargsCollector() { return true; } @Override public MethodHandle asFixedArity() { return target; } @Override public MethodHandle asTypeUncached(MethodType newType) { MethodType type = this.type(); int collectArg = type.parameterCount() - 1; int newArity = newType.parameterCount(); if (newArity == collectArg+1 && type.parameterType(collectArg).isAssignableFrom(newType.parameterType(collectArg))) { // if arity and trailing parameter are compatible, do normal thing return asTypeCache = asFixedArity().asType(newType); } // check cache MethodHandle acc = asCollectorCache; if (acc != null && acc.type().parameterCount() == newArity) return asTypeCache = acc.asType(newType); // build and cache a collector int arrayLength = newArity - collectArg; MethodHandle collector; try { collector = asFixedArity().asCollector(arrayType, arrayLength); assert(collector.type().parameterCount() == newArity) : "newArity="+newArity+" but collector="+collector; } catch (IllegalArgumentException ex) { throw new WrongMethodTypeException("cannot build collector", ex); } asCollectorCache = collector; return asTypeCache = collector.asType(newType); } @Override MethodHandle setVarargs(MemberName member) { if (member.isVarargs()) return this; return asFixedArity(); } @Override MethodHandle viewAsType(MethodType newType) { if (newType.lastParameterType() != type().lastParameterType()) throw new InternalError(); MethodHandle newTarget = asFixedArity().viewAsType(newType); // put back the varargs bit: return new AsVarargsCollector(newTarget, newType, arrayType); } @Override MemberName internalMemberName() { return asFixedArity().internalMemberName(); } @Override Class internalCallerClass() { return asFixedArity().internalCallerClass(); } /*non-public*/ @Override boolean isInvokeSpecial() { return asFixedArity().isInvokeSpecial(); } @Override MethodHandle bindArgument(int pos, char basicType, Object value) { return asFixedArity().bindArgument(pos, basicType, value); } @Override MethodHandle bindReceiver(Object receiver) { return asFixedArity().bindReceiver(receiver); } @Override MethodHandle dropArguments(MethodType srcType, int pos, int drops) { return asFixedArity().dropArguments(srcType, pos, drops); } @Override MethodHandle permuteArguments(MethodType newType, int[] reorder) { return asFixedArity().permuteArguments(newType, reorder); } } /** Factory method: Spread selected argument. */ static MethodHandle makeSpreadArguments(MethodHandle target, Class spreadArgType, int spreadArgPos, int spreadArgCount) { MethodType targetType = target.type(); for (int i = 0; i < spreadArgCount; i++) { Class arg = VerifyType.spreadArgElementType(spreadArgType, i); if (arg == null) arg = Object.class; targetType = targetType.changeParameterType(spreadArgPos + i, arg); } target = target.asType(targetType); MethodType srcType = targetType .replaceParameterTypes(spreadArgPos, spreadArgPos + spreadArgCount, spreadArgType); // Now build a LambdaForm. MethodType lambdaType = srcType.invokerType(); Name[] names = arguments(spreadArgCount + 2, lambdaType); int nameCursor = lambdaType.parameterCount(); int[] indexes = new int[targetType.parameterCount()]; for (int i = 0, argIndex = 1; i < targetType.parameterCount() + 1; i++, argIndex++) { Class src = lambdaType.parameterType(i); if (i == spreadArgPos) { // Spread the array. MethodHandle aload = MethodHandles.arrayElementGetter(spreadArgType); Name array = names[argIndex]; names[nameCursor++] = new Name(Lazy.NF_checkSpreadArgument, array, spreadArgCount); for (int j = 0; j < spreadArgCount; i++, j++) { indexes[i] = nameCursor; names[nameCursor++] = new Name(aload, array, j); } } else if (i < indexes.length) { indexes[i] = argIndex; } } assert(nameCursor == names.length-1); // leave room for the final call // Build argument array for the call. Name[] targetArgs = new Name[targetType.parameterCount()]; for (int i = 0; i < targetType.parameterCount(); i++) { int idx = indexes[i]; targetArgs[i] = names[idx]; } names[names.length - 1] = new Name(target, (Object[]) targetArgs); LambdaForm form = new LambdaForm("spread", lambdaType.parameterCount(), names); return SimpleMethodHandle.make(srcType, form); } static void checkSpreadArgument(Object av, int n) { if (av == null) { if (n == 0) return; } else if (av instanceof Object[]) { int len = ((Object[])av).length; if (len == n) return; } else { int len = java.lang.reflect.Array.getLength(av); if (len == n) return; } // fall through to error: throw newIllegalArgumentException("array is not of length "+n); } /** * Pre-initialized NamedFunctions for bootstrapping purposes. * Factored in an inner class to delay initialization until first usage. */ private static class Lazy { static final NamedFunction NF_checkSpreadArgument; static { try { NF_checkSpreadArgument = new NamedFunction(MethodHandleImpl.class .getDeclaredMethod("checkSpreadArgument", Object.class, int.class)); NF_checkSpreadArgument.resolve(); } catch (ReflectiveOperationException ex) { throw newInternalError(ex); } } } /** Factory method: Collect or filter selected argument(s). */ static MethodHandle makeCollectArguments(MethodHandle target, MethodHandle collector, int collectArgPos, boolean retainOriginalArgs) { MethodType targetType = target.type(); // (a..., c, [b...])=>r MethodType collectorType = collector.type(); // (b...)=>c int collectArgCount = collectorType.parameterCount(); Class collectValType = collectorType.returnType(); int collectValCount = (collectValType == void.class ? 0 : 1); MethodType srcType = targetType // (a..., [b...])=>r .dropParameterTypes(collectArgPos, collectArgPos+collectValCount); if (!retainOriginalArgs) { // (a..., b...)=>r srcType = srcType.insertParameterTypes(collectArgPos, collectorType.parameterList()); } // in arglist: [0: ...keep1 | cpos: collect... | cpos+cacount: keep2... ] // out arglist: [0: ...keep1 | cpos: collectVal? | cpos+cvcount: keep2... ] // out(retain): [0: ...keep1 | cpos: cV? coll... | cpos+cvc+cac: keep2... ] // Now build a LambdaForm. MethodType lambdaType = srcType.invokerType(); Name[] names = arguments(2, lambdaType); final int collectNamePos = names.length - 2; final int targetNamePos = names.length - 1; Name[] collectorArgs = Arrays.copyOfRange(names, 1 + collectArgPos, 1 + collectArgPos + collectArgCount); names[collectNamePos] = new Name(collector, (Object[]) collectorArgs); // Build argument array for the target. // Incoming LF args to copy are: [ (mh) headArgs collectArgs tailArgs ]. // Output argument array is [ headArgs (collectVal)? (collectArgs)? tailArgs ]. Name[] targetArgs = new Name[targetType.parameterCount()]; int inputArgPos = 1; // incoming LF args to copy to target int targetArgPos = 0; // fill pointer for targetArgs int chunk = collectArgPos; // |headArgs| System.arraycopy(names, inputArgPos, targetArgs, targetArgPos, chunk); inputArgPos += chunk; targetArgPos += chunk; if (collectValType != void.class) { targetArgs[targetArgPos++] = names[collectNamePos]; } chunk = collectArgCount; if (retainOriginalArgs) { System.arraycopy(names, inputArgPos, targetArgs, targetArgPos, chunk); targetArgPos += chunk; // optionally pass on the collected chunk } inputArgPos += chunk; chunk = targetArgs.length - targetArgPos; // all the rest System.arraycopy(names, inputArgPos, targetArgs, targetArgPos, chunk); assert(inputArgPos + chunk == collectNamePos); // use of rest of input args also names[targetNamePos] = new Name(target, (Object[]) targetArgs); LambdaForm form = new LambdaForm("collect", lambdaType.parameterCount(), names); return SimpleMethodHandle.make(srcType, form); } static MethodHandle selectAlternative(boolean testResult, MethodHandle target, MethodHandle fallback) { return testResult ? target : fallback; } static MethodHandle SELECT_ALTERNATIVE; static MethodHandle selectAlternative() { if (SELECT_ALTERNATIVE != null) return SELECT_ALTERNATIVE; try { SELECT_ALTERNATIVE = IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "selectAlternative", MethodType.methodType(MethodHandle.class, boolean.class, MethodHandle.class, MethodHandle.class)); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } return SELECT_ALTERNATIVE; } static MethodHandle makeGuardWithTest(MethodHandle test, MethodHandle target, MethodHandle fallback) { MethodType basicType = target.type().basicType(); MethodHandle invokeBasic = MethodHandles.basicInvoker(basicType); int arity = basicType.parameterCount(); int extraNames = 3; MethodType lambdaType = basicType.invokerType(); Name[] names = arguments(extraNames, lambdaType); Object[] testArgs = Arrays.copyOfRange(names, 1, 1 + arity, Object[].class); Object[] targetArgs = Arrays.copyOfRange(names, 0, 1 + arity, Object[].class); // call test names[arity + 1] = new Name(test, testArgs); // call selectAlternative Object[] selectArgs = { names[arity + 1], target, fallback }; names[arity + 2] = new Name(MethodHandleImpl.selectAlternative(), selectArgs); targetArgs[0] = names[arity + 2]; // call target or fallback names[arity + 3] = new Name(new NamedFunction(invokeBasic), targetArgs); LambdaForm form = new LambdaForm("guard", lambdaType.parameterCount(), names); return SimpleMethodHandle.make(target.type(), form); } private static class GuardWithCatch { private final MethodHandle target; private final Class exType; private final MethodHandle catcher; // FIXME: Build the control flow out of foldArguments. GuardWithCatch(MethodHandle target, Class exType, MethodHandle catcher) { this.target = target; this.exType = exType; this.catcher = catcher; } @LambdaForm.Hidden private Object invoke_V(Object... av) throws Throwable { try { return target.invokeExact(av); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, av); } } @LambdaForm.Hidden private Object invoke_L0() throws Throwable { try { return target.invokeExact(); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t); } } @LambdaForm.Hidden private Object invoke_L1(Object a0) throws Throwable { try { return target.invokeExact(a0); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0); } } @LambdaForm.Hidden private Object invoke_L2(Object a0, Object a1) throws Throwable { try { return target.invokeExact(a0, a1); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1); } } @LambdaForm.Hidden private Object invoke_L3(Object a0, Object a1, Object a2) throws Throwable { try { return target.invokeExact(a0, a1, a2); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2); } } @LambdaForm.Hidden private Object invoke_L4(Object a0, Object a1, Object a2, Object a3) throws Throwable { try { return target.invokeExact(a0, a1, a2, a3); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2, a3); } } @LambdaForm.Hidden private Object invoke_L5(Object a0, Object a1, Object a2, Object a3, Object a4) throws Throwable { try { return target.invokeExact(a0, a1, a2, a3, a4); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2, a3, a4); } } @LambdaForm.Hidden private Object invoke_L6(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5) throws Throwable { try { return target.invokeExact(a0, a1, a2, a3, a4, a5); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5); } } @LambdaForm.Hidden private Object invoke_L7(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) throws Throwable { try { return target.invokeExact(a0, a1, a2, a3, a4, a5, a6); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5, a6); } } @LambdaForm.Hidden private Object invoke_L8(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) throws Throwable { try { return target.invokeExact(a0, a1, a2, a3, a4, a5, a6, a7); } catch (Throwable t) { if (!exType.isInstance(t)) throw t; return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5, a6, a7); } } static MethodHandle[] makeInvokes() { ArrayList invokes = new ArrayList<>(); MethodHandles.Lookup lookup = IMPL_LOOKUP; for (;;) { int nargs = invokes.size(); String name = "invoke_L"+nargs; MethodHandle invoke = null; try { invoke = lookup.findVirtual(GuardWithCatch.class, name, MethodType.genericMethodType(nargs)); } catch (ReflectiveOperationException ex) { } if (invoke == null) break; invokes.add(invoke); } assert(invokes.size() == 9); // current number of methods return invokes.toArray(new MethodHandle[0]); }; static final MethodHandle[] INVOKES = makeInvokes(); // For testing use this: //static final MethodHandle[] INVOKES = Arrays.copyOf(makeInvokes(), 2); static final MethodHandle VARARGS_INVOKE; static { try { VARARGS_INVOKE = IMPL_LOOKUP.findVirtual(GuardWithCatch.class, "invoke_V", MethodType.genericMethodType(0, true)); } catch (ReflectiveOperationException ex) { throw uncaughtException(ex); } } } static MethodHandle makeGuardWithCatch(MethodHandle target, Class exType, MethodHandle catcher) { MethodType type = target.type(); MethodType ctype = catcher.type(); int nargs = type.parameterCount(); if (nargs < GuardWithCatch.INVOKES.length) { MethodType gtype = type.generic(); MethodType gcatchType = gtype.insertParameterTypes(0, Throwable.class); // Note: convertArguments(...2) avoids interface casts present in convertArguments(...0) MethodHandle gtarget = makePairwiseConvert(target, gtype, 2); MethodHandle gcatcher = makePairwiseConvert(catcher, gcatchType, 2); GuardWithCatch gguard = new GuardWithCatch(gtarget, exType, gcatcher); if (gtarget == null || gcatcher == null) throw new InternalError(); MethodHandle ginvoker = GuardWithCatch.INVOKES[nargs].bindReceiver(gguard); return makePairwiseConvert(ginvoker, type, 2); } else { target = target.asType(type.changeReturnType(Object.class)); MethodHandle gtarget = makeSpreadArguments(target, Object[].class, 0, nargs); MethodType catcherType = ctype.changeParameterType(0, Throwable.class) .changeReturnType(Object.class); catcher = catcher.asType(catcherType); MethodHandle gcatcher = makeSpreadArguments(catcher, Object[].class, 1, nargs); GuardWithCatch gguard = new GuardWithCatch(gtarget, exType, gcatcher); if (gtarget == null || gcatcher == null) throw new InternalError(); MethodHandle ginvoker = GuardWithCatch.VARARGS_INVOKE.bindReceiver(gguard); MethodHandle gcollect = makeCollectArguments(ginvoker, ValueConversions.varargsArray(nargs), 0, false); return makePairwiseConvert(gcollect, type, 2); } } static MethodHandle throwException(MethodType type) { assert(Throwable.class.isAssignableFrom(type.parameterType(0))); int arity = type.parameterCount(); if (arity > 1) { return throwException(type.dropParameterTypes(1, arity)).dropArguments(type, 1, arity-1); } return makePairwiseConvert(throwException(), type, 2); } static MethodHandle THROW_EXCEPTION; static MethodHandle throwException() { MethodHandle mh = THROW_EXCEPTION; if (mh != null) return mh; try { mh = IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "throwException", MethodType.methodType(Empty.class, Throwable.class)); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } THROW_EXCEPTION = mh; return mh; } static Empty throwException(T t) throws T { throw t; } static MethodHandle[] FAKE_METHOD_HANDLE_INVOKE = new MethodHandle[2]; static MethodHandle fakeMethodHandleInvoke(MemberName method) { int idx; assert(method.isMethodHandleInvoke()); switch (method.getName()) { case "invoke": idx = 0; break; case "invokeExact": idx = 1; break; default: throw new InternalError(method.getName()); } MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE[idx]; if (mh != null) return mh; MethodType type = MethodType.methodType(Object.class, UnsupportedOperationException.class, MethodHandle.class, Object[].class); mh = throwException(type); mh = mh.bindTo(new UnsupportedOperationException("cannot reflectively invoke MethodHandle")); if (!method.getInvocationType().equals(mh.type())) throw new InternalError(method.toString()); mh = mh.withInternalMemberName(method); mh = mh.asVarargsCollector(Object[].class); assert(method.isVarargs()); FAKE_METHOD_HANDLE_INVOKE[idx] = mh; return mh; } /** * Create an alias for the method handle which, when called, * appears to be called from the same class loader and protection domain * as hostClass. * This is an expensive no-op unless the method which is called * is sensitive to its caller. A small number of system methods * are in this category, including Class.forName and Method.invoke. */ static MethodHandle bindCaller(MethodHandle mh, Class hostClass) { return BindCaller.bindCaller(mh, hostClass); } // Put the whole mess into its own nested class. // That way we can lazily load the code and set up the constants. private static class BindCaller { static MethodHandle bindCaller(MethodHandle mh, Class hostClass) { // Do not use this function to inject calls into system classes. if (hostClass == null || (hostClass.isArray() || hostClass.isPrimitive() || hostClass.getName().startsWith("java.") || hostClass.getName().startsWith("sun."))) { throw new InternalError(); // does not happen, and should not anyway } // For simplicity, convert mh to a varargs-like method. MethodHandle vamh = prepareForInvoker(mh); // Cache the result of makeInjectedInvoker once per argument class. MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass); return restoreToType(bccInvoker.bindTo(vamh), mh.type(), mh.internalMemberName(), hostClass); } private static MethodHandle makeInjectedInvoker(Class hostClass) { Class bcc = UNSAFE.defineAnonymousClass(hostClass, T_BYTES, null); if (hostClass.getClassLoader() != bcc.getClassLoader()) throw new InternalError(hostClass.getName()+" (CL)"); try { if (hostClass.getProtectionDomain() != bcc.getProtectionDomain()) throw new InternalError(hostClass.getName()+" (PD)"); } catch (SecurityException ex) { // Self-check was blocked by security manager. This is OK. // In fact the whole try body could be turned into an assertion. } try { MethodHandle init = IMPL_LOOKUP.findStatic(bcc, "init", MethodType.methodType(void.class)); init.invokeExact(); // force initialization of the class } catch (Throwable ex) { throw uncaughtException(ex); } MethodHandle bccInvoker; try { MethodType invokerMT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class); bccInvoker = IMPL_LOOKUP.findStatic(bcc, "invoke_V", invokerMT); } catch (ReflectiveOperationException ex) { throw uncaughtException(ex); } // Test the invoker, to ensure that it really injects into the right place. try { MethodHandle vamh = prepareForInvoker(MH_checkCallerClass); Object ok = bccInvoker.invokeExact(vamh, new Object[]{hostClass, bcc}); } catch (Throwable ex) { throw new InternalError(ex); } return bccInvoker; } private static ClassValue CV_makeInjectedInvoker = new ClassValue() { @Override protected MethodHandle computeValue(Class hostClass) { return makeInjectedInvoker(hostClass); } }; // Adapt mh so that it can be called directly from an injected invoker: private static MethodHandle prepareForInvoker(MethodHandle mh) { mh = mh.asFixedArity(); MethodType mt = mh.type(); int arity = mt.parameterCount(); MethodHandle vamh = mh.asType(mt.generic()); vamh.internalForm().compileToBytecode(); // eliminate LFI stack frames vamh = vamh.asSpreader(Object[].class, arity); vamh.internalForm().compileToBytecode(); // eliminate LFI stack frames return vamh; } // Undo the adapter effect of prepareForInvoker: private static MethodHandle restoreToType(MethodHandle vamh, MethodType type, MemberName member, Class hostClass) { MethodHandle mh = vamh.asCollector(Object[].class, type.parameterCount()); mh = mh.asType(type); mh = new WrappedMember(mh, type, member, hostClass); return mh; } private static final MethodHandle MH_checkCallerClass; static { final Class THIS_CLASS = BindCaller.class; assert(checkCallerClass(THIS_CLASS, THIS_CLASS)); try { MH_checkCallerClass = IMPL_LOOKUP .findStatic(THIS_CLASS, "checkCallerClass", MethodType.methodType(boolean.class, Class.class, Class.class)); assert((boolean) MH_checkCallerClass.invokeExact(THIS_CLASS, THIS_CLASS)); } catch (Throwable ex) { throw new InternalError(ex); } } @CallerSensitive private static boolean checkCallerClass(Class expected, Class expected2) { // This method is called via MH_checkCallerClass and so it's // correct to ask for the immediate caller here. Class actual = Reflection.getCallerClass(); if (actual != expected && actual != expected2) throw new InternalError("found "+actual.getName()+", expected "+expected.getName() +(expected == expected2 ? "" : ", or else "+expected2.getName())); return true; } private static final byte[] T_BYTES; static { final Object[] values = {null}; AccessController.doPrivileged(new PrivilegedAction() { public Void run() { try { Class tClass = T.class; String tName = tClass.getName(); String tResource = tName.substring(tName.lastIndexOf('.')+1)+".class"; java.net.URLConnection uconn = tClass.getResource(tResource).openConnection(); int len = uconn.getContentLength(); byte[] bytes = new byte[len]; try (java.io.InputStream str = uconn.getInputStream()) { int nr = str.read(bytes); if (nr != len) throw new java.io.IOException(tResource); } values[0] = bytes; } catch (java.io.IOException ex) { throw new InternalError(ex); } return null; } }); T_BYTES = (byte[]) values[0]; } // The following class is used as a template for Unsafe.defineAnonymousClass: private static class T { static void init() { } // side effect: initializes this class static Object invoke_V(MethodHandle vamh, Object[] args) throws Throwable { return vamh.invokeExact(args); } } } /** This subclass allows a wrapped method handle to be re-associated with an arbitrary member name. */ static class WrappedMember extends MethodHandle { private final MethodHandle target; private final MemberName member; private final Class callerClass; private WrappedMember(MethodHandle target, MethodType type, MemberName member, Class callerClass) { super(type, reinvokerForm(target)); this.target = target; this.member = member; this.callerClass = callerClass; } @Override MethodHandle reinvokerTarget() { return target; } @Override public MethodHandle asTypeUncached(MethodType newType) { // This MH is an alias for target, except for the MemberName // Drop the MemberName if there is any conversion. return asTypeCache = target.asType(newType); } @Override MemberName internalMemberName() { return member; } @Override Class internalCallerClass() { return callerClass; } @Override boolean isInvokeSpecial() { return target.isInvokeSpecial(); } @Override MethodHandle viewAsType(MethodType newType) { return new WrappedMember(target, newType, member, callerClass); } } static MethodHandle makeWrappedMember(MethodHandle target, MemberName member) { if (member.equals(target.internalMemberName())) return target; return new WrappedMember(target, target.type(), member, null); } }