/* * Copyright (c) 2017, 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 jdk.internal.reflect.ConstantPool; import jdk.internal.vm.annotation.Stable; import java.lang.constant.Constable; import java.lang.constant.ConstantDesc; import java.lang.constant.DynamicConstantDesc; import java.lang.reflect.Array; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.lang.invoke.AbstractBootstrapCallInfo.*; import static java.lang.invoke.AbstractBootstrapCallInfo.maybeShareArguments; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; import static java.lang.invoke.MethodHandles.Lookup; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.util.Objects.requireNonNull; final class BootstrapMethodInvoker { /** * Factored code for invoking a bootstrap method for invokedynamic * or a dynamic constant. * @param lookup the capability to access the caller class's constants * @param bsci the BootstrapCallInfo provided by the JVM * @param the kind of the type argument, {@code Class} or {@code MethodType} * @return the expected value, either a CallSite or a constant value */ static > Object invoke(Lookup lookup, BootstrapCallInfo bsci) { Objects.requireNonNull(lookup); MethodHandle bsm = bsci.bootstrapMethod(); MethodType bsmType = bsm.type(); boolean bsmIsVarargs = bsm.isVarargsCollector(); Object result; try { // By using maybeShareArguments, we avoid extra copy steps // on the argument list between a BSM created for the JVM // (by MethodHandleNatives) and the invocation logic of // invokeCommon. if (isPullModeBSM(bsm)) { // Pull-mode API is (Lookup, BootstrapCallInfo) -> Object result = bsm.invoke(lookup, bsci); } else if (isExpressionBSM(bsm)) { if (bsci.invocationName().equals("invoke")) // short circuit in common case result = invokeCommon(bsm, null, null, null, maybeShareArguments(bsci), bsci.argumentList()); else result = ConstantBootstraps.linkExpression(lookup, bsci); } else { // Push-mode BSM, needs to get (lookup, name, type, arg...). result = invokeCommon(bsm, lookup, bsci.invocationName(), bsci.invocationType(), maybeShareArguments(bsci), bsci.argumentList()); } return BootstrapCallInfo.convertResult(bsci.invocationType(), bsmType.returnType(), result); } catch (Error e) { // Pass through an Error, including BootstrapMethodError, any other // form of linkage error, such as IllegalAccessError if the bootstrap // method is inaccessible, or say ThreadDeath/OutOfMemoryError // See the "Linking Exceptions" section for the invokedynamic // instruction in JVMS 6.5. throw e; } catch (Throwable ex) { // Wrap anything else in BootstrapMethodError throw new BootstrapMethodError("bootstrap method initialization exception", ex); } } /** A BSM runs in pull mode if and only if its sole arguments * are {@code (Lookup, BootstrapCallInfo)}. * Since the trailing argument is not an array type, it cannot be of variable arity. * The return type of the BSM is unconstrained, but it will often be {@code Object}. */ public static boolean isPullModeBSM(MethodHandle bsm) { MethodType mtype = bsm.type(); return mtype.parameterCount() == 2 && mtype.parameterType(0) == Lookup.class && mtype.parameterType(1) == BootstrapCallInfo.class; } /** Most BSM calls take a standard set of metadata arguments, * one of {@code (Lookup, String, Class)}, {@code (Lookup, String, MethodType)}, * or {@code (Lookup, BootstrapCallInfo)}. * But if the lead argument is not {@code Lookup} and * the invocation type being linked is a field type ({@code Class}), * then only the static arguments will be passed. * Such a BSM is called an expression mode BSM. * The standard bootstrap method {@link ConstantBootstraps#linkExpression} * is consulted to correctly invoke such an expression bootstrap method. * Often, it is just applied directly to the static arguments. */ static boolean isExpressionBSM(MethodHandle bsm) { MethodType mtype = bsm.type(); return (mtype.parameterCount() == 0 || mtype.parameterType(0) != Lookup.class); } /** Given a poorly typed BSM, wrap it in a BSM whose * type starts with Lookup. This will prevent it from * being accidentally treated as an expression-mode BSM. * We only do this, of course, if we know that the BSM * is not supposed to have expression mode. This is the * case when the BSM links an invokedynamic instruction. */ static MethodHandle forceLookupParameter(MethodHandle bsm) { if (TRACE_METHOD_LINKAGE) System.out.println("[TRACE_METHOD_LINKAGE] forceLookupParameter "+bsm); MethodHandle mh = MH_forceLookupParameter; if (mh == null) { try { mh = IMPL_LOOKUP.findStatic(BootstrapMethodInvoker.class, "forceLookupParameter", MethodType.methodType(Object.class, MethodHandle.class, Lookup.class, Object[].class)); } catch (ReflectiveOperationException ex) { throw new InternalError(ex); } MH_forceLookupParameter = mh; } return mh.bindTo(bsm).withVarargs(true); } private static @Stable MethodHandle MH_forceLookupParameter; private static Object forceLookupParameter(MethodHandle bsm, Lookup lookup, Object... otherArgs) throws Throwable { // There are often faster ways to get this done, but this formulation // covers all the corner cases. Poorly typed BSMs are rare, anyway. Object[] args = new Object[1 + otherArgs.length]; args[0] = lookup; System.arraycopy(otherArgs, 0, args, 1, otherArgs.length); return bsm.invokeWithArguments(args); } /// Signatures which have bespoke invocation paths: // Lambda metafactory, standard. private static final MethodType LMF_INDY_MT = MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class); // Lambda metafactory, alternate. private static final MethodType LMF_ALT_MT = MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class); // Lambda metafactory, condy. private static final MethodType LMF_CONDY_MT = MethodType.methodType(Object.class, Lookup.class, String.class, Class.class, MethodType.class, MethodHandle.class, MethodType.class); // String concat metafactory. private static final MethodType SCF_MT = MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, String.class, Object[].class); /** The JVM produces java.lang.Integer values to box * CONSTANT_Integer boxes but does not intern them. * Let's intern them. This is slightly wrong for * a {@code CONSTANT_Dynamic} which produces an * un-interned integer (e.g., {@code new Integer(0)}). */ static Object maybeReBox(Object x) { if (x instanceof Integer) { int xi = (int) x; if (xi == (byte) xi) x = xi; // must rebox; see JLS 5.1.7 } return x; } static void maybeReBoxElements(Object[] xa) { for (int i = 0; i < xa.length; i++) { xa[i] = maybeReBox(xa[i]); } } static Object invokeCommon(MethodHandle handle, Lookup lookup, String name, TypeDescriptor type, Object[] argv, List args) throws Throwable { // Efficiently invoke a method (probably a BSM) on a particular // set of bootstrap arguments, perhaps including the usual // metadata values of (lookup, name, type). // To avoid copying, source the arguments from whatever // the caller has on hand: A Java array, a List, or both. // // Support fast arity-specific invocation paths for both // method types and field types (Class and MethodType), and // no-metadata calls also. To get to these paths as often // as possible, executet some varargs processing directly. // // For further optimization we special-case various known BSMs, // including LambdaMetafactory::metafactory and // StringConcatFactory::makeConcatWithConstants. // (Actually, we special-case their *types*, which amounts // to the same thing.) // // By providing static type information or even invoking // exactly, we avoid emitting code to perform runtime // checking. // // Because we usually don't examine the BSM return type, // we can't use the MH.invokeExact mode as much as we would // like, but at least the MH.invoke path has a reasonably // small type mismatch. if (TRACE_METHOD_LINKAGE) { System.out.println("invoking " + handle + " on " + (argv == null ? args : Arrays.asList(argv))); } int argc = argv != null ? argv.length : args.size(); boolean isFieldType = (type instanceof Class); boolean isMethodType = (type instanceof MethodType); boolean passMetadata = (lookup != null); final int MAX_ARITY = 7, FT = MAX_ARITY+1, MT = FT*2, NMD = 0; final int METADATA_ARG_COUNT = 3; // (lookup, name, type) final int metaArgc = passMetadata ? METADATA_ARG_COUNT : 0; if (passMetadata) { requireNonNull(name); requireNonNull(type); } MethodType handleType = handle.type(); boolean handleVarargs = handle.isVarargsCollector(); if (!passMetadata) { // Must not lead with a Lookup formal parameter type. // Lookup leading types are reserved for metadata-using BSMs. assert(handleType.parameterCount() == 0 || handleType.parameterType(0) != Lookup.class); assert(name == null); assert(type == null); } else if (isFieldType) { // With condy, we require an explicit leading type of Lookup, // if metadata is to be passed. assert(handleType.parameterCount() == 0 || handleType.parameterType(0) == Lookup.class); // Also, the caller should not try to handle a BSCI call here. assert(handleType.parameterCount() != 2 || handleType.parameterType(1) != BootstrapCallInfo.class); // Otherwise, we are doing an old-style indy bootstrap, // which tolerates Object in the metadata positions. } // Some varargs methods can be handled quickly here, // rather than going through more dynamic invocation paths. // Do the varargs spreading if: the BSM is varargs, the BSM // directly accepts at least the metadata arguments (if applicable), // the given static arguments fill in the BSM's inital set // of arguments, and the BSM's trailing argument is a // reference array. In that case, split the static argument // list into head and tail arrays, and link the latter onto the // end of the former. if (handleVarargs) { int headArgc = (handleType.parameterCount() - 1) - metaArgc; Class tailType = handleType.lastParameterType(); Class tailElementType = tailType.getComponentType(); if (headArgc >= 0 && headArgc <= argc && Object[].class.isAssignableFrom(tailType) && tailElementType != null) { // Make a shortened argument list for a fixed-arity call. Object[] headArgv = new Object[headArgc + 1]; if (argv != null) { System.arraycopy(argv, 0, headArgv, 0, headArgc); } else { List headArgs = args.subList(0, headArgc); headArgv = headArgs.toArray(headArgv); } // Collect the trailing arguments into an array of the // required type. Also, make sure they fit into the array. int tailArgc = argc - headArgc; Object[] tailArgv = (tailElementType == Object.class) ? new Object[tailArgc] : (Object[]) Array.newInstance(tailElementType, tailArgc); try { if (argv != null) { System.arraycopy(argv, headArgc, tailArgv, 0, tailArgc); } else { List tailArgs = args.subList(headArgc, argc); tailArgv = tailArgs.toArray(tailArgv); } } catch (ArrayStoreException ex) { // Oops, one of the actuals doesn't fit in the varargs formal. // For example, f(String...) is passed ("foo", 42, "bar"). tailArgv = null; // bail out } if (tailArgv != null) { // Link the head to the tail: headArgv[headArgc] = tailArgv; // Now swap in a new MH and arglist: handle = handle.asFixedArity(); assert(handleType == handle.type()); // still true handleVarargs = false; argv = headArgv; args = null; argc = headArgc + 1; } } } // Now try to make an inline call: if (argc <= MAX_ARITY && argv == null) argv = args.toArray(); assert(argv == null || argc == argv.length); assert(args == null || argc == args.size()); // If we don't provide static type information for the type, // we'll generate runtime checks. The cases marked FT and MT // avoid that problem. switch (argc > MAX_ARITY ? -1 : argc + (isFieldType ? FT : isMethodType ? MT : NMD)) { // Fixed-arity cases for a Class (field) type: case 0+FT: return handle.invoke(lookup, name, (Class)type); case 1+FT: return handle.invoke(lookup, name, (Class)type, argv[0]); case 2+FT: return handle.invoke(lookup, name, (Class)type, argv[0], argv[1]); case 3+FT: if (handleType == LMF_CONDY_MT && !handleVarargs) { return handle.invokeExact(lookup, name, (Class)type, (MethodType)argv[0], (MethodHandle)argv[1], (MethodType)argv[2]); } return handle.invoke(lookup, name, (Class)type, argv[0], argv[1], argv[2]); case 4+FT: return handle.invoke(lookup, name, (Class)type, argv[0], argv[1], argv[2], argv[3]); case 5+FT: return handle.invoke(lookup, name, (Class)type, argv[0], argv[1], argv[2], argv[3], argv[4]); case 6+FT: return handle.invoke(lookup, name, (Class)type, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); case MAX_ARITY+FT: return handle.invoke(lookup, name, (Class)type, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); // Same cases, but for a MethodType argument: case 0+MT: return handle.invoke(lookup, name, (MethodType)type); case 1+MT: if (handleType == LMF_ALT_MT && !handleVarargs) { return (CallSite) handle.invokeExact(lookup, name, (MethodType)type, (Object[])argv[0]); } return handle.invoke(lookup, name, (MethodType)type, argv[0]); case 2+MT: if (handleType == SCF_MT && !handleVarargs) { return (CallSite) handle.invokeExact(lookup, name, (MethodType)type, (String)argv[0], (Object[])argv[1]); } return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1]); case 3+MT: if (handleType == LMF_INDY_MT && !handleVarargs) { return (CallSite) handle.invokeExact(lookup, name, (MethodType)type, (MethodType)argv[0], (MethodHandle)argv[1], (MethodType)argv[2]); } return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1], argv[2]); case 4+MT: return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1], argv[2], argv[3]); case 5+MT: return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1], argv[2], argv[3], argv[4]); case 6+MT: return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); case MAX_ARITY+MT: return handle.invoke(lookup, name, (MethodType)type, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); // Same cases again, but without metadata: case 0+NMD: return handle.invoke(); case 1+NMD: return handle.invoke(argv[0]); case 2+NMD: return handle.invoke(argv[0], argv[1]); case 3+NMD: return handle.invoke(argv[0], argv[1], argv[2]); case 4+NMD: return handle.invoke(argv[0], argv[1], argv[2], argv[3]); case 5+NMD: return handle.invoke(argv[0], argv[1], argv[2], argv[3], argv[4]); case 6+NMD: return handle.invoke(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); case MAX_ARITY+NMD: return handle.invoke(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); } // We have to stop at some point and use invokeWithArguments instead. // Note that invokeWithArguments handles jumbo argument lists. if (passMetadata) { Object[] allArgv = new Object[metaArgc + argc]; int pos = 0; allArgv[pos++] = lookup; allArgv[pos++] = name; allArgv[pos++] = type; assert(pos == metaArgc); if (argv != null) { System.arraycopy(argv, 0, allArgv, pos, argc); pos += argc; } else { for (Object arg : args) { allArgv[pos++] = arg; } } assert(pos == allArgv.length); return handle.invokeWithArguments(allArgv); } else if (argv != null) { // It's simple if there are no metadata arguments to prepend. return handle.invokeWithArguments(argv); } else { return handle.invokeWithArguments(args); } } /** Canonical VM-aware implementation of BootstrapCallInfo. * Knows how to dig into the JVM for lazily resolved (pull-mode) constants. */ static final class VM_BSCI> extends WithCache { private final int bssIndex; private final int indyIndex; private final ConstantPool pool; private int[] argIndexes; // lazy public VM_BSCI(ConstantPool pool, int bssIndex, int indyIndex, MethodHandle bsm, String name, TypeView type, Object[] arguments) { super(bsm, name, type, arguments); this.pool = pool; this.bssIndex = bssIndex; this.indyIndex = indyIndex; } /** Special VM-specific hook to resolve static arguments. */ @Override Object resolveConstant(int i) { Object[] buf = { null }; copyVMArguments(i, i+1, buf, 0, BAR_FORCE, null); Object res = buf[0]; int next = i + 1; if (next < cache.length && cache[next] == null) maybePrefetchIntoCache(cache, next, false); // try to prefetch return res; } /** Special VM-specific hook to find symbolic references. */ @Override ConstantDesc findSymbol(int i) { Object[] buf = { null }; copyVMArguments(i, i+1, buf, 0, BAR_SYMREF, null); ConstantDesc res = (ConstantDesc) buf[0]; //int next = i + 1; //if (next < symCache.length && symCache[next] == null) // maybePrefetchIntoCache(symCache, next, false); // try to prefetch return res; } private Class caller() { return pool.getHolder(); } /*non-public contract with ArgList*/ void copyVMArguments(int start, int end, Object[] buf, int pos, byte resolving, Object ifNotPresent) { if (buf.getClass() != Object[].class) throw new InternalError(); int i = start, bufi = pos; if (resolving == BAR_SYMREF) { while (i < end) { Object x = symCache[i]; if (x == null) break; buf[bufi++] = x; i++; } if (i >= end) return; // pull in all of the symbols into symCache int[] temp = new int[end - i]; int tempi = 0; pool.copyOutBootstrapArgumentsAt(bssIndex, start, end, temp, 0, BAR_SYMREF, null, null, false); for (; i < end; i++) { ConstantDesc x = symCache[i]; int symIndex = temp[tempi++]; if (x == null) { x = makeConstantFromPool(symIndex); symCache[i] = x; } buf[bufi++] = x; } return; } assert(resolving == BAR_IFPRESENT || resolving == BAR_FORCE); for (; i < end; i++) { Object x = cache[i]; if (x == null) break; buf[bufi++] = unwrapNull(x); } // give up at first null and grab the rest in one big block if (i >= end) return; if (TRACE_METHOD_LINKAGE) { System.out.println("resolving more BSM arguments: " + Arrays.asList(caller().getSimpleName(), bssIndex, indyIndex, i, end)); } pool.copyOutBootstrapArgumentsAt(bssIndex, i, end, cache, i, resolving, null, wrapNull(null), true); for (; i < end; i++) { Object x = cache[i]; Object x2 = maybeReBox(x); if (x2 != x) cache[i] = x = x2; buf[bufi++] = (x == null) ? ifNotPresent : unwrapNull(x); } if (end < cache.length && cache[end] == null) maybePrefetchIntoCache(cache, end, true); // try to prefetch } private ConstantDesc makeConstantFromPool(int symIndex) { return pool.getConstantDescAt(symIndex); } private static final int MIN_PF = 4; private void maybePrefetchIntoCache(Object[] cache, int i, boolean bulk) { int len = cache.length; assert(0 <= i && i <= len); int pfLimit = i; if (bulk) pfLimit += i; // exponential prefetch expansion // try to prefetch at least MIN_PF elements if (pfLimit < i + MIN_PF) pfLimit = i + MIN_PF; if (pfLimit > len || pfLimit < 0) pfLimit = len; // stop prefetching where cache is more full than empty int empty = 0, nonEmpty = 0, lastEmpty = i; for (int j = i; j < pfLimit; j++) { if (cache[j] == null) { empty++; lastEmpty = j; } else { nonEmpty++; if (nonEmpty > empty) { pfLimit = lastEmpty + 1; break; } if (pfLimit < len) pfLimit++; } } if (bulk && empty < MIN_PF && pfLimit < len) return; // not worth the effort prefetchIntoCache(cache, i, pfLimit); } private void prefetchIntoCache(Object[] cache, int i, int pfLimit) { if (pfLimit <= i) return; // corner case if (TRACE_METHOD_LINKAGE) { System.out.println("prefetching BSM arguments: " + Arrays.asList(caller().getSimpleName(), bssIndex, indyIndex, i, pfLimit)); } pool.copyOutBootstrapArgumentsAt(bssIndex, i, pfLimit, cache, i, BAR_IFPRESENT, null, wrapNull(null), true); } void setArgIndexes(int[] argIndexes) { assert(this.argIndexes == null); this.argIndexes = argIndexes; } } /** Canonical DynamicConstantDesc implementation of BootstrapCallInfo. */ static final class DCD_BSCI extends WithCache> { private Lookup lookup; private DynamicConstantDesc desc; private final List> args; public DCD_BSCI(Lookup lookup, DynamicConstantDesc desc) throws ReflectiveOperationException { super(desc.bootstrapMethod().resolveConstantDesc(lookup), desc.constantName(), new TypeView>(desc.constantType(), lookup), desc.bootstrapArgs()); this.lookup = lookup; this.desc = desc; this.args = desc.bootstrapArgsList(); } @Override Object resolveConstant(int i) throws BootstrapMethodError { try { return argumentDesc(i).resolveConstantDesc(lookup); } catch (ReflectiveOperationException ex) { throw new BootstrapMethodError(ex); } } @Override ConstantDesc findSymbol(int i) { return args.get(i); } } /** * Create a BootstrapCallInfo whose symbolic content is derived from the given * descriptor. When it resolves constants, it will use the given Lookup object. * @param desc the dynamic constant descriptor * @param lookup a lookup object which is capable of resolving the dynamic constant * @return a BootstrapCallInfo which carries the given * @throws ReflectiveOperationException if the bootstrap method fails to resolve */ static BootstrapCallInfo> ofConstantDesc(DynamicConstantDesc desc, Lookup lookup) throws ReflectiveOperationException { return new BootstrapMethodInvoker.DCD_BSCI(lookup, desc); } /** * Resolve a dynamic constant, by creating a temporary BootstrapCallInfo for it * and immediately invoking its resolution behavior. Any cached resolution state * will be lost after the temporary BootstrapCallInfo is discarded. * @param desc the dynamic constant descriptor * @param lookup a lookup object which is capable of resolving the dynamic constant * @return the result of resolving the descriptor in the lookup class * @throws ReflectiveOperationException */ static Object resolveDynamicConstant(DynamicConstantDesc desc, Lookup lookup) throws ReflectiveOperationException { try { return invoke(lookup, ofConstantDesc(desc, lookup)); } catch (LinkageError ex) { var roe = ex.getCause(); if (roe instanceof ReflectiveOperationException) throw (ReflectiveOperationException) roe; throw ex; } } }