diff -r 000000000000 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleImpl.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1013 @@ +/* + * 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); + } + +}