# HG changeset patch # User Jaroslav Tulach # Date 1407575473 -7200 # Node ID c880a8a8803b2abac1424d55d17c6e41d102ed00 # Parent 0101d10bd2e0d7132c1a409b9e96508caab44af9 Batch of classes necessary to implement invoke dynamic interfaces. Taken from JDK8 build 132 diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/AbstractValidatingLambdaMetafactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2012, 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 sun.invoke.util.Wrapper; + +import static sun.invoke.util.Wrapper.forPrimitiveType; +import static sun.invoke.util.Wrapper.forWrapperType; +import static sun.invoke.util.Wrapper.isWrapperType; + +/** + * Abstract implementation of a lambda metafactory which provides parameter + * unrolling and input validation. + * + * @see LambdaMetafactory + */ +/* package */ abstract class AbstractValidatingLambdaMetafactory { + + /* + * For context, the comments for the following fields are marked in quotes + * with their values, given this program: + * interface II { Object foo(T x); } + * interface JJ extends II { } + * class CC { String impl(int i) { return "impl:"+i; }} + * class X { + * public static void main(String[] args) { + * JJ iii = (new CC())::impl; + * System.out.printf(">>> %s\n", iii.foo(44)); + * }} + */ + final Class targetClass; // The class calling the meta-factory via invokedynamic "class X" + final MethodType invokedType; // The type of the invoked method "(CC)II" + final Class samBase; // The type of the returned instance "interface JJ" + final String samMethodName; // Name of the SAM method "foo" + final MethodType samMethodType; // Type of the SAM method "(Object)Object" + final MethodHandle implMethod; // Raw method handle for the implementation method + final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]" + final int implKind; // Invocation kind for implementation "5"=invokevirtual + final boolean implIsInstanceMethod; // Is the implementation an instance method "true" + final Class implDefiningClass; // Type defining the implementation "class CC" + final MethodType implMethodType; // Type of the implementation method "(int)String" + final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" + final boolean isSerializable; // Should the returned instance be serializable + final Class[] markerInterfaces; // Additional marker interfaces to be implemented + final MethodType[] additionalBridges; // Signatures of additional methods to bridge + + + /** + * Meta-factory constructor. + * + * @param caller Stacked automatically by VM; represents a lookup context + * with the accessibility privileges of the caller. + * @param invokedType Stacked automatically by VM; the signature of the + * invoked method, which includes the expected static + * type of the returned lambda object, and the static + * types of the captured arguments for the lambda. In + * the event that the implementation method is an + * instance method, the first argument in the invocation + * signature will correspond to the receiver. + * @param samMethodName Name of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a String. + * @param samMethodType Type of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a MethodType. + * @param implMethod The implementation method which should be called + * (with suitable adaptation of argument types, return + * types, and adjustment for captured arguments) when + * methods of the resulting functional interface instance + * are invoked. + * @param instantiatedMethodType The signature of the primary functional + * interface method after type variables are + * substituted with their instantiation from + * the capture site + * @param isSerializable Should the lambda be made serializable? If set, + * either the target type or one of the additional SAM + * types must extend {@code Serializable}. + * @param markerInterfaces Additional interfaces which the lambda object + * should implement. + * @param additionalBridges Method types for additional signatures to be + * bridged to the implementation method + * @throws LambdaConversionException If any of the meta-factory protocol + * invariants are violated + */ + AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + String samMethodName, + MethodType samMethodType, + MethodHandle implMethod, + MethodType instantiatedMethodType, + boolean isSerializable, + Class[] markerInterfaces, + MethodType[] additionalBridges) + throws LambdaConversionException { + if ((caller.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { + throw new LambdaConversionException(String.format( + "Invalid caller: %s", + caller.lookupClass().getName())); + } + this.targetClass = caller.lookupClass(); + this.invokedType = invokedType; + + this.samBase = invokedType.returnType(); + + this.samMethodName = samMethodName; + this.samMethodType = samMethodType; + + this.implMethod = implMethod; + this.implInfo = caller.revealDirect(implMethod); + this.implKind = implInfo.getReferenceKind(); + this.implIsInstanceMethod = + implKind == MethodHandleInfo.REF_invokeVirtual || + implKind == MethodHandleInfo.REF_invokeSpecial || + implKind == MethodHandleInfo.REF_invokeInterface; + this.implDefiningClass = implInfo.getDeclaringClass(); + this.implMethodType = implInfo.getMethodType(); + this.instantiatedMethodType = instantiatedMethodType; + this.isSerializable = isSerializable; + this.markerInterfaces = markerInterfaces; + this.additionalBridges = additionalBridges; + + if (!samBase.isInterface()) { + throw new LambdaConversionException(String.format( + "Functional interface %s is not an interface", + samBase.getName())); + } + + for (Class c : markerInterfaces) { + if (!c.isInterface()) { + throw new LambdaConversionException(String.format( + "Marker interface %s is not an interface", + c.getName())); + } + } + } + + /** + * Build the CallSite. + * + * @return a CallSite, which, when invoked, will return an instance of the + * functional interface + * @throws ReflectiveOperationException + */ + abstract CallSite buildCallSite() + throws LambdaConversionException; + + /** + * Check the meta-factory arguments for errors + * @throws LambdaConversionException if there are improper conversions + */ + void validateMetafactoryArgs() throws LambdaConversionException { + switch (implKind) { + case MethodHandleInfo.REF_invokeInterface: + case MethodHandleInfo.REF_invokeVirtual: + case MethodHandleInfo.REF_invokeStatic: + case MethodHandleInfo.REF_newInvokeSpecial: + case MethodHandleInfo.REF_invokeSpecial: + break; + default: + throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo)); + } + + // Check arity: optional-receiver + captured + SAM == impl + final int implArity = implMethodType.parameterCount(); + final int receiverArity = implIsInstanceMethod ? 1 : 0; + final int capturedArity = invokedType.parameterCount(); + final int samArity = samMethodType.parameterCount(); + final int instantiatedArity = instantiatedMethodType.parameterCount(); + if (implArity + receiverArity != capturedArity + samArity) { + throw new LambdaConversionException( + String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + capturedArity, samArity, implArity)); + } + if (instantiatedArity != samArity) { + throw new LambdaConversionException( + String.format("Incorrect number of parameters for %s method %s; %d instantiated parameters, %d functional interface method parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + instantiatedArity, samArity)); + } + for (MethodType bridgeMT : additionalBridges) { + if (bridgeMT.parameterCount() != samArity) { + throw new LambdaConversionException( + String.format("Incorrect number of parameters for bridge signature %s; incompatible with %s", + bridgeMT, samMethodType)); + } + } + + // If instance: first captured arg (receiver) must be subtype of class where impl method is defined + final int capturedStart; + final int samStart; + if (implIsInstanceMethod) { + final Class receiverClass; + + // implementation is an instance method, adjust for receiver in captured variables / SAM arguments + if (capturedArity == 0) { + // receiver is function parameter + capturedStart = 0; + samStart = 1; + receiverClass = instantiatedMethodType.parameterType(0); + } else { + // receiver is a captured variable + capturedStart = 1; + samStart = 0; + receiverClass = invokedType.parameterType(0); + } + + // check receiver type + if (!implDefiningClass.isAssignableFrom(receiverClass)) { + throw new LambdaConversionException( + String.format("Invalid receiver type %s; not a subtype of implementation type %s", + receiverClass, implDefiningClass)); + } + + Class implReceiverClass = implMethod.type().parameterType(0); + if (implReceiverClass != implDefiningClass && !implReceiverClass.isAssignableFrom(receiverClass)) { + throw new LambdaConversionException( + String.format("Invalid receiver type %s; not a subtype of implementation receiver type %s", + receiverClass, implReceiverClass)); + } + } else { + // no receiver + capturedStart = 0; + samStart = 0; + } + + // Check for exact match on non-receiver captured arguments + final int implFromCaptured = capturedArity - capturedStart; + for (int i=0; i implParamType = implMethodType.parameterType(i); + Class capturedParamType = invokedType.parameterType(i + capturedStart); + if (!capturedParamType.equals(implParamType)) { + throw new LambdaConversionException( + String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", + i, capturedParamType, implParamType)); + } + } + // Check for adaptation match on SAM arguments + final int samOffset = samStart - implFromCaptured; + for (int i=implFromCaptured; i implParamType = implMethodType.parameterType(i); + Class instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset); + if (!isAdaptableTo(instantiatedParamType, implParamType, true)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", + i, instantiatedParamType, implParamType)); + } + } + + // Adaptation match: return type + Class expectedType = instantiatedMethodType.returnType(); + Class actualReturnType = + (implKind == MethodHandleInfo.REF_newInvokeSpecial) + ? implDefiningClass + : implMethodType.returnType(); + Class samReturnType = samMethodType.returnType(); + if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda return: %s is not convertible to %s", + actualReturnType, expectedType)); + } + if (!isAdaptableToAsReturnStrict(expectedType, samReturnType)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda expected return: %s is not convertible to %s", + expectedType, samReturnType)); + } + for (MethodType bridgeMT : additionalBridges) { + if (!isAdaptableToAsReturnStrict(expectedType, bridgeMT.returnType())) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda expected return: %s is not convertible to %s", + expectedType, bridgeMT.returnType())); + } + } + } + + /** + * Check type adaptability for parameter types. + * @param fromType Type to convert from + * @param toType Type to convert to + * @param strict If true, do strict checks, else allow that fromType may be parameterized + * @return True if 'fromType' can be passed to an argument of 'toType' + */ + private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { + if (fromType.equals(toType)) { + return true; + } + if (fromType.isPrimitive()) { + Wrapper wfrom = forPrimitiveType(fromType); + if (toType.isPrimitive()) { + // both are primitive: widening + Wrapper wto = forPrimitiveType(toType); + return wto.isConvertibleFrom(wfrom); + } else { + // from primitive to reference: boxing + return toType.isAssignableFrom(wfrom.wrapperType()); + } + } else { + if (toType.isPrimitive()) { + // from reference to primitive: unboxing + Wrapper wfrom; + if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) { + // fromType is a primitive wrapper; unbox+widen + Wrapper wto = forPrimitiveType(toType); + return wto.isConvertibleFrom(wfrom); + } else { + // must be convertible to primitive + return !strict; + } + } else { + // both are reference types: fromType should be a superclass of toType. + return !strict || toType.isAssignableFrom(fromType); + } + } + } + + /** + * Check type adaptability for return types -- + * special handling of void type) and parameterized fromType + * @return True if 'fromType' can be converted to 'toType' + */ + private boolean isAdaptableToAsReturn(Class fromType, Class toType) { + return toType.equals(void.class) + || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false); + } + private boolean isAdaptableToAsReturnStrict(Class fromType, Class toType) { + if (fromType.equals(void.class)) return toType.equals(void.class); + return isAdaptableTo(fromType, toType, true); + } + + + /*********** Logging support -- for debugging only, uncomment as needed + static final Executor logPool = Executors.newSingleThreadExecutor(); + protected static void log(final String s) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + } + }); + } + + protected static void log(final String s, final Throwable e) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + e.printStackTrace(System.out); + } + }); + } + ***********************/ + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/BoundMethodHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/BoundMethodHandle.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,864 @@ +/* + * 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 static jdk.internal.org.objectweb.asm.Opcodes.*; +import static java.lang.invoke.LambdaForm.basicTypes; +import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeStatic; +import static java.lang.invoke.MethodHandleStatics.*; + +import java.lang.invoke.LambdaForm.Name; +import java.lang.invoke.LambdaForm.NamedFunction; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; + +import sun.invoke.util.ValueConversions; +import sun.invoke.util.Wrapper; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Type; + +/** + * The flavor of method handle which emulates an invoke instruction + * on a predetermined argument. The JVM dispatches to the correct method + * when the handle is created, not when it is invoked. + * + * All bound arguments are encapsulated in dedicated species. + */ +/* non-public */ abstract class BoundMethodHandle extends MethodHandle { + + /* non-public */ BoundMethodHandle(MethodType type, LambdaForm form) { + super(type, form); + } + + // + // BMH API and internals + // + + static MethodHandle bindSingle(MethodType type, LambdaForm form, char xtype, Object x) { + // for some type signatures, there exist pre-defined concrete BMH classes + try { + switch (xtype) { + case 'L': + if (true) return bindSingle(type, form, x); // Use known fast path. + return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('L').constructor[0].invokeBasic(type, form, x); + case 'I': + return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('I').constructor[0].invokeBasic(type, form, ValueConversions.widenSubword(x)); + case 'J': + return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('J').constructor[0].invokeBasic(type, form, (long) x); + case 'F': + return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('F').constructor[0].invokeBasic(type, form, (float) x); + case 'D': + return (BoundMethodHandle) SpeciesData.EMPTY.extendWithType('D').constructor[0].invokeBasic(type, form, (double) x); + default : throw new InternalError("unexpected xtype: " + xtype); + } + } catch (Throwable t) { + throw newInternalError(t); + } + } + + static MethodHandle bindSingle(MethodType type, LambdaForm form, Object x) { + return new Species_L(type, form, x); + } + + MethodHandle cloneExtend(MethodType type, LambdaForm form, char xtype, Object x) { + try { + switch (xtype) { + case 'L': return cloneExtendL(type, form, x); + case 'I': return cloneExtendI(type, form, ValueConversions.widenSubword(x)); + case 'J': return cloneExtendJ(type, form, (long) x); + case 'F': return cloneExtendF(type, form, (float) x); + case 'D': return cloneExtendD(type, form, (double) x); + } + } catch (Throwable t) { + throw newInternalError(t); + } + throw new InternalError("unexpected type: " + xtype); + } + + @Override + MethodHandle bindArgument(int pos, char basicType, Object value) { + MethodType type = type().dropParameterTypes(pos, pos+1); + LambdaForm form = internalForm().bind(1+pos, speciesData()); + return cloneExtend(type, form, basicType, value); + } + + @Override + MethodHandle dropArguments(MethodType srcType, int pos, int drops) { + LambdaForm form = internalForm().addArguments(pos, srcType.parameterList().subList(pos, pos+drops)); + try { + return clone(srcType, form); + } catch (Throwable t) { + throw newInternalError(t); + } + } + + @Override + MethodHandle permuteArguments(MethodType newType, int[] reorder) { + try { + return clone(newType, form.permuteArguments(1, reorder, basicTypes(newType.parameterList()))); + } catch (Throwable t) { + throw newInternalError(t); + } + } + + static final String EXTENSION_TYPES = "LIJFD"; + static final byte INDEX_L = 0, INDEX_I = 1, INDEX_J = 2, INDEX_F = 3, INDEX_D = 4; + static byte extensionIndex(char type) { + int i = EXTENSION_TYPES.indexOf(type); + if (i < 0) throw new InternalError(); + return (byte) i; + } + + /** + * Return the {@link SpeciesData} instance representing this BMH species. All subclasses must provide a + * static field containing this value, and they must accordingly implement this method. + */ + protected abstract SpeciesData speciesData(); + + @Override + final Object internalProperties() { + return "/BMH="+internalValues(); + } + + @Override + final Object internalValues() { + Object[] boundValues = new Object[speciesData().fieldCount()]; + for (int i = 0; i < boundValues.length; ++i) { + boundValues[i] = arg(i); + } + return Arrays.asList(boundValues); + } + + public final Object arg(int i) { + try { + switch (speciesData().fieldType(i)) { + case 'L': return argL(i); + case 'I': return argI(i); + case 'F': return argF(i); + case 'D': return argD(i); + case 'J': return argJ(i); + } + } catch (Throwable ex) { + throw newInternalError(ex); + } + throw new InternalError("unexpected type: " + speciesData().types+"."+i); + } + public final Object argL(int i) throws Throwable { return speciesData().getters[i].invokeBasic(this); } + public final int argI(int i) throws Throwable { return (int) speciesData().getters[i].invokeBasic(this); } + public final float argF(int i) throws Throwable { return (float) speciesData().getters[i].invokeBasic(this); } + public final double argD(int i) throws Throwable { return (double) speciesData().getters[i].invokeBasic(this); } + public final long argJ(int i) throws Throwable { return (long) speciesData().getters[i].invokeBasic(this); } + + // + // cloning API + // + + public abstract BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable; + public abstract BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable; + public abstract BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable; + public abstract BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable; + public abstract BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable; + public abstract BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable; + + // The following is a grossly irregular hack: + @Override MethodHandle reinvokerTarget() { + try { + return (MethodHandle) argL(0); + } catch (Throwable ex) { + throw newInternalError(ex); + } + } + + // + // concrete BMH classes required to close bootstrap loops + // + + private // make it private to force users to access the enclosing class first + static final class Species_L extends BoundMethodHandle { + final Object argL0; + public Species_L(MethodType mt, LambdaForm lf, Object argL0) { + super(mt, lf); + this.argL0 = argL0; + } + // The following is a grossly irregular hack: + @Override MethodHandle reinvokerTarget() { return (MethodHandle) argL0; } + @Override + public SpeciesData speciesData() { + return SPECIES_DATA; + } + public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("L", Species_L.class); + @Override + public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable { + return new Species_L(mt, lf, argL0); + } + @Override + public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, narg); + } + @Override + public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, narg); + } + @Override + public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, narg); + } + @Override + public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, narg); + } + @Override + public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, narg); + } + } + +/* + static final class Species_LL extends BoundMethodHandle { + final Object argL0; + final Object argL1; + public Species_LL(MethodType mt, LambdaForm lf, Object argL0, Object argL1) { + super(mt, lf); + this.argL0 = argL0; + this.argL1 = argL1; + } + @Override + public SpeciesData speciesData() { + return SPECIES_DATA; + } + public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("LL", Species_LL.class); + @Override + public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable { + return new Species_LL(mt, lf, argL0, argL1); + } + @Override + public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, argL1, narg); + } + } + + static final class Species_JL extends BoundMethodHandle { + final long argJ0; + final Object argL1; + public Species_JL(MethodType mt, LambdaForm lf, long argJ0, Object argL1) { + super(mt, lf); + this.argJ0 = argJ0; + this.argL1 = argL1; + } + @Override + public SpeciesData speciesData() { + return SPECIES_DATA; + } + public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("JL", Species_JL.class); + @Override public final long argJ0() { return argJ0; } + @Override public final Object argL1() { return argL1; } + @Override + public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) throws Throwable { + return new Species_JL(mt, lf, argJ0, argL1); + } + @Override + public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg); + } + @Override + public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) throws Throwable { + return (BoundMethodHandle) SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argJ0, argL1, narg); + } + } +*/ + + // + // BMH species meta-data + // + + /** + * Meta-data wrapper for concrete BMH classes. + */ + static class SpeciesData { + final String types; + final Class clazz; + // Bootstrapping requires circular relations MH -> BMH -> SpeciesData -> MH + // Therefore, we need a non-final link in the chain. Use array elements. + final MethodHandle[] constructor; + final MethodHandle[] getters; + final SpeciesData[] extensions; + + public int fieldCount() { + return types.length(); + } + public char fieldType(int i) { + return types.charAt(i); + } + + public String toString() { + return "SpeciesData["+(isPlaceholder() ? "" : clazz.getSimpleName())+":"+types+"]"; + } + + /** + * Return a {@link LambdaForm.Name} containing a {@link LambdaForm.NamedFunction} that + * represents a MH bound to a generic invoker, which in turn forwards to the corresponding + * getter. + */ + Name getterName(Name mhName, int i) { + MethodHandle mh = getters[i]; + assert(mh != null) : this+"."+i; + return new Name(mh, mhName); + } + + NamedFunction getterFunction(int i) { + return new NamedFunction(getters[i]); + } + + static final SpeciesData EMPTY = new SpeciesData("", BoundMethodHandle.class); + + private SpeciesData(String types, Class clazz) { + this.types = types; + this.clazz = clazz; + if (!INIT_DONE) { + this.constructor = new MethodHandle[1]; + this.getters = new MethodHandle[types.length()]; + } else { + this.constructor = Factory.makeCtors(clazz, types, null); + this.getters = Factory.makeGetters(clazz, types, null); + } + this.extensions = new SpeciesData[EXTENSION_TYPES.length()]; + } + + private void initForBootstrap() { + assert(!INIT_DONE); + if (constructor[0] == null) { + Factory.makeCtors(clazz, types, this.constructor); + Factory.makeGetters(clazz, types, this.getters); + } + } + + private SpeciesData(String types) { + // Placeholder only. + this.types = types; + this.clazz = null; + this.constructor = null; + this.getters = null; + this.extensions = null; + } + private boolean isPlaceholder() { return clazz == null; } + + private static final HashMap CACHE = new HashMap<>(); + static { CACHE.put("", EMPTY); } // make bootstrap predictable + private static final boolean INIT_DONE; // set after finishes... + + SpeciesData extendWithType(char type) { + int i = extensionIndex(type); + SpeciesData d = extensions[i]; + if (d != null) return d; + extensions[i] = d = get(types+type); + return d; + } + + SpeciesData extendWithIndex(byte index) { + SpeciesData d = extensions[index]; + if (d != null) return d; + extensions[index] = d = get(types+EXTENSION_TYPES.charAt(index)); + return d; + } + + private static SpeciesData get(String types) { + // Acquire cache lock for query. + SpeciesData d = lookupCache(types); + if (!d.isPlaceholder()) + return d; + synchronized (d) { + // Use synch. on the placeholder to prevent multiple instantiation of one species. + // Creating this class forces a recursive call to getForClass. + if (lookupCache(types).isPlaceholder()) + Factory.generateConcreteBMHClass(types); + } + // Reacquire cache lock. + d = lookupCache(types); + // Class loading must have upgraded the cache. + assert(d != null && !d.isPlaceholder()); + return d; + } + static SpeciesData getForClass(String types, Class clazz) { + // clazz is a new class which is initializing its SPECIES_DATA field + return updateCache(types, new SpeciesData(types, clazz)); + } + private static synchronized SpeciesData lookupCache(String types) { + SpeciesData d = CACHE.get(types); + if (d != null) return d; + d = new SpeciesData(types); + assert(d.isPlaceholder()); + CACHE.put(types, d); + return d; + } + private static synchronized SpeciesData updateCache(String types, SpeciesData d) { + SpeciesData d2; + assert((d2 = CACHE.get(types)) == null || d2.isPlaceholder()); + assert(!d.isPlaceholder()); + CACHE.put(types, d); + return d; + } + + static { + // pre-fill the BMH speciesdata cache with BMH's inner classes + final Class rootCls = BoundMethodHandle.class; + SpeciesData d0 = BoundMethodHandle.SPECIES_DATA; // trigger class init + assert(d0 == null || d0 == lookupCache("")) : d0; + try { + for (Class c : rootCls.getDeclaredClasses()) { + if (rootCls.isAssignableFrom(c)) { + final Class cbmh = c.asSubclass(BoundMethodHandle.class); + SpeciesData d = Factory.speciesDataFromConcreteBMHClass(cbmh); + assert(d != null) : cbmh.getName(); + assert(d.clazz == cbmh); + assert(d == lookupCache(d.types)); + } + } + } catch (Throwable e) { + throw newInternalError(e); + } + + for (SpeciesData d : CACHE.values()) { + d.initForBootstrap(); + } + // Note: Do not simplify this, because INIT_DONE must not be + // a compile-time constant during bootstrapping. + INIT_DONE = Boolean.TRUE; + } + } + + static SpeciesData getSpeciesData(String types) { + return SpeciesData.get(types); + } + + /** + * Generation of concrete BMH classes. + * + * A concrete BMH species is fit for binding a number of values adhering to a + * given type pattern. Reference types are erased. + * + * BMH species are cached by type pattern. + * + * A BMH species has a number of fields with the concrete (possibly erased) types of + * bound values. Setters are provided as an API in BMH. Getters are exposed as MHs, + * which can be included as names in lambda forms. + */ + static class Factory { + + static final String JLO_SIG = "Ljava/lang/Object;"; + static final String JLS_SIG = "Ljava/lang/String;"; + static final String JLC_SIG = "Ljava/lang/Class;"; + static final String MH = "java/lang/invoke/MethodHandle"; + static final String MH_SIG = "L"+MH+";"; + static final String BMH = "java/lang/invoke/BoundMethodHandle"; + static final String BMH_SIG = "L"+BMH+";"; + static final String SPECIES_DATA = "java/lang/invoke/BoundMethodHandle$SpeciesData"; + static final String SPECIES_DATA_SIG = "L"+SPECIES_DATA+";"; + + static final String SPECIES_PREFIX_NAME = "Species_"; + static final String SPECIES_PREFIX_PATH = BMH + "$" + SPECIES_PREFIX_NAME; + + static final String BMHSPECIES_DATA_EWI_SIG = "(B)" + SPECIES_DATA_SIG; + static final String BMHSPECIES_DATA_GFC_SIG = "(" + JLS_SIG + JLC_SIG + ")" + SPECIES_DATA_SIG; + static final String MYSPECIES_DATA_SIG = "()" + SPECIES_DATA_SIG; + static final String VOID_SIG = "()V"; + + static final String SIG_INCIPIT = "(Ljava/lang/invoke/MethodType;Ljava/lang/invoke/LambdaForm;"; + + static final Class[] TYPES = new Class[] { Object.class, int.class, long.class, float.class, double.class }; + + static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" }; + + /** + * Generate a concrete subclass of BMH for a given combination of bound types. + * + * A concrete BMH species adheres to the following schema: + * + *
+         * class Species_[[types]] extends BoundMethodHandle {
+         *     [[fields]]
+         *     final SpeciesData speciesData() { return SpeciesData.get("[[types]]"); }
+         * }
+         * 
+ * + * The {@code [[types]]} signature is precisely the string that is passed to this + * method. + * + * The {@code [[fields]]} section consists of one field definition per character in + * the type signature, adhering to the naming schema described in the definition of + * {@link #makeFieldName}. + * + * For example, a concrete BMH species for two reference and one integral bound values + * would have the following shape: + * + *
+         * class BoundMethodHandle { ... private static
+         * final class Species_LLI extends BoundMethodHandle {
+         *     final Object argL0;
+         *     final Object argL1;
+         *     final int argI2;
+         *     public Species_LLI(MethodType mt, LambdaForm lf, Object argL0, Object argL1, int argI2) {
+         *         super(mt, lf);
+         *         this.argL0 = argL0;
+         *         this.argL1 = argL1;
+         *         this.argI2 = argI2;
+         *     }
+         *     public final SpeciesData speciesData() { return SPECIES_DATA; }
+         *     public static final SpeciesData SPECIES_DATA = SpeciesData.getForClass("LLI", Species_LLI.class);
+         *     public final BoundMethodHandle clone(MethodType mt, LambdaForm lf) {
+         *         return SPECIES_DATA.constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2);
+         *     }
+         *     public final BoundMethodHandle cloneExtendL(MethodType mt, LambdaForm lf, Object narg) {
+         *         return SPECIES_DATA.extendWithIndex(INDEX_L).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
+         *     }
+         *     public final BoundMethodHandle cloneExtendI(MethodType mt, LambdaForm lf, int narg) {
+         *         return SPECIES_DATA.extendWithIndex(INDEX_I).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
+         *     }
+         *     public final BoundMethodHandle cloneExtendJ(MethodType mt, LambdaForm lf, long narg) {
+         *         return SPECIES_DATA.extendWithIndex(INDEX_J).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
+         *     }
+         *     public final BoundMethodHandle cloneExtendF(MethodType mt, LambdaForm lf, float narg) {
+         *         return SPECIES_DATA.extendWithIndex(INDEX_F).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
+         *     }
+         *     public final BoundMethodHandle cloneExtendD(MethodType mt, LambdaForm lf, double narg) {
+         *         return SPECIES_DATA.extendWithIndex(INDEX_D).constructor[0].invokeBasic(mt, lf, argL0, argL1, argI2, narg);
+         *     }
+         * }
+         * 
+ * + * @param types the type signature, wherein reference types are erased to 'L' + * @return the generated concrete BMH class + */ + static Class generateConcreteBMHClass(String types) { + final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + + final String className = SPECIES_PREFIX_PATH + types; + final String sourceFile = SPECIES_PREFIX_NAME + types; + cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, BMH, null); + cw.visitSource(sourceFile, null); + + // emit static types and SPECIES_DATA fields + cw.visitField(ACC_PUBLIC + ACC_STATIC, "SPECIES_DATA", SPECIES_DATA_SIG, null, null).visitEnd(); + + // emit bound argument fields + for (int i = 0; i < types.length(); ++i) { + final char t = types.charAt(i); + final String fieldName = makeFieldName(types, i); + final String fieldDesc = t == 'L' ? JLO_SIG : String.valueOf(t); + cw.visitField(ACC_FINAL, fieldName, fieldDesc, null, null).visitEnd(); + } + + MethodVisitor mv; + + // emit constructor + mv = cw.visitMethod(ACC_PUBLIC, "", makeSignature(types, true), null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + + mv.visitMethodInsn(INVOKESPECIAL, BMH, "", makeSignature("", true)); + + for (int i = 0, j = 0; i < types.length(); ++i, ++j) { + // i counts the arguments, j counts corresponding argument slots + char t = types.charAt(i); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(typeLoadOp(t), j + 3); // parameters start at 3 + mv.visitFieldInsn(PUTFIELD, className, makeFieldName(types, i), typeSig(t)); + if (t == 'J' || t == 'D') { + ++j; // adjust argument register access + } + } + + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // emit implementation of reinvokerTarget() + mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "reinvokerTarget", "()" + MH_SIG, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, className, "argL0", JLO_SIG); + mv.visitTypeInsn(CHECKCAST, MH); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // emit implementation of speciesData() + mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "speciesData", MYSPECIES_DATA_SIG, null, null); + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // emit clone() + mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "clone", makeSignature("", false), null, E_THROWABLE); + mv.visitCode(); + // return speciesData().constructor[0].invokeBasic(mt, lf, argL0, ...) + // obtain constructor + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG); + mv.visitFieldInsn(GETFIELD, SPECIES_DATA, "constructor", "[" + MH_SIG); + mv.visitInsn(ICONST_0); + mv.visitInsn(AALOAD); + // load mt, lf + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + // put fields on the stack + emitPushFields(types, className, mv); + // finally, invoke the constructor and return + mv.visitMethodInsn(INVOKEVIRTUAL, MH, "invokeBasic", makeSignature(types, false)); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // for each type, emit cloneExtendT() + for (Class c : TYPES) { + char t = Wrapper.basicTypeChar(c); + mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "cloneExtend" + t, makeSignature(String.valueOf(t), false), null, E_THROWABLE); + mv.visitCode(); + // return SPECIES_DATA.extendWithIndex(extensionIndex(t)).constructor[0].invokeBasic(mt, lf, argL0, ..., narg) + // obtain constructor + mv.visitFieldInsn(GETSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG); + int iconstInsn = ICONST_0 + extensionIndex(t); + assert(iconstInsn <= ICONST_5); + mv.visitInsn(iconstInsn); + mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA, "extendWithIndex", BMHSPECIES_DATA_EWI_SIG); + mv.visitFieldInsn(GETFIELD, SPECIES_DATA, "constructor", "[" + MH_SIG); + mv.visitInsn(ICONST_0); + mv.visitInsn(AALOAD); + // load mt, lf + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + // put fields on the stack + emitPushFields(types, className, mv); + // put narg on stack + mv.visitVarInsn(typeLoadOp(t), 3); + // finally, invoke the constructor and return + mv.visitMethodInsn(INVOKEVIRTUAL, MH, "invokeBasic", makeSignature(types + t, false)); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + // emit class initializer + mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "", VOID_SIG, null, null); + mv.visitCode(); + mv.visitLdcInsn(types); + mv.visitLdcInsn(Type.getObjectType(className)); + mv.visitMethodInsn(INVOKESTATIC, SPECIES_DATA, "getForClass", BMHSPECIES_DATA_GFC_SIG); + mv.visitFieldInsn(PUTSTATIC, className, "SPECIES_DATA", SPECIES_DATA_SIG); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + + // load class + final byte[] classFile = cw.toByteArray(); + InvokerBytecodeGenerator.maybeDump(className, classFile); + Class bmhClass = + //UNSAFE.defineAnonymousClass(BoundMethodHandle.class, classFile, null).asSubclass(BoundMethodHandle.class); + UNSAFE.defineClass(className, classFile, 0, classFile.length, + BoundMethodHandle.class.getClassLoader(), null) + .asSubclass(BoundMethodHandle.class); + UNSAFE.ensureClassInitialized(bmhClass); + + return bmhClass; + } + + private static int typeLoadOp(char t) { + switch (t) { + case 'L': return ALOAD; + case 'I': return ILOAD; + case 'J': return LLOAD; + case 'F': return FLOAD; + case 'D': return DLOAD; + default : throw new InternalError("unrecognized type " + t); + } + } + + private static void emitPushFields(String types, String className, MethodVisitor mv) { + for (int i = 0; i < types.length(); ++i) { + char tc = types.charAt(i); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, className, makeFieldName(types, i), typeSig(tc)); + } + } + + static String typeSig(char t) { + return t == 'L' ? JLO_SIG : String.valueOf(t); + } + + // + // Getter MH generation. + // + + private static MethodHandle makeGetter(Class cbmhClass, String types, int index) { + String fieldName = makeFieldName(types, index); + Class fieldType = Wrapper.forBasicType(types.charAt(index)).primitiveType(); + try { + return LOOKUP.findGetter(cbmhClass, fieldName, fieldType); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw newInternalError(e); + } + } + + static MethodHandle[] makeGetters(Class cbmhClass, String types, MethodHandle[] mhs) { + if (mhs == null) mhs = new MethodHandle[types.length()]; + for (int i = 0; i < mhs.length; ++i) { + mhs[i] = makeGetter(cbmhClass, types, i); + assert(mhs[i].internalMemberName().getDeclaringClass() == cbmhClass); + } + return mhs; + } + + static MethodHandle[] makeCtors(Class cbmh, String types, MethodHandle mhs[]) { + if (mhs == null) mhs = new MethodHandle[1]; + mhs[0] = makeCbmhCtor(cbmh, types); + return mhs; + } + + // + // Auxiliary methods. + // + + static SpeciesData speciesDataFromConcreteBMHClass(Class cbmh) { + try { + Field F_SPECIES_DATA = cbmh.getDeclaredField("SPECIES_DATA"); + return (SpeciesData) F_SPECIES_DATA.get(null); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } + + /** + * Field names in concrete BMHs adhere to this pattern: + * arg + type + index + * where type is a single character (L, I, J, F, D). + */ + private static String makeFieldName(String types, int index) { + assert index >= 0 && index < types.length(); + return "arg" + types.charAt(index) + index; + } + + private static String makeSignature(String types, boolean ctor) { + StringBuilder buf = new StringBuilder(SIG_INCIPIT); + for (char c : types.toCharArray()) { + buf.append(typeSig(c)); + } + return buf.append(')').append(ctor ? "V" : BMH_SIG).toString(); + } + + static MethodHandle makeCbmhCtor(Class cbmh, String types) { + try { + return linkConstructor(LOOKUP.findConstructor(cbmh, MethodType.fromMethodDescriptorString(makeSignature(types, true), null))); + } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | TypeNotPresentException e) { + throw newInternalError(e); + } + } + + /** + * Wrap a constructor call in a {@link LambdaForm}. + * + * If constructors ({@code } methods) are called in LFs, problems might arise if the LFs + * are turned into bytecode, because the call to the allocator is routed through an MH, and the + * verifier cannot find a {@code NEW} instruction preceding the {@code INVOKESPECIAL} to + * {@code }. To avoid this, we add an indirection by invoking {@code } through + * {@link MethodHandle#linkToSpecial}. + * + * The last {@link LambdaForm.Name Name} in the argument's form is expected to be the {@code void} + * result of the {@code } invocation. This entry is replaced. + */ + private static MethodHandle linkConstructor(MethodHandle cmh) { + final LambdaForm lf = cmh.form; + final int initNameIndex = lf.names.length - 1; + final Name initName = lf.names[initNameIndex]; + final MemberName ctorMN = initName.function.member; + final MethodType ctorMT = ctorMN.getInvocationType(); + + // obtain function member (call target) + // linker method type replaces initial parameter (BMH species) with BMH to avoid naming a species (anonymous class!) + final MethodType linkerMT = ctorMT.changeParameterType(0, BoundMethodHandle.class).appendParameterTypes(MemberName.class); + MemberName linkerMN = new MemberName(MethodHandle.class, "linkToSpecial", linkerMT, REF_invokeStatic); + try { + linkerMN = MemberName.getFactory().resolveOrFail(REF_invokeStatic, linkerMN, null, NoSuchMethodException.class); + assert(linkerMN.isStatic()); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + // extend arguments array + Object[] newArgs = Arrays.copyOf(initName.arguments, initName.arguments.length + 1); + newArgs[newArgs.length - 1] = ctorMN; + // replace function + final NamedFunction nf = new NamedFunction(linkerMN); + final Name linkedCtor = new Name(nf, newArgs); + linkedCtor.initIndex(initNameIndex); + lf.names[initNameIndex] = linkedCtor; + return cmh; + } + + } + + private static final Lookup LOOKUP = Lookup.IMPL_LOOKUP; + + /** + * All subclasses must provide such a value describing their type signature. + */ + static final SpeciesData SPECIES_DATA = SpeciesData.EMPTY; +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/CallSite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/CallSite.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,347 @@ +/* + * 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 sun.invoke.empty.Empty; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; + +/** + * A {@code CallSite} is a holder for a variable {@link MethodHandle}, + * which is called its {@code target}. + * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates + * all calls to the site's current target. + * A {@code CallSite} may be associated with several {@code invokedynamic} + * instructions, or it may be "free floating", associated with none. + * In any case, it may be invoked through an associated method handle + * called its {@linkplain #dynamicInvoker dynamic invoker}. + *

+ * {@code CallSite} is an abstract class which does not allow + * direct subclassing by users. It has three immediate, + * concrete subclasses that may be either instantiated or subclassed. + *

    + *
  • If a mutable target is not required, an {@code invokedynamic} instruction + * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}. + *
  • If a mutable target is required which has volatile variable semantics, + * because updates to the target must be immediately and reliably witnessed by other threads, + * a {@linkplain VolatileCallSite volatile call site} may be used. + *
  • Otherwise, if a mutable target is required, + * a {@linkplain MutableCallSite mutable call site} may be used. + *
+ *

+ * A non-constant call site may be relinked by changing its target. + * The new target must have the same {@linkplain MethodHandle#type() type} + * as the previous target. + * Thus, though a call site can be relinked to a series of + * successive targets, it cannot change its type. + *

+ * Here is a sample use of call sites and bootstrap methods which links every + * dynamic call site to print its arguments: +

{@code
+static void test() throws Throwable {
+    // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION
+    InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14);
+}
+private static void printArgs(Object... args) {
+  System.out.println(java.util.Arrays.deepToString(args));
+}
+private static final MethodHandle printArgs;
+static {
+  MethodHandles.Lookup lookup = MethodHandles.lookup();
+  Class thisClass = lookup.lookupClass();  // (who am I?)
+  printArgs = lookup.findStatic(thisClass,
+      "printArgs", MethodType.methodType(void.class, Object[].class));
+}
+private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
+  // ignore caller and name, but match the type:
+  return new ConstantCallSite(printArgs.asType(type));
+}
+}
+ * @author John Rose, JSR 292 EG + */ +abstract +public class CallSite { + static { MethodHandleImpl.initStatics(); } + + // The actual payload of this call site: + /*package-private*/ + MethodHandle target; // Note: This field is known to the JVM. Do not change. + + /** + * Make a blank call site object with the given method type. + * An initial target method is supplied which will throw + * an {@link IllegalStateException} if called. + *

+ * Before this {@code CallSite} object is returned from a bootstrap method, + * it is usually provided with a more useful target method, + * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}. + * @throws NullPointerException if the proposed type is null + */ + /*package-private*/ + CallSite(MethodType type) { + target = type.invokers().uninitializedCallSite(); + } + + /** + * Make a call site object equipped with an initial target method handle. + * @param target the method handle which will be the initial target of the call site + * @throws NullPointerException if the proposed target is null + */ + /*package-private*/ + CallSite(MethodHandle target) { + target.type(); // null check + this.target = target; + } + + /** + * Make a call site object equipped with an initial target method handle. + * @param targetType the desired type of the call site + * @param createTargetHook a hook which will bind the call site to the target method handle + * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments, + * or if the target returned by the hook is not of the given {@code targetType} + * @throws NullPointerException if the hook returns a null value + * @throws ClassCastException if the hook returns something other than a {@code MethodHandle} + * @throws Throwable anything else thrown by the hook function + */ + /*package-private*/ + CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable { + this(targetType); + ConstantCallSite selfCCS = (ConstantCallSite) this; + MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS); + checkTargetChange(this.target, boundTarget); + this.target = boundTarget; + } + + /** + * Returns the type of this call site's target. + * Although targets may change, any call site's type is permanent, and can never change to an unequal type. + * The {@code setTarget} method enforces this invariant by refusing any new target that does + * not have the previous target's type. + * @return the type of the current target, which is also the type of any future target + */ + public MethodType type() { + // warning: do not call getTarget here, because CCS.getTarget can throw IllegalStateException + return target.type(); + } + + /** + * Returns the target method of the call site, according to the + * behavior defined by this call site's specific class. + * The immediate subclasses of {@code CallSite} document the + * class-specific behaviors of this method. + * + * @return the current linkage state of the call site, its target method handle + * @see ConstantCallSite + * @see VolatileCallSite + * @see #setTarget + * @see ConstantCallSite#getTarget + * @see MutableCallSite#getTarget + * @see VolatileCallSite#getTarget + */ + public abstract MethodHandle getTarget(); + + /** + * Updates the target method of this call site, according to the + * behavior defined by this call site's specific class. + * The immediate subclasses of {@code CallSite} document the + * class-specific behaviors of this method. + *

+ * The type of the new target must be {@linkplain MethodType#equals equal to} + * the type of the old target. + * + * @param newTarget the new target + * @throws NullPointerException if the proposed new target is null + * @throws WrongMethodTypeException if the proposed new target + * has a method type that differs from the previous target + * @see CallSite#getTarget + * @see ConstantCallSite#setTarget + * @see MutableCallSite#setTarget + * @see VolatileCallSite#setTarget + */ + public abstract void setTarget(MethodHandle newTarget); + + void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) { + MethodType oldType = oldTarget.type(); + MethodType newType = newTarget.type(); // null check! + if (!newType.equals(oldType)) + throw wrongTargetType(newTarget, oldType); + } + + private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) { + return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type); + } + + /** + * Produces a method handle equivalent to an invokedynamic instruction + * which has been linked to this call site. + *

+ * This method is equivalent to the following code: + *

{@code
+     * MethodHandle getTarget, invoker, result;
+     * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
+     * invoker = MethodHandles.exactInvoker(this.type());
+     * result = MethodHandles.foldArguments(invoker, getTarget)
+     * }
+ * + * @return a method handle which always invokes this call site's current target + */ + public abstract MethodHandle dynamicInvoker(); + + /*non-public*/ MethodHandle makeDynamicInvoker() { + MethodHandle getTarget = GET_TARGET.bindReceiver(this); + MethodHandle invoker = MethodHandles.exactInvoker(this.type()); + return MethodHandles.foldArguments(invoker, getTarget); + } + + private static final MethodHandle GET_TARGET; + static { + try { + GET_TARGET = IMPL_LOOKUP. + findVirtual(CallSite.class, "getTarget", MethodType.methodType(MethodHandle.class)); + } catch (ReflectiveOperationException e) { + throw newInternalError(e); + } + } + + /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */ + /*package-private*/ + static Empty uninitializedCallSite() { + throw new IllegalStateException("uninitialized call site"); + } + + // unsafe stuff: + private static final long TARGET_OFFSET; + static { + try { + TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target")); + } catch (Exception ex) { throw new Error(ex); } + } + + /*package-private*/ + void setTargetNormal(MethodHandle newTarget) { + MethodHandleNatives.setCallSiteTargetNormal(this, newTarget); + } + /*package-private*/ + MethodHandle getTargetVolatile() { + return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET); + } + /*package-private*/ + void setTargetVolatile(MethodHandle newTarget) { + MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget); + } + + // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite: + static CallSite makeSite(MethodHandle bootstrapMethod, + // Callee information: + String name, MethodType type, + // Extra arguments for BSM, if any: + Object info, + // Caller information: + Class callerClass) { + MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass); + CallSite site; + try { + Object binding; + info = maybeReBox(info); + if (info == null) { + binding = bootstrapMethod.invoke(caller, name, type); + } else if (!info.getClass().isArray()) { + binding = bootstrapMethod.invoke(caller, name, type, info); + } else { + Object[] argv = (Object[]) info; + maybeReBoxElements(argv); + switch (argv.length) { + case 0: + binding = bootstrapMethod.invoke(caller, name, type); + break; + case 1: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0]); + break; + case 2: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0], argv[1]); + break; + case 3: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0], argv[1], argv[2]); + break; + case 4: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0], argv[1], argv[2], argv[3]); + break; + case 5: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0], argv[1], argv[2], argv[3], argv[4]); + break; + case 6: + binding = bootstrapMethod.invoke(caller, name, type, + argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); + break; + default: + final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type) + if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY) + throw new BootstrapMethodError("too many bootstrap method arguments"); + MethodType bsmType = bootstrapMethod.type(); + MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length); + MethodHandle typedBSM = bootstrapMethod.asType(invocationType); + MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT); + binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv); + } + } + //System.out.println("BSM for "+name+type+" => "+binding); + if (binding instanceof CallSite) { + site = (CallSite) binding; + } else { + throw new ClassCastException("bootstrap method failed to produce a CallSite"); + } + if (!site.getTarget().type().equals(type)) + throw new WrongMethodTypeException("wrong type: "+site.getTarget()); + } catch (Throwable ex) { + BootstrapMethodError bex; + if (ex instanceof BootstrapMethodError) + bex = (BootstrapMethodError) ex; + else + bex = new BootstrapMethodError("call site initialization exception", ex); + throw bex; + } + return site; + } + + private 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; + } + private static void maybeReBoxElements(Object[] xa) { + for (int i = 0; i < xa.length; i++) { + xa[i] = maybeReBox(xa[i]); + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/ConstantCallSite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/ConstantCallSite.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010, 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; + +/** + * A {@code ConstantCallSite} is a {@link CallSite} whose target is permanent, and can never be changed. + * An {@code invokedynamic} instruction linked to a {@code ConstantCallSite} is permanently + * bound to the call site's target. + * @author John Rose, JSR 292 EG + */ +public class ConstantCallSite extends CallSite { + private final boolean isFrozen; + + /** + * Creates a call site with a permanent target. + * @param target the target to be permanently associated with this call site + * @throws NullPointerException if the proposed target is null + */ + public ConstantCallSite(MethodHandle target) { + super(target); + isFrozen = true; + } + + /** + * Creates a call site with a permanent target, possibly bound to the call site itself. + *

+ * During construction of the call site, the {@code createTargetHook} is invoked to + * produce the actual target, as if by a call of the form + * {@code (MethodHandle) createTargetHook.invoke(this)}. + *

+ * Note that user code cannot perform such an action directly in a subclass constructor, + * since the target must be fixed before the {@code ConstantCallSite} constructor returns. + *

+ * The hook is said to bind the call site to a target method handle, + * and a typical action would be {@code someTarget.bindTo(this)}. + * However, the hook is free to take any action whatever, + * including ignoring the call site and returning a constant target. + *

+ * The result returned by the hook must be a method handle of exactly + * the same type as the call site. + *

+ * While the hook is being called, the new {@code ConstantCallSite} + * object is in a partially constructed state. + * In this state, + * a call to {@code getTarget}, or any other attempt to use the target, + * will result in an {@code IllegalStateException}. + * It is legal at all times to obtain the call site's type using the {@code type} method. + * + * @param targetType the type of the method handle to be permanently associated with this call site + * @param createTargetHook a method handle to invoke (on the call site) to produce the call site's target + * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments, + * or if the target returned by the hook is not of the given {@code targetType} + * @throws NullPointerException if the hook returns a null value + * @throws ClassCastException if the hook returns something other than a {@code MethodHandle} + * @throws Throwable anything else thrown by the hook function + */ + protected ConstantCallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable { + super(targetType, createTargetHook); + isFrozen = true; + } + + /** + * Returns the target method of the call site, which behaves + * like a {@code final} field of the {@code ConstantCallSite}. + * That is, the target is always the original value passed + * to the constructor call which created this instance. + * + * @return the immutable linkage state of this call site, a constant method handle + * @throws IllegalStateException if the {@code ConstantCallSite} constructor has not completed + */ + @Override public final MethodHandle getTarget() { + if (!isFrozen) throw new IllegalStateException(); + return target; + } + + /** + * Always throws an {@link UnsupportedOperationException}. + * This kind of call site cannot change its target. + * @param ignore a new target proposed for the call site, which is ignored + * @throws UnsupportedOperationException because this kind of call site cannot change its target + */ + @Override public final void setTarget(MethodHandle ignore) { + throw new UnsupportedOperationException(); + } + + /** + * Returns this call site's permanent target. + * Since that target will never change, this is a correct implementation + * of {@link CallSite#dynamicInvoker CallSite.dynamicInvoker}. + * @return the immutable linkage state of this call site, a constant method handle + * @throws IllegalStateException if the {@code ConstantCallSite} constructor has not completed + */ + @Override + public final MethodHandle dynamicInvoker() { + return getTarget(); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/DirectMethodHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/DirectMethodHandle.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,718 @@ +/* + * 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 sun.misc.Unsafe; +import java.lang.reflect.Method; +import java.util.Arrays; +import sun.invoke.util.VerifyAccess; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.MethodTypeForm.*; +import static java.lang.invoke.MethodHandleStatics.*; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyType; +import sun.invoke.util.Wrapper; + +/** + * The flavor of method handle which implements a constant reference + * to a class member. + * @author jrose + */ +class DirectMethodHandle extends MethodHandle { + final MemberName member; + + // Constructors and factory methods in this class *must* be package scoped or private. + private DirectMethodHandle(MethodType mtype, LambdaForm form, MemberName member) { + super(mtype, form); + if (!member.isResolved()) throw new InternalError(); + + if (member.getDeclaringClass().isInterface() && + member.isMethod() && !member.isAbstract()) { + // Check for corner case: invokeinterface of Object method + MemberName m = new MemberName(Object.class, member.getName(), member.getMethodType(), member.getReferenceKind()); + m = MemberName.getFactory().resolveOrNull(m.getReferenceKind(), m, null); + if (m != null && m.isPublic()) { + member = m; + } + } + + this.member = member; + } + + // Factory methods: + static DirectMethodHandle make(byte refKind, Class receiver, MemberName member) { + MethodType mtype = member.getMethodOrFieldType(); + if (!member.isStatic()) { + if (!member.getDeclaringClass().isAssignableFrom(receiver) || member.isConstructor()) + throw new InternalError(member.toString()); + mtype = mtype.insertParameterTypes(0, receiver); + } + if (!member.isField()) { + if (refKind == REF_invokeSpecial) { + member = member.asSpecial(); + LambdaForm lform = preparedLambdaForm(member); + return new Special(mtype, lform, member); + } else { + LambdaForm lform = preparedLambdaForm(member); + return new DirectMethodHandle(mtype, lform, member); + } + } else { + LambdaForm lform = preparedFieldLambdaForm(member); + if (member.isStatic()) { + long offset = MethodHandleNatives.staticFieldOffset(member); + Object base = MethodHandleNatives.staticFieldBase(member); + return new StaticAccessor(mtype, lform, member, base, offset); + } else { + long offset = MethodHandleNatives.objectFieldOffset(member); + assert(offset == (int)offset); + return new Accessor(mtype, lform, member, (int)offset); + } + } + } + static DirectMethodHandle make(Class receiver, MemberName member) { + byte refKind = member.getReferenceKind(); + if (refKind == REF_invokeSpecial) + refKind = REF_invokeVirtual; + return make(refKind, receiver, member); + } + static DirectMethodHandle make(MemberName member) { + if (member.isConstructor()) + return makeAllocator(member); + return make(member.getDeclaringClass(), member); + } + static DirectMethodHandle make(Method method) { + return make(method.getDeclaringClass(), new MemberName(method)); + } + static DirectMethodHandle make(Field field) { + return make(field.getDeclaringClass(), new MemberName(field)); + } + private static DirectMethodHandle makeAllocator(MemberName ctor) { + assert(ctor.isConstructor() && ctor.getName().equals("")); + Class instanceClass = ctor.getDeclaringClass(); + ctor = ctor.asConstructor(); + assert(ctor.isConstructor() && ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor; + MethodType mtype = ctor.getMethodType().changeReturnType(instanceClass); + LambdaForm lform = preparedLambdaForm(ctor); + MemberName init = ctor.asSpecial(); + assert(init.getMethodType().returnType() == void.class); + return new Constructor(mtype, lform, ctor, init, instanceClass); + } + + @Override + MethodHandle copyWith(MethodType mt, LambdaForm lf) { + return new DirectMethodHandle(mt, lf, member); + } + + @Override + String internalProperties() { + return "/DMH="+member.toString(); + } + + //// Implementation methods. + @Override + MethodHandle viewAsType(MethodType newType) { + return new DirectMethodHandle(newType, form, member); + } + @Override + @ForceInline + MemberName internalMemberName() { + return member; + } + + @Override + MethodHandle bindArgument(int pos, char basicType, Object value) { + // If the member needs dispatching, do so. + if (pos == 0 && basicType == 'L') { + DirectMethodHandle concrete = maybeRebind(value); + if (concrete != null) + return concrete.bindReceiver(value); + } + return super.bindArgument(pos, basicType, value); + } + + @Override + MethodHandle bindReceiver(Object receiver) { + // If the member needs dispatching, do so. + DirectMethodHandle concrete = maybeRebind(receiver); + if (concrete != null) + return concrete.bindReceiver(receiver); + return super.bindReceiver(receiver); + } + + private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory(); + + private DirectMethodHandle maybeRebind(Object receiver) { + if (receiver != null) { + switch (member.getReferenceKind()) { + case REF_invokeInterface: + case REF_invokeVirtual: + // Pre-dispatch the member. + Class concreteClass = receiver.getClass(); + MemberName concrete = new MemberName(concreteClass, member.getName(), member.getMethodType(), REF_invokeSpecial); + concrete = IMPL_NAMES.resolveOrNull(REF_invokeSpecial, concrete, concreteClass); + if (concrete != null) + return new DirectMethodHandle(type(), preparedLambdaForm(concrete), concrete); + break; + } + } + return null; + } + + /** + * Create a LF which can invoke the given method. + * Cache and share this structure among all methods with + * the same basicType and refKind. + */ + private static LambdaForm preparedLambdaForm(MemberName m) { + assert(m.isInvocable()) : m; // call preparedFieldLambdaForm instead + MethodType mtype = m.getInvocationType().basicType(); + assert(!m.isMethodHandleInvoke() || "invokeBasic".equals(m.getName())) : m; + int which; + switch (m.getReferenceKind()) { + case REF_invokeVirtual: which = LF_INVVIRTUAL; break; + case REF_invokeStatic: which = LF_INVSTATIC; break; + case REF_invokeSpecial: which = LF_INVSPECIAL; break; + case REF_invokeInterface: which = LF_INVINTERFACE; break; + case REF_newInvokeSpecial: which = LF_NEWINVSPECIAL; break; + default: throw new InternalError(m.toString()); + } + if (which == LF_INVSTATIC && shouldBeInitialized(m)) { + // precompute the barrier-free version: + preparedLambdaForm(mtype, which); + which = LF_INVSTATIC_INIT; + } + LambdaForm lform = preparedLambdaForm(mtype, which); + maybeCompile(lform, m); + assert(lform.methodType().dropParameterTypes(0, 1) + .equals(m.getInvocationType().basicType())) + : Arrays.asList(m, m.getInvocationType().basicType(), lform, lform.methodType()); + return lform; + } + + private static LambdaForm preparedLambdaForm(MethodType mtype, int which) { + LambdaForm lform = mtype.form().cachedLambdaForm(which); + if (lform != null) return lform; + lform = makePreparedLambdaForm(mtype, which); + return mtype.form().setCachedLambdaForm(which, lform); + } + + private static LambdaForm makePreparedLambdaForm(MethodType mtype, int which) { + boolean needsInit = (which == LF_INVSTATIC_INIT); + boolean doesAlloc = (which == LF_NEWINVSPECIAL); + String linkerName, lambdaName; + switch (which) { + case LF_INVVIRTUAL: linkerName = "linkToVirtual"; lambdaName = "DMH.invokeVirtual"; break; + case LF_INVSTATIC: linkerName = "linkToStatic"; lambdaName = "DMH.invokeStatic"; break; + case LF_INVSTATIC_INIT:linkerName = "linkToStatic"; lambdaName = "DMH.invokeStaticInit"; break; + case LF_INVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.invokeSpecial"; break; + case LF_INVINTERFACE: linkerName = "linkToInterface"; lambdaName = "DMH.invokeInterface"; break; + case LF_NEWINVSPECIAL: linkerName = "linkToSpecial"; lambdaName = "DMH.newInvokeSpecial"; break; + default: throw new InternalError("which="+which); + } + MethodType mtypeWithArg = mtype.appendParameterTypes(MemberName.class); + if (doesAlloc) + mtypeWithArg = mtypeWithArg + .insertParameterTypes(0, Object.class) // insert newly allocated obj + .changeReturnType(void.class); // returns void + MemberName linker = new MemberName(MethodHandle.class, linkerName, mtypeWithArg, REF_invokeStatic); + try { + linker = IMPL_NAMES.resolveOrFail(REF_invokeStatic, linker, null, NoSuchMethodException.class); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + final int DMH_THIS = 0; + final int ARG_BASE = 1; + final int ARG_LIMIT = ARG_BASE + mtype.parameterCount(); + int nameCursor = ARG_LIMIT; + final int NEW_OBJ = (doesAlloc ? nameCursor++ : -1); + final int GET_MEMBER = nameCursor++; + final int LINKER_CALL = nameCursor++; + Name[] names = arguments(nameCursor - ARG_LIMIT, mtype.invokerType()); + assert(names.length == nameCursor); + if (doesAlloc) { + // names = { argx,y,z,... new C, init method } + names[NEW_OBJ] = new Name(Lazy.NF_allocateInstance, names[DMH_THIS]); + names[GET_MEMBER] = new Name(Lazy.NF_constructorMethod, names[DMH_THIS]); + } else if (needsInit) { + names[GET_MEMBER] = new Name(Lazy.NF_internalMemberNameEnsureInit, names[DMH_THIS]); + } else { + names[GET_MEMBER] = new Name(Lazy.NF_internalMemberName, names[DMH_THIS]); + } + Object[] outArgs = Arrays.copyOfRange(names, ARG_BASE, GET_MEMBER+1, Object[].class); + assert(outArgs[outArgs.length-1] == names[GET_MEMBER]); // look, shifted args! + int result = LambdaForm.LAST_RESULT; + if (doesAlloc) { + assert(outArgs[outArgs.length-2] == names[NEW_OBJ]); // got to move this one + System.arraycopy(outArgs, 0, outArgs, 1, outArgs.length-2); + outArgs[0] = names[NEW_OBJ]; + result = NEW_OBJ; + } + names[LINKER_CALL] = new Name(linker, outArgs); + lambdaName += "_" + LambdaForm.basicTypeSignature(mtype); + LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result); + // This is a tricky bit of code. Don't send it through the LF interpreter. + lform.compileToBytecode(); + return lform; + } + + private static void maybeCompile(LambdaForm lform, MemberName m) { + if (VerifyAccess.isSamePackage(m.getDeclaringClass(), MethodHandle.class)) + // Help along bootstrapping... + lform.compileToBytecode(); + } + + /** Static wrapper for DirectMethodHandle.internalMemberName. */ + @ForceInline + /*non-public*/ static Object internalMemberName(Object mh) { + return ((DirectMethodHandle)mh).member; + } + + /** Static wrapper for DirectMethodHandle.internalMemberName. + * This one also forces initialization. + */ + /*non-public*/ static Object internalMemberNameEnsureInit(Object mh) { + DirectMethodHandle dmh = (DirectMethodHandle)mh; + dmh.ensureInitialized(); + return dmh.member; + } + + /*non-public*/ static + boolean shouldBeInitialized(MemberName member) { + switch (member.getReferenceKind()) { + case REF_invokeStatic: + case REF_getStatic: + case REF_putStatic: + case REF_newInvokeSpecial: + break; + default: + // No need to initialize the class on this kind of member. + return false; + } + Class cls = member.getDeclaringClass(); + if (cls == ValueConversions.class || + cls == MethodHandleImpl.class || + cls == Invokers.class) { + // These guys have lots of DMH creation but we know + // the MHs will not be used until the system is booted. + return false; + } + if (VerifyAccess.isSamePackage(MethodHandle.class, cls) || + VerifyAccess.isSamePackage(ValueConversions.class, cls)) { + // It is a system class. It is probably in the process of + // being initialized, but we will help it along just to be safe. + if (UNSAFE.shouldBeInitialized(cls)) { + UNSAFE.ensureClassInitialized(cls); + } + return false; + } + return UNSAFE.shouldBeInitialized(cls); + } + + private static class EnsureInitialized extends ClassValue> { + @Override + protected WeakReference computeValue(Class type) { + UNSAFE.ensureClassInitialized(type); + if (UNSAFE.shouldBeInitialized(type)) + // If the previous call didn't block, this can happen. + // We are executing inside . + return new WeakReference<>(Thread.currentThread()); + return null; + } + static final EnsureInitialized INSTANCE = new EnsureInitialized(); + } + + private void ensureInitialized() { + if (checkInitialized(member)) { + // The coast is clear. Delete the barrier. + if (member.isField()) + updateForm(preparedFieldLambdaForm(member)); + else + updateForm(preparedLambdaForm(member)); + } + } + private static boolean checkInitialized(MemberName member) { + Class defc = member.getDeclaringClass(); + WeakReference ref = EnsureInitialized.INSTANCE.get(defc); + if (ref == null) { + return true; // the final state + } + Thread clinitThread = ref.get(); + // Somebody may still be running defc.. + if (clinitThread == Thread.currentThread()) { + // If anybody is running defc., it is this thread. + if (UNSAFE.shouldBeInitialized(defc)) + // Yes, we are running it; keep the barrier for now. + return false; + } else { + // We are in a random thread. Block. + UNSAFE.ensureClassInitialized(defc); + } + assert(!UNSAFE.shouldBeInitialized(defc)); + // put it into the final state + EnsureInitialized.INSTANCE.remove(defc); + return true; + } + + /*non-public*/ static void ensureInitialized(Object mh) { + ((DirectMethodHandle)mh).ensureInitialized(); + } + + /** This subclass represents invokespecial instructions. */ + static class Special extends DirectMethodHandle { + private Special(MethodType mtype, LambdaForm form, MemberName member) { + super(mtype, form, member); + } + @Override + boolean isInvokeSpecial() { + return true; + } + @Override + MethodHandle viewAsType(MethodType newType) { + return new Special(newType, form, member); + } + } + + /** This subclass handles constructor references. */ + static class Constructor extends DirectMethodHandle { + final MemberName initMethod; + final Class instanceClass; + + private Constructor(MethodType mtype, LambdaForm form, MemberName constructor, + MemberName initMethod, Class instanceClass) { + super(mtype, form, constructor); + this.initMethod = initMethod; + this.instanceClass = instanceClass; + assert(initMethod.isResolved()); + } + @Override + MethodHandle viewAsType(MethodType newType) { + return new Constructor(newType, form, member, initMethod, instanceClass); + } + } + + /*non-public*/ static Object constructorMethod(Object mh) { + Constructor dmh = (Constructor)mh; + return dmh.initMethod; + } + + /*non-public*/ static Object allocateInstance(Object mh) throws InstantiationException { + Constructor dmh = (Constructor)mh; + return UNSAFE.allocateInstance(dmh.instanceClass); + } + + /** This subclass handles non-static field references. */ + static class Accessor extends DirectMethodHandle { + final Class fieldType; + final int fieldOffset; + private Accessor(MethodType mtype, LambdaForm form, MemberName member, + int fieldOffset) { + super(mtype, form, member); + this.fieldType = member.getFieldType(); + this.fieldOffset = fieldOffset; + } + + @Override Object checkCast(Object obj) { + return fieldType.cast(obj); + } + @Override + MethodHandle viewAsType(MethodType newType) { + return new Accessor(newType, form, member, fieldOffset); + } + } + + @ForceInline + /*non-public*/ static long fieldOffset(Object accessorObj) { + // Note: We return a long because that is what Unsafe.getObject likes. + // We store a plain int because it is more compact. + return ((Accessor)accessorObj).fieldOffset; + } + + @ForceInline + /*non-public*/ static Object checkBase(Object obj) { + // Note that the object's class has already been verified, + // since the parameter type of the Accessor method handle + // is either member.getDeclaringClass or a subclass. + // This was verified in DirectMethodHandle.make. + // Therefore, the only remaining check is for null. + // Since this check is *not* guaranteed by Unsafe.getInt + // and its siblings, we need to make an explicit one here. + obj.getClass(); // maybe throw NPE + return obj; + } + + /** This subclass handles static field references. */ + static class StaticAccessor extends DirectMethodHandle { + final private Class fieldType; + final private Object staticBase; + final private long staticOffset; + + private StaticAccessor(MethodType mtype, LambdaForm form, MemberName member, + Object staticBase, long staticOffset) { + super(mtype, form, member); + this.fieldType = member.getFieldType(); + this.staticBase = staticBase; + this.staticOffset = staticOffset; + } + + @Override Object checkCast(Object obj) { + return fieldType.cast(obj); + } + @Override + MethodHandle viewAsType(MethodType newType) { + return new StaticAccessor(newType, form, member, staticBase, staticOffset); + } + } + + @ForceInline + /*non-public*/ static Object nullCheck(Object obj) { + obj.getClass(); + return obj; + } + + @ForceInline + /*non-public*/ static Object staticBase(Object accessorObj) { + return ((StaticAccessor)accessorObj).staticBase; + } + + @ForceInline + /*non-public*/ static long staticOffset(Object accessorObj) { + return ((StaticAccessor)accessorObj).staticOffset; + } + + @ForceInline + /*non-public*/ static Object checkCast(Object mh, Object obj) { + return ((DirectMethodHandle) mh).checkCast(obj); + } + + Object checkCast(Object obj) { + return member.getReturnType().cast(obj); + } + + // Caching machinery for field accessors: + private static byte + AF_GETFIELD = 0, + AF_PUTFIELD = 1, + AF_GETSTATIC = 2, + AF_PUTSTATIC = 3, + AF_GETSTATIC_INIT = 4, + AF_PUTSTATIC_INIT = 5, + AF_LIMIT = 6; + // Enumerate the different field kinds using Wrapper, + // with an extra case added for checked references. + private static int + FT_LAST_WRAPPER = Wrapper.values().length-1, + FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(), + FT_CHECKED_REF = FT_LAST_WRAPPER+1, + FT_LIMIT = FT_LAST_WRAPPER+2; + private static int afIndex(byte formOp, boolean isVolatile, int ftypeKind) { + return ((formOp * FT_LIMIT * 2) + + (isVolatile ? FT_LIMIT : 0) + + ftypeKind); + } + private static final LambdaForm[] ACCESSOR_FORMS + = new LambdaForm[afIndex(AF_LIMIT, false, 0)]; + private static int ftypeKind(Class ftype) { + if (ftype.isPrimitive()) + return Wrapper.forPrimitiveType(ftype).ordinal(); + else if (VerifyType.isNullReferenceConversion(Object.class, ftype)) + return FT_UNCHECKED_REF; + else + return FT_CHECKED_REF; + } + + /** + * Create a LF which can access the given field. + * Cache and share this structure among all fields with + * the same basicType and refKind. + */ + private static LambdaForm preparedFieldLambdaForm(MemberName m) { + Class ftype = m.getFieldType(); + boolean isVolatile = m.isVolatile(); + byte formOp; + switch (m.getReferenceKind()) { + case REF_getField: formOp = AF_GETFIELD; break; + case REF_putField: formOp = AF_PUTFIELD; break; + case REF_getStatic: formOp = AF_GETSTATIC; break; + case REF_putStatic: formOp = AF_PUTSTATIC; break; + default: throw new InternalError(m.toString()); + } + if (shouldBeInitialized(m)) { + // precompute the barrier-free version: + preparedFieldLambdaForm(formOp, isVolatile, ftype); + assert((AF_GETSTATIC_INIT - AF_GETSTATIC) == + (AF_PUTSTATIC_INIT - AF_PUTSTATIC)); + formOp += (AF_GETSTATIC_INIT - AF_GETSTATIC); + } + LambdaForm lform = preparedFieldLambdaForm(formOp, isVolatile, ftype); + maybeCompile(lform, m); + assert(lform.methodType().dropParameterTypes(0, 1) + .equals(m.getInvocationType().basicType())) + : Arrays.asList(m, m.getInvocationType().basicType(), lform, lform.methodType()); + return lform; + } + private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class ftype) { + int afIndex = afIndex(formOp, isVolatile, ftypeKind(ftype)); + LambdaForm lform = ACCESSOR_FORMS[afIndex]; + if (lform != null) return lform; + lform = makePreparedFieldLambdaForm(formOp, isVolatile, ftypeKind(ftype)); + ACCESSOR_FORMS[afIndex] = lform; // don't bother with a CAS + return lform; + } + + private static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, int ftypeKind) { + boolean isGetter = (formOp & 1) == (AF_GETFIELD & 1); + boolean isStatic = (formOp >= AF_GETSTATIC); + boolean needsInit = (formOp >= AF_GETSTATIC_INIT); + boolean needsCast = (ftypeKind == FT_CHECKED_REF); + Wrapper fw = (needsCast ? Wrapper.OBJECT : Wrapper.values()[ftypeKind]); + Class ft = fw.primitiveType(); + assert(ftypeKind(needsCast ? String.class : ft) == ftypeKind); + String tname = fw.primitiveSimpleName(); + String ctname = Character.toUpperCase(tname.charAt(0)) + tname.substring(1); + if (isVolatile) ctname += "Volatile"; + String getOrPut = (isGetter ? "get" : "put"); + String linkerName = (getOrPut + ctname); // getObject, putIntVolatile, etc. + MethodType linkerType; + if (isGetter) + linkerType = MethodType.methodType(ft, Object.class, long.class); + else + linkerType = MethodType.methodType(void.class, Object.class, long.class, ft); + MemberName linker = new MemberName(Unsafe.class, linkerName, linkerType, REF_invokeVirtual); + try { + linker = IMPL_NAMES.resolveOrFail(REF_invokeVirtual, linker, null, NoSuchMethodException.class); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + + // What is the external type of the lambda form? + MethodType mtype; + if (isGetter) + mtype = MethodType.methodType(ft); + else + mtype = MethodType.methodType(void.class, ft); + mtype = mtype.basicType(); // erase short to int, etc. + if (!isStatic) + mtype = mtype.insertParameterTypes(0, Object.class); + final int DMH_THIS = 0; + final int ARG_BASE = 1; + final int ARG_LIMIT = ARG_BASE + mtype.parameterCount(); + // if this is for non-static access, the base pointer is stored at this index: + final int OBJ_BASE = isStatic ? -1 : ARG_BASE; + // if this is for write access, the value to be written is stored at this index: + final int SET_VALUE = isGetter ? -1 : ARG_LIMIT - 1; + int nameCursor = ARG_LIMIT; + final int F_HOLDER = (isStatic ? nameCursor++ : -1); // static base if any + final int F_OFFSET = nameCursor++; // Either static offset or field offset. + final int OBJ_CHECK = (OBJ_BASE >= 0 ? nameCursor++ : -1); + final int INIT_BAR = (needsInit ? nameCursor++ : -1); + final int PRE_CAST = (needsCast && !isGetter ? nameCursor++ : -1); + final int LINKER_CALL = nameCursor++; + final int POST_CAST = (needsCast && isGetter ? nameCursor++ : -1); + final int RESULT = nameCursor-1; // either the call or the cast + Name[] names = arguments(nameCursor - ARG_LIMIT, mtype.invokerType()); + if (needsInit) + names[INIT_BAR] = new Name(Lazy.NF_ensureInitialized, names[DMH_THIS]); + if (needsCast && !isGetter) + names[PRE_CAST] = new Name(Lazy.NF_checkCast, names[DMH_THIS], names[SET_VALUE]); + Object[] outArgs = new Object[1 + linkerType.parameterCount()]; + assert(outArgs.length == (isGetter ? 3 : 4)); + outArgs[0] = UNSAFE; + if (isStatic) { + outArgs[1] = names[F_HOLDER] = new Name(Lazy.NF_staticBase, names[DMH_THIS]); + outArgs[2] = names[F_OFFSET] = new Name(Lazy.NF_staticOffset, names[DMH_THIS]); + } else { + outArgs[1] = names[OBJ_CHECK] = new Name(Lazy.NF_checkBase, names[OBJ_BASE]); + outArgs[2] = names[F_OFFSET] = new Name(Lazy.NF_fieldOffset, names[DMH_THIS]); + } + if (!isGetter) { + outArgs[3] = (needsCast ? names[PRE_CAST] : names[SET_VALUE]); + } + for (Object a : outArgs) assert(a != null); + names[LINKER_CALL] = new Name(linker, outArgs); + if (needsCast && isGetter) + names[POST_CAST] = new Name(Lazy.NF_checkCast, names[DMH_THIS], names[LINKER_CALL]); + for (Name n : names) assert(n != null); + String fieldOrStatic = (isStatic ? "Static" : "Field"); + String lambdaName = (linkerName + fieldOrStatic); // significant only for debugging + if (needsCast) lambdaName += "Cast"; + if (needsInit) lambdaName += "Init"; + return new LambdaForm(lambdaName, ARG_LIMIT, names, RESULT); + } + + /** + * 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_internalMemberName, + NF_internalMemberNameEnsureInit, + NF_ensureInitialized, + NF_fieldOffset, + NF_checkBase, + NF_staticBase, + NF_staticOffset, + NF_checkCast, + NF_allocateInstance, + NF_constructorMethod; + static { + try { + NamedFunction nfs[] = { + NF_internalMemberName = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("internalMemberName", Object.class)), + NF_internalMemberNameEnsureInit = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("internalMemberNameEnsureInit", Object.class)), + NF_ensureInitialized = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("ensureInitialized", Object.class)), + NF_fieldOffset = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("fieldOffset", Object.class)), + NF_checkBase = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("checkBase", Object.class)), + NF_staticBase = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("staticBase", Object.class)), + NF_staticOffset = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("staticOffset", Object.class)), + NF_checkCast = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("checkCast", Object.class, Object.class)), + NF_allocateInstance = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("allocateInstance", Object.class)), + NF_constructorMethod = new NamedFunction(DirectMethodHandle.class + .getDeclaredMethod("constructorMethod", Object.class)) + }; + for (NamedFunction nf : nfs) { + // Each nf must be statically invocable or we get tied up in our bootstraps. + assert(InvokerBytecodeGenerator.isStaticallyInvocable(nf.member)) : nf; + nf.resolve(); + } + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/DontInline.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/DontInline.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, 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.lang.annotation.*; + +/** + * Internal marker for some methods in the JSR 292 implementation. + */ +/*non-public*/ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +@interface DontInline { +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/ForceInline.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/ForceInline.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, 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.lang.annotation.*; + +/** + * Internal marker for some methods in the JSR 292 implementation. + */ +/*non-public*/ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +@interface ForceInline { +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InfoFromMemberName.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InfoFromMemberName.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012, 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.*; +import java.lang.reflect.*; +import java.lang.invoke.MethodHandleNatives.Constants; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandleStatics.*; + +/* + * Auxiliary to MethodHandleInfo, wants to nest in MethodHandleInfo but must be non-public. + */ +/*non-public*/ +final +class InfoFromMemberName implements MethodHandleInfo { + private final MemberName member; + private final int referenceKind; + + InfoFromMemberName(Lookup lookup, MemberName member, byte referenceKind) { + assert(member.isResolved() || member.isMethodHandleInvoke()); + assert(member.referenceKindIsConsistentWith(referenceKind)); + this.member = member; + this.referenceKind = referenceKind; + } + + @Override + public Class getDeclaringClass() { + return member.getDeclaringClass(); + } + + @Override + public String getName() { + return member.getName(); + } + + @Override + public MethodType getMethodType() { + return member.getMethodOrFieldType(); + } + + @Override + public int getModifiers() { + return member.getModifiers(); + } + + @Override + public int getReferenceKind() { + return referenceKind; + } + + @Override + public String toString() { + return MethodHandleInfo.toString(getReferenceKind(), getDeclaringClass(), getName(), getMethodType()); + } + + @Override + public T reflectAs(Class expected, Lookup lookup) { + if (member.isMethodHandleInvoke() && !member.isVarargs()) { + // This member is an instance of a signature-polymorphic method, which cannot be reflected + // A method handle invoker can come in either of two forms: + // A generic placeholder (present in the source code, and varargs) + // and a signature-polymorphic instance (synthetic and not varargs). + // For more information see comments on {@link MethodHandleNatives#linkMethod}. + throw new IllegalArgumentException("cannot reflect signature polymorphic method"); + } + Member mem = AccessController.doPrivileged(new PrivilegedAction() { + public Member run() { + try { + return reflectUnchecked(); + } catch (ReflectiveOperationException ex) { + throw new IllegalArgumentException(ex); + } + } + }); + try { + Class defc = getDeclaringClass(); + byte refKind = (byte) getReferenceKind(); + lookup.checkAccess(refKind, defc, convertToMemberName(refKind, mem)); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } + return expected.cast(mem); + } + + private Member reflectUnchecked() throws ReflectiveOperationException { + byte refKind = (byte) getReferenceKind(); + Class defc = getDeclaringClass(); + boolean isPublic = Modifier.isPublic(getModifiers()); + if (MethodHandleNatives.refKindIsMethod(refKind)) { + if (isPublic) + return defc.getMethod(getName(), getMethodType().parameterArray()); + else + return defc.getDeclaredMethod(getName(), getMethodType().parameterArray()); + } else if (MethodHandleNatives.refKindIsConstructor(refKind)) { + if (isPublic) + return defc.getConstructor(getMethodType().parameterArray()); + else + return defc.getDeclaredConstructor(getMethodType().parameterArray()); + } else if (MethodHandleNatives.refKindIsField(refKind)) { + if (isPublic) + return defc.getField(getName()); + else + return defc.getDeclaredField(getName()); + } else { + throw new IllegalArgumentException("referenceKind="+refKind); + } + } + + private static MemberName convertToMemberName(byte refKind, Member mem) throws IllegalAccessException { + if (mem instanceof Method) { + boolean wantSpecial = (refKind == REF_invokeSpecial); + return new MemberName((Method) mem, wantSpecial); + } else if (mem instanceof Constructor) { + return new MemberName((Constructor) mem); + } else if (mem instanceof Field) { + boolean isSetter = (refKind == REF_putField || refKind == REF_putStatic); + return new MemberName((Field) mem, isSetter); + } + throw new InternalError(mem.getClass().getName()); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InnerClassLambdaMetafactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InnerClassLambdaMetafactory.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2012, 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 jdk.internal.org.objectweb.asm.*; +import sun.invoke.util.BytecodeDescriptor; +import sun.misc.Unsafe; +import sun.security.action.GetPropertyAction; + +import java.io.FilePermission; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.LinkedHashSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.PropertyPermission; +import java.util.Set; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + * Lambda metafactory implementation which dynamically creates an + * inner-class-like class per lambda callsite. + * + * @see LambdaMetafactory + */ +/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final int CLASSFILE_VERSION = 52; + private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); + private static final String JAVA_LANG_OBJECT = "java/lang/Object"; + private static final String NAME_CTOR = ""; + private static final String NAME_FACTORY = "get$Lambda"; + + //Serialization support + private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; + private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException"; + private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; + private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V"; + private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V"; + private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; + private static final String NAME_METHOD_READ_OBJECT = "readObject"; + private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; + private static final String DESCR_CTOR_SERIALIZED_LAMBDA + = MethodType.methodType(void.class, + Class.class, + String.class, String.class, String.class, + int.class, String.class, String.class, String.class, + String.class, + Object[].class).toMethodDescriptorString(); + private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION + = MethodType.methodType(void.class, String.class).toMethodDescriptorString(); + private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION}; + + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + // Used to ensure that each spun class name is unique + private static final AtomicInteger counter = new AtomicInteger(0); + + // For dumping generated classes to disk, for debugging purposes + private static final ProxyClassesDumper dumper; + + static { + final String key = "jdk.internal.lambda.dumpProxyClasses"; + String path = AccessController.doPrivileged( + new GetPropertyAction(key), null, + new PropertyPermission(key , "read")); + dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path); + } + + // See context values in AbstractValidatingLambdaMetafactory + private final String implMethodClassName; // Name of type containing implementation "CC" + private final String implMethodName; // Name of implementation method "impl" + private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" + private final Class implMethodReturnClass; // class for implementaion method return type "Ljava/lang/String;" + private final MethodType constructorType; // Generated class constructor type "(CC)void" + private final ClassWriter cw; // ASM class writer + private final String[] argNames; // Generated names for the constructor arguments + private final String[] argDescs; // Type descriptors for the constructor arguments + private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + + /** + * General meta-factory constructor, supporting both standard cases and + * allowing for uncommon options such as serialization or bridging. + * + * @param caller Stacked automatically by VM; represents a lookup context + * with the accessibility privileges of the caller. + * @param invokedType Stacked automatically by VM; the signature of the + * invoked method, which includes the expected static + * type of the returned lambda object, and the static + * types of the captured arguments for the lambda. In + * the event that the implementation method is an + * instance method, the first argument in the invocation + * signature will correspond to the receiver. + * @param samMethodName Name of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a String. + * @param samMethodType Type of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a MethodType. + * @param implMethod The implementation method which should be called (with + * suitable adaptation of argument types, return types, + * and adjustment for captured arguments) when methods of + * the resulting functional interface instance are invoked. + * @param instantiatedMethodType The signature of the primary functional + * interface method after type variables are + * substituted with their instantiation from + * the capture site + * @param isSerializable Should the lambda be made serializable? If set, + * either the target type or one of the additional SAM + * types must extend {@code Serializable}. + * @param markerInterfaces Additional interfaces which the lambda object + * should implement. + * @param additionalBridges Method types for additional signatures to be + * bridged to the implementation method + * @throws LambdaConversionException If any of the meta-factory protocol + * invariants are violated + */ + public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + String samMethodName, + MethodType samMethodType, + MethodHandle implMethod, + MethodType instantiatedMethodType, + boolean isSerializable, + Class[] markerInterfaces, + MethodType[] additionalBridges) + throws LambdaConversionException { + super(caller, invokedType, samMethodName, samMethodType, + implMethod, instantiatedMethodType, + isSerializable, markerInterfaces, additionalBridges); + implMethodClassName = implDefiningClass.getName().replace('.', '/'); + implMethodName = implInfo.getName(); + implMethodDesc = implMethodType.toMethodDescriptorString(); + implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial) + ? implDefiningClass + : implMethodType.returnType(); + constructorType = invokedType.changeReturnType(Void.TYPE); + lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); + cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + int parameterCount = invokedType.parameterCount(); + if (parameterCount > 0) { + argNames = new String[parameterCount]; + argDescs = new String[parameterCount]; + for (int i = 0; i < parameterCount; i++) { + argNames[i] = "arg$" + (i + 1); + argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i)); + } + } else { + argNames = argDescs = EMPTY_STRING_ARRAY; + } + } + + /** + * Build the CallSite. Generate a class file which implements the functional + * interface, define the class, if there are no parameters create an instance + * of the class which the CallSite will return, otherwise, generate handles + * which will call the class' constructor. + * + * @return a CallSite, which, when invoked, will return an instance of the + * functional interface + * @throws ReflectiveOperationException + * @throws LambdaConversionException If properly formed functional interface + * is not found + */ + @Override + CallSite buildCallSite() throws LambdaConversionException { + final Class innerClass = spinInnerClass(); + if (invokedType.parameterCount() == 0) { + final Constructor[] ctrs = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Constructor[] run() { + Constructor[] ctrs = innerClass.getDeclaredConstructors(); + if (ctrs.length == 1) { + // The lambda implementing inner class constructor is private, set + // it accessible (by us) before creating the constant sole instance + ctrs[0].setAccessible(true); + } + return ctrs; + } + }); + if (ctrs.length != 1) { + throw new LambdaConversionException("Expected one lambda constructor for " + + innerClass.getCanonicalName() + ", got " + ctrs.length); + } + + try { + Object inst = ctrs[0].newInstance(); + return new ConstantCallSite(MethodHandles.constant(samBase, inst)); + } + catch (ReflectiveOperationException e) { + throw new LambdaConversionException("Exception instantiating lambda object", e); + } + } else { + try { + UNSAFE.ensureClassInitialized(innerClass); + return new ConstantCallSite( + MethodHandles.Lookup.IMPL_LOOKUP + .findStatic(innerClass, NAME_FACTORY, invokedType)); + } + catch (ReflectiveOperationException e) { + throw new LambdaConversionException("Exception finding constructor", e); + } + } + } + + /** + * Generate a class file which implements the functional + * interface, define and return the class. + * + * @implNote The class that is generated does not include signature + * information for exceptions that may be present on the SAM method. + * This is to reduce classfile size, and is harmless as checked exceptions + * are erased anyway, no one will ever compile against this classfile, + * and we make no guarantees about the reflective properties of lambda + * objects. + * + * @return a Class which implements the functional interface + * @throws LambdaConversionException If properly formed functional interface + * is not found + */ + private Class spinInnerClass() throws LambdaConversionException { + String[] interfaces; + String samIntf = samBase.getName().replace('.', '/'); + boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase); + if (markerInterfaces.length == 0) { + interfaces = new String[]{samIntf}; + } else { + // Assure no duplicate interfaces (ClassFormatError) + Set itfs = new LinkedHashSet<>(markerInterfaces.length + 1); + itfs.add(samIntf); + for (Class markerInterface : markerInterfaces) { + itfs.add(markerInterface.getName().replace('.', '/')); + accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface); + } + interfaces = itfs.toArray(new String[itfs.size()]); + } + + cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, + lambdaClassName, null, + JAVA_LANG_OBJECT, interfaces); + + // Generate final fields to be filled in by constructor + for (int i = 0; i < argDescs.length; i++) { + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, + argNames[i], + argDescs[i], + null, null); + fv.visitEnd(); + } + + generateConstructor(); + + if (invokedType.parameterCount() != 0) { + generateFactory(); + } + + // Forward the SAM method + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, + samMethodType.toMethodDescriptorString(), null, null); + new ForwardingMethodGenerator(mv).generate(samMethodType); + + // Forward the bridges + if (additionalBridges != null) { + for (MethodType mt : additionalBridges) { + mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName, + mt.toMethodDescriptorString(), null, null); + new ForwardingMethodGenerator(mv).generate(mt); + } + } + + if (isSerializable) + generateSerializationFriendlyMethods(); + else if (accidentallySerializable) + generateSerializationHostileMethods(); + + cw.visitEnd(); + + // Define the generated class in this VM. + + final byte[] classBytes = cw.toByteArray(); + + // If requested, dump out to a file for debugging purposes + if (dumper != null) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + dumper.dumpClass(lambdaClassName, classBytes); + return null; + } + }, null, + new FilePermission("<>", "read, write"), + // createDirectories may need it + new PropertyPermission("user.dir", "read")); + } + + return UNSAFE.defineAnonymousClass(targetClass, classBytes, null); + } + + /** + * Generate the factory method for the class + */ + private void generateFactory() { + MethodVisitor m = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_FACTORY, invokedType.toMethodDescriptorString(), null, null); + m.visitCode(); + m.visitTypeInsn(NEW, lambdaClassName); + m.visitInsn(Opcodes.DUP); + int parameterCount = invokedType.parameterCount(); + for (int typeIndex = 0, varIndex = 0; typeIndex < parameterCount; typeIndex++) { + Class argType = invokedType.parameterType(typeIndex); + m.visitVarInsn(getLoadOpcode(argType), varIndex); + varIndex += getParameterSize(argType); + } + m.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString()); + m.visitInsn(ARETURN); + m.visitMaxs(-1, -1); + m.visitEnd(); + } + + /** + * Generate the constructor for the class + */ + private void generateConstructor() { + // Generate constructor + MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, + constructorType.toMethodDescriptorString(), null, null); + ctor.visitCode(); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR, + METHOD_DESCRIPTOR_VOID); + int parameterCount = invokedType.parameterCount(); + for (int i = 0, lvIndex = 0; i < parameterCount; i++) { + ctor.visitVarInsn(ALOAD, 0); + Class argType = invokedType.parameterType(i); + ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1); + lvIndex += getParameterSize(argType); + ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]); + } + ctor.visitInsn(RETURN); + // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + ctor.visitMaxs(-1, -1); + ctor.visitEnd(); + } + + /** + * Generate a writeReplace method that supports serialization + */ + private void generateSerializationFriendlyMethods() { + TypeConvertingMethodAdapter mv + = new TypeConvertingMethodAdapter( + cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, + null, null)); + + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); + mv.visitInsn(DUP); + mv.visitLdcInsn(Type.getType(targetClass)); + mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/')); + mv.visitLdcInsn(samMethodName); + mv.visitLdcInsn(samMethodType.toMethodDescriptorString()); + mv.visitLdcInsn(implInfo.getReferenceKind()); + mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); + mv.visitLdcInsn(implInfo.getName()); + mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); + mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString()); + mv.iconst(argDescs.length); + mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT); + for (int i = 0; i < argDescs.length; i++) { + mv.visitInsn(DUP); + mv.iconst(i); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); + mv.boxIfTypePrimitive(Type.getType(argDescs[i])); + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, + DESCR_CTOR_SERIALIZED_LAMBDA); + mv.visitInsn(ARETURN); + // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + /** + * Generate a readObject/writeObject method that is hostile to serialization + */ + private void generateSerializationHostileMethods() { + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_WRITE_OBJECT, DESCR_METHOD_WRITE_OBJECT, + null, SER_HOSTILE_EXCEPTIONS); + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(DUP); + mv.visitLdcInsn("Non-serializable lambda"); + mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, + DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(ATHROW); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + + mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_READ_OBJECT, DESCR_METHOD_READ_OBJECT, + null, SER_HOSTILE_EXCEPTIONS); + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(DUP); + mv.visitLdcInsn("Non-serializable lambda"); + mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, + DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(ATHROW); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + /** + * This class generates a method body which calls the lambda implementation + * method, converting arguments, as needed. + */ + private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter { + + ForwardingMethodGenerator(MethodVisitor mv) { + super(mv); + } + + void generate(MethodType methodType) { + visitCode(); + + if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { + visitTypeInsn(NEW, implMethodClassName); + visitInsn(DUP); + } + for (int i = 0; i < argNames.length; i++) { + visitVarInsn(ALOAD, 0); + visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); + } + + convertArgumentTypes(methodType); + + // Invoke the method we want to forward to + visitMethodInsn(invocationOpcode(), implMethodClassName, + implMethodName, implMethodDesc, + implDefiningClass.isInterface()); + + // Convert the return value (if any) and return it + // Note: if adapting from non-void to void, the 'return' + // instruction will pop the unneeded result + Class samReturnClass = methodType.returnType(); + convertType(implMethodReturnClass, samReturnClass, samReturnClass); + visitInsn(getReturnOpcode(samReturnClass)); + // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored + visitMaxs(-1, -1); + visitEnd(); + } + + private void convertArgumentTypes(MethodType samType) { + int lvIndex = 0; + boolean samIncludesReceiver = implIsInstanceMethod && + invokedType.parameterCount() == 0; + int samReceiverLength = samIncludesReceiver ? 1 : 0; + if (samIncludesReceiver) { + // push receiver + Class rcvrType = samType.parameterType(0); + visitVarInsn(getLoadOpcode(rcvrType), lvIndex + 1); + lvIndex += getParameterSize(rcvrType); + convertType(rcvrType, implDefiningClass, instantiatedMethodType.parameterType(0)); + } + int samParametersLength = samType.parameterCount(); + int argOffset = implMethodType.parameterCount() - samParametersLength; + for (int i = samReceiverLength; i < samParametersLength; i++) { + Class argType = samType.parameterType(i); + visitVarInsn(getLoadOpcode(argType), lvIndex + 1); + lvIndex += getParameterSize(argType); + convertType(argType, implMethodType.parameterType(argOffset + i), instantiatedMethodType.parameterType(i)); + } + } + + private int invocationOpcode() throws InternalError { + switch (implKind) { + case MethodHandleInfo.REF_invokeStatic: + return INVOKESTATIC; + case MethodHandleInfo.REF_newInvokeSpecial: + return INVOKESPECIAL; + case MethodHandleInfo.REF_invokeVirtual: + return INVOKEVIRTUAL; + case MethodHandleInfo.REF_invokeInterface: + return INVOKEINTERFACE; + case MethodHandleInfo.REF_invokeSpecial: + return INVOKESPECIAL; + default: + throw new InternalError("Unexpected invocation kind: " + implKind); + } + } + } + + static int getParameterSize(Class c) { + if (c == Void.TYPE) { + return 0; + } else if (c == Long.TYPE || c == Double.TYPE) { + return 2; + } + return 1; + } + + static int getLoadOpcode(Class c) { + if(c == Void.TYPE) { + throw new InternalError("Unexpected void type of load opcode"); + } + return ILOAD + getOpcodeOffset(c); + } + + static int getReturnOpcode(Class c) { + if(c == Void.TYPE) { + return RETURN; + } + return IRETURN + getOpcodeOffset(c); + } + + private static int getOpcodeOffset(Class c) { + if (c.isPrimitive()) { + if (c == Long.TYPE) { + return 1; + } else if (c == Float.TYPE) { + return 2; + } else if (c == Double.TYPE) { + return 3; + } + return 0; + } else { + return 4; + } + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InvokeDynamic.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InvokeDynamic.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008, 2011, 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; + +/** + * This is a place-holder class. Some HotSpot implementations need to see it. + */ +final class InvokeDynamic { + private InvokeDynamic() { throw new InternalError(); } // do not instantiate +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InvokerBytecodeGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InvokerBytecodeGenerator.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1052 @@ +/* + * Copyright (c) 2012, 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 sun.invoke.util.VerifyAccess; +import java.lang.invoke.LambdaForm.Name; +import java.lang.invoke.MethodHandles.Lookup; + +import sun.invoke.util.Wrapper; + +import java.io.*; +import java.util.*; + +import jdk.internal.org.objectweb.asm.*; + +import java.lang.reflect.*; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyType; + +/** + * Code generation backend for LambdaForm. + *

+ * @author John Rose, JSR 292 EG + */ +class InvokerBytecodeGenerator { + /** Define class names for convenience. */ + private static final String MH = "java/lang/invoke/MethodHandle"; + private static final String BMH = "java/lang/invoke/BoundMethodHandle"; + private static final String LF = "java/lang/invoke/LambdaForm"; + private static final String LFN = "java/lang/invoke/LambdaForm$Name"; + private static final String CLS = "java/lang/Class"; + private static final String OBJ = "java/lang/Object"; + private static final String OBJARY = "[Ljava/lang/Object;"; + + private static final String LF_SIG = "L" + LF + ";"; + private static final String LFN_SIG = "L" + LFN + ";"; + private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";"; + + /** Name of its super class*/ + private static final String superName = LF; + + /** Name of new class */ + private final String className; + + /** Name of the source file (for stack trace printing). */ + private final String sourceFile; + + private final LambdaForm lambdaForm; + private final String invokerName; + private final MethodType invokerType; + private final int[] localsMap; + + /** ASM bytecode generation. */ + private ClassWriter cw; + private MethodVisitor mv; + + private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); + private static final Class HOST_CLASS = LambdaForm.class; + + private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, + String className, String invokerName, MethodType invokerType) { + if (invokerName.contains(".")) { + int p = invokerName.indexOf("."); + className = invokerName.substring(0, p); + invokerName = invokerName.substring(p+1); + } + if (DUMP_CLASS_FILES) { + className = makeDumpableClassName(className); + } + this.className = superName + "$" + className; + this.sourceFile = "LambdaForm$" + className; + this.lambdaForm = lambdaForm; + this.invokerName = invokerName; + this.invokerType = invokerType; + this.localsMap = new int[localsMapSize]; + } + + private InvokerBytecodeGenerator(String className, String invokerName, MethodType invokerType) { + this(null, invokerType.parameterCount(), + className, invokerName, invokerType); + // Create an array to map name indexes to locals indexes. + for (int i = 0; i < localsMap.length; i++) { + localsMap[i] = invokerType.parameterSlotCount() - invokerType.parameterSlotDepth(i); + } + } + + private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) { + this(form, form.names.length, + className, form.debugName, invokerType); + // Create an array to map name indexes to locals indexes. + Name[] names = form.names; + for (int i = 0, index = 0; i < localsMap.length; i++) { + localsMap[i] = index; + index += Wrapper.forBasicType(names[i].type).stackSlots(); + } + } + + + /** instance counters for dumped classes */ + private final static HashMap DUMP_CLASS_FILES_COUNTERS; + /** debugging flag for saving generated class files */ + private final static File DUMP_CLASS_FILES_DIR; + + static { + if (DUMP_CLASS_FILES) { + DUMP_CLASS_FILES_COUNTERS = new HashMap<>(); + try { + File dumpDir = new File("DUMP_CLASS_FILES"); + if (!dumpDir.exists()) { + dumpDir.mkdirs(); + } + DUMP_CLASS_FILES_DIR = dumpDir; + System.out.println("Dumping class files to "+DUMP_CLASS_FILES_DIR+"/..."); + } catch (Exception e) { + throw newInternalError(e); + } + } else { + DUMP_CLASS_FILES_COUNTERS = null; + DUMP_CLASS_FILES_DIR = null; + } + } + + static void maybeDump(final String className, final byte[] classFile) { + if (DUMP_CLASS_FILES) { + System.out.println("dump: " + className); + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Void run() { + try { + String dumpName = className; + //dumpName = dumpName.replace('/', '-'); + File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class"); + dumpFile.getParentFile().mkdirs(); + FileOutputStream file = new FileOutputStream(dumpFile); + file.write(classFile); + file.close(); + return null; + } catch (IOException ex) { + throw newInternalError(ex); + } + } + }); + } + + } + + private static String makeDumpableClassName(String className) { + Integer ctr; + synchronized (DUMP_CLASS_FILES_COUNTERS) { + ctr = DUMP_CLASS_FILES_COUNTERS.get(className); + if (ctr == null) ctr = 0; + DUMP_CLASS_FILES_COUNTERS.put(className, ctr+1); + } + String sfx = ctr.toString(); + while (sfx.length() < 3) + sfx = "0"+sfx; + className += sfx; + return className; + } + + class CpPatch { + final int index; + final String placeholder; + final Object value; + CpPatch(int index, String placeholder, Object value) { + this.index = index; + this.placeholder = placeholder; + this.value = value; + } + public String toString() { + return "CpPatch/index="+index+",placeholder="+placeholder+",value="+value; + } + } + + Map cpPatches = new HashMap<>(); + + int cph = 0; // for counting constant placeholders + + String constantPlaceholder(Object arg) { + String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++; + if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + arg.toString() + ">>"; // debugging aid + if (cpPatches.containsKey(cpPlaceholder)) { + throw new InternalError("observed CP placeholder twice: " + cpPlaceholder); + } + // insert placeholder in CP and remember the patch + int index = cw.newConst((Object) cpPlaceholder); // TODO check if aready in the constant pool + cpPatches.put(cpPlaceholder, new CpPatch(index, cpPlaceholder, arg)); + return cpPlaceholder; + } + + Object[] cpPatches(byte[] classFile) { + int size = getConstantPoolSize(classFile); + Object[] res = new Object[size]; + for (CpPatch p : cpPatches.values()) { + if (p.index >= size) + throw new InternalError("in cpool["+size+"]: "+p+"\n"+Arrays.toString(Arrays.copyOf(classFile, 20))); + res[p.index] = p.value; + } + return res; + } + + /** + * Extract the number of constant pool entries from a given class file. + * + * @param classFile the bytes of the class file in question. + * @return the number of entries in the constant pool. + */ + private static int getConstantPoolSize(byte[] classFile) { + // The first few bytes: + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + return ((classFile[8] & 0xFF) << 8) | (classFile[9] & 0xFF); + } + + /** + * Extract the MemberName of a newly-defined method. + */ + private MemberName loadMethod(byte[] classFile) { + Class invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile)); + return resolveInvokerMember(invokerClass, invokerName, invokerType); + } + + /** + * Define a given class as anonymous class in the runtime system. + */ + private static Class loadAndInitializeInvokerClass(byte[] classBytes, Object[] patches) { + Class invokerClass = UNSAFE.defineAnonymousClass(HOST_CLASS, classBytes, patches); + UNSAFE.ensureClassInitialized(invokerClass); // Make sure the class is initialized; VM might complain. + return invokerClass; + } + + private static MemberName resolveInvokerMember(Class invokerClass, String name, MethodType type) { + MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic); + //System.out.println("resolveInvokerMember => "+member); + //for (Method m : invokerClass.getDeclaredMethods()) System.out.println(" "+m); + try { + member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class); + } catch (ReflectiveOperationException e) { + throw newInternalError(e); + } + //System.out.println("resolveInvokerMember => "+member); + return member; + } + + /** + * Set up class file generation. + */ + private void classFilePrologue() { + cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null); + cw.visitSource(sourceFile, null); + + String invokerDesc = invokerType.toMethodDescriptorString(); + mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null); + } + + /** + * Tear down class file generation. + */ + private void classFileEpilogue() { + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + /* + * Low-level emit helpers. + */ + private void emitConst(Object con) { + if (con == null) { + mv.visitInsn(Opcodes.ACONST_NULL); + return; + } + if (con instanceof Integer) { + emitIconstInsn((int) con); + return; + } + if (con instanceof Long) { + long x = (long) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2L); + return; + } + } + if (con instanceof Float) { + float x = (float) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2F); + return; + } + } + if (con instanceof Double) { + double x = (double) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2D); + return; + } + } + if (con instanceof Boolean) { + emitIconstInsn((boolean) con ? 1 : 0); + return; + } + // fall through: + mv.visitLdcInsn(con); + } + + private void emitIconstInsn(int i) { + int opcode; + switch (i) { + case 0: opcode = Opcodes.ICONST_0; break; + case 1: opcode = Opcodes.ICONST_1; break; + case 2: opcode = Opcodes.ICONST_2; break; + case 3: opcode = Opcodes.ICONST_3; break; + case 4: opcode = Opcodes.ICONST_4; break; + case 5: opcode = Opcodes.ICONST_5; break; + default: + if (i == (byte) i) { + mv.visitIntInsn(Opcodes.BIPUSH, i & 0xFF); + } else if (i == (short) i) { + mv.visitIntInsn(Opcodes.SIPUSH, (char) i); + } else { + mv.visitLdcInsn(i); + } + return; + } + mv.visitInsn(opcode); + } + + /* + * NOTE: These load/store methods use the localsMap to find the correct index! + */ + private void emitLoadInsn(char type, int index) { + int opcode; + switch (type) { + case 'I': opcode = Opcodes.ILOAD; break; + case 'J': opcode = Opcodes.LLOAD; break; + case 'F': opcode = Opcodes.FLOAD; break; + case 'D': opcode = Opcodes.DLOAD; break; + case 'L': opcode = Opcodes.ALOAD; break; + default: + throw new InternalError("unknown type: " + type); + } + mv.visitVarInsn(opcode, localsMap[index]); + } + private void emitAloadInsn(int index) { + emitLoadInsn('L', index); + } + + private void emitStoreInsn(char type, int index) { + int opcode; + switch (type) { + case 'I': opcode = Opcodes.ISTORE; break; + case 'J': opcode = Opcodes.LSTORE; break; + case 'F': opcode = Opcodes.FSTORE; break; + case 'D': opcode = Opcodes.DSTORE; break; + case 'L': opcode = Opcodes.ASTORE; break; + default: + throw new InternalError("unknown type: " + type); + } + mv.visitVarInsn(opcode, localsMap[index]); + } + private void emitAstoreInsn(int index) { + emitStoreInsn('L', index); + } + + /** + * Emit a boxing call. + * + * @param type primitive type class to box. + */ + private void emitBoxing(Class type) { + Wrapper wrapper = Wrapper.forPrimitiveType(type); + String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + String name = "valueOf"; + String desc = "(" + wrapper.basicTypeChar() + ")L" + owner + ";"; + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc); + } + + /** + * Emit an unboxing call (plus preceding checkcast). + * + * @param type wrapper type class to unbox. + */ + private void emitUnboxing(Class type) { + Wrapper wrapper = Wrapper.forWrapperType(type); + String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + String name = wrapper.primitiveSimpleName() + "Value"; + String desc = "()" + wrapper.basicTypeChar(); + mv.visitTypeInsn(Opcodes.CHECKCAST, owner); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc); + } + + /** + * Emit an implicit conversion. + * + * @param ptype type of value present on stack + * @param pclass type of value required on stack + */ + private void emitImplicitConversion(char ptype, Class pclass) { + switch (ptype) { + case 'L': + if (VerifyType.isNullConversion(Object.class, pclass)) + return; + if (isStaticallyNameable(pclass)) { + mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(pclass)); + } else { + mv.visitLdcInsn(constantPlaceholder(pclass)); + mv.visitTypeInsn(Opcodes.CHECKCAST, CLS); + mv.visitInsn(Opcodes.SWAP); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG); + if (pclass.isArray()) + mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + } + return; + case 'I': + if (!VerifyType.isNullConversion(int.class, pclass)) + emitPrimCast(ptype, Wrapper.basicTypeChar(pclass)); + return; + case 'J': + assert(pclass == long.class); + return; + case 'F': + assert(pclass == float.class); + return; + case 'D': + assert(pclass == double.class); + return; + } + throw new InternalError("bad implicit conversion: tc="+ptype+": "+pclass); + } + + /** + * Emits an actual return instruction conforming to the given return type. + */ + private void emitReturnInsn(Class type) { + int opcode; + switch (Wrapper.basicTypeChar(type)) { + case 'I': opcode = Opcodes.IRETURN; break; + case 'J': opcode = Opcodes.LRETURN; break; + case 'F': opcode = Opcodes.FRETURN; break; + case 'D': opcode = Opcodes.DRETURN; break; + case 'L': opcode = Opcodes.ARETURN; break; + case 'V': opcode = Opcodes.RETURN; break; + default: + throw new InternalError("unknown return type: " + type); + } + mv.visitInsn(opcode); + } + + private static String getInternalName(Class c) { + assert(VerifyAccess.isTypeVisible(c, Object.class)); + return c.getName().replace('.', '/'); + } + + /** + * Generate customized bytecode for a given LambdaForm. + */ + static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType) { + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("MH", form, invokerType); + return g.loadMethod(g.generateCustomizedCodeBytes()); + } + + /** + * Generate an invoker method for the passed {@link LambdaForm}. + */ + private byte[] generateCustomizedCodeBytes() { + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Mark this method as a compiled LambdaForm + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Compiled;", true); + + // Force inlining of this invoker method. + mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true); + + // iterate over the form's names, generating bytecode instructions for each + // start iterating at the first name following the arguments + for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { + Name name = lambdaForm.names[i]; + MemberName member = name.function.member(); + + if (isSelectAlternative(member)) { + // selectAlternative idiom + // FIXME: make sure this idiom is really present! + emitSelectAlternative(name, lambdaForm.names[i + 1]); + i++; // skip MH.invokeBasic of the selectAlternative result + } else if (isStaticallyInvocable(member)) { + emitStaticInvoke(member, name); + } else { + emitInvoke(name); + } + + // store the result from evaluating to the target name in a local if required + // (if this is the last value, i.e., the one that is going to be returned, + // avoid store/load/return and just return) + if (i == lambdaForm.names.length - 1 && i == lambdaForm.result) { + // return value - do nothing + } else if (name.type != 'V') { + // non-void: actually assign + emitStoreInsn(name.type, name.index()); + } + } + + // return statement + emitReturn(); + + classFileEpilogue(); + bogusMethod(lambdaForm); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Emit an invoke for the given name. + */ + void emitInvoke(Name name) { + if (true) { + // push receiver + MethodHandle target = name.function.resolvedHandle; + assert(target != null) : name.exprString(); + mv.visitLdcInsn(constantPlaceholder(target)); + mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + } else { + // load receiver + emitAloadInsn(0); + mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG); + mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG); + // TODO more to come + } + + // push arguments + for (int i = 0; i < name.arguments.length; i++) { + emitPushArgument(name, i); + } + + // invocation + MethodType type = name.function.methodType(); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString()); + } + + static private Class[] STATICALLY_INVOCABLE_PACKAGES = { + // Sample classes from each package we are willing to bind to statically: + java.lang.Object.class, + java.util.Arrays.class, + sun.misc.Unsafe.class + //MethodHandle.class already covered + }; + + static boolean isStaticallyInvocable(MemberName member) { + if (member == null) return false; + if (member.isConstructor()) return false; + Class cls = member.getDeclaringClass(); + if (cls.isArray() || cls.isPrimitive()) + return false; // FIXME + if (cls.isAnonymousClass() || cls.isLocalClass()) + return false; // inner class of some sort + if (cls.getClassLoader() != MethodHandle.class.getClassLoader()) + return false; // not on BCP + MethodType mtype = member.getMethodOrFieldType(); + if (!isStaticallyNameable(mtype.returnType())) + return false; + for (Class ptype : mtype.parameterArray()) + if (!isStaticallyNameable(ptype)) + return false; + if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls)) + return true; // in java.lang.invoke package + if (member.isPublic() && isStaticallyNameable(cls)) + return true; + return false; + } + + static boolean isStaticallyNameable(Class cls) { + while (cls.isArray()) + cls = cls.getComponentType(); + if (cls.isPrimitive()) + return true; // int[].class, for example + // could use VerifyAccess.isClassAccessible but the following is a safe approximation + if (cls.getClassLoader() != Object.class.getClassLoader()) + return false; + if (VerifyAccess.isSamePackage(MethodHandle.class, cls)) + return true; + if (!Modifier.isPublic(cls.getModifiers())) + return false; + for (Class pkgcls : STATICALLY_INVOCABLE_PACKAGES) { + if (VerifyAccess.isSamePackage(pkgcls, cls)) + return true; + } + return false; + } + + /** + * Emit an invoke for the given name, using the MemberName directly. + */ + void emitStaticInvoke(MemberName member, Name name) { + assert(member.equals(name.function.member())); + String cname = getInternalName(member.getDeclaringClass()); + String mname = member.getName(); + String mtype; + byte refKind = member.getReferenceKind(); + if (refKind == REF_invokeSpecial) { + // in order to pass the verifier, we need to convert this to invokevirtual in all cases + assert(member.canBeStaticallyBound()) : member; + refKind = REF_invokeVirtual; + } + + if (member.getDeclaringClass().isInterface() && refKind == REF_invokeVirtual) { + // Methods from Object declared in an interface can be resolved by JVM to invokevirtual kind. + // Need to convert it back to invokeinterface to pass verification and make the invocation works as expected. + refKind = REF_invokeInterface; + } + + // push arguments + for (int i = 0; i < name.arguments.length; i++) { + emitPushArgument(name, i); + } + + // invocation + if (member.isMethod()) { + mtype = member.getMethodType().toMethodDescriptorString(); + mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype, + member.getDeclaringClass().isInterface()); + } else { + mtype = MethodType.toFieldDescriptorString(member.getFieldType()); + mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype); + } + } + int refKindOpcode(byte refKind) { + switch (refKind) { + case REF_invokeVirtual: return Opcodes.INVOKEVIRTUAL; + case REF_invokeStatic: return Opcodes.INVOKESTATIC; + case REF_invokeSpecial: return Opcodes.INVOKESPECIAL; + case REF_invokeInterface: return Opcodes.INVOKEINTERFACE; + case REF_getField: return Opcodes.GETFIELD; + case REF_putField: return Opcodes.PUTFIELD; + case REF_getStatic: return Opcodes.GETSTATIC; + case REF_putStatic: return Opcodes.PUTSTATIC; + } + throw new InternalError("refKind="+refKind); + } + + /** + * Check if MemberName is a call to MethodHandleImpl.selectAlternative. + */ + private boolean isSelectAlternative(MemberName member) { + return member != null && + member.getDeclaringClass() == MethodHandleImpl.class && + member.getName().equals("selectAlternative"); + } + + /** + * Emit bytecode for the selectAlternative idiom. + * + * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest): + *

{@code
+     *   Lambda(a0:L,a1:I)=>{
+     *     t2:I=foo.test(a1:I);
+     *     t3:L=MethodHandleImpl.selectAlternative(t2:I,(MethodHandle(int)int),(MethodHandle(int)int));
+     *     t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I}
+     * }
+ */ + private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + MethodType type = selectAlternativeName.function.methodType(); + + Name receiver = (Name) invokeBasicName.arguments[0]; + + Label L_fallback = new Label(); + Label L_done = new Label(); + + // load test result + emitPushArgument(selectAlternativeName, 0); + mv.visitInsn(Opcodes.ICONST_1); + + // if_icmpne L_fallback + mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback); + + // invoke selectAlternativeName.arguments[1] + MethodHandle target = (MethodHandle) selectAlternativeName.arguments[1]; + emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative + emitAstoreInsn(receiver.index()); // store the MH in the receiver slot + emitInvoke(invokeBasicName); + + // goto L_done + mv.visitJumpInsn(Opcodes.GOTO, L_done); + + // L_fallback: + mv.visitLabel(L_fallback); + + // invoke selectAlternativeName.arguments[2] + MethodHandle fallback = (MethodHandle) selectAlternativeName.arguments[2]; + emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative + emitAstoreInsn(receiver.index()); // store the MH in the receiver slot + emitInvoke(invokeBasicName); + + // L_done: + mv.visitLabel(L_done); + } + + private void emitPushArgument(Name name, int paramIndex) { + Object arg = name.arguments[paramIndex]; + char ptype = name.function.parameterType(paramIndex); + MethodType mtype = name.function.methodType(); + if (arg instanceof Name) { + Name n = (Name) arg; + emitLoadInsn(n.type, n.index()); + emitImplicitConversion(n.type, mtype.parameterType(paramIndex)); + } else if ((arg == null || arg instanceof String) && ptype == 'L') { + emitConst(arg); + } else { + if (Wrapper.isWrapperType(arg.getClass()) && ptype != 'L') { + emitConst(arg); + } else { + mv.visitLdcInsn(constantPlaceholder(arg)); + emitImplicitConversion('L', mtype.parameterType(paramIndex)); + } + } + } + + /** + * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. + */ + private void emitReturn() { + // return statement + if (lambdaForm.result == -1) { + // void + mv.visitInsn(Opcodes.RETURN); + } else { + LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; + char rtype = Wrapper.basicTypeChar(invokerType.returnType()); + + // put return value on the stack if it is not already there + if (lambdaForm.result != lambdaForm.names.length - 1) { + emitLoadInsn(rn.type, lambdaForm.result); + } + + // potentially generate cast + // rtype is the return type of the invoker - generated code must conform to this + // rn.type is the type of the result Name in the LF + if (rtype != rn.type) { + // need cast + if (rtype == 'L') { + // possibly cast the primitive to the correct type for boxing + char boxedType = Wrapper.forWrapperType(invokerType.returnType()).basicTypeChar(); + if (boxedType != rn.type) { + emitPrimCast(rn.type, boxedType); + } + // cast primitive to reference ("boxing") + emitBoxing(invokerType.returnType()); + } else { + // to-primitive cast + if (rn.type != 'L') { + // prim-to-prim cast + emitPrimCast(rn.type, rtype); + } else { + // ref-to-prim cast ("unboxing") + throw new InternalError("no ref-to-prim (unboxing) casts supported right now"); + } + } + } + + // generate actual return statement + emitReturnInsn(invokerType.returnType()); + } + } + + /** + * Emit a type conversion bytecode casting from "from" to "to". + */ + private void emitPrimCast(char from, char to) { + // Here's how. + // - indicates forbidden + // <-> indicates implicit + // to ----> boolean byte short char int long float double + // from boolean <-> - - - - - - - + // byte - <-> i2s i2c <-> i2l i2f i2d + // short - i2b <-> i2c <-> i2l i2f i2d + // char - i2b i2s <-> <-> i2l i2f i2d + // int - i2b i2s i2c <-> i2l i2f i2d + // long - l2i,i2b l2i,i2s l2i,i2c l2i <-> l2f l2d + // float - f2i,i2b f2i,i2s f2i,i2c f2i f2l <-> f2d + // double - d2i,i2b d2i,i2s d2i,i2c d2i d2l d2f <-> + if (from == to) { + // no cast required, should be dead code anyway + return; + } + Wrapper wfrom = Wrapper.forBasicType(from); + Wrapper wto = Wrapper.forBasicType(to); + if (wfrom.isSubwordOrInt()) { + // cast from {byte,short,char,int} to anything + emitI2X(to); + } else { + // cast from {long,float,double} to anything + if (wto.isSubwordOrInt()) { + // cast to {byte,short,char,int} + emitX2I(from); + if (wto.bitWidth() < 32) { + // targets other than int require another conversion + emitI2X(to); + } + } else { + // cast to {long,float,double} - this is verbose + boolean error = false; + switch (from) { + case 'J': + if (to == 'F') { mv.visitInsn(Opcodes.L2F); } + else if (to == 'D') { mv.visitInsn(Opcodes.L2D); } + else error = true; + break; + case 'F': + if (to == 'J') { mv.visitInsn(Opcodes.F2L); } + else if (to == 'D') { mv.visitInsn(Opcodes.F2D); } + else error = true; + break; + case 'D': + if (to == 'J') { mv.visitInsn(Opcodes.D2L); } + else if (to == 'F') { mv.visitInsn(Opcodes.D2F); } + else error = true; + break; + default: + error = true; + break; + } + if (error) { + throw new IllegalStateException("unhandled prim cast: " + from + "2" + to); + } + } + } + } + + private void emitI2X(char type) { + switch (type) { + case 'B': mv.visitInsn(Opcodes.I2B); break; + case 'S': mv.visitInsn(Opcodes.I2S); break; + case 'C': mv.visitInsn(Opcodes.I2C); break; + case 'I': /* naught */ break; + case 'J': mv.visitInsn(Opcodes.I2L); break; + case 'F': mv.visitInsn(Opcodes.I2F); break; + case 'D': mv.visitInsn(Opcodes.I2D); break; + case 'Z': + // For compatibility with ValueConversions and explicitCastArguments: + mv.visitInsn(Opcodes.ICONST_1); + mv.visitInsn(Opcodes.IAND); + break; + default: throw new InternalError("unknown type: " + type); + } + } + + private void emitX2I(char type) { + switch (type) { + case 'J': mv.visitInsn(Opcodes.L2I); break; + case 'F': mv.visitInsn(Opcodes.F2I); break; + case 'D': mv.visitInsn(Opcodes.D2I); break; + default: throw new InternalError("unknown type: " + type); + } + } + + private static String basicTypeCharSignature(String prefix, MethodType type) { + StringBuilder buf = new StringBuilder(prefix); + for (Class ptype : type.parameterList()) + buf.append(Wrapper.forBasicType(ptype).basicTypeChar()); + buf.append('_').append(Wrapper.forBasicType(type.returnType()).basicTypeChar()); + return buf.toString(); + } + + /** + * Generate bytecode for a LambdaForm.vmentry which calls interpretWithArguments. + */ + static MemberName generateLambdaFormInterpreterEntryPoint(String sig) { + assert(LambdaForm.isValidSignature(sig)); + //System.out.println("generateExactInvoker "+sig); + // compute method type + // first parameter and return type + char tret = LambdaForm.signatureReturn(sig); + MethodType type = MethodType.methodType(LambdaForm.typeClass(tret), MethodHandle.class); + // other parameter types + int arity = LambdaForm.signatureArity(sig); + for (int i = 1; i < arity; i++) { + type = type.appendParameterTypes(LambdaForm.typeClass(sig.charAt(i))); + } + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("LFI", "interpret_"+tret, type); + return g.loadMethod(g.generateLambdaFormInterpreterEntryPointBytes()); + } + + private byte[] generateLambdaFormInterpreterEntryPointBytes() { + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Don't inline the interpreter entry. + mv.visitAnnotation("Ljava/lang/invoke/DontInline;", true); + + // create parameter array + emitIconstInsn(invokerType.parameterCount()); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + // fill parameter array + for (int i = 0; i < invokerType.parameterCount(); i++) { + Class ptype = invokerType.parameterType(i); + mv.visitInsn(Opcodes.DUP); + emitIconstInsn(i); + emitLoadInsn(Wrapper.basicTypeChar(ptype), i); + // box if primitive type + if (ptype.isPrimitive()) { + emitBoxing(ptype); + } + mv.visitInsn(Opcodes.AASTORE); + } + // invoke + emitAloadInsn(0); + mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", "Ljava/lang/invoke/LambdaForm;"); + mv.visitInsn(Opcodes.SWAP); // swap form and array; avoid local variable + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;"); + + // maybe unbox + Class rtype = invokerType.returnType(); + if (rtype.isPrimitive() && rtype != void.class) { + emitUnboxing(Wrapper.asWrapperType(rtype)); + } + + // return statement + emitReturnInsn(rtype); + + classFileEpilogue(); + bogusMethod(invokerType); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Generate bytecode for a NamedFunction invoker. + */ + static MemberName generateNamedFunctionInvoker(MethodTypeForm typeForm) { + MethodType invokerType = LambdaForm.NamedFunction.INVOKER_METHOD_TYPE; + String invokerName = basicTypeCharSignature("invoke_", typeForm.erasedType()); + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("NFI", invokerName, invokerType); + return g.loadMethod(g.generateNamedFunctionInvokerImpl(typeForm)); + } + + static int nfi = 0; + + private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) { + MethodType dstType = typeForm.erasedType(); + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Force inlining of this invoker method. + mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true); + + // Load receiver + emitAloadInsn(0); + + // Load arguments from array + for (int i = 0; i < dstType.parameterCount(); i++) { + emitAloadInsn(1); + emitIconstInsn(i); + mv.visitInsn(Opcodes.AALOAD); + + // Maybe unbox + Class dptype = dstType.parameterType(i); + if (dptype.isPrimitive()) { + Class sptype = dstType.basicType().wrap().parameterType(i); + Wrapper dstWrapper = Wrapper.forBasicType(dptype); + Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int + emitUnboxing(srcWrapper.wrapperType()); + emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar()); + } + } + + // Invoke + String targetDesc = dstType.basicType().toMethodDescriptorString(); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", targetDesc); + + // Box primitive types + Class rtype = dstType.returnType(); + if (rtype != void.class && rtype.isPrimitive()) { + Wrapper srcWrapper = Wrapper.forBasicType(rtype); + Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper; // widen subword to int + // boolean casts not allowed + emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar()); + emitBoxing(dstWrapper.primitiveType()); + } + + // If the return type is void we return a null reference. + if (rtype == void.class) { + mv.visitInsn(Opcodes.ACONST_NULL); + } + emitReturnInsn(Object.class); // NOTE: NamedFunction invokers always return a reference value. + + classFileEpilogue(); + bogusMethod(dstType); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool + * for debugging purposes. + */ + private void bogusMethod(Object... os) { + if (DUMP_CLASS_FILES) { + mv = cw.visitMethod(Opcodes.ACC_STATIC, "dummy", "()V", null, null); + for (Object o : os) { + mv.visitLdcInsn(o.toString()); + mv.visitInsn(Opcodes.POP); + } + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/Invokers.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/Invokers.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,462 @@ +/* + * 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.util.Arrays; +import sun.invoke.empty.Empty; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; +import static java.lang.invoke.LambdaForm.*; + +/** + * Construction and caching of often-used invokers. + * @author jrose + */ +class Invokers { + // exact type (sans leading taget MH) for the outgoing call + private final MethodType targetType; + + // FIXME: Get rid of the invokers that are not useful. + + // exact invoker for the outgoing call + private /*lazy*/ MethodHandle exactInvoker; + private /*lazy*/ MethodHandle basicInvoker; // invokeBasic (unchecked exact) + + // erased (partially untyped but with primitives) invoker for the outgoing call + // FIXME: get rid of + private /*lazy*/ MethodHandle erasedInvoker; + // FIXME: get rid of + /*lazy*/ MethodHandle erasedInvokerWithDrops; // for InvokeGeneric + + // general invoker for the outgoing call + private /*lazy*/ MethodHandle generalInvoker; + + // general invoker for the outgoing call, uses varargs + private /*lazy*/ MethodHandle varargsInvoker; + + // general invoker for the outgoing call; accepts a trailing Object[] + private final /*lazy*/ MethodHandle[] spreadInvokers; + + // invoker for an unbound callsite + private /*lazy*/ MethodHandle uninitializedCallSite; + + /** Compute and cache information common to all collecting adapters + * that implement members of the erasure-family of the given erased type. + */ + /*non-public*/ Invokers(MethodType targetType) { + this.targetType = targetType; + this.spreadInvokers = new MethodHandle[targetType.parameterCount()+1]; + } + + /*non-public*/ MethodHandle exactInvoker() { + MethodHandle invoker = exactInvoker; + if (invoker != null) return invoker; + invoker = makeExactOrGeneralInvoker(true); + exactInvoker = invoker; + return invoker; + } + + /*non-public*/ MethodHandle generalInvoker() { + MethodHandle invoker = generalInvoker; + if (invoker != null) return invoker; + invoker = makeExactOrGeneralInvoker(false); + generalInvoker = invoker; + return invoker; + } + + private MethodHandle makeExactOrGeneralInvoker(boolean isExact) { + MethodType mtype = targetType; + MethodType invokerType = mtype.invokerType(); + int which = (isExact ? MethodTypeForm.LF_EX_INVOKER : MethodTypeForm.LF_GEN_INVOKER); + LambdaForm lform = invokeHandleForm(mtype, false, which); + MethodHandle invoker = BoundMethodHandle.bindSingle(invokerType, lform, mtype); + String whichName = (isExact ? "invokeExact" : "invoke"); + invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke(whichName, mtype)); + assert(checkInvoker(invoker)); + maybeCompileToBytecode(invoker); + return invoker; + } + + /** If the target type seems to be common enough, eagerly compile the invoker to bytecodes. */ + private void maybeCompileToBytecode(MethodHandle invoker) { + final int EAGER_COMPILE_ARITY_LIMIT = 10; + if (targetType == targetType.erase() && + targetType.parameterCount() < EAGER_COMPILE_ARITY_LIMIT) { + invoker.form.compileToBytecode(); + } + } + + /*non-public*/ MethodHandle basicInvoker() { + MethodHandle invoker = basicInvoker; + if (invoker != null) return invoker; + MethodType basicType = targetType.basicType(); + if (basicType != targetType) { + // double cache; not used significantly + return basicInvoker = basicType.invokers().basicInvoker(); + } + MemberName method = invokeBasicMethod(basicType); + invoker = DirectMethodHandle.make(method); + assert(checkInvoker(invoker)); + basicInvoker = invoker; + return invoker; + } + + // This next one is called from LambdaForm.NamedFunction.. + /*non-public*/ static MemberName invokeBasicMethod(MethodType basicType) { + assert(basicType == basicType.basicType()); + try { + //Lookup.findVirtual(MethodHandle.class, name, type); + return IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, MethodHandle.class, "invokeBasic", basicType); + } catch (ReflectiveOperationException ex) { + throw newInternalError("JVM cannot find invoker for "+basicType, ex); + } + } + + private boolean checkInvoker(MethodHandle invoker) { + assert(targetType.invokerType().equals(invoker.type())) + : java.util.Arrays.asList(targetType, targetType.invokerType(), invoker); + assert(invoker.internalMemberName() == null || + invoker.internalMemberName().getMethodType().equals(targetType)); + assert(!invoker.isVarargsCollector()); + return true; + } + + // FIXME: get rid of + /*non-public*/ MethodHandle erasedInvoker() { + MethodHandle xinvoker = exactInvoker(); + MethodHandle invoker = erasedInvoker; + if (invoker != null) return invoker; + MethodType erasedType = targetType.erase(); + invoker = xinvoker.asType(erasedType.invokerType()); + erasedInvoker = invoker; + return invoker; + } + + /*non-public*/ MethodHandle spreadInvoker(int leadingArgCount) { + MethodHandle vaInvoker = spreadInvokers[leadingArgCount]; + if (vaInvoker != null) return vaInvoker; + int spreadArgCount = targetType.parameterCount() - leadingArgCount; + MethodType spreadInvokerType = targetType + .replaceParameterTypes(leadingArgCount, targetType.parameterCount(), Object[].class); + if (targetType.parameterSlotCount() <= MethodType.MAX_MH_INVOKER_ARITY) { + // Factor sinvoker.invoke(mh, a) into ginvoker.asSpreader().invoke(mh, a) + // where ginvoker.invoke(mh, a*) => mh.invoke(a*). + MethodHandle genInvoker = generalInvoker(); + vaInvoker = genInvoker.asSpreader(Object[].class, spreadArgCount); + } else { + // Cannot build a general invoker here of type ginvoker.invoke(mh, a*[254]). + // Instead, factor sinvoker.invoke(mh, a) into ainvoker.invoke(filter(mh), a) + // where filter(mh) == mh.asSpreader(Object[], spreadArgCount) + MethodHandle arrayInvoker = MethodHandles.exactInvoker(spreadInvokerType); + MethodHandle makeSpreader; + try { + makeSpreader = IMPL_LOOKUP + .findVirtual(MethodHandle.class, "asSpreader", + MethodType.methodType(MethodHandle.class, Class.class, int.class)); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + makeSpreader = MethodHandles.insertArguments(makeSpreader, 1, Object[].class, spreadArgCount); + vaInvoker = MethodHandles.filterArgument(arrayInvoker, 0, makeSpreader); + } + assert(vaInvoker.type().equals(spreadInvokerType.invokerType())); + maybeCompileToBytecode(vaInvoker); + spreadInvokers[leadingArgCount] = vaInvoker; + return vaInvoker; + } + + /*non-public*/ MethodHandle varargsInvoker() { + MethodHandle vaInvoker = varargsInvoker; + if (vaInvoker != null) return vaInvoker; + vaInvoker = spreadInvoker(0).asType(MethodType.genericMethodType(0, true).invokerType()); + varargsInvoker = vaInvoker; + return vaInvoker; + } + + private static MethodHandle THROW_UCS = null; + + /*non-public*/ MethodHandle uninitializedCallSite() { + MethodHandle invoker = uninitializedCallSite; + if (invoker != null) return invoker; + if (targetType.parameterCount() > 0) { + MethodType type0 = targetType.dropParameterTypes(0, targetType.parameterCount()); + Invokers invokers0 = type0.invokers(); + invoker = MethodHandles.dropArguments(invokers0.uninitializedCallSite(), + 0, targetType.parameterList()); + assert(invoker.type().equals(targetType)); + uninitializedCallSite = invoker; + return invoker; + } + invoker = THROW_UCS; + if (invoker == null) { + try { + THROW_UCS = invoker = IMPL_LOOKUP + .findStatic(CallSite.class, "uninitializedCallSite", + MethodType.methodType(Empty.class)); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } + invoker = MethodHandles.explicitCastArguments(invoker, MethodType.methodType(targetType.returnType())); + invoker = invoker.dropArguments(targetType, 0, targetType.parameterCount()); + assert(invoker.type().equals(targetType)); + uninitializedCallSite = invoker; + return invoker; + } + + public String toString() { + return "Invokers"+targetType; + } + + static MemberName methodHandleInvokeLinkerMethod(String name, + MethodType mtype, + Object[] appendixResult) { + int which; + switch (name) { + case "invokeExact": which = MethodTypeForm.LF_EX_LINKER; break; + case "invoke": which = MethodTypeForm.LF_GEN_LINKER; break; + default: throw new InternalError("not invoker: "+name); + } + LambdaForm lform; + if (mtype.parameterSlotCount() <= MethodType.MAX_MH_ARITY - MH_LINKER_ARG_APPENDED) { + lform = invokeHandleForm(mtype, false, which); + appendixResult[0] = mtype; + } else { + lform = invokeHandleForm(mtype, true, which); + } + return lform.vmentry; + } + + // argument count to account for trailing "appendix value" (typically the mtype) + private static final int MH_LINKER_ARG_APPENDED = 1; + + /** Returns an adapter for invokeExact or generic invoke, as a MH or constant pool linker. + * If !customized, caller is responsible for supplying, during adapter execution, + * a copy of the exact mtype. This is because the adapter might be generalized to + * a basic type. + * @param mtype the caller's method type (either basic or full-custom) + * @param customized whether to use a trailing appendix argument (to carry the mtype) + * @param which bit-encoded 0x01 whether it is a CP adapter ("linker") or MHs.invoker value ("invoker"); + * 0x02 whether it is for invokeExact or generic invoke + */ + private static LambdaForm invokeHandleForm(MethodType mtype, boolean customized, int which) { + boolean isCached; + if (!customized) { + mtype = mtype.basicType(); // normalize Z to I, String to Object, etc. + isCached = true; + } else { + isCached = false; // maybe cache if mtype == mtype.basicType() + } + boolean isLinker, isGeneric; + String debugName; + switch (which) { + case MethodTypeForm.LF_EX_LINKER: isLinker = true; isGeneric = false; debugName = "invokeExact_MT"; break; + case MethodTypeForm.LF_EX_INVOKER: isLinker = false; isGeneric = false; debugName = "exactInvoker"; break; + case MethodTypeForm.LF_GEN_LINKER: isLinker = true; isGeneric = true; debugName = "invoke_MT"; break; + case MethodTypeForm.LF_GEN_INVOKER: isLinker = false; isGeneric = true; debugName = "invoker"; break; + default: throw new InternalError(); + } + LambdaForm lform; + if (isCached) { + lform = mtype.form().cachedLambdaForm(which); + if (lform != null) return lform; + } + // exactInvokerForm (Object,Object)Object + // link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial + final int THIS_MH = 0; + final int CALL_MH = THIS_MH + (isLinker ? 0 : 1); + final int ARG_BASE = CALL_MH + 1; + final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount(); + final int INARG_LIMIT = OUTARG_LIMIT + (isLinker && !customized ? 1 : 0); + int nameCursor = OUTARG_LIMIT; + final int MTYPE_ARG = customized ? -1 : nameCursor++; // might be last in-argument + final int CHECK_TYPE = nameCursor++; + final int LINKER_CALL = nameCursor++; + MethodType invokerFormType = mtype.invokerType(); + if (isLinker) { + if (!customized) + invokerFormType = invokerFormType.appendParameterTypes(MemberName.class); + } else { + invokerFormType = invokerFormType.invokerType(); + } + Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType); + assert(names.length == nameCursor) + : Arrays.asList(mtype, customized, which, nameCursor, names.length); + if (MTYPE_ARG >= INARG_LIMIT) { + assert(names[MTYPE_ARG] == null); + NamedFunction getter = BoundMethodHandle.getSpeciesData("L").getterFunction(0); + names[MTYPE_ARG] = new Name(getter, names[THIS_MH]); + // else if isLinker, then MTYPE is passed in from the caller (e.g., the JVM) + } + + // Make the final call. If isGeneric, then prepend the result of type checking. + MethodType outCallType = mtype.basicType(); + Object[] outArgs = Arrays.copyOfRange(names, CALL_MH, OUTARG_LIMIT, Object[].class); + Object mtypeArg = (customized ? mtype : names[MTYPE_ARG]); + if (!isGeneric) { + names[CHECK_TYPE] = new Name(NF_checkExactType, names[CALL_MH], mtypeArg); + // mh.invokeExact(a*):R => checkExactType(mh, TYPEOF(a*:R)); mh.invokeBasic(a*) + } else { + names[CHECK_TYPE] = new Name(NF_checkGenericType, names[CALL_MH], mtypeArg); + // mh.invokeGeneric(a*):R => checkGenericType(mh, TYPEOF(a*:R)).invokeBasic(a*) + outArgs[0] = names[CHECK_TYPE]; + } + names[LINKER_CALL] = new Name(outCallType, outArgs); + lform = new LambdaForm(debugName, INARG_LIMIT, names); + if (isLinker) + lform.compileToBytecode(); // JVM needs a real methodOop + if (isCached) + lform = mtype.form().setCachedLambdaForm(which, lform); + return lform; + } + + /*non-public*/ static + WrongMethodTypeException newWrongMethodTypeException(MethodType actual, MethodType expected) { + // FIXME: merge with JVM logic for throwing WMTE + return new WrongMethodTypeException("expected "+expected+" but found "+actual); + } + + /** Static definition of MethodHandle.invokeExact checking code. */ + /*non-public*/ static + @ForceInline + void checkExactType(Object mhObj, Object expectedObj) { + MethodHandle mh = (MethodHandle) mhObj; + MethodType expected = (MethodType) expectedObj; + MethodType actual = mh.type(); + if (actual != expected) + throw newWrongMethodTypeException(expected, actual); + } + + /** Static definition of MethodHandle.invokeGeneric checking code. + * Directly returns the type-adjusted MH to invoke, as follows: + * {@code (R)MH.invoke(a*) => MH.asType(TYPEOF(a*:R)).invokeBasic(a*)} + */ + /*non-public*/ static + @ForceInline + Object checkGenericType(Object mhObj, Object expectedObj) { + MethodHandle mh = (MethodHandle) mhObj; + MethodType expected = (MethodType) expectedObj; + if (mh.type() == expected) return mh; + MethodHandle atc = mh.asTypeCache; + if (atc != null && atc.type() == expected) return atc; + return mh.asType(expected); + /* Maybe add more paths here. Possible optimizations: + * for (R)MH.invoke(a*), + * let MT0 = TYPEOF(a*:R), MT1 = MH.type + * + * if MT0==MT1 or MT1 can be safely called by MT0 + * => MH.invokeBasic(a*) + * if MT1 can be safely called by MT0[R := Object] + * => MH.invokeBasic(a*) & checkcast(R) + * if MT1 can be safely called by MT0[* := Object] + * => checkcast(A)* & MH.invokeBasic(a*) & checkcast(R) + * if a big adapter BA can be pulled out of (MT0,MT1) + * => BA.invokeBasic(MT0,MH,a*) + * if a local adapter LA can cached on static CS0 = new GICS(MT0) + * => CS0.LA.invokeBasic(MH,a*) + * else + * => MH.asType(MT0).invokeBasic(A*) + */ + } + + static MemberName linkToCallSiteMethod(MethodType mtype) { + LambdaForm lform = callSiteForm(mtype, false); + return lform.vmentry; + } + + static MemberName linkToTargetMethod(MethodType mtype) { + LambdaForm lform = callSiteForm(mtype, true); + return lform.vmentry; + } + + // skipCallSite is true if we are optimizing a ConstantCallSite + private static LambdaForm callSiteForm(MethodType mtype, boolean skipCallSite) { + mtype = mtype.basicType(); // normalize Z to I, String to Object, etc. + final int which = (skipCallSite ? MethodTypeForm.LF_MH_LINKER : MethodTypeForm.LF_CS_LINKER); + LambdaForm lform = mtype.form().cachedLambdaForm(which); + if (lform != null) return lform; + // exactInvokerForm (Object,Object)Object + // link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial + final int ARG_BASE = 0; + final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount(); + final int INARG_LIMIT = OUTARG_LIMIT + 1; + int nameCursor = OUTARG_LIMIT; + final int APPENDIX_ARG = nameCursor++; // the last in-argument + final int CSITE_ARG = skipCallSite ? -1 : APPENDIX_ARG; + final int CALL_MH = skipCallSite ? APPENDIX_ARG : nameCursor++; // result of getTarget + final int LINKER_CALL = nameCursor++; + MethodType invokerFormType = mtype.appendParameterTypes(skipCallSite ? MethodHandle.class : CallSite.class); + Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType); + assert(names.length == nameCursor); + assert(names[APPENDIX_ARG] != null); + if (!skipCallSite) + names[CALL_MH] = new Name(NF_getCallSiteTarget, names[CSITE_ARG]); + // (site.)invokedynamic(a*):R => mh = site.getTarget(); mh.invokeBasic(a*) + final int PREPEND_MH = 0, PREPEND_COUNT = 1; + Object[] outArgs = Arrays.copyOfRange(names, ARG_BASE, OUTARG_LIMIT + PREPEND_COUNT, Object[].class); + // prepend MH argument: + System.arraycopy(outArgs, 0, outArgs, PREPEND_COUNT, outArgs.length - PREPEND_COUNT); + outArgs[PREPEND_MH] = names[CALL_MH]; + names[LINKER_CALL] = new Name(mtype, outArgs); + lform = new LambdaForm((skipCallSite ? "linkToTargetMethod" : "linkToCallSite"), INARG_LIMIT, names); + lform.compileToBytecode(); // JVM needs a real methodOop + lform = mtype.form().setCachedLambdaForm(which, lform); + return lform; + } + + /** Static definition of MethodHandle.invokeGeneric checking code. */ + /*non-public*/ static + @ForceInline + Object getCallSiteTarget(Object site) { + return ((CallSite)site).getTarget(); + } + + // Local constant functions: + private static final NamedFunction NF_checkExactType; + private static final NamedFunction NF_checkGenericType; + private static final NamedFunction NF_asType; + private static final NamedFunction NF_getCallSiteTarget; + static { + try { + NF_checkExactType = new NamedFunction(Invokers.class + .getDeclaredMethod("checkExactType", Object.class, Object.class)); + NF_checkGenericType = new NamedFunction(Invokers.class + .getDeclaredMethod("checkGenericType", Object.class, Object.class)); + NF_asType = new NamedFunction(MethodHandle.class + .getDeclaredMethod("asType", MethodType.class)); + NF_getCallSiteTarget = new NamedFunction(Invokers.class + .getDeclaredMethod("getCallSiteTarget", Object.class)); + NF_checkExactType.resolve(); + NF_checkGenericType.resolve(); + NF_getCallSiteTarget.resolve(); + // bound + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/LambdaConversionException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/LambdaConversionException.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012, 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; + +/** + * LambdaConversionException + */ +public class LambdaConversionException extends Exception { + private static final long serialVersionUID = 292L + 8L; + + /** + * Constructs a {@code LambdaConversionException}. + */ + public LambdaConversionException() { + } + + /** + * Constructs a {@code LambdaConversionException} with a message. + * @param message the detail message + */ + public LambdaConversionException(String message) { + super(message); + } + + /** + * Constructs a {@code LambdaConversionException} with a message and cause. + * @param message the detail message + * @param cause the cause + */ + public LambdaConversionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a {@code LambdaConversionException} with a cause. + * @param cause the cause + */ + public LambdaConversionException(Throwable cause) { + super(cause); + } + + /** + * Constructs a {@code LambdaConversionException} with a message, + * cause, and other settings. + * @param message the detail message + * @param cause the cause + * @param enableSuppression whether or not suppressed exceptions are enabled + * @param writableStackTrace whether or not the stack trace is writable + */ + public LambdaConversionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/LambdaForm.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/LambdaForm.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1646 @@ +/* + * Copyright (c) 2011, 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.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import sun.invoke.util.Wrapper; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import java.lang.reflect.Field; +import java.util.Objects; + +/** + * The symbolic, non-executable form of a method handle's invocation semantics. + * It consists of a series of names. + * The first N (N=arity) names are parameters, + * while any remaining names are temporary values. + * Each temporary specifies the application of a function to some arguments. + * The functions are method handles, while the arguments are mixes of + * constant values and local names. + * The result of the lambda is defined as one of the names, often the last one. + *

+ * Here is an approximate grammar: + *

{@code
+ * LambdaForm = "(" ArgName* ")=>{" TempName* Result "}"
+ * ArgName = "a" N ":" T
+ * TempName = "t" N ":" T "=" Function "(" Argument* ");"
+ * Function = ConstantValue
+ * Argument = NameRef | ConstantValue
+ * Result = NameRef | "void"
+ * NameRef = "a" N | "t" N
+ * N = (any whole number)
+ * T = "L" | "I" | "J" | "F" | "D" | "V"
+ * }
+ * Names are numbered consecutively from left to right starting at zero. + * (The letters are merely a taste of syntax sugar.) + * Thus, the first temporary (if any) is always numbered N (where N=arity). + * Every occurrence of a name reference in an argument list must refer to + * a name previously defined within the same lambda. + * A lambda has a void result if and only if its result index is -1. + * If a temporary has the type "V", it cannot be the subject of a NameRef, + * even though possesses a number. + * Note that all reference types are erased to "L", which stands for {@code Object}. + * All subword types (boolean, byte, short, char) are erased to "I" which is {@code int}. + * The other types stand for the usual primitive types. + *

+ * Function invocation closely follows the static rules of the Java verifier. + * Arguments and return values must exactly match when their "Name" types are + * considered. + * Conversions are allowed only if they do not change the erased type. + *

    + *
  • L = Object: casts are used freely to convert into and out of reference types + *
  • I = int: subword types are forcibly narrowed when passed as arguments (see {@code explicitCastArguments}) + *
  • J = long: no implicit conversions + *
  • F = float: no implicit conversions + *
  • D = double: no implicit conversions + *
  • V = void: a function result may be void if and only if its Name is of type "V" + *
+ * Although implicit conversions are not allowed, explicit ones can easily be + * encoded by using temporary expressions which call type-transformed identity functions. + *

+ * Examples: + *

{@code
+ * (a0:J)=>{ a0 }
+ *     == identity(long)
+ * (a0:I)=>{ t1:V = System.out#println(a0); void }
+ *     == System.out#println(int)
+ * (a0:L)=>{ t1:V = System.out#println(a0); a0 }
+ *     == identity, with printing side-effect
+ * (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0);
+ *                 t3:L = BoundMethodHandle#target(a0);
+ *                 t4:L = MethodHandle#invoke(t3, t2, a1); t4 }
+ *     == general invoker for unary insertArgument combination
+ * (a0:L, a1:L)=>{ t2:L = FilterMethodHandle#filter(a0);
+ *                 t3:L = MethodHandle#invoke(t2, a1);
+ *                 t4:L = FilterMethodHandle#target(a0);
+ *                 t5:L = MethodHandle#invoke(t4, t3); t5 }
+ *     == general invoker for unary filterArgument combination
+ * (a0:L, a1:L)=>{ ...(same as previous example)...
+ *                 t5:L = MethodHandle#invoke(t4, t3, a1); t5 }
+ *     == general invoker for unary/unary foldArgument combination
+ * (a0:L, a1:I)=>{ t2:I = identity(long).asType((int)->long)(a1); t2 }
+ *     == invoker for identity method handle which performs i2l
+ * (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0);
+ *                 t3:L = Class#cast(t2,a1); t3 }
+ *     == invoker for identity method handle which performs cast
+ * }
+ *

+ * @author John Rose, JSR 292 EG + */ +class LambdaForm { + final int arity; + final int result; + @Stable final Name[] names; + final String debugName; + MemberName vmentry; // low-level behavior, or null if not yet prepared + private boolean isCompiled; + + // Caches for common structural transforms: + LambdaForm[] bindCache; + + public static final int VOID_RESULT = -1, LAST_RESULT = -2; + + LambdaForm(String debugName, + int arity, Name[] names, int result) { + assert(namesOK(arity, names)); + this.arity = arity; + this.result = fixResult(result, names); + this.names = names.clone(); + this.debugName = debugName; + normalize(); + } + + LambdaForm(String debugName, + int arity, Name[] names) { + this(debugName, + arity, names, LAST_RESULT); + } + + LambdaForm(String debugName, + Name[] formals, Name[] temps, Name result) { + this(debugName, + formals.length, buildNames(formals, temps, result), LAST_RESULT); + } + + private static Name[] buildNames(Name[] formals, Name[] temps, Name result) { + int arity = formals.length; + int length = arity + temps.length + (result == null ? 0 : 1); + Name[] names = Arrays.copyOf(formals, length); + System.arraycopy(temps, 0, names, arity, temps.length); + if (result != null) + names[length - 1] = result; + return names; + } + + private LambdaForm(String sig) { + // Make a blank lambda form, which returns a constant zero or null. + // It is used as a template for managing the invocation of similar forms that are non-empty. + // Called only from getPreparedForm. + assert(isValidSignature(sig)); + this.arity = signatureArity(sig); + this.result = (signatureReturn(sig) == 'V' ? -1 : arity); + this.names = buildEmptyNames(arity, sig); + this.debugName = "LF.zero"; + assert(nameRefsAreLegal()); + assert(isEmpty()); + assert(sig.equals(basicTypeSignature())); + } + + private static Name[] buildEmptyNames(int arity, String basicTypeSignature) { + assert(isValidSignature(basicTypeSignature)); + int resultPos = arity + 1; // skip '_' + if (arity < 0 || basicTypeSignature.length() != resultPos+1) + throw new IllegalArgumentException("bad arity for "+basicTypeSignature); + int numRes = (basicTypeSignature.charAt(resultPos) == 'V' ? 0 : 1); + Name[] names = arguments(numRes, basicTypeSignature.substring(0, arity)); + for (int i = 0; i < numRes; i++) { + names[arity + i] = constantZero(arity + i, basicTypeSignature.charAt(resultPos + i)); + } + return names; + } + + private static int fixResult(int result, Name[] names) { + if (result >= 0) { + if (names[result].type == 'V') + return -1; + } else if (result == LAST_RESULT) { + return names.length - 1; + } + return result; + } + + private static boolean namesOK(int arity, Name[] names) { + for (int i = 0; i < names.length; i++) { + Name n = names[i]; + assert(n != null) : "n is null"; + if (i < arity) + assert( n.isParam()) : n + " is not param at " + i; + else + assert(!n.isParam()) : n + " is param at " + i; + } + return true; + } + + /** Renumber and/or replace params so that they are interned and canonically numbered. */ + private void normalize() { + Name[] oldNames = null; + int changesStart = 0; + for (int i = 0; i < names.length; i++) { + Name n = names[i]; + if (!n.initIndex(i)) { + if (oldNames == null) { + oldNames = names.clone(); + changesStart = i; + } + names[i] = n.cloneWithIndex(i); + } + } + if (oldNames != null) { + int startFixing = arity; + if (startFixing <= changesStart) + startFixing = changesStart+1; + for (int i = startFixing; i < names.length; i++) { + Name fixed = names[i].replaceNames(oldNames, names, changesStart, i); + names[i] = fixed.newIndex(i); + } + } + assert(nameRefsAreLegal()); + int maxInterned = Math.min(arity, INTERNED_ARGUMENT_LIMIT); + boolean needIntern = false; + for (int i = 0; i < maxInterned; i++) { + Name n = names[i], n2 = internArgument(n); + if (n != n2) { + names[i] = n2; + needIntern = true; + } + } + if (needIntern) { + for (int i = arity; i < names.length; i++) { + names[i].internArguments(); + } + assert(nameRefsAreLegal()); + } + } + + /** + * Check that all embedded Name references are localizable to this lambda, + * and are properly ordered after their corresponding definitions. + *

+ * Note that a Name can be local to multiple lambdas, as long as + * it possesses the same index in each use site. + * This allows Name references to be freely reused to construct + * fresh lambdas, without confusion. + */ + private boolean nameRefsAreLegal() { + assert(arity >= 0 && arity <= names.length); + assert(result >= -1 && result < names.length); + // Do all names possess an index consistent with their local definition order? + for (int i = 0; i < arity; i++) { + Name n = names[i]; + assert(n.index() == i) : Arrays.asList(n.index(), i); + assert(n.isParam()); + } + // Also, do all local name references + for (int i = arity; i < names.length; i++) { + Name n = names[i]; + assert(n.index() == i); + for (Object arg : n.arguments) { + if (arg instanceof Name) { + Name n2 = (Name) arg; + int i2 = n2.index; + assert(0 <= i2 && i2 < names.length) : n.debugString() + ": 0 <= i2 && i2 < names.length: 0 <= " + i2 + " < " + names.length; + assert(names[i2] == n2) : Arrays.asList("-1-", i, "-2-", n.debugString(), "-3-", i2, "-4-", n2.debugString(), "-5-", names[i2].debugString(), "-6-", this); + assert(i2 < i); // ref must come after def! + } + } + } + return true; + } + + /** Invoke this form on the given arguments. */ + // final Object invoke(Object... args) throws Throwable { + // // NYI: fit this into the fast path? + // return interpretWithArguments(args); + // } + + /** Report the return type. */ + char returnType() { + if (result < 0) return 'V'; + Name n = names[result]; + return n.type; + } + + /** Report the N-th argument type. */ + char parameterType(int n) { + assert(n < arity); + return names[n].type; + } + + /** Report the arity. */ + int arity() { + return arity; + } + + /** Return the method type corresponding to my basic type signature. */ + MethodType methodType() { + return signatureType(basicTypeSignature()); + } + /** Return ABC_Z, where the ABC are parameter type characters, and Z is the return type character. */ + final String basicTypeSignature() { + StringBuilder buf = new StringBuilder(arity() + 3); + for (int i = 0, a = arity(); i < a; i++) + buf.append(parameterType(i)); + return buf.append('_').append(returnType()).toString(); + } + static int signatureArity(String sig) { + assert(isValidSignature(sig)); + return sig.indexOf('_'); + } + static char signatureReturn(String sig) { + return sig.charAt(signatureArity(sig)+1); + } + static boolean isValidSignature(String sig) { + int arity = sig.indexOf('_'); + if (arity < 0) return false; // must be of the form *_* + int siglen = sig.length(); + if (siglen != arity + 2) return false; // *_X + for (int i = 0; i < siglen; i++) { + if (i == arity) continue; // skip '_' + char c = sig.charAt(i); + if (c == 'V') + return (i == siglen - 1 && arity == siglen - 2); + if (ALL_TYPES.indexOf(c) < 0) return false; // must be [LIJFD] + } + return true; // [LIJFD]*_[LIJFDV] + } + static Class typeClass(char t) { + switch (t) { + case 'I': return int.class; + case 'J': return long.class; + case 'F': return float.class; + case 'D': return double.class; + case 'L': return Object.class; + case 'V': return void.class; + default: assert false; + } + return null; + } + static MethodType signatureType(String sig) { + Class[] ptypes = new Class[signatureArity(sig)]; + for (int i = 0; i < ptypes.length; i++) + ptypes[i] = typeClass(sig.charAt(i)); + Class rtype = typeClass(signatureReturn(sig)); + return MethodType.methodType(rtype, ptypes); + } + + /* + * Code generation issues: + * + * Compiled LFs should be reusable in general. + * The biggest issue is how to decide when to pull a name into + * the bytecode, versus loading a reified form from the MH data. + * + * For example, an asType wrapper may require execution of a cast + * after a call to a MH. The target type of the cast can be placed + * as a constant in the LF itself. This will force the cast type + * to be compiled into the bytecodes and native code for the MH. + * Or, the target type of the cast can be erased in the LF, and + * loaded from the MH data. (Later on, if the MH as a whole is + * inlined, the data will flow into the inlined instance of the LF, + * as a constant, and the end result will be an optimal cast.) + * + * This erasure of cast types can be done with any use of + * reference types. It can also be done with whole method + * handles. Erasing a method handle might leave behind + * LF code that executes correctly for any MH of a given + * type, and load the required MH from the enclosing MH's data. + * Or, the erasure might even erase the expected MT. + * + * Also, for direct MHs, the MemberName of the target + * could be erased, and loaded from the containing direct MH. + * As a simple case, a LF for all int-valued non-static + * field getters would perform a cast on its input argument + * (to non-constant base type derived from the MemberName) + * and load an integer value from the input object + * (at a non-constant offset also derived from the MemberName). + * Such MN-erased LFs would be inlinable back to optimized + * code, whenever a constant enclosing DMH is available + * to supply a constant MN from its data. + * + * The main problem here is to keep LFs reasonably generic, + * while ensuring that hot spots will inline good instances. + * "Reasonably generic" means that we don't end up with + * repeated versions of bytecode or machine code that do + * not differ in their optimized form. Repeated versions + * of machine would have the undesirable overheads of + * (a) redundant compilation work and (b) extra I$ pressure. + * To control repeated versions, we need to be ready to + * erase details from LFs and move them into MH data, + * whevener those details are not relevant to significant + * optimization. "Significant" means optimization of + * code that is actually hot. + * + * Achieving this may require dynamic splitting of MHs, by replacing + * a generic LF with a more specialized one, on the same MH, + * if (a) the MH is frequently executed and (b) the MH cannot + * be inlined into a containing caller, such as an invokedynamic. + * + * Compiled LFs that are no longer used should be GC-able. + * If they contain non-BCP references, they should be properly + * interlinked with the class loader(s) that their embedded types + * depend on. This probably means that reusable compiled LFs + * will be tabulated (indexed) on relevant class loaders, + * or else that the tables that cache them will have weak links. + */ + + /** + * Make this LF directly executable, as part of a MethodHandle. + * Invariant: Every MH which is invoked must prepare its LF + * before invocation. + * (In principle, the JVM could do this very lazily, + * as a sort of pre-invocation linkage step.) + */ + public void prepare() { + if (COMPILE_THRESHOLD == 0) { + compileToBytecode(); + } + if (this.vmentry != null) { + // already prepared (e.g., a primitive DMH invoker form) + return; + } + LambdaForm prep = getPreparedForm(basicTypeSignature()); + this.vmentry = prep.vmentry; + // TO DO: Maybe add invokeGeneric, invokeWithArguments + } + + /** Generate optimizable bytecode for this form. */ + MemberName compileToBytecode() { + MethodType invokerType = methodType(); + assert(vmentry == null || vmentry.getMethodType().basicType().equals(invokerType)); + if (vmentry != null && isCompiled) { + return vmentry; // already compiled somehow + } + try { + vmentry = InvokerBytecodeGenerator.generateCustomizedCode(this, invokerType); + if (TRACE_INTERPRETER) + traceInterpreter("compileToBytecode", this); + isCompiled = true; + return vmentry; + } catch (Error | Exception ex) { + throw newInternalError("compileToBytecode", ex); + } + } + + private static final ConcurrentHashMap PREPARED_FORMS; + static { + int capacity = 512; // expect many distinct signatures over time + float loadFactor = 0.75f; // normal default + int writers = 1; + PREPARED_FORMS = new ConcurrentHashMap<>(capacity, loadFactor, writers); + } + + private static Map computeInitialPreparedForms() { + // Find all predefined invokers and associate them with canonical empty lambda forms. + HashMap forms = new HashMap<>(); + for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) { + if (!m.isStatic() || !m.isPackage()) continue; + MethodType mt = m.getMethodType(); + if (mt.parameterCount() > 0 && + mt.parameterType(0) == MethodHandle.class && + m.getName().startsWith("interpret_")) { + String sig = basicTypeSignature(mt); + assert(m.getName().equals("interpret" + sig.substring(sig.indexOf('_')))); + LambdaForm form = new LambdaForm(sig); + form.vmentry = m; + mt.form().setCachedLambdaForm(MethodTypeForm.LF_COUNTER, form); + // FIXME: get rid of PREPARED_FORMS; use MethodTypeForm cache only + forms.put(sig, form); + } + } + //System.out.println("computeInitialPreparedForms => "+forms); + return forms; + } + + // Set this false to disable use of the interpret_L methods defined in this file. + private static final boolean USE_PREDEFINED_INTERPRET_METHODS = true; + + // The following are predefined exact invokers. The system must build + // a separate invoker for each distinct signature. + static Object interpret_L(MethodHandle mh) throws Throwable { + Object[] av = {mh}; + String sig = null; + assert(argumentTypesMatch(sig = "L_L", av)); + Object res = mh.form.interpretWithArguments(av); + assert(returnTypesMatch(sig, av, res)); + return res; + } + static Object interpret_L(MethodHandle mh, Object x1) throws Throwable { + Object[] av = {mh, x1}; + String sig = null; + assert(argumentTypesMatch(sig = "LL_L", av)); + Object res = mh.form.interpretWithArguments(av); + assert(returnTypesMatch(sig, av, res)); + return res; + } + static Object interpret_L(MethodHandle mh, Object x1, Object x2) throws Throwable { + Object[] av = {mh, x1, x2}; + String sig = null; + assert(argumentTypesMatch(sig = "LLL_L", av)); + Object res = mh.form.interpretWithArguments(av); + assert(returnTypesMatch(sig, av, res)); + return res; + } + private static LambdaForm getPreparedForm(String sig) { + MethodType mtype = signatureType(sig); + //LambdaForm prep = PREPARED_FORMS.get(sig); + LambdaForm prep = mtype.form().cachedLambdaForm(MethodTypeForm.LF_INTERPRET); + if (prep != null) return prep; + assert(isValidSignature(sig)); + prep = new LambdaForm(sig); + prep.vmentry = InvokerBytecodeGenerator.generateLambdaFormInterpreterEntryPoint(sig); + //LambdaForm prep2 = PREPARED_FORMS.putIfAbsent(sig.intern(), prep); + return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_INTERPRET, prep); + } + + // The next few routines are called only from assert expressions + // They verify that the built-in invokers process the correct raw data types. + private static boolean argumentTypesMatch(String sig, Object[] av) { + int arity = signatureArity(sig); + assert(av.length == arity) : "av.length == arity: av.length=" + av.length + ", arity=" + arity; + assert(av[0] instanceof MethodHandle) : "av[0] not instace of MethodHandle: " + av[0]; + MethodHandle mh = (MethodHandle) av[0]; + MethodType mt = mh.type(); + assert(mt.parameterCount() == arity-1); + for (int i = 0; i < av.length; i++) { + Class pt = (i == 0 ? MethodHandle.class : mt.parameterType(i-1)); + assert(valueMatches(sig.charAt(i), pt, av[i])); + } + return true; + } + private static boolean valueMatches(char tc, Class type, Object x) { + // The following line is needed because (...)void method handles can use non-void invokers + if (type == void.class) tc = 'V'; // can drop any kind of value + assert tc == basicType(type) : tc + " == basicType(" + type + ")=" + basicType(type); + switch (tc) { + case 'I': assert checkInt(type, x) : "checkInt(" + type + "," + x +")"; break; + case 'J': assert x instanceof Long : "instanceof Long: " + x; break; + case 'F': assert x instanceof Float : "instanceof Float: " + x; break; + case 'D': assert x instanceof Double : "instanceof Double: " + x; break; + case 'L': assert checkRef(type, x) : "checkRef(" + type + "," + x + ")"; break; + case 'V': break; // allow anything here; will be dropped + default: assert(false); + } + return true; + } + private static boolean returnTypesMatch(String sig, Object[] av, Object res) { + MethodHandle mh = (MethodHandle) av[0]; + return valueMatches(signatureReturn(sig), mh.type().returnType(), res); + } + private static boolean checkInt(Class type, Object x) { + assert(x instanceof Integer); + if (type == int.class) return true; + Wrapper w = Wrapper.forBasicType(type); + assert(w.isSubwordOrInt()); + Object x1 = Wrapper.INT.wrap(w.wrap(x)); + return x.equals(x1); + } + private static boolean checkRef(Class type, Object x) { + assert(!type.isPrimitive()); + if (x == null) return true; + if (type.isInterface()) return true; + return type.isInstance(x); + } + + /** If the invocation count hits the threshold we spin bytecodes and call that subsequently. */ + private static final int COMPILE_THRESHOLD; + static { + if (MethodHandleStatics.COMPILE_THRESHOLD != null) + COMPILE_THRESHOLD = MethodHandleStatics.COMPILE_THRESHOLD; + else + COMPILE_THRESHOLD = 30; // default value + } + private int invocationCounter = 0; + + @Hidden + @DontInline + /** Interpretively invoke this form on the given arguments. */ + Object interpretWithArguments(Object... argumentValues) throws Throwable { + if (TRACE_INTERPRETER) + return interpretWithArgumentsTracing(argumentValues); + checkInvocationCounter(); + assert(arityCheck(argumentValues)); + Object[] values = Arrays.copyOf(argumentValues, names.length); + for (int i = argumentValues.length; i < values.length; i++) { + values[i] = interpretName(names[i], values); + } + return (result < 0) ? null : values[result]; + } + + @Hidden + @DontInline + /** Evaluate a single Name within this form, applying its function to its arguments. */ + Object interpretName(Name name, Object[] values) throws Throwable { + if (TRACE_INTERPRETER) + traceInterpreter("| interpretName", name.debugString(), (Object[]) null); + Object[] arguments = Arrays.copyOf(name.arguments, name.arguments.length, Object[].class); + for (int i = 0; i < arguments.length; i++) { + Object a = arguments[i]; + if (a instanceof Name) { + int i2 = ((Name)a).index(); + assert(names[i2] == a); + a = values[i2]; + arguments[i] = a; + } + } + return name.function.invokeWithArguments(arguments); + } + + private void checkInvocationCounter() { + if (COMPILE_THRESHOLD != 0 && + invocationCounter < COMPILE_THRESHOLD) { + invocationCounter++; // benign race + if (invocationCounter >= COMPILE_THRESHOLD) { + // Replace vmentry with a bytecode version of this LF. + compileToBytecode(); + } + } + } + Object interpretWithArgumentsTracing(Object... argumentValues) throws Throwable { + traceInterpreter("[ interpretWithArguments", this, argumentValues); + if (invocationCounter < COMPILE_THRESHOLD) { + int ctr = invocationCounter++; // benign race + traceInterpreter("| invocationCounter", ctr); + if (invocationCounter >= COMPILE_THRESHOLD) { + compileToBytecode(); + } + } + Object rval; + try { + assert(arityCheck(argumentValues)); + Object[] values = Arrays.copyOf(argumentValues, names.length); + for (int i = argumentValues.length; i < values.length; i++) { + values[i] = interpretName(names[i], values); + } + rval = (result < 0) ? null : values[result]; + } catch (Throwable ex) { + traceInterpreter("] throw =>", ex); + throw ex; + } + traceInterpreter("] return =>", rval); + return rval; + } + + //** This transform is applied (statically) to every name.function. */ + /* + private static MethodHandle eraseSubwordTypes(MethodHandle mh) { + MethodType mt = mh.type(); + if (mt.hasPrimitives()) { + mt = mt.changeReturnType(eraseSubwordType(mt.returnType())); + for (int i = 0; i < mt.parameterCount(); i++) { + mt = mt.changeParameterType(i, eraseSubwordType(mt.parameterType(i))); + } + mh = MethodHandles.explicitCastArguments(mh, mt); + } + return mh; + } + private static Class eraseSubwordType(Class type) { + if (!type.isPrimitive()) return type; + if (type == int.class) return type; + Wrapper w = Wrapper.forPrimitiveType(type); + if (w.isSubwordOrInt()) return int.class; + return type; + } + */ + + static void traceInterpreter(String event, Object obj, Object... args) { + if (TRACE_INTERPRETER) { + System.out.println("LFI: "+event+" "+(obj != null ? obj : "")+(args != null && args.length != 0 ? Arrays.asList(args) : "")); + } + } + static void traceInterpreter(String event, Object obj) { + traceInterpreter(event, obj, (Object[])null); + } + private boolean arityCheck(Object[] argumentValues) { + assert(argumentValues.length == arity) : arity+"!="+Arrays.asList(argumentValues)+".length"; + // also check that the leading (receiver) argument is somehow bound to this LF: + assert(argumentValues[0] instanceof MethodHandle) : "not MH: " + argumentValues[0]; + assert(((MethodHandle)argumentValues[0]).internalForm() == this); + // note: argument #0 could also be an interface wrapper, in the future + return true; + } + + private boolean isEmpty() { + if (result < 0) + return (names.length == arity); + else if (result == arity && names.length == arity + 1) + return names[arity].isConstantZero(); + else + return false; + } + + public String toString() { + StringBuilder buf = new StringBuilder(debugName+"=Lambda("); + for (int i = 0; i < names.length; i++) { + if (i == arity) buf.append(")=>{"); + Name n = names[i]; + if (i >= arity) buf.append("\n "); + buf.append(n); + if (i < arity) { + if (i+1 < arity) buf.append(","); + continue; + } + buf.append("=").append(n.exprString()); + buf.append(";"); + } + buf.append(result < 0 ? "void" : names[result]).append("}"); + if (TRACE_INTERPRETER) { + // Extra verbosity: + buf.append(":").append(basicTypeSignature()); + buf.append("/").append(vmentry); + } + return buf.toString(); + } + + /** + * Apply immediate binding for a Name in this form indicated by its position relative to the form. + * The first parameter to a LambdaForm, a0:L, always represents the form's method handle, so 0 is not + * accepted as valid. + */ + LambdaForm bindImmediate(int pos, char basicType, Object value) { + // must be an argument, and the types must match + assert pos > 0 && pos < arity && names[pos].type == basicType && Name.typesMatch(basicType, value); + + int arity2 = arity - 1; + Name[] names2 = new Name[names.length - 1]; + for (int r = 0, w = 0; r < names.length; ++r, ++w) { // (r)ead from names, (w)rite to names2 + Name n = names[r]; + if (n.isParam()) { + if (n.index == pos) { + // do not copy over the argument that is to be replaced with a literal, + // but adjust the write index + --w; + } else { + names2[w] = new Name(w, n.type); + } + } else { + Object[] arguments2 = new Object[n.arguments.length]; + for (int i = 0; i < n.arguments.length; ++i) { + Object arg = n.arguments[i]; + if (arg instanceof Name) { + int ni = ((Name) arg).index; + if (ni == pos) { + arguments2[i] = value; + } else if (ni < pos) { + // replacement position not yet passed + arguments2[i] = names2[ni]; + } else { + // replacement position passed + arguments2[i] = names2[ni - 1]; + } + } else { + arguments2[i] = arg; + } + } + names2[w] = new Name(n.function, arguments2); + names2[w].initIndex(w); + } + } + + int result2 = result == -1 ? -1 : result - 1; + return new LambdaForm(debugName, arity2, names2, result2); + } + + LambdaForm bind(int namePos, BoundMethodHandle.SpeciesData oldData) { + Name name = names[namePos]; + BoundMethodHandle.SpeciesData newData = oldData.extendWithType(name.type); + return bind(name, newData.getterName(names[0], oldData.fieldCount()), oldData, newData); + } + LambdaForm bind(Name name, Name binding, + BoundMethodHandle.SpeciesData oldData, + BoundMethodHandle.SpeciesData newData) { + int pos = name.index; + assert(name.isParam()); + assert(!binding.isParam()); + assert(name.type == binding.type); + assert(0 <= pos && pos < arity && names[pos] == name); + assert(binding.function.memberDeclaringClassOrNull() == newData.clazz); + assert(oldData.getters.length == newData.getters.length-1); + if (bindCache != null) { + LambdaForm form = bindCache[pos]; + if (form != null) { + assert(form.contains(binding)) : "form << " + form + " >> does not contain binding << " + binding + " >>"; + return form; + } + } else { + bindCache = new LambdaForm[arity]; + } + assert(nameRefsAreLegal()); + int arity2 = arity-1; + Name[] names2 = names.clone(); + names2[pos] = binding; // we might move this in a moment + + // The newly created LF will run with a different BMH. + // Switch over any pre-existing BMH field references to the new BMH class. + int firstOldRef = -1; + for (int i = 0; i < names2.length; i++) { + Name n = names[i]; + if (n.function != null && + n.function.memberDeclaringClassOrNull() == oldData.clazz) { + MethodHandle oldGetter = n.function.resolvedHandle; + MethodHandle newGetter = null; + for (int j = 0; j < oldData.getters.length; j++) { + if (oldGetter == oldData.getters[j]) + newGetter = newData.getters[j]; + } + if (newGetter != null) { + if (firstOldRef < 0) firstOldRef = i; + Name n2 = new Name(newGetter, n.arguments); + names2[i] = n2; + } + } + } + + // Walk over the new list of names once, in forward order. + // Replace references to 'name' with 'binding'. + // Replace data structure references to the old BMH species with the new. + // This might cause a ripple effect, but it will settle in one pass. + assert(firstOldRef < 0 || firstOldRef > pos); + for (int i = pos+1; i < names2.length; i++) { + if (i <= arity2) continue; + names2[i] = names2[i].replaceNames(names, names2, pos, i); + } + + // (a0, a1, name=a2, a3, a4) => (a0, a1, a3, a4, binding) + int insPos = pos; + for (; insPos+1 < names2.length; insPos++) { + Name n = names2[insPos+1]; + if (n.isSiblingBindingBefore(binding)) { + names2[insPos] = n; + } else { + break; + } + } + names2[insPos] = binding; + + // Since we moved some stuff, maybe update the result reference: + int result2 = result; + if (result2 == pos) + result2 = insPos; + else if (result2 > pos && result2 <= insPos) + result2 -= 1; + + return bindCache[pos] = new LambdaForm(debugName, arity2, names2, result2); + } + + boolean contains(Name name) { + int pos = name.index(); + if (pos >= 0) { + return pos < names.length && name.equals(names[pos]); + } + for (int i = arity; i < names.length; i++) { + if (name.equals(names[i])) + return true; + } + return false; + } + + LambdaForm addArguments(int pos, char... types) { + assert(pos <= arity); + int length = names.length; + int inTypes = types.length; + Name[] names2 = Arrays.copyOf(names, length + inTypes); + int arity2 = arity + inTypes; + int result2 = result; + if (result2 >= arity) + result2 += inTypes; + // names array has MH in slot 0; skip it. + int argpos = pos + 1; + // Note: The LF constructor will rename names2[argpos...]. + // Make space for new arguments (shift temporaries). + System.arraycopy(names, argpos, names2, argpos + inTypes, length - argpos); + for (int i = 0; i < inTypes; i++) { + names2[argpos + i] = new Name(types[i]); + } + return new LambdaForm(debugName, arity2, names2, result2); + } + + LambdaForm addArguments(int pos, List> types) { + char[] basicTypes = new char[types.size()]; + for (int i = 0; i < basicTypes.length; i++) + basicTypes[i] = basicType(types.get(i)); + return addArguments(pos, basicTypes); + } + + LambdaForm permuteArguments(int skip, int[] reorder, char[] types) { + // Note: When inArg = reorder[outArg], outArg is fed by a copy of inArg. + // The types are the types of the new (incoming) arguments. + int length = names.length; + int inTypes = types.length; + int outArgs = reorder.length; + assert(skip+outArgs == arity); + assert(permutedTypesMatch(reorder, types, names, skip)); + int pos = 0; + // skip trivial first part of reordering: + while (pos < outArgs && reorder[pos] == pos) pos += 1; + Name[] names2 = new Name[length - outArgs + inTypes]; + System.arraycopy(names, 0, names2, 0, skip+pos); + // copy the body: + int bodyLength = length - arity; + System.arraycopy(names, skip+outArgs, names2, skip+inTypes, bodyLength); + int arity2 = names2.length - bodyLength; + int result2 = result; + if (result2 >= 0) { + if (result2 < skip+outArgs) { + // return the corresponding inArg + result2 = reorder[result2-skip]; + } else { + result2 = result2 - outArgs + inTypes; + } + } + // rework names in the body: + for (int j = pos; j < outArgs; j++) { + Name n = names[skip+j]; + int i = reorder[j]; + // replace names[skip+j] by names2[skip+i] + Name n2 = names2[skip+i]; + if (n2 == null) + names2[skip+i] = n2 = new Name(types[i]); + else + assert(n2.type == types[i]); + for (int k = arity2; k < names2.length; k++) { + names2[k] = names2[k].replaceName(n, n2); + } + } + // some names are unused, but must be filled in + for (int i = skip+pos; i < arity2; i++) { + if (names2[i] == null) + names2[i] = argument(i, types[i - skip]); + } + for (int j = arity; j < names.length; j++) { + int i = j - arity + arity2; + // replace names2[i] by names[j] + Name n = names[j]; + Name n2 = names2[i]; + if (n != n2) { + for (int k = i+1; k < names2.length; k++) { + names2[k] = names2[k].replaceName(n, n2); + } + } + } + return new LambdaForm(debugName, arity2, names2, result2); + } + + static boolean permutedTypesMatch(int[] reorder, char[] types, Name[] names, int skip) { + int inTypes = types.length; + int outArgs = reorder.length; + for (int i = 0; i < outArgs; i++) { + assert(names[skip+i].isParam()); + assert(names[skip+i].type == types[reorder[i]]); + } + return true; + } + + static class NamedFunction { + final MemberName member; + @Stable MethodHandle resolvedHandle; + @Stable MethodHandle invoker; + + NamedFunction(MethodHandle resolvedHandle) { + this(resolvedHandle.internalMemberName(), resolvedHandle); + } + NamedFunction(MemberName member, MethodHandle resolvedHandle) { + this.member = member; + //resolvedHandle = eraseSubwordTypes(resolvedHandle); + this.resolvedHandle = resolvedHandle; + } + NamedFunction(MethodType basicInvokerType) { + assert(basicInvokerType == basicInvokerType.basicType()) : basicInvokerType; + if (basicInvokerType.parameterSlotCount() < MethodType.MAX_MH_INVOKER_ARITY) { + this.resolvedHandle = basicInvokerType.invokers().basicInvoker(); + this.member = resolvedHandle.internalMemberName(); + } else { + // necessary to pass BigArityTest + this.member = Invokers.invokeBasicMethod(basicInvokerType); + } + } + + // The next 3 constructors are used to break circular dependencies on MH.invokeStatic, etc. + // Any LambdaForm containing such a member is not interpretable. + // This is OK, since all such LFs are prepared with special primitive vmentry points. + // And even without the resolvedHandle, the name can still be compiled and optimized. + NamedFunction(Method method) { + this(new MemberName(method)); + } + NamedFunction(Field field) { + this(new MemberName(field)); + } + NamedFunction(MemberName member) { + this.member = member; + this.resolvedHandle = null; + } + + MethodHandle resolvedHandle() { + if (resolvedHandle == null) resolve(); + return resolvedHandle; + } + + void resolve() { + resolvedHandle = DirectMethodHandle.make(member); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null) return false; + if (!(other instanceof NamedFunction)) return false; + NamedFunction that = (NamedFunction) other; + return this.member != null && this.member.equals(that.member); + } + + @Override + public int hashCode() { + if (member != null) + return member.hashCode(); + return super.hashCode(); + } + + // Put the predefined NamedFunction invokers into the table. + static void initializeInvokers() { + for (MemberName m : MemberName.getFactory().getMethods(NamedFunction.class, false, null, null, null)) { + if (!m.isStatic() || !m.isPackage()) continue; + MethodType type = m.getMethodType(); + if (type.equals(INVOKER_METHOD_TYPE) && + m.getName().startsWith("invoke_")) { + String sig = m.getName().substring("invoke_".length()); + int arity = LambdaForm.signatureArity(sig); + MethodType srcType = MethodType.genericMethodType(arity); + if (LambdaForm.signatureReturn(sig) == 'V') + srcType = srcType.changeReturnType(void.class); + MethodTypeForm typeForm = srcType.form(); + typeForm.namedFunctionInvoker = DirectMethodHandle.make(m); + } + } + } + + // The following are predefined NamedFunction invokers. The system must build + // a separate invoker for each distinct signature. + /** void return type invokers. */ + @Hidden + static Object invoke__V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 0); + mh.invokeBasic(); + return null; + } + @Hidden + static Object invoke_L_V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 1); + mh.invokeBasic(a[0]); + return null; + } + @Hidden + static Object invoke_LL_V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 2); + mh.invokeBasic(a[0], a[1]); + return null; + } + @Hidden + static Object invoke_LLL_V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 3); + mh.invokeBasic(a[0], a[1], a[2]); + return null; + } + @Hidden + static Object invoke_LLLL_V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 4); + mh.invokeBasic(a[0], a[1], a[2], a[3]); + return null; + } + @Hidden + static Object invoke_LLLLL_V(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 5); + mh.invokeBasic(a[0], a[1], a[2], a[3], a[4]); + return null; + } + /** Object return type invokers. */ + @Hidden + static Object invoke__L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 0); + return mh.invokeBasic(); + } + @Hidden + static Object invoke_L_L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 1); + return mh.invokeBasic(a[0]); + } + @Hidden + static Object invoke_LL_L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 2); + return mh.invokeBasic(a[0], a[1]); + } + @Hidden + static Object invoke_LLL_L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 3); + return mh.invokeBasic(a[0], a[1], a[2]); + } + @Hidden + static Object invoke_LLLL_L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 4); + return mh.invokeBasic(a[0], a[1], a[2], a[3]); + } + @Hidden + static Object invoke_LLLLL_L(MethodHandle mh, Object[] a) throws Throwable { + assert(a.length == 5); + return mh.invokeBasic(a[0], a[1], a[2], a[3], a[4]); + } + + static final MethodType INVOKER_METHOD_TYPE = + MethodType.methodType(Object.class, MethodHandle.class, Object[].class); + + private static MethodHandle computeInvoker(MethodTypeForm typeForm) { + MethodHandle mh = typeForm.namedFunctionInvoker; + if (mh != null) return mh; + MemberName invoker = InvokerBytecodeGenerator.generateNamedFunctionInvoker(typeForm); // this could take a while + mh = DirectMethodHandle.make(invoker); + MethodHandle mh2 = typeForm.namedFunctionInvoker; + if (mh2 != null) return mh2; // benign race + if (!mh.type().equals(INVOKER_METHOD_TYPE)) + throw new InternalError(mh.debugString()); + return typeForm.namedFunctionInvoker = mh; + } + + @Hidden + Object invokeWithArguments(Object... arguments) throws Throwable { + // If we have a cached invoker, call it right away. + // NOTE: The invoker always returns a reference value. + if (TRACE_INTERPRETER) return invokeWithArgumentsTracing(arguments); + assert(checkArgumentTypes(arguments, methodType())); + return invoker().invokeBasic(resolvedHandle(), arguments); + } + + @Hidden + Object invokeWithArgumentsTracing(Object[] arguments) throws Throwable { + Object rval; + try { + traceInterpreter("[ call", this, arguments); + if (invoker == null) { + traceInterpreter("| getInvoker", this); + invoker(); + } + if (resolvedHandle == null) { + traceInterpreter("| resolve", this); + resolvedHandle(); + } + assert(checkArgumentTypes(arguments, methodType())); + rval = invoker().invokeBasic(resolvedHandle(), arguments); + } catch (Throwable ex) { + traceInterpreter("] throw =>", ex); + throw ex; + } + traceInterpreter("] return =>", rval); + return rval; + } + + private MethodHandle invoker() { + if (invoker != null) return invoker; + // Get an invoker and cache it. + return invoker = computeInvoker(methodType().form()); + } + + private static boolean checkArgumentTypes(Object[] arguments, MethodType methodType) { + if (true) return true; // FIXME + MethodType dstType = methodType.form().erasedType(); + MethodType srcType = dstType.basicType().wrap(); + Class[] ptypes = new Class[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + Object arg = arguments[i]; + Class ptype = arg == null ? Object.class : arg.getClass(); + // If the dest. type is a primitive we keep the + // argument type. + ptypes[i] = dstType.parameterType(i).isPrimitive() ? ptype : Object.class; + } + MethodType argType = MethodType.methodType(srcType.returnType(), ptypes).wrap(); + assert(argType.isConvertibleTo(srcType)) : "wrong argument types: cannot convert " + argType + " to " + srcType; + return true; + } + + String basicTypeSignature() { + //return LambdaForm.basicTypeSignature(resolvedHandle.type()); + return LambdaForm.basicTypeSignature(methodType()); + } + + MethodType methodType() { + if (resolvedHandle != null) + return resolvedHandle.type(); + else + // only for certain internal LFs during bootstrapping + return member.getInvocationType(); + } + + MemberName member() { + assert(assertMemberIsConsistent()); + return member; + } + + // Called only from assert. + private boolean assertMemberIsConsistent() { + if (resolvedHandle instanceof DirectMethodHandle) { + MemberName m = resolvedHandle.internalMemberName(); + assert(m.equals(member)); + } + return true; + } + + Class memberDeclaringClassOrNull() { + return (member == null) ? null : member.getDeclaringClass(); + } + + char returnType() { + return basicType(methodType().returnType()); + } + + char parameterType(int n) { + return basicType(methodType().parameterType(n)); + } + + int arity() { + //int siglen = member.getMethodType().parameterCount(); + //if (!member.isStatic()) siglen += 1; + //return siglen; + return methodType().parameterCount(); + } + + public String toString() { + if (member == null) return String.valueOf(resolvedHandle); + return member.getDeclaringClass().getSimpleName()+"."+member.getName(); + } + } + + void resolve() { + for (Name n : names) n.resolve(); + } + + public static char basicType(Class type) { + char c = Wrapper.basicTypeChar(type); + if ("ZBSC".indexOf(c) >= 0) c = 'I'; + assert("LIJFDV".indexOf(c) >= 0); + return c; + } + public static char[] basicTypes(List> types) { + char[] btypes = new char[types.size()]; + for (int i = 0; i < btypes.length; i++) { + btypes[i] = basicType(types.get(i)); + } + return btypes; + } + public static String basicTypeSignature(MethodType type) { + char[] sig = new char[type.parameterCount() + 2]; + int sigp = 0; + for (Class pt : type.parameterList()) { + sig[sigp++] = basicType(pt); + } + sig[sigp++] = '_'; + sig[sigp++] = basicType(type.returnType()); + assert(sigp == sig.length); + return String.valueOf(sig); + } + + static final class Name { + final char type; + private short index; + final NamedFunction function; + @Stable final Object[] arguments; + + private Name(int index, char type, NamedFunction function, Object[] arguments) { + this.index = (short)index; + this.type = type; + this.function = function; + this.arguments = arguments; + assert(this.index == index); + } + Name(MethodHandle function, Object... arguments) { + this(new NamedFunction(function), arguments); + } + Name(MethodType functionType, Object... arguments) { + this(new NamedFunction(functionType), arguments); + assert(arguments[0] instanceof Name && ((Name)arguments[0]).type == 'L'); + } + Name(MemberName function, Object... arguments) { + this(new NamedFunction(function), arguments); + } + Name(NamedFunction function, Object... arguments) { + this(-1, function.returnType(), function, arguments = arguments.clone()); + assert(arguments.length == function.arity()) : "arity mismatch: arguments.length=" + arguments.length + " == function.arity()=" + function.arity() + " in " + debugString(); + for (int i = 0; i < arguments.length; i++) + assert(typesMatch(function.parameterType(i), arguments[i])) : "types don't match: function.parameterType(" + i + ")=" + function.parameterType(i) + ", arguments[" + i + "]=" + arguments[i] + " in " + debugString(); + } + Name(int index, char type) { + this(index, type, null, null); + } + Name(char type) { + this(-1, type); + } + + char type() { return type; } + int index() { return index; } + boolean initIndex(int i) { + if (index != i) { + if (index != -1) return false; + index = (short)i; + } + return true; + } + + + void resolve() { + if (function != null) + function.resolve(); + } + + Name newIndex(int i) { + if (initIndex(i)) return this; + return cloneWithIndex(i); + } + Name cloneWithIndex(int i) { + Object[] newArguments = (arguments == null) ? null : arguments.clone(); + return new Name(i, type, function, newArguments); + } + Name replaceName(Name oldName, Name newName) { // FIXME: use replaceNames uniformly + if (oldName == newName) return this; + @SuppressWarnings("LocalVariableHidesMemberVariable") + Object[] arguments = this.arguments; + if (arguments == null) return this; + boolean replaced = false; + for (int j = 0; j < arguments.length; j++) { + if (arguments[j] == oldName) { + if (!replaced) { + replaced = true; + arguments = arguments.clone(); + } + arguments[j] = newName; + } + } + if (!replaced) return this; + return new Name(function, arguments); + } + Name replaceNames(Name[] oldNames, Name[] newNames, int start, int end) { + @SuppressWarnings("LocalVariableHidesMemberVariable") + Object[] arguments = this.arguments; + boolean replaced = false; + eachArg: + for (int j = 0; j < arguments.length; j++) { + if (arguments[j] instanceof Name) { + Name n = (Name) arguments[j]; + int check = n.index; + // harmless check to see if the thing is already in newNames: + if (check >= 0 && check < newNames.length && n == newNames[check]) + continue eachArg; + // n might not have the correct index: n != oldNames[n.index]. + for (int i = start; i < end; i++) { + if (n == oldNames[i]) { + if (n == newNames[i]) + continue eachArg; + if (!replaced) { + replaced = true; + arguments = arguments.clone(); + } + arguments[j] = newNames[i]; + continue eachArg; + } + } + } + } + if (!replaced) return this; + return new Name(function, arguments); + } + void internArguments() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + Object[] arguments = this.arguments; + for (int j = 0; j < arguments.length; j++) { + if (arguments[j] instanceof Name) { + Name n = (Name) arguments[j]; + if (n.isParam() && n.index < INTERNED_ARGUMENT_LIMIT) + arguments[j] = internArgument(n); + } + } + } + boolean isParam() { + return function == null; + } + boolean isConstantZero() { + return !isParam() && arguments.length == 0 && function.equals(constantZero(0, type).function); + } + + public String toString() { + return (isParam()?"a":"t")+(index >= 0 ? index : System.identityHashCode(this))+":"+type; + } + public String debugString() { + String s = toString(); + return (function == null) ? s : s + "=" + exprString(); + } + public String exprString() { + if (function == null) return "null"; + StringBuilder buf = new StringBuilder(function.toString()); + buf.append("("); + String cma = ""; + for (Object a : arguments) { + buf.append(cma); cma = ","; + if (a instanceof Name || a instanceof Integer) + buf.append(a); + else + buf.append("(").append(a).append(")"); + } + buf.append(")"); + return buf.toString(); + } + + private static boolean typesMatch(char parameterType, Object object) { + if (object instanceof Name) { + return ((Name)object).type == parameterType; + } + switch (parameterType) { + case 'I': return object instanceof Integer; + case 'J': return object instanceof Long; + case 'F': return object instanceof Float; + case 'D': return object instanceof Double; + } + assert(parameterType == 'L'); + return true; + } + + /** + * Does this Name precede the given binding node in some canonical order? + * This predicate is used to order data bindings (via insertion sort) + * with some stability. + */ + boolean isSiblingBindingBefore(Name binding) { + assert(!binding.isParam()); + if (isParam()) return true; + if (function.equals(binding.function) && + arguments.length == binding.arguments.length) { + boolean sawInt = false; + for (int i = 0; i < arguments.length; i++) { + Object a1 = arguments[i]; + Object a2 = binding.arguments[i]; + if (!a1.equals(a2)) { + if (a1 instanceof Integer && a2 instanceof Integer) { + if (sawInt) continue; + sawInt = true; + if ((int)a1 < (int)a2) continue; // still might be true + } + return false; + } + } + return sawInt; + } + return false; + } + + public boolean equals(Name that) { + if (this == that) return true; + if (isParam()) + // each parameter is a unique atom + return false; // this != that + return + //this.index == that.index && + this.type == that.type && + this.function.equals(that.function) && + Arrays.equals(this.arguments, that.arguments); + } + @Override + public boolean equals(Object x) { + return x instanceof Name && equals((Name)x); + } + @Override + public int hashCode() { + if (isParam()) + return index | (type << 8); + return function.hashCode() ^ Arrays.hashCode(arguments); + } + } + + static Name argument(int which, char type) { + int tn = ALL_TYPES.indexOf(type); + if (tn < 0 || which >= INTERNED_ARGUMENT_LIMIT) + return new Name(which, type); + return INTERNED_ARGUMENTS[tn][which]; + } + static Name internArgument(Name n) { + assert(n.isParam()) : "not param: " + n; + assert(n.index < INTERNED_ARGUMENT_LIMIT); + return argument(n.index, n.type); + } + static Name[] arguments(int extra, String types) { + int length = types.length(); + Name[] names = new Name[length + extra]; + for (int i = 0; i < length; i++) + names[i] = argument(i, types.charAt(i)); + return names; + } + static Name[] arguments(int extra, char... types) { + int length = types.length; + Name[] names = new Name[length + extra]; + for (int i = 0; i < length; i++) + names[i] = argument(i, types[i]); + return names; + } + static Name[] arguments(int extra, List> types) { + int length = types.size(); + Name[] names = new Name[length + extra]; + for (int i = 0; i < length; i++) + names[i] = argument(i, basicType(types.get(i))); + return names; + } + static Name[] arguments(int extra, Class... types) { + int length = types.length; + Name[] names = new Name[length + extra]; + for (int i = 0; i < length; i++) + names[i] = argument(i, basicType(types[i])); + return names; + } + static Name[] arguments(int extra, MethodType types) { + int length = types.parameterCount(); + Name[] names = new Name[length + extra]; + for (int i = 0; i < length; i++) + names[i] = argument(i, basicType(types.parameterType(i))); + return names; + } + static final String ALL_TYPES = "LIJFD"; // omit V, not an argument type + static final int INTERNED_ARGUMENT_LIMIT = 10; + private static final Name[][] INTERNED_ARGUMENTS + = new Name[ALL_TYPES.length()][INTERNED_ARGUMENT_LIMIT]; + static { + for (int tn = 0; tn < ALL_TYPES.length(); tn++) { + for (int i = 0; i < INTERNED_ARGUMENTS[tn].length; i++) { + char type = ALL_TYPES.charAt(tn); + INTERNED_ARGUMENTS[tn][i] = new Name(i, type); + } + } + } + + private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory(); + + static Name constantZero(int which, char type) { + return CONSTANT_ZERO[ALL_TYPES.indexOf(type)].newIndex(which); + } + private static final Name[] CONSTANT_ZERO + = new Name[ALL_TYPES.length()]; + static { + for (int tn = 0; tn < ALL_TYPES.length(); tn++) { + char bt = ALL_TYPES.charAt(tn); + Wrapper wrap = Wrapper.forBasicType(bt); + MemberName zmem = new MemberName(LambdaForm.class, "zero"+bt, MethodType.methodType(wrap.primitiveType()), REF_invokeStatic); + try { + zmem = IMPL_NAMES.resolveOrFail(REF_invokeStatic, zmem, null, NoSuchMethodException.class); + } catch (IllegalAccessException|NoSuchMethodException ex) { + throw newInternalError(ex); + } + NamedFunction zcon = new NamedFunction(zmem); + Name n = new Name(zcon).newIndex(0); + assert(n.type == ALL_TYPES.charAt(tn)); + CONSTANT_ZERO[tn] = n; + assert(n.isConstantZero()); + } + } + + // Avoid appealing to ValueConversions at bootstrap time: + private static int zeroI() { return 0; } + private static long zeroJ() { return 0; } + private static float zeroF() { return 0; } + private static double zeroD() { return 0; } + private static Object zeroL() { return null; } + + // Put this last, so that previous static inits can run before. + static { + if (USE_PREDEFINED_INTERPRET_METHODS) + PREPARED_FORMS.putAll(computeInitialPreparedForms()); + } + + /** + * Internal marker for byte-compiled LambdaForms. + */ + /*non-public*/ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface Compiled { + } + + /** + * Internal marker for LambdaForm interpreter frames. + */ + /*non-public*/ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface Hidden { + } + + +/* + // Smoke-test for the invokers used in this file. + static void testMethodHandleLinkers() throws Throwable { + MemberName.Factory lookup = MemberName.getFactory(); + MemberName asList_MN = new MemberName(Arrays.class, "asList", + MethodType.methodType(List.class, Object[].class), + REF_invokeStatic); + //MethodHandleNatives.resolve(asList_MN, null); + asList_MN = lookup.resolveOrFail(asList_MN, REF_invokeStatic, null, NoSuchMethodException.class); + System.out.println("about to call "+asList_MN); + Object[] abc = { "a", "bc" }; + List lst = (List) MethodHandle.linkToStatic(abc, asList_MN); + System.out.println("lst="+lst); + MemberName toString_MN = new MemberName(Object.class.getMethod("toString")); + String s1 = (String) MethodHandle.linkToVirtual(lst, toString_MN); + toString_MN = new MemberName(Object.class.getMethod("toString"), true); + String s2 = (String) MethodHandle.linkToSpecial(lst, toString_MN); + System.out.println("[s1,s2,lst]="+Arrays.asList(s1, s2, lst.toString())); + MemberName toArray_MN = new MemberName(List.class.getMethod("toArray")); + Object[] arr = (Object[]) MethodHandle.linkToInterface(lst, toArray_MN); + System.out.println("toArray="+Arrays.toString(arr)); + } + static { try { testMethodHandleLinkers(); } catch (Throwable ex) { throw new RuntimeException(ex); } } + // Requires these definitions in MethodHandle: + static final native Object linkToStatic(Object x1, MemberName mn) throws Throwable; + static final native Object linkToVirtual(Object x1, MemberName mn) throws Throwable; + static final native Object linkToSpecial(Object x1, MemberName mn) throws Throwable; + static final native Object linkToInterface(Object x1, MemberName mn) throws Throwable; + */ + + static { NamedFunction.initializeInvokers(); } + + // The following hack is necessary in order to suppress TRACE_INTERPRETER + // during execution of the static initializes of this class. + // Turning on TRACE_INTERPRETER too early will cause + // stack overflows and other misbehavior during attempts to trace events + // that occur during LambdaForm.. + // Therefore, do not move this line higher in this file, and do not remove. + private static final boolean TRACE_INTERPRETER = MethodHandleStatics.TRACE_INTERPRETER; +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/LambdaMetafactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/LambdaMetafactory.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2012, 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.io.Serializable; +import java.util.Arrays; + +/** + *

Methods to facilitate the creation of simple "function objects" that + * implement one or more interfaces by delegation to a provided {@link MethodHandle}, + * possibly after type adaptation and partial evaluation of arguments. These + * methods are typically used as bootstrap methods for {@code invokedynamic} + * call sites, to support the lambda expression and method + * reference expression features of the Java Programming Language. + * + *

Indirect access to the behavior specified by the provided {@code MethodHandle} + * proceeds in order through three phases: + *

    + *
  • Linkage occurs when the methods in this class are invoked. + * They take as arguments an interface to be implemented (typically a + * functional interface, one with a single abstract method), a + * name and signature of a method from that interface to be implemented, a + * method handle describing the desired implementation behavior + * for that method, and possibly other additional metadata, and produce a + * {@link CallSite} whose target can be used to create suitable function + * objects. Linkage may involve dynamically loading a new class that + * implements the target interface. The {@code CallSite} can be considered a + * "factory" for function objects and so these linkage methods are referred + * to as "metafactories".
  • + * + *
  • Capture occurs when the {@code CallSite}'s target is + * invoked, typically through an {@code invokedynamic} call site, + * producing a function object. This may occur many times for + * a single factory {@code CallSite}. Capture may involve allocation of a + * new function object, or may return an existing function object. The + * behavior {@code MethodHandle} may have additional parameters beyond those + * of the specified interface method; these are referred to as captured + * parameters, which must be provided as arguments to the + * {@code CallSite} target, and which may be early-bound to the behavior + * {@code MethodHandle}. The number of captured parameters and their types + * are determined during linkage.
  • + * + *
  • Invocation occurs when an implemented interface method + * is invoked on a function object. This may occur many times for a single + * function object. The method referenced by the behavior {@code MethodHandle} + * is invoked with the captured arguments and any additional arguments + * provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.
  • + *
+ * + *

It is sometimes useful to restrict the set of inputs or results permitted + * at invocation. For example, when the generic interface {@code Predicate} + * is parameterized as {@code Predicate}, the input must be a + * {@code String}, even though the method to implement allows any {@code Object}. + * At linkage time, an additional {@link MethodType} parameter describes the + * "instantiated" method type; on invocation, the arguments and eventual result + * are checked against this {@code MethodType}. + * + *

This class provides two forms of linkage methods: a standard version + * ({@link #metafactory(MethodHandles.Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}) + * using an optimized protocol, and an alternate version + * {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}). + * The alternate version is a generalization of the standard version, providing + * additional control over the behavior of the generated function objects via + * flags and additional arguments. The alternate version adds the ability to + * manage the following attributes of function objects: + * + *

    + *
  • Bridging. It is sometimes useful to implement multiple + * variations of the method signature, involving argument or return type + * adaptation. This occurs when multiple distinct VM signatures for a method + * are logically considered to be the same method by the language. The + * flag {@code FLAG_BRIDGES} indicates that a list of additional + * {@code MethodType}s will be provided, each of which will be implemented + * by the resulting function object. These methods will share the same + * name and instantiated type.
  • + * + *
  • Multiple interfaces. If needed, more than one interface + * can be implemented by the function object. (These additional interfaces + * are typically marker interfaces with no methods.) The flag {@code FLAG_MARKERS} + * indicates that a list of additional interfaces will be provided, each of + * which should be implemented by the resulting function object.
  • + * + *
  • Serializability. The generated function objects do not + * generally support serialization. If desired, {@code FLAG_SERIALIZABLE} + * can be used to indicate that the function objects should be serializable. + * Serializable function objects will use, as their serialized form, + * instances of the class {@code SerializedLambda}, which requires additional + * assistance from the capturing class (the class described by the + * {@link MethodHandles.Lookup} parameter {@code caller}); see + * {@link SerializedLambda} for details.
  • + *
+ * + *

Assume the linkage arguments are as follows: + *

    + *
  • {@code invokedType} (describing the {@code CallSite} signature) has + * K parameters of types (D1..Dk) and return type Rd;
  • + *
  • {@code samMethodType} (describing the implemented method type) has N + * parameters, of types (U1..Un) and return type Ru;
  • + *
  • {@code implMethod} (the {@code MethodHandle} providing the + * implementation has M parameters, of types (A1..Am) and return type Ra + * (if the method describes an instance method, the method type of this + * method handle already includes an extra first argument corresponding to + * the receiver);
  • + *
  • {@code instantiatedMethodType} (allowing restrictions on invocation) + * has N parameters, of types (T1..Tn) and return type Rt.
  • + *
+ * + *

Then the following linkage invariants must hold: + *

    + *
  • Rd is an interface
  • + *
  • {@code implMethod} is a direct method handle
  • + *
  • {@code samMethodType} and {@code instantiatedMethodType} have the same + * arity N, and for i=1..N, Ti and Ui are the same type, or Ti and Ui are + * both reference types and Ti is a subtype of Ui
  • + *
  • Either Rt and Ru are the same type, or both are reference types and + * Rt is a subtype of Ru
  • + *
  • K + N = M
  • + *
  • For i=1..K, Di = Ai
  • + *
  • For i=1..N, Ti is adaptable to Aj, where j=i+k
  • + *
  • The return type Rt is void, or the return type Ra is not void and is + * adaptable to Rt
  • + *
+ * + *

Further, at capture time, if {@code implMethod} corresponds to an instance + * method, and there are any capture arguments ({@code K > 0}), then the first + * capture argument (corresponding to the receiver) must be non-null. + * + *

A type Q is considered adaptable to S as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
QSLink-time checksInvocation-time checks
PrimitivePrimitiveQ can be converted to S via a primitive widening conversionNone
PrimitiveReferenceS is a supertype of the Wrapper(Q)Cast from Wrapper(Q) to S
ReferencePrimitivefor parameter types: Q is a primitive wrapper and Primitive(Q) + * can be widened to S + *
for return types: If Q is a primitive wrapper, check that + * Primitive(Q) can be widened to S
If Q is not a primitive wrapper, cast Q to the base Wrapper(S); + * for example Number for numeric types
ReferenceReferencefor parameter types: S is a supertype of Q + *
for return types: none
Cast from Q to S
+ * + * @apiNote These linkage methods are designed to support the evaluation + * of lambda expressions and method references in the Java + * Language. For every lambda expressions or method reference in the source code, + * there is a target type which is a functional interface. Evaluating a lambda + * expression produces an object of its target type. The recommended mechanism + * for evaluating lambda expressions is to desugar the lambda body to a method, + * invoke an invokedynamic call site whose static argument list describes the + * sole method of the functional interface and the desugared implementation + * method, and returns an object (the lambda object) that implements the target + * type. (For method references, the implementation method is simply the + * referenced method; no desugaring is needed.) + * + *

The argument list of the implementation method and the argument list of + * the interface method(s) may differ in several ways. The implementation + * methods may have additional arguments to accommodate arguments captured by + * the lambda expression; there may also be differences resulting from permitted + * adaptations of arguments, such as casting, boxing, unboxing, and primitive + * widening. (Varargs adaptations are not handled by the metafactories; these are + * expected to be handled by the caller.) + * + *

Invokedynamic call sites have two argument lists: a static argument list + * and a dynamic argument list. The static argument list is stored in the + * constant pool; the dynamic argument is pushed on the operand stack at capture + * time. The bootstrap method has access to the entire static argument list + * (which in this case, includes information describing the implementation method, + * the target interface, and the target interface method(s)), as well as a + * method signature describing the number and static types (but not the values) + * of the dynamic arguments and the static return type of the invokedynamic site. + * + * @implNote The implementation method is described with a method handle. In + * theory, any method handle could be used. Currently supported are direct method + * handles representing invocation of virtual, interface, constructor and static + * methods. + */ +public class LambdaMetafactory { + + /** Flag for alternate metafactories indicating the lambda object + * must be serializable */ + public static final int FLAG_SERIALIZABLE = 1 << 0; + + /** + * Flag for alternate metafactories indicating the lambda object implements + * other marker interfaces + * besides Serializable + */ + public static final int FLAG_MARKERS = 1 << 1; + + /** + * Flag for alternate metafactories indicating the lambda object requires + * additional bridge methods + */ + public static final int FLAG_BRIDGES = 1 << 2; + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private static final MethodType[] EMPTY_MT_ARRAY = new MethodType[0]; + + /** + * Facilitates the creation of simple "function objects" that implement one + * or more interfaces by delegation to a provided {@link MethodHandle}, + * after appropriate type adaptation and partial evaluation of arguments. + * Typically used as a bootstrap method for {@code invokedynamic} + * call sites, to support the lambda expression and method + * reference expression features of the Java Programming Language. + * + *

This is the standard, streamlined metafactory; additional flexibility + * is provided by {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}. + * A general description of the behavior of this method is provided + * {@link LambdaMetafactory above}. + * + *

When the target of the {@code CallSite} returned from this method is + * invoked, the resulting function objects are instances of a class which + * implements the interface named by the return type of {@code invokedType}, + * declares a method with the name given by {@code invokedName} and the + * signature given by {@code samMethodType}. It may also override additional + * methods from {@code Object}. + * + * @param caller Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invokedName The name of the method to implement. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invokedType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * In the event that the implementation method is an + * instance method and this signature has any parameters, + * the first parameter in the invocation signature must + * correspond to the receiver. + * @param samMethodType Signature and return type of method to be implemented + * by the function object. + * @param implMethod A direct method handle describing the implementation + * method which should be called (with suitable adaptation + * of argument types, return types, and with captured + * arguments prepended to the invocation arguments) at + * invocation time. + * @param instantiatedMethodType The signature and return type that should + * be enforced dynamically at invocation time. + * This may be the same as {@code samMethodType}, + * or may be a specialization of it. + * @return a CallSite whose target can be used to perform capture, generating + * instances of the interface named by {@code invokedType} + * @throws LambdaConversionException If any of the linkage invariants + * described {@link LambdaMetafactory above} + * are violated + */ + public static CallSite metafactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + MethodType samMethodType, + MethodHandle implMethod, + MethodType instantiatedMethodType) + throws LambdaConversionException { + AbstractValidatingLambdaMetafactory mf; + mf = new InnerClassLambdaMetafactory(caller, invokedType, + invokedName, samMethodType, + implMethod, instantiatedMethodType, + false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); + mf.validateMetafactoryArgs(); + return mf.buildCallSite(); + } + + /** + * Facilitates the creation of simple "function objects" that implement one + * or more interfaces by delegation to a provided {@link MethodHandle}, + * after appropriate type adaptation and partial evaluation of arguments. + * Typically used as a bootstrap method for {@code invokedynamic} + * call sites, to support the lambda expression and method + * reference expression features of the Java Programming Language. + * + *

This is the general, more flexible metafactory; a streamlined version + * is provided by {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}. + * A general description of the behavior of this method is provided + * {@link LambdaMetafactory above}. + * + *

The argument list for this method includes three fixed parameters, + * corresponding to the parameters automatically stacked by the VM for the + * bootstrap method in an {@code invokedynamic} invocation, and an {@code Object[]} + * parameter that contains additional parameters. The declared argument + * list for this method is: + * + *

{@code
+     *  CallSite altMetafactory(MethodHandles.Lookup caller,
+     *                          String invokedName,
+     *                          MethodType invokedType,
+     *                          Object... args)
+     * }
+ * + *

but it behaves as if the argument list is as follows: + * + *

{@code
+     *  CallSite altMetafactory(MethodHandles.Lookup caller,
+     *                          String invokedName,
+     *                          MethodType invokedType,
+     *                          MethodType samMethodType,
+     *                          MethodHandle implMethod,
+     *                          MethodType instantiatedMethodType,
+     *                          int flags,
+     *                          int markerInterfaceCount,  // IF flags has MARKERS set
+     *                          Class... markerInterfaces, // IF flags has MARKERS set
+     *                          int bridgeCount,           // IF flags has BRIDGES set
+     *                          MethodType... bridges      // IF flags has BRIDGES set
+     *                          )
+     * }
+ * + *

Arguments that appear in the argument list for + * {@link #metafactory(MethodHandles.Lookup, String, MethodType, MethodType, MethodHandle, MethodType)} + * have the same specification as in that method. The additional arguments + * are interpreted as follows: + *

    + *
  • {@code flags} indicates additional options; this is a bitwise + * OR of desired flags. Defined flags are {@link #FLAG_BRIDGES}, + * {@link #FLAG_MARKERS}, and {@link #FLAG_SERIALIZABLE}.
  • + *
  • {@code markerInterfaceCount} is the number of additional interfaces + * the function object should implement, and is present if and only if the + * {@code FLAG_MARKERS} flag is set.
  • + *
  • {@code markerInterfaces} is a variable-length list of additional + * interfaces to implement, whose length equals {@code markerInterfaceCount}, + * and is present if and only if the {@code FLAG_MARKERS} flag is set.
  • + *
  • {@code bridgeCount} is the number of additional method signatures + * the function object should implement, and is present if and only if + * the {@code FLAG_BRIDGES} flag is set.
  • + *
  • {@code bridges} is a variable-length list of additional + * methods signatures to implement, whose length equals {@code bridgeCount}, + * and is present if and only if the {@code FLAG_BRIDGES} flag is set.
  • + *
+ * + *

Each class named by {@code markerInterfaces} is subject to the same + * restrictions as {@code Rd}, the return type of {@code invokedType}, + * as described {@link LambdaMetafactory above}. Each {@code MethodType} + * named by {@code bridges} is subject to the same restrictions as + * {@code samMethodType}, as described {@link LambdaMetafactory above}. + * + *

When FLAG_SERIALIZABLE is set in {@code flags}, the function objects + * will implement {@code Serializable}, and will have a {@code writeReplace} + * method that returns an appropriate {@link SerializedLambda}. The + * {@code caller} class must have an appropriate {@code $deserializeLambda$} + * method, as described in {@link SerializedLambda}. + * + *

When the target of the {@code CallSite} returned from this method is + * invoked, the resulting function objects are instances of a class with + * the following properties: + *

    + *
  • The class implements the interface named by the return type + * of {@code invokedType} and any interfaces named by {@code markerInterfaces}
  • + *
  • The class declares methods with the name given by {@code invokedName}, + * and the signature given by {@code samMethodType} and additional signatures + * given by {@code bridges}
  • + *
  • The class may override methods from {@code Object}, and may + * implement methods related to serialization.
  • + *
+ * + * @param caller Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invokedName The name of the method to implement. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invokedType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * In the event that the implementation method is an + * instance method and this signature has any parameters, + * the first parameter in the invocation signature must + * correspond to the receiver. + * @param args An {@code Object[]} array containing the required + * arguments {@code samMethodType}, {@code implMethod}, + * {@code instantiatedMethodType}, {@code flags}, and any + * optional arguments, as described + * {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)} above} + * @return a CallSite whose target can be used to perform capture, generating + * instances of the interface named by {@code invokedType} + * @throws LambdaConversionException If any of the linkage invariants + * described {@link LambdaMetafactory above} + * are violated + */ + public static CallSite altMetafactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + Object... args) + throws LambdaConversionException { + MethodType samMethodType = (MethodType)args[0]; + MethodHandle implMethod = (MethodHandle)args[1]; + MethodType instantiatedMethodType = (MethodType)args[2]; + int flags = (Integer) args[3]; + Class[] markerInterfaces; + MethodType[] bridges; + int argIndex = 4; + if ((flags & FLAG_MARKERS) != 0) { + int markerCount = (Integer) args[argIndex++]; + markerInterfaces = new Class[markerCount]; + System.arraycopy(args, argIndex, markerInterfaces, 0, markerCount); + argIndex += markerCount; + } + else + markerInterfaces = EMPTY_CLASS_ARRAY; + if ((flags & FLAG_BRIDGES) != 0) { + int bridgeCount = (Integer) args[argIndex++]; + bridges = new MethodType[bridgeCount]; + System.arraycopy(args, argIndex, bridges, 0, bridgeCount); + argIndex += bridgeCount; + } + else + bridges = EMPTY_MT_ARRAY; + + boolean isSerializable = ((flags & FLAG_SERIALIZABLE) != 0); + if (isSerializable) { + boolean foundSerializableSupertype = Serializable.class.isAssignableFrom(invokedType.returnType()); + for (Class c : markerInterfaces) + foundSerializableSupertype |= Serializable.class.isAssignableFrom(c); + if (!foundSerializableSupertype) { + markerInterfaces = Arrays.copyOf(markerInterfaces, markerInterfaces.length + 1); + markerInterfaces[markerInterfaces.length-1] = Serializable.class; + } + } + + AbstractValidatingLambdaMetafactory mf + = new InnerClassLambdaMetafactory(caller, invokedType, + invokedName, samMethodType, + implMethod, + instantiatedMethodType, + isSerializable, + markerInterfaces, bridges); + mf.validateMetafactoryArgs(); + return mf.buildCallSite(); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MemberName.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MemberName.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1080 @@ +/* + * 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 sun.invoke.util.BytecodeDescriptor; +import sun.invoke.util.VerifyAccess; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.MethodHandleStatics.*; +import java.util.Objects; + +/** + * A {@code MemberName} is a compact symbolic datum which fully characterizes + * a method or field reference. + * A member name refers to a field, method, constructor, or member type. + * Every member name has a simple name (a string) and a type (either a Class or MethodType). + * A member name may also have a non-null declaring class, or it may be simply + * a naked name/type pair. + * A member name may also have non-zero modifier flags. + * Finally, a member name may be either resolved or unresolved. + * If it is resolved, the existence of the named + *

+ * Whether resolved or not, a member name provides no access rights or + * invocation capability to its possessor. It is merely a compact + * representation of all symbolic information necessary to link to + * and properly use the named member. + *

+ * When resolved, a member name's internal implementation may include references to JVM metadata. + * This representation is stateless and only decriptive. + * It provides no private information and no capability to use the member. + *

+ * By contrast, a {@linkplain java.lang.reflect.Method} contains fuller information + * about the internals of a method (except its bytecodes) and also + * allows invocation. A MemberName is much lighter than a Method, + * since it contains about 7 fields to the 16 of Method (plus its sub-arrays), + * and those seven fields omit much of the information in Method. + * @author jrose + */ +/*non-public*/ final class MemberName implements Member, Cloneable { + private Class clazz; // class in which the method is defined + private String name; // may be null if not yet materialized + private Object type; // may be null if not yet materialized + private int flags; // modifier bits; see reflect.Modifier + //@Injected JVM_Method* vmtarget; + //@Injected int vmindex; + private Object resolution; // if null, this guy is resolved + + /** Return the declaring class of this member. + * In the case of a bare name and type, the declaring class will be null. + */ + public Class getDeclaringClass() { + return clazz; + } + + /** Utility method producing the class loader of the declaring class. */ + public ClassLoader getClassLoader() { + return clazz.getClassLoader(); + } + + /** Return the simple name of this member. + * For a type, it is the same as {@link Class#getSimpleName}. + * For a method or field, it is the simple name of the member. + * For a constructor, it is always {@code "<init>"}. + */ + public String getName() { + if (name == null) { + expandFromVM(); + if (name == null) { + return null; + } + } + return name; + } + + public MethodType getMethodOrFieldType() { + if (isInvocable()) + return getMethodType(); + if (isGetter()) + return MethodType.methodType(getFieldType()); + if (isSetter()) + return MethodType.methodType(void.class, getFieldType()); + throw new InternalError("not a method or field: "+this); + } + + /** Return the declared type of this member, which + * must be a method or constructor. + */ + public MethodType getMethodType() { + if (type == null) { + expandFromVM(); + if (type == null) { + return null; + } + } + if (!isInvocable()) { + throw newIllegalArgumentException("not invocable, no method type"); + } + + { + // Get a snapshot of type which doesn't get changed by racing threads. + final Object type = this.type; + if (type instanceof MethodType) { + return (MethodType) type; + } + } + + // type is not a MethodType yet. Convert it thread-safely. + synchronized (this) { + if (type instanceof String) { + String sig = (String) type; + MethodType res = MethodType.fromMethodDescriptorString(sig, getClassLoader()); + type = res; + } else if (type instanceof Object[]) { + Object[] typeInfo = (Object[]) type; + Class[] ptypes = (Class[]) typeInfo[1]; + Class rtype = (Class) typeInfo[0]; + MethodType res = MethodType.methodType(rtype, ptypes); + type = res; + } + // Make sure type is a MethodType for racing threads. + assert type instanceof MethodType : "bad method type " + type; + } + return (MethodType) type; + } + + /** Return the actual type under which this method or constructor must be invoked. + * For non-static methods or constructors, this is the type with a leading parameter, + * a reference to declaring class. For static methods, it is the same as the declared type. + */ + public MethodType getInvocationType() { + MethodType itype = getMethodOrFieldType(); + if (isConstructor() && getReferenceKind() == REF_newInvokeSpecial) + return itype.changeReturnType(clazz); + if (!isStatic()) + return itype.insertParameterTypes(0, clazz); + return itype; + } + + /** Utility method producing the parameter types of the method type. */ + public Class[] getParameterTypes() { + return getMethodType().parameterArray(); + } + + /** Utility method producing the return type of the method type. */ + public Class getReturnType() { + return getMethodType().returnType(); + } + + /** Return the declared type of this member, which + * must be a field or type. + * If it is a type member, that type itself is returned. + */ + public Class getFieldType() { + if (type == null) { + expandFromVM(); + if (type == null) { + return null; + } + } + if (isInvocable()) { + throw newIllegalArgumentException("not a field or nested class, no simple type"); + } + + { + // Get a snapshot of type which doesn't get changed by racing threads. + final Object type = this.type; + if (type instanceof Class) { + return (Class) type; + } + } + + // type is not a Class yet. Convert it thread-safely. + synchronized (this) { + if (type instanceof String) { + String sig = (String) type; + MethodType mtype = MethodType.fromMethodDescriptorString("()"+sig, getClassLoader()); + Class res = mtype.returnType(); + type = res; + } + // Make sure type is a Class for racing threads. + assert type instanceof Class : "bad field type " + type; + } + return (Class) type; + } + + /** Utility method to produce either the method type or field type of this member. */ + public Object getType() { + return (isInvocable() ? getMethodType() : getFieldType()); + } + + /** Utility method to produce the signature of this member, + * used within the class file format to describe its type. + */ + public String getSignature() { + if (type == null) { + expandFromVM(); + if (type == null) { + return null; + } + } + if (isInvocable()) + return BytecodeDescriptor.unparse(getMethodType()); + else + return BytecodeDescriptor.unparse(getFieldType()); + } + + /** Return the modifier flags of this member. + * @see java.lang.reflect.Modifier + */ + public int getModifiers() { + return (flags & RECOGNIZED_MODIFIERS); + } + + /** Return the reference kind of this member, or zero if none. + */ + public byte getReferenceKind() { + return (byte) ((flags >>> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK); + } + private boolean referenceKindIsConsistent() { + byte refKind = getReferenceKind(); + if (refKind == REF_NONE) return isType(); + if (isField()) { + assert(staticIsConsistent()); + assert(MethodHandleNatives.refKindIsField(refKind)); + } else if (isConstructor()) { + assert(refKind == REF_newInvokeSpecial || refKind == REF_invokeSpecial); + } else if (isMethod()) { + assert(staticIsConsistent()); + assert(MethodHandleNatives.refKindIsMethod(refKind)); + if (clazz.isInterface()) + assert(refKind == REF_invokeInterface || + refKind == REF_invokeStatic || + refKind == REF_invokeSpecial || + refKind == REF_invokeVirtual && isObjectPublicMethod()); + } else { + assert(false); + } + return true; + } + private boolean isObjectPublicMethod() { + if (clazz == Object.class) return true; + MethodType mtype = getMethodType(); + if (name.equals("toString") && mtype.returnType() == String.class && mtype.parameterCount() == 0) + return true; + if (name.equals("hashCode") && mtype.returnType() == int.class && mtype.parameterCount() == 0) + return true; + if (name.equals("equals") && mtype.returnType() == boolean.class && mtype.parameterCount() == 1 && mtype.parameterType(0) == Object.class) + return true; + return false; + } + /*non-public*/ boolean referenceKindIsConsistentWith(int originalRefKind) { + int refKind = getReferenceKind(); + if (refKind == originalRefKind) return true; + switch (originalRefKind) { + case REF_invokeInterface: + // Looking up an interface method, can get (e.g.) Object.hashCode + assert(refKind == REF_invokeVirtual || + refKind == REF_invokeSpecial) : this; + return true; + case REF_invokeVirtual: + case REF_newInvokeSpecial: + // Looked up a virtual, can get (e.g.) final String.hashCode. + assert(refKind == REF_invokeSpecial) : this; + return true; + } + assert(false) : this+" != "+MethodHandleNatives.refKindName((byte)originalRefKind); + return true; + } + private boolean staticIsConsistent() { + byte refKind = getReferenceKind(); + return MethodHandleNatives.refKindIsStatic(refKind) == isStatic() || getModifiers() == 0; + } + private boolean vminfoIsConsistent() { + byte refKind = getReferenceKind(); + assert(isResolved()); // else don't call + Object vminfo = MethodHandleNatives.getMemberVMInfo(this); + assert(vminfo instanceof Object[]); + long vmindex = (Long) ((Object[])vminfo)[0]; + Object vmtarget = ((Object[])vminfo)[1]; + if (MethodHandleNatives.refKindIsField(refKind)) { + assert(vmindex >= 0) : vmindex + ":" + this; + assert(vmtarget instanceof Class); + } else { + if (MethodHandleNatives.refKindDoesDispatch(refKind)) + assert(vmindex >= 0) : vmindex + ":" + this; + else + assert(vmindex < 0) : vmindex; + assert(vmtarget instanceof MemberName) : vmtarget + " in " + this; + } + return true; + } + + private MemberName changeReferenceKind(byte refKind, byte oldKind) { + assert(getReferenceKind() == oldKind); + assert(MethodHandleNatives.refKindIsValid(refKind)); + flags += (((int)refKind - oldKind) << MN_REFERENCE_KIND_SHIFT); +// if (isConstructor() && refKind != REF_newInvokeSpecial) +// flags += (IS_METHOD - IS_CONSTRUCTOR); +// else if (refKind == REF_newInvokeSpecial && isMethod()) +// flags += (IS_CONSTRUCTOR - IS_METHOD); + return this; + } + + private boolean testFlags(int mask, int value) { + return (flags & mask) == value; + } + private boolean testAllFlags(int mask) { + return testFlags(mask, mask); + } + private boolean testAnyFlags(int mask) { + return !testFlags(mask, 0); + } + + /** Utility method to query if this member is a method handle invocation (invoke or invokeExact). */ + public boolean isMethodHandleInvoke() { + final int bits = MH_INVOKE_MODS; + final int negs = Modifier.STATIC; + if (testFlags(bits | negs, bits) && + clazz == MethodHandle.class) { + return isMethodHandleInvokeName(name); + } + return false; + } + public static boolean isMethodHandleInvokeName(String name) { + return name.equals("invoke") || name.equals("invokeExact"); + } + private static final int MH_INVOKE_MODS = Modifier.NATIVE | Modifier.FINAL | Modifier.PUBLIC; + + /** Utility method to query the modifier flags of this member. */ + public boolean isStatic() { + return Modifier.isStatic(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isPublic() { + return Modifier.isPublic(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isPrivate() { + return Modifier.isPrivate(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isProtected() { + return Modifier.isProtected(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isFinal() { + return Modifier.isFinal(flags); + } + /** Utility method to query whether this member or its defining class is final. */ + public boolean canBeStaticallyBound() { + return Modifier.isFinal(flags | clazz.getModifiers()); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isVolatile() { + return Modifier.isVolatile(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isAbstract() { + return Modifier.isAbstract(flags); + } + /** Utility method to query the modifier flags of this member. */ + public boolean isNative() { + return Modifier.isNative(flags); + } + // let the rest (native, volatile, transient, etc.) be tested via Modifier.isFoo + + // unofficial modifier flags, used by HotSpot: + static final int BRIDGE = 0x00000040; + static final int VARARGS = 0x00000080; + static final int SYNTHETIC = 0x00001000; + static final int ANNOTATION= 0x00002000; + static final int ENUM = 0x00004000; + /** Utility method to query the modifier flags of this member; returns false if the member is not a method. */ + public boolean isBridge() { + return testAllFlags(IS_METHOD | BRIDGE); + } + /** Utility method to query the modifier flags of this member; returns false if the member is not a method. */ + public boolean isVarargs() { + return testAllFlags(VARARGS) && isInvocable(); + } + /** Utility method to query the modifier flags of this member; returns false if the member is not a method. */ + public boolean isSynthetic() { + return testAllFlags(SYNTHETIC); + } + + static final String CONSTRUCTOR_NAME = ""; // the ever-popular + + // modifiers exported by the JVM: + static final int RECOGNIZED_MODIFIERS = 0xFFFF; + + // private flags, not part of RECOGNIZED_MODIFIERS: + static final int + IS_METHOD = MN_IS_METHOD, // method (not constructor) + IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor + IS_FIELD = MN_IS_FIELD, // field + IS_TYPE = MN_IS_TYPE, // nested type + CALLER_SENSITIVE = MN_CALLER_SENSITIVE; // @CallerSensitive annotation detected + + static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED; + static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE; + static final int IS_INVOCABLE = IS_METHOD | IS_CONSTRUCTOR; + static final int IS_FIELD_OR_METHOD = IS_METHOD | IS_FIELD; + static final int SEARCH_ALL_SUPERS = MN_SEARCH_SUPERCLASSES | MN_SEARCH_INTERFACES; + + /** Utility method to query whether this member is a method or constructor. */ + public boolean isInvocable() { + return testAnyFlags(IS_INVOCABLE); + } + /** Utility method to query whether this member is a method, constructor, or field. */ + public boolean isFieldOrMethod() { + return testAnyFlags(IS_FIELD_OR_METHOD); + } + /** Query whether this member is a method. */ + public boolean isMethod() { + return testAllFlags(IS_METHOD); + } + /** Query whether this member is a constructor. */ + public boolean isConstructor() { + return testAllFlags(IS_CONSTRUCTOR); + } + /** Query whether this member is a field. */ + public boolean isField() { + return testAllFlags(IS_FIELD); + } + /** Query whether this member is a type. */ + public boolean isType() { + return testAllFlags(IS_TYPE); + } + /** Utility method to query whether this member is neither public, private, nor protected. */ + public boolean isPackage() { + return !testAnyFlags(ALL_ACCESS); + } + /** Query whether this member has a CallerSensitive annotation. */ + public boolean isCallerSensitive() { + return testAllFlags(CALLER_SENSITIVE); + } + + /** Utility method to query whether this member is accessible from a given lookup class. */ + public boolean isAccessibleFrom(Class lookupClass) { + return VerifyAccess.isMemberAccessible(this.getDeclaringClass(), this.getDeclaringClass(), flags, + lookupClass, ALL_ACCESS|MethodHandles.Lookup.PACKAGE); + } + + /** Initialize a query. It is not resolved. */ + private void init(Class defClass, String name, Object type, int flags) { + // defining class is allowed to be null (for a naked name/type pair) + //name.toString(); // null check + //type.equals(type); // null check + // fill in fields: + this.clazz = defClass; + this.name = name; + this.type = type; + this.flags = flags; + assert(testAnyFlags(ALL_KINDS)); + assert(this.resolution == null); // nobody should have touched this yet + //assert(referenceKindIsConsistent()); // do this after resolution + } + + /** + * Calls down to the VM to fill in the fields. This method is + * synchronized to avoid racing calls. + */ + private void expandFromVM() { + if (type != null) { + return; + } + if (!isResolved()) { + return; + } + MethodHandleNatives.expand(this); + } + + // Capturing information from the Core Reflection API: + private static int flagsMods(int flags, int mods, byte refKind) { + assert((flags & RECOGNIZED_MODIFIERS) == 0); + assert((mods & ~RECOGNIZED_MODIFIERS) == 0); + assert((refKind & ~MN_REFERENCE_KIND_MASK) == 0); + return flags | mods | (refKind << MN_REFERENCE_KIND_SHIFT); + } + /** Create a name for the given reflected method. The resulting name will be in a resolved state. */ + public MemberName(Method m) { + this(m, false); + } + @SuppressWarnings("LeakingThisInConstructor") + public MemberName(Method m, boolean wantSpecial) { + m.getClass(); // NPE check + // fill in vmtarget, vmindex while we have m in hand: + MethodHandleNatives.init(this, m); + if (clazz == null) { // MHN.init failed + if (m.getDeclaringClass() == MethodHandle.class && + isMethodHandleInvokeName(m.getName())) { + // The JVM did not reify this signature-polymorphic instance. + // Need a special case here. + // See comments on MethodHandleNatives.linkMethod. + MethodType type = MethodType.methodType(m.getReturnType(), m.getParameterTypes()); + int flags = flagsMods(IS_METHOD, m.getModifiers(), REF_invokeVirtual); + init(MethodHandle.class, m.getName(), type, flags); + if (isMethodHandleInvoke()) + return; + } + throw new LinkageError(m.toString()); + } + assert(isResolved() && this.clazz != null); + this.name = m.getName(); + if (this.type == null) + this.type = new Object[] { m.getReturnType(), m.getParameterTypes() }; + if (wantSpecial) { + if (isAbstract()) + throw new AbstractMethodError(this.toString()); + if (getReferenceKind() == REF_invokeVirtual) + changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual); + else if (getReferenceKind() == REF_invokeInterface) + // invokeSpecial on a default method + changeReferenceKind(REF_invokeSpecial, REF_invokeInterface); + } + } + public MemberName asSpecial() { + switch (getReferenceKind()) { + case REF_invokeSpecial: return this; + case REF_invokeVirtual: return clone().changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual); + case REF_invokeInterface: return clone().changeReferenceKind(REF_invokeSpecial, REF_invokeInterface); + case REF_newInvokeSpecial: return clone().changeReferenceKind(REF_invokeSpecial, REF_newInvokeSpecial); + } + throw new IllegalArgumentException(this.toString()); + } + /** If this MN is not REF_newInvokeSpecial, return a clone with that ref. kind. + * In that case it must already be REF_invokeSpecial. + */ + public MemberName asConstructor() { + switch (getReferenceKind()) { + case REF_invokeSpecial: return clone().changeReferenceKind(REF_newInvokeSpecial, REF_invokeSpecial); + case REF_newInvokeSpecial: return this; + } + throw new IllegalArgumentException(this.toString()); + } + /** If this MN is a REF_invokeSpecial, return a clone with the "normal" kind + * REF_invokeVirtual; also switch either to REF_invokeInterface if clazz.isInterface. + * The end result is to get a fully virtualized version of the MN. + * (Note that resolving in the JVM will sometimes devirtualize, changing + * REF_invokeVirtual of a final to REF_invokeSpecial, and REF_invokeInterface + * in some corner cases to either of the previous two; this transform + * undoes that change under the assumption that it occurred.) + */ + public MemberName asNormalOriginal() { + byte normalVirtual = clazz.isInterface() ? REF_invokeInterface : REF_invokeVirtual; + byte refKind = getReferenceKind(); + byte newRefKind = refKind; + MemberName result = this; + switch (refKind) { + case REF_invokeInterface: + case REF_invokeVirtual: + case REF_invokeSpecial: + newRefKind = normalVirtual; + break; + } + if (newRefKind == refKind) + return this; + result = clone().changeReferenceKind(newRefKind, refKind); + assert(this.referenceKindIsConsistentWith(result.getReferenceKind())); + return result; + } + /** Create a name for the given reflected constructor. The resulting name will be in a resolved state. */ + @SuppressWarnings("LeakingThisInConstructor") + public MemberName(Constructor ctor) { + ctor.getClass(); // NPE check + // fill in vmtarget, vmindex while we have ctor in hand: + MethodHandleNatives.init(this, ctor); + assert(isResolved() && this.clazz != null); + this.name = CONSTRUCTOR_NAME; + if (this.type == null) + this.type = new Object[] { void.class, ctor.getParameterTypes() }; + } + /** Create a name for the given reflected field. The resulting name will be in a resolved state. + */ + public MemberName(Field fld) { + this(fld, false); + } + @SuppressWarnings("LeakingThisInConstructor") + public MemberName(Field fld, boolean makeSetter) { + fld.getClass(); // NPE check + // fill in vmtarget, vmindex while we have fld in hand: + MethodHandleNatives.init(this, fld); + assert(isResolved() && this.clazz != null); + this.name = fld.getName(); + this.type = fld.getType(); + assert((REF_putStatic - REF_getStatic) == (REF_putField - REF_getField)); + byte refKind = this.getReferenceKind(); + assert(refKind == (isStatic() ? REF_getStatic : REF_getField)); + if (makeSetter) { + changeReferenceKind((byte)(refKind + (REF_putStatic - REF_getStatic)), refKind); + } + } + public boolean isGetter() { + return MethodHandleNatives.refKindIsGetter(getReferenceKind()); + } + public boolean isSetter() { + return MethodHandleNatives.refKindIsSetter(getReferenceKind()); + } + public MemberName asSetter() { + byte refKind = getReferenceKind(); + assert(MethodHandleNatives.refKindIsGetter(refKind)); + assert((REF_putStatic - REF_getStatic) == (REF_putField - REF_getField)); + byte setterRefKind = (byte)(refKind + (REF_putField - REF_getField)); + return clone().changeReferenceKind(setterRefKind, refKind); + } + /** Create a name for the given class. The resulting name will be in a resolved state. */ + public MemberName(Class type) { + init(type.getDeclaringClass(), type.getSimpleName(), type, + flagsMods(IS_TYPE, type.getModifiers(), REF_NONE)); + initResolved(true); + } + + /** + * Create a name for a signature-polymorphic invoker. + * This is a placeholder for a signature-polymorphic instance + * (of MH.invokeExact, etc.) that the JVM does not reify. + * See comments on {@link MethodHandleNatives#linkMethod}. + */ + static MemberName makeMethodHandleInvoke(String name, MethodType type) { + return makeMethodHandleInvoke(name, type, MH_INVOKE_MODS | SYNTHETIC); + } + static MemberName makeMethodHandleInvoke(String name, MethodType type, int mods) { + MemberName mem = new MemberName(MethodHandle.class, name, type, REF_invokeVirtual); + mem.flags |= mods; // it's not resolved, but add these modifiers anyway + assert(mem.isMethodHandleInvoke()) : mem; + return mem; + } + + // bare-bones constructor; the JVM will fill it in + MemberName() { } + + // locally useful cloner + @Override protected MemberName clone() { + try { + return (MemberName) super.clone(); + } catch (CloneNotSupportedException ex) { + throw newInternalError(ex); + } + } + + /** Get the definition of this member name. + * This may be in a super-class of the declaring class of this member. + */ + public MemberName getDefinition() { + if (!isResolved()) throw new IllegalStateException("must be resolved: "+this); + if (isType()) return this; + MemberName res = this.clone(); + res.clazz = null; + res.type = null; + res.name = null; + res.resolution = res; + res.expandFromVM(); + assert(res.getName().equals(this.getName())); + return res; + } + + @Override + public int hashCode() { + return Objects.hash(clazz, getReferenceKind(), name, getType()); + } + @Override + public boolean equals(Object that) { + return (that instanceof MemberName && this.equals((MemberName)that)); + } + + /** Decide if two member names have exactly the same symbolic content. + * Does not take into account any actual class members, so even if + * two member names resolve to the same actual member, they may + * be distinct references. + */ + public boolean equals(MemberName that) { + if (this == that) return true; + if (that == null) return false; + return this.clazz == that.clazz + && this.getReferenceKind() == that.getReferenceKind() + && Objects.equals(this.name, that.name) + && Objects.equals(this.getType(), that.getType()); + } + + // Construction from symbolic parts, for queries: + /** Create a field or type name from the given components: + * Declaring class, name, type, reference kind. + * The declaring class may be supplied as null if this is to be a bare name and type. + * The resulting name will in an unresolved state. + */ + public MemberName(Class defClass, String name, Class type, byte refKind) { + init(defClass, name, type, flagsMods(IS_FIELD, 0, refKind)); + initResolved(false); + } + /** Create a field or type name from the given components: Declaring class, name, type. + * The declaring class may be supplied as null if this is to be a bare name and type. + * The modifier flags default to zero. + * The resulting name will in an unresolved state. + */ + public MemberName(Class defClass, String name, Class type, Void unused) { + this(defClass, name, type, REF_NONE); + initResolved(false); + } + /** Create a method or constructor name from the given components: Declaring class, name, type, modifiers. + * It will be a constructor if and only if the name is {@code "<init>"}. + * The declaring class may be supplied as null if this is to be a bare name and type. + * The last argument is optional, a boolean which requests REF_invokeSpecial. + * The resulting name will in an unresolved state. + */ + public MemberName(Class defClass, String name, MethodType type, byte refKind) { + int initFlags = (name != null && name.equals(CONSTRUCTOR_NAME) ? IS_CONSTRUCTOR : IS_METHOD); + init(defClass, name, type, flagsMods(initFlags, 0, refKind)); + initResolved(false); + } + /** Create a method, constructor, or field name from the given components: + * Reference kind, declaring class, name, type. + */ + public MemberName(byte refKind, Class defClass, String name, Object type) { + int kindFlags; + if (MethodHandleNatives.refKindIsField(refKind)) { + kindFlags = IS_FIELD; + if (!(type instanceof Class)) + throw newIllegalArgumentException("not a field type"); + } else if (MethodHandleNatives.refKindIsMethod(refKind)) { + kindFlags = IS_METHOD; + if (!(type instanceof MethodType)) + throw newIllegalArgumentException("not a method type"); + } else if (refKind == REF_newInvokeSpecial) { + kindFlags = IS_CONSTRUCTOR; + if (!(type instanceof MethodType) || + !CONSTRUCTOR_NAME.equals(name)) + throw newIllegalArgumentException("not a constructor type or name"); + } else { + throw newIllegalArgumentException("bad reference kind "+refKind); + } + init(defClass, name, type, flagsMods(kindFlags, 0, refKind)); + initResolved(false); + } + /** Query whether this member name is resolved to a non-static, non-final method. + */ + public boolean hasReceiverTypeDispatch() { + return MethodHandleNatives.refKindDoesDispatch(getReferenceKind()); + } + + /** Query whether this member name is resolved. + * A resolved member name is one for which the JVM has found + * a method, constructor, field, or type binding corresponding exactly to the name. + * (Document?) + */ + public boolean isResolved() { + return resolution == null; + } + + private void initResolved(boolean isResolved) { + assert(this.resolution == null); // not initialized yet! + if (!isResolved) + this.resolution = this; + assert(isResolved() == isResolved); + } + + void checkForTypeAlias() { + if (isInvocable()) { + MethodType type; + if (this.type instanceof MethodType) + type = (MethodType) this.type; + else + this.type = type = getMethodType(); + if (type.erase() == type) return; + if (VerifyAccess.isTypeVisible(type, clazz)) return; + throw new LinkageError("bad method type alias: "+type+" not visible from "+clazz); + } else { + Class type; + if (this.type instanceof Class) + type = (Class) this.type; + else + this.type = type = getFieldType(); + if (VerifyAccess.isTypeVisible(type, clazz)) return; + throw new LinkageError("bad field type alias: "+type+" not visible from "+clazz); + } + } + + + /** Produce a string form of this member name. + * For types, it is simply the type's own string (as reported by {@code toString}). + * For fields, it is {@code "DeclaringClass.name/type"}. + * For methods and constructors, it is {@code "DeclaringClass.name(ptype...)rtype"}. + * If the declaring class is null, the prefix {@code "DeclaringClass."} is omitted. + * If the member is unresolved, a prefix {@code "*."} is prepended. + */ + @SuppressWarnings("LocalVariableHidesMemberVariable") + @Override + public String toString() { + if (isType()) + return type.toString(); // class java.lang.String + // else it is a field, method, or constructor + StringBuilder buf = new StringBuilder(); + if (getDeclaringClass() != null) { + buf.append(getName(clazz)); + buf.append('.'); + } + String name = getName(); + buf.append(name == null ? "*" : name); + Object type = getType(); + if (!isInvocable()) { + buf.append('/'); + buf.append(type == null ? "*" : getName(type)); + } else { + buf.append(type == null ? "(*)*" : getName(type)); + } + byte refKind = getReferenceKind(); + if (refKind != REF_NONE) { + buf.append('/'); + buf.append(MethodHandleNatives.refKindName(refKind)); + } + //buf.append("#").append(System.identityHashCode(this)); + return buf.toString(); + } + private static String getName(Object obj) { + if (obj instanceof Class) + return ((Class)obj).getName(); + return String.valueOf(obj); + } + + public IllegalAccessException makeAccessException(String message, Object from) { + message = message + ": "+ toString(); + if (from != null) message += ", from " + from; + return new IllegalAccessException(message); + } + private String message() { + if (isResolved()) + return "no access"; + else if (isConstructor()) + return "no such constructor"; + else if (isMethod()) + return "no such method"; + else + return "no such field"; + } + public ReflectiveOperationException makeAccessException() { + String message = message() + ": "+ toString(); + ReflectiveOperationException ex; + if (isResolved() || !(resolution instanceof NoSuchMethodError || + resolution instanceof NoSuchFieldError)) + ex = new IllegalAccessException(message); + else if (isConstructor()) + ex = new NoSuchMethodException(message); + else if (isMethod()) + ex = new NoSuchMethodException(message); + else + ex = new NoSuchFieldException(message); + if (resolution instanceof Throwable) + ex.initCause((Throwable) resolution); + return ex; + } + + /** Actually making a query requires an access check. */ + /*non-public*/ static Factory getFactory() { + return Factory.INSTANCE; + } + /** A factory type for resolving member names with the help of the VM. + * TBD: Define access-safe public constructors for this factory. + */ + /*non-public*/ static class Factory { + private Factory() { } // singleton pattern + static Factory INSTANCE = new Factory(); + + private static int ALLOWED_FLAGS = ALL_KINDS; + + /// Queries + List getMembers(Class defc, + String matchName, Object matchType, + int matchFlags, Class lookupClass) { + matchFlags &= ALLOWED_FLAGS; + String matchSig = null; + if (matchType != null) { + matchSig = BytecodeDescriptor.unparse(matchType); + if (matchSig.startsWith("(")) + matchFlags &= ~(ALL_KINDS & ~IS_INVOCABLE); + else + matchFlags &= ~(ALL_KINDS & ~IS_FIELD); + } + final int BUF_MAX = 0x2000; + int len1 = matchName == null ? 10 : matchType == null ? 4 : 1; + MemberName[] buf = newMemberBuffer(len1); + int totalCount = 0; + ArrayList bufs = null; + int bufCount = 0; + for (;;) { + bufCount = MethodHandleNatives.getMembers(defc, + matchName, matchSig, matchFlags, + lookupClass, + totalCount, buf); + if (bufCount <= buf.length) { + if (bufCount < 0) bufCount = 0; + totalCount += bufCount; + break; + } + // JVM returned to us with an intentional overflow! + totalCount += buf.length; + int excess = bufCount - buf.length; + if (bufs == null) bufs = new ArrayList<>(1); + bufs.add(buf); + int len2 = buf.length; + len2 = Math.max(len2, excess); + len2 = Math.max(len2, totalCount / 4); + buf = newMemberBuffer(Math.min(BUF_MAX, len2)); + } + ArrayList result = new ArrayList<>(totalCount); + if (bufs != null) { + for (MemberName[] buf0 : bufs) { + Collections.addAll(result, buf0); + } + } + result.addAll(Arrays.asList(buf).subList(0, bufCount)); + // Signature matching is not the same as type matching, since + // one signature might correspond to several types. + // So if matchType is a Class or MethodType, refilter the results. + if (matchType != null && matchType != matchSig) { + for (Iterator it = result.iterator(); it.hasNext();) { + MemberName m = it.next(); + if (!matchType.equals(m.getType())) + it.remove(); + } + } + return result; + } + /** Produce a resolved version of the given member. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * If lookup fails or access is not permitted, null is returned. + * Otherwise a fresh copy of the given member is returned, with modifier bits filled in. + */ + private MemberName resolve(byte refKind, MemberName ref, Class lookupClass) { + MemberName m = ref.clone(); // JVM will side-effect the ref + assert(refKind == m.getReferenceKind()); + try { + m = MethodHandleNatives.resolve(m, lookupClass); + m.checkForTypeAlias(); + m.resolution = null; + } catch (LinkageError ex) { + // JVM reports that the "bytecode behavior" would get an error + assert(!m.isResolved()); + m.resolution = ex; + return m; + } + assert(m.referenceKindIsConsistent()); + m.initResolved(true); + assert(m.vminfoIsConsistent()); + return m; + } + /** Produce a resolved version of the given member. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * If lookup fails or access is not permitted, a {@linkplain ReflectiveOperationException} is thrown. + * Otherwise a fresh copy of the given member is returned, with modifier bits filled in. + */ + public + + MemberName resolveOrFail(byte refKind, MemberName m, Class lookupClass, + Class nsmClass) + throws IllegalAccessException, NoSuchMemberException { + MemberName result = resolve(refKind, m, lookupClass); + if (result.isResolved()) + return result; + ReflectiveOperationException ex = result.makeAccessException(); + if (ex instanceof IllegalAccessException) throw (IllegalAccessException) ex; + throw nsmClass.cast(ex); + } + /** Produce a resolved version of the given member. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * If lookup fails or access is not permitted, return null. + * Otherwise a fresh copy of the given member is returned, with modifier bits filled in. + */ + public + MemberName resolveOrNull(byte refKind, MemberName m, Class lookupClass) { + MemberName result = resolve(refKind, m, lookupClass); + if (result.isResolved()) + return result; + return null; + } + /** Return a list of all methods defined by the given class. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getMethods(Class defc, boolean searchSupers, + Class lookupClass) { + return getMethods(defc, searchSupers, null, null, lookupClass); + } + /** Return a list of matching methods defined by the given class. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Returned methods will match the name (if not null) and the type (if not null). + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getMethods(Class defc, boolean searchSupers, + String name, MethodType type, Class lookupClass) { + int matchFlags = IS_METHOD | (searchSupers ? SEARCH_ALL_SUPERS : 0); + return getMembers(defc, name, type, matchFlags, lookupClass); + } + /** Return a list of all constructors defined by the given class. + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getConstructors(Class defc, Class lookupClass) { + return getMembers(defc, null, null, IS_CONSTRUCTOR, lookupClass); + } + /** Return a list of all fields defined by the given class. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getFields(Class defc, boolean searchSupers, + Class lookupClass) { + return getFields(defc, searchSupers, null, null, lookupClass); + } + /** Return a list of all fields defined by the given class. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Returned fields will match the name (if not null) and the type (if not null). + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getFields(Class defc, boolean searchSupers, + String name, Class type, Class lookupClass) { + int matchFlags = IS_FIELD | (searchSupers ? SEARCH_ALL_SUPERS : 0); + return getMembers(defc, name, type, matchFlags, lookupClass); + } + /** Return a list of all nested types defined by the given class. + * Super types are searched (for inherited members) if {@code searchSupers} is true. + * Access checking is performed on behalf of the given {@code lookupClass}. + * Inaccessible members are not added to the last. + */ + public List getNestedTypes(Class defc, boolean searchSupers, + Class lookupClass) { + int matchFlags = IS_TYPE | (searchSupers ? SEARCH_ALL_SUPERS : 0); + return getMembers(defc, null, null, matchFlags, lookupClass); + } + private static MemberName[] newMemberBuffer(int length) { + MemberName[] buf = new MemberName[length]; + // fill the buffer with dummy structs for the JVM to fill in + for (int i = 0; i < length; i++) + buf[i] = new MemberName(); + return buf; + } + } + +// static { +// System.out.println("Hello world! My methods are:"); +// System.out.println(Factory.INSTANCE.getMethods(MemberName.class, true, null)); +// } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandle.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1505 @@ +/* + * 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.util.*; +import sun.invoke.util.*; +import sun.misc.Unsafe; + +import static java.lang.invoke.MethodHandleStatics.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A method handle is a typed, directly executable reference to an underlying method, + * constructor, field, or similar low-level operation, with optional + * transformations of arguments or return values. + * These transformations are quite general, and include such patterns as + * {@linkplain #asType conversion}, + * {@linkplain #bindTo insertion}, + * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion}, + * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}. + * + *

Method handle contents

+ * Method handles are dynamically and strongly typed according to their parameter and return types. + * They are not distinguished by the name or the defining class of their underlying methods. + * A method handle must be invoked using a symbolic type descriptor which matches + * the method handle's own {@linkplain #type type descriptor}. + *

+ * Every method handle reports its type descriptor via the {@link #type type} accessor. + * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object, + * whose structure is a series of classes, one of which is + * the return type of the method (or {@code void.class} if none). + *

+ * A method handle's type controls the types of invocations it accepts, + * and the kinds of transformations that apply to it. + *

+ * A method handle contains a pair of special invoker methods + * called {@link #invokeExact invokeExact} and {@link #invoke invoke}. + * Both invoker methods provide direct access to the method handle's + * underlying method, constructor, field, or other operation, + * as modified by transformations of arguments and return values. + * Both invokers accept calls which exactly match the method handle's own type. + * The plain, inexact invoker also accepts a range of other call types. + *

+ * Method handles are immutable and have no visible state. + * Of course, they can be bound to underlying methods or data which exhibit state. + * With respect to the Java Memory Model, any method handle will behave + * as if all of its (internal) fields are final variables. This means that any method + * handle made visible to the application will always be fully formed. + * This is true even if the method handle is published through a shared + * variable in a data race. + *

+ * Method handles cannot be subclassed by the user. + * Implementations may (or may not) create internal subclasses of {@code MethodHandle} + * which may be visible via the {@link java.lang.Object#getClass Object.getClass} + * operation. The programmer should not draw conclusions about a method handle + * from its specific class, as the method handle class hierarchy (if any) + * may change from time to time or across implementations from different vendors. + * + *

Method handle compilation

+ * A Java method call expression naming {@code invokeExact} or {@code invoke} + * can invoke a method handle from Java source code. + * From the viewpoint of source code, these methods can take any arguments + * and their result can be cast to any return type. + * Formally this is accomplished by giving the invoker methods + * {@code Object} return types and variable arity {@code Object} arguments, + * but they have an additional quality called signature polymorphism + * which connects this freedom of invocation directly to the JVM execution stack. + *

+ * As is usual with virtual methods, source-level calls to {@code invokeExact} + * and {@code invoke} compile to an {@code invokevirtual} instruction. + * More unusually, the compiler must record the actual argument types, + * and may not perform method invocation conversions on the arguments. + * Instead, it must push them on the stack according to their own unconverted types. + * The method handle object itself is pushed on the stack before the arguments. + * The compiler then calls the method handle with a symbolic type descriptor which + * describes the argument and return types. + *

+ * To issue a complete symbolic type descriptor, the compiler must also determine + * the return type. This is based on a cast on the method invocation expression, + * if there is one, or else {@code Object} if the invocation is an expression + * or else {@code void} if the invocation is a statement. + * The cast may be to a primitive type (but not {@code void}). + *

+ * As a corner case, an uncasted {@code null} argument is given + * a symbolic type descriptor of {@code java.lang.Void}. + * The ambiguity with the type {@code Void} is harmless, since there are no references of type + * {@code Void} except the null reference. + * + *

Method handle invocation

+ * The first time a {@code invokevirtual} instruction is executed + * it is linked, by symbolically resolving the names in the instruction + * and verifying that the method call is statically legal. + * This is true of calls to {@code invokeExact} and {@code invoke}. + * In this case, the symbolic type descriptor emitted by the compiler is checked for + * correct syntax and names it contains are resolved. + * Thus, an {@code invokevirtual} instruction which invokes + * a method handle will always link, as long + * as the symbolic type descriptor is syntactically well-formed + * and the types exist. + *

+ * When the {@code invokevirtual} is executed after linking, + * the receiving method handle's type is first checked by the JVM + * to ensure that it matches the symbolic type descriptor. + * If the type match fails, it means that the method which the + * caller is invoking is not present on the individual + * method handle being invoked. + *

+ * In the case of {@code invokeExact}, the type descriptor of the invocation + * (after resolving symbolic type names) must exactly match the method type + * of the receiving method handle. + * In the case of plain, inexact {@code invoke}, the resolved type descriptor + * must be a valid argument to the receiver's {@link #asType asType} method. + * Thus, plain {@code invoke} is more permissive than {@code invokeExact}. + *

+ * After type matching, a call to {@code invokeExact} directly + * and immediately invoke the method handle's underlying method + * (or other behavior, as the case may be). + *

+ * A call to plain {@code invoke} works the same as a call to + * {@code invokeExact}, if the symbolic type descriptor specified by the caller + * exactly matches the method handle's own type. + * If there is a type mismatch, {@code invoke} attempts + * to adjust the type of the receiving method handle, + * as if by a call to {@link #asType asType}, + * to obtain an exactly invokable method handle {@code M2}. + * This allows a more powerful negotiation of method type + * between caller and callee. + *

+ * (Note: The adjusted method handle {@code M2} is not directly observable, + * and implementations are therefore not required to materialize it.) + * + *

Invocation checking

+ * In typical programs, method handle type matching will usually succeed. + * But if a match fails, the JVM will throw a {@link WrongMethodTypeException}, + * either directly (in the case of {@code invokeExact}) or indirectly as if + * by a failed call to {@code asType} (in the case of {@code invoke}). + *

+ * Thus, a method type mismatch which might show up as a linkage error + * in a statically typed program can show up as + * a dynamic {@code WrongMethodTypeException} + * in a program which uses method handles. + *

+ * Because method types contain "live" {@code Class} objects, + * method type matching takes into account both types names and class loaders. + * Thus, even if a method handle {@code M} is created in one + * class loader {@code L1} and used in another {@code L2}, + * method handle calls are type-safe, because the caller's symbolic type + * descriptor, as resolved in {@code L2}, + * is matched against the original callee method's symbolic type descriptor, + * as resolved in {@code L1}. + * The resolution in {@code L1} happens when {@code M} is created + * and its type is assigned, while the resolution in {@code L2} happens + * when the {@code invokevirtual} instruction is linked. + *

+ * Apart from the checking of type descriptors, + * a method handle's capability to call its underlying method is unrestricted. + * If a method handle is formed on a non-public method by a class + * that has access to that method, the resulting handle can be used + * in any place by any caller who receives a reference to it. + *

+ * Unlike with the Core Reflection API, where access is checked every time + * a reflective method is invoked, + * method handle access checking is performed + * when the method handle is created. + * In the case of {@code ldc} (see below), access checking is performed as part of linking + * the constant pool entry underlying the constant method handle. + *

+ * Thus, handles to non-public methods, or to methods in non-public classes, + * should generally be kept secret. + * They should not be passed to untrusted code unless their use from + * the untrusted code would be harmless. + * + *

Method handle creation

+ * Java code can create a method handle that directly accesses + * any method, constructor, or field that is accessible to that code. + * This is done via a reflective, capability-based API called + * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup} + * For example, a static method handle can be obtained + * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}. + * There are also conversion methods from Core Reflection API objects, + * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}. + *

+ * Like classes and strings, method handles that correspond to accessible + * fields, methods, and constructors can also be represented directly + * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes. + * A new type of constant pool entry, {@code CONSTANT_MethodHandle}, + * refers directly to an associated {@code CONSTANT_Methodref}, + * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref} + * constant pool entry. + * (For full details on method handle constants, + * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.) + *

+ * Method handles produced by lookups or constant loads from methods or + * constructors with the variable arity modifier bit ({@code 0x0080}) + * have a corresponding variable arity, as if they were defined with + * the help of {@link #asVarargsCollector asVarargsCollector}. + *

+ * A method reference may refer either to a static or non-static method. + * In the non-static case, the method handle type includes an explicit + * receiver argument, prepended before any other arguments. + * In the method handle's type, the initial receiver argument is typed + * according to the class under which the method was initially requested. + * (E.g., if a non-static method handle is obtained via {@code ldc}, + * the type of the receiver is the class named in the constant pool entry.) + *

+ * Method handle constants are subject to the same link-time access checks + * their corresponding bytecode instructions, and the {@code ldc} instruction + * will throw corresponding linkage errors if the bytecode behaviors would + * throw such errors. + *

+ * As a corollary of this, access to protected members is restricted + * to receivers only of the accessing class, or one of its subclasses, + * and the accessing class must in turn be a subclass (or package sibling) + * of the protected member's defining class. + * If a method reference refers to a protected non-static method or field + * of a class outside the current package, the receiver argument will + * be narrowed to the type of the accessing class. + *

+ * When a method handle to a virtual method is invoked, the method is + * always looked up in the receiver (that is, the first argument). + *

+ * A non-virtual method handle to a specific virtual method implementation + * can also be created. These do not perform virtual lookup based on + * receiver type. Such a method handle simulates the effect of + * an {@code invokespecial} instruction to the same method. + * + *

Usage examples

+ * Here are some examples of usage: + *
{@code
+Object x, y; String s; int i;
+MethodType mt; MethodHandle mh;
+MethodHandles.Lookup lookup = MethodHandles.lookup();
+// mt is (char,char)String
+mt = MethodType.methodType(String.class, char.class, char.class);
+mh = lookup.findVirtual(String.class, "replace", mt);
+s = (String) mh.invokeExact("daddy",'d','n');
+// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
+assertEquals(s, "nanny");
+// weakly typed invocation (using MHs.invoke)
+s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
+assertEquals(s, "savvy");
+// mt is (Object[])List
+mt = MethodType.methodType(java.util.List.class, Object[].class);
+mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
+assert(mh.isVarargsCollector());
+x = mh.invoke("one", "two");
+// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList("one","two"));
+// mt is (Object,Object,Object)Object
+mt = MethodType.genericMethodType(3);
+mh = mh.asType(mt);
+x = mh.invokeExact((Object)1, (Object)2, (Object)3);
+// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList(1,2,3));
+// mt is ()int
+mt = MethodType.methodType(int.class);
+mh = lookup.findVirtual(java.util.List.class, "size", mt);
+i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
+// invokeExact(Ljava/util/List;)I
+assert(i == 3);
+mt = MethodType.methodType(void.class, String.class);
+mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
+mh.invokeExact(System.out, "Hello, world.");
+// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
+ * }
+ * Each of the above calls to {@code invokeExact} or plain {@code invoke} + * generates a single invokevirtual instruction with + * the symbolic type descriptor indicated in the following comment. + * In these examples, the helper method {@code assertEquals} is assumed to + * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals} + * on its arguments, and asserts that the result is true. + * + *

Exceptions

+ * The methods {@code invokeExact} and {@code invoke} are declared + * to throw {@link java.lang.Throwable Throwable}, + * which is to say that there is no static restriction on what a method handle + * can throw. Since the JVM does not distinguish between checked + * and unchecked exceptions (other than by their class, of course), + * there is no particular effect on bytecode shape from ascribing + * checked exceptions to method handle invocations. But in Java source + * code, methods which perform method handle calls must either explicitly + * throw {@code Throwable}, or else must catch all + * throwables locally, rethrowing only those which are legal in the context, + * and wrapping ones which are illegal. + * + *

Signature polymorphism

+ * The unusual compilation and linkage behavior of + * {@code invokeExact} and plain {@code invoke} + * is referenced by the term signature polymorphism. + * As defined in the Java Language Specification, + * a signature polymorphic method is one which can operate with + * any of a wide range of call signatures and return types. + *

+ * In source code, a call to a signature polymorphic method will + * compile, regardless of the requested symbolic type descriptor. + * As usual, the Java compiler emits an {@code invokevirtual} + * instruction with the given symbolic type descriptor against the named method. + * The unusual part is that the symbolic type descriptor is derived from + * the actual argument and return types, not from the method declaration. + *

+ * When the JVM processes bytecode containing signature polymorphic calls, + * it will successfully link any such call, regardless of its symbolic type descriptor. + * (In order to retain type safety, the JVM will guard such calls with suitable + * dynamic type checks, as described elsewhere.) + *

+ * Bytecode generators, including the compiler back end, are required to emit + * untransformed symbolic type descriptors for these methods. + * Tools which determine symbolic linkage are required to accept such + * untransformed descriptors, without reporting linkage errors. + * + *

Interoperation between method handles and the Core Reflection API

+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API, + * any class member represented by a Core Reflection API object + * can be converted to a behaviorally equivalent method handle. + * For example, a reflective {@link java.lang.reflect.Method Method} can + * be converted to a method handle using + * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}. + * The resulting method handles generally provide more direct and efficient + * access to the underlying class members. + *

+ * As a special case, + * when the Core Reflection API is used to view the signature polymorphic + * methods {@code invokeExact} or plain {@code invoke} in this class, + * they appear as ordinary non-polymorphic methods. + * Their reflective appearance, as viewed by + * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod}, + * is unaffected by their special status in this API. + * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers} + * will report exactly those modifier bits required for any similarly + * declared method, including in this case {@code native} and {@code varargs} bits. + *

+ * As with any reflected method, these methods (when reflected) may be + * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}. + * However, such reflective calls do not result in method handle invocations. + * Such a call, if passed the required argument + * (a single one, of type {@code Object[]}), will ignore the argument and + * will throw an {@code UnsupportedOperationException}. + *

+ * Since {@code invokevirtual} instructions can natively + * invoke method handles under any symbolic type descriptor, this reflective view conflicts + * with the normal presentation of these methods via bytecodes. + * Thus, these two native methods, when reflectively viewed by + * {@code Class.getDeclaredMethod}, may be regarded as placeholders only. + *

+ * In order to obtain an invoker method for a particular type descriptor, + * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker}, + * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}. + * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual} + * API is also able to return a method handle + * to call {@code invokeExact} or plain {@code invoke}, + * for any specified type descriptor . + * + *

Interoperation between method handles and Java generics

+ * A method handle can be obtained on a method, constructor, or field + * which is declared with Java generic types. + * As with the Core Reflection API, the type of the method handle + * will constructed from the erasure of the source-level type. + * When a method handle is invoked, the types of its arguments + * or the return value cast type may be generic types or type instances. + * If this occurs, the compiler will replace those + * types by their erasures when it constructs the symbolic type descriptor + * for the {@code invokevirtual} instruction. + *

+ * Method handles do not represent + * their function-like types in terms of Java parameterized (generic) types, + * because there are three mismatches between function-like types and parameterized + * Java types. + *

    + *
  • Method types range over all possible arities, + * from no arguments to up to the maximum number of allowed arguments. + * Generics are not variadic, and so cannot represent this.
  • + *
  • Method types can specify arguments of primitive types, + * which Java generic types cannot range over.
  • + *
  • Higher order functions over method handles (combinators) are + * often generic across a wide range of function types, including + * those of multiple arities. It is impossible to represent such + * genericity with a Java type parameter.
  • + *
+ * + *

Arity limits

+ * The JVM imposes on all methods and constructors of any kind an absolute + * limit of 255 stacked arguments. This limit can appear more restrictive + * in certain cases: + *
    + *
  • A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots. + *
  • A non-static method consumes an extra argument for the object on which the method is called. + *
  • A constructor consumes an extra argument for the object which is being constructed. + *
  • Since a method handle’s {@code invoke} method (or other signature-polymorphic method) is non-virtual, + * it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object. + *
+ * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments. + * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it. + * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}. + * In particular, a method handle’s type must not have an arity of the exact maximum 255. + * + * @see MethodType + * @see MethodHandles + * @author John Rose, JSR 292 EG + */ +public abstract class MethodHandle { + static { MethodHandleImpl.initStatics(); } + + /** + * Internal marker interface which distinguishes (to the Java compiler) + * those methods which are signature polymorphic. + */ + @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) + @interface PolymorphicSignature { } + + private final MethodType type; + /*private*/ final LambdaForm form; + // form is not private so that invokers can easily fetch it + /*private*/ MethodHandle asTypeCache; + // asTypeCache is not private so that invokers can easily fetch it + + /** + * Reports the type of this method handle. + * Every invocation of this method handle via {@code invokeExact} must exactly match this type. + * @return the method handle type + */ + public MethodType type() { + return type; + } + + /** + * Package-private constructor for the method handle implementation hierarchy. + * Method handle inheritance will be contained completely within + * the {@code java.lang.invoke} package. + */ + // @param type type (permanently assigned) of the new method handle + /*non-public*/ MethodHandle(MethodType type, LambdaForm form) { + type.getClass(); // explicit NPE + form.getClass(); // explicit NPE + this.type = type; + this.form = form; + + form.prepare(); // TO DO: Try to delay this step until just before invocation. + } + + /** + * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match. + * The symbolic type descriptor at the call site of {@code invokeExact} must + * exactly match this method handle's {@link #type type}. + * No conversions are allowed on arguments or return values. + *

+ * When this method is observed via the Core Reflection API, + * it will appear as a single native method, taking an object array and returning an object. + * If this native method is invoked directly via + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI, + * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}, + * it will throw an {@code UnsupportedOperationException}. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor + * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call + */ + public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable; + + /** + * Invokes the method handle, allowing any caller type descriptor, + * and optionally performing conversions on arguments and return values. + *

+ * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type}, + * the call proceeds as if by {@link #invokeExact invokeExact}. + *

+ * Otherwise, the call proceeds as if this method handle were first + * adjusted by calling {@link #asType asType} to adjust this method handle + * to the required type, and then the call proceeds as if by + * {@link #invokeExact invokeExact} on the adjusted method handle. + *

+ * There is no guarantee that the {@code asType} call is actually made. + * If the JVM can predict the results of making the call, it may perform + * adaptations directly on the caller's arguments, + * and call the target method handle according to its own exact type. + *

+ * The resolved type descriptor at the call site of {@code invoke} must + * be a valid argument to the receivers {@code asType} method. + * In particular, the caller must specify the same argument arity + * as the callee's type, + * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}. + *

+ * When this method is observed via the Core Reflection API, + * it will appear as a single native method, taking an object array and returning an object. + * If this native method is invoked directly via + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI, + * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}, + * it will throw an {@code UnsupportedOperationException}. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor + * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails + * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call + */ + public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable; + + /** + * Private method for trusted invocation of a method handle respecting simplified signatures. + * Type mismatches will not throw {@code WrongMethodTypeException}, but could crash the JVM. + *

+ * The caller signature is restricted to the following basic types: + * Object, int, long, float, double, and void return. + *

+ * The caller is responsible for maintaining type correctness by ensuring + * that the each outgoing argument value is a member of the range of the corresponding + * callee argument type. + * (The caller should therefore issue appropriate casts and integer narrowing + * operations on outgoing argument values.) + * The caller can assume that the incoming result value is part of the range + * of the callee's return type. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + */ + /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args) throws Throwable; + + /** + * Private method for trusted invocation of a MemberName of kind {@code REF_invokeVirtual}. + * The caller signature is restricted to basic types as with {@code invokeBasic}. + * The trailing (not leading) argument must be a MemberName. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + */ + /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args) throws Throwable; + + /** + * Private method for trusted invocation of a MemberName of kind {@code REF_invokeStatic}. + * The caller signature is restricted to basic types as with {@code invokeBasic}. + * The trailing (not leading) argument must be a MemberName. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + */ + /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args) throws Throwable; + + /** + * Private method for trusted invocation of a MemberName of kind {@code REF_invokeSpecial}. + * The caller signature is restricted to basic types as with {@code invokeBasic}. + * The trailing (not leading) argument must be a MemberName. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + */ + /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args) throws Throwable; + + /** + * Private method for trusted invocation of a MemberName of kind {@code REF_invokeInterface}. + * The caller signature is restricted to basic types as with {@code invokeBasic}. + * The trailing (not leading) argument must be a MemberName. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + */ + /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args) throws Throwable; + + /** + * Performs a variable arity invocation, passing the arguments in the given list + * to the method handle, as if via an inexact {@link #invoke invoke} from a call site + * which mentions only the type {@code Object}, and whose arity is the length + * of the argument list. + *

+ * Specifically, execution proceeds as if by the following steps, + * although the methods are not guaranteed to be called if the JVM + * can predict their effects. + *

    + *
  • Determine the length of the argument array as {@code N}. + * For a null reference, {@code N=0}.
  • + *
  • Determine the general type {@code TN} of {@code N} arguments as + * as {@code TN=MethodType.genericMethodType(N)}.
  • + *
  • Force the original target method handle {@code MH0} to the + * required type, as {@code MH1 = MH0.asType(TN)}.
  • + *
  • Spread the array into {@code N} separate arguments {@code A0, ...}.
  • + *
  • Invoke the type-adjusted method handle on the unpacked arguments: + * MH1.invokeExact(A0, ...).
  • + *
  • Take the return value as an {@code Object} reference.
  • + *
+ *

+ * Because of the action of the {@code asType} step, the following argument + * conversions are applied as necessary: + *

    + *
  • reference casting + *
  • unboxing + *
  • widening primitive conversions + *
+ *

+ * The result returned by the call is boxed if it is a primitive, + * or forced to null if the return type is void. + *

+ * This call is equivalent to the following code: + *

{@code
+     * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
+     * Object result = invoker.invokeExact(this, arguments);
+     * }
+ *

+ * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke}, + * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI. + * It can therefore be used as a bridge between native or reflective code and method handles. + * + * @param arguments the arguments to pass to the target + * @return the result returned by the target + * @throws ClassCastException if an argument cannot be converted by reference casting + * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments + * @throws Throwable anything thrown by the target method invocation + * @see MethodHandles#spreadInvoker + */ + public Object invokeWithArguments(Object... arguments) throws Throwable { + int argc = arguments == null ? 0 : arguments.length; + @SuppressWarnings("LocalVariableHidesMemberVariable") + MethodType type = type(); + if (type.parameterCount() != argc || isVarargsCollector()) { + // simulate invoke + return asType(MethodType.genericMethodType(argc)).invokeWithArguments(arguments); + } + MethodHandle invoker = type.invokers().varargsInvoker(); + return invoker.invokeExact(this, arguments); + } + + /** + * Performs a variable arity invocation, passing the arguments in the given array + * to the method handle, as if via an inexact {@link #invoke invoke} from a call site + * which mentions only the type {@code Object}, and whose arity is the length + * of the argument array. + *

+ * This method is also equivalent to the following code: + *

{@code
+     *   invokeWithArguments(arguments.toArray()
+     * }
+ * + * @param arguments the arguments to pass to the target + * @return the result returned by the target + * @throws NullPointerException if {@code arguments} is a null reference + * @throws ClassCastException if an argument cannot be converted by reference casting + * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments + * @throws Throwable anything thrown by the target method invocation + */ + public Object invokeWithArguments(java.util.List arguments) throws Throwable { + return invokeWithArguments(arguments.toArray()); + } + + /** + * Produces an adapter method handle which adapts the type of the + * current method handle to a new type. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + *

+ * If the original type and new type are equal, returns {@code this}. + *

+ * The new method handle, when invoked, will perform the following + * steps: + *

    + *
  • Convert the incoming argument list to match the original + * method handle's argument list. + *
  • Invoke the original method handle on the converted argument list. + *
  • Convert any result returned by the original method handle + * to the return type of new method handle. + *
+ *

+ * This method provides the crucial behavioral difference between + * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}. + * The two methods + * perform the same steps when the caller's type descriptor exactly m atches + * the callee's, but when the types differ, plain {@link #invoke invoke} + * also calls {@code asType} (or some internal equivalent) in order + * to match up the caller's and callee's types. + *

+ * If the current method is a variable arity method handle + * argument list conversion may involve the conversion and collection + * of several arguments into an array, as + * {@linkplain #asVarargsCollector described elsewhere}. + * In every other case, all conversions are applied pairwise, + * which means that each argument or return value is converted to + * exactly one argument or return value (or no return value). + * The applied conversions are defined by consulting the + * the corresponding component types of the old and new + * method handle types. + *

+ * Let T0 and T1 be corresponding new and old parameter types, + * or old and new return types. Specifically, for some valid index {@code i}, let + * T0{@code =newType.parameterType(i)} and T1{@code =this.type().parameterType(i)}. + * Or else, going the other way for return values, let + * T0{@code =this.type().returnType()} and T1{@code =newType.returnType()}. + * If the types are the same, the new method handle makes no change + * to the corresponding argument or return value (if any). + * Otherwise, one of the following conversions is applied + * if possible: + *

    + *
  • If T0 and T1 are references, then a cast to T1 is applied. + * (The types do not need to be related in any particular way. + * This is because a dynamic value of null can convert to any reference type.) + *
  • If T0 and T1 are primitives, then a Java method invocation + * conversion (JLS 5.3) is applied, if one exists. + * (Specifically, T0 must convert to T1 by a widening primitive conversion.) + *
  • If T0 is a primitive and T1 a reference, + * a Java casting conversion (JLS 5.5) is applied if one exists. + * (Specifically, the value is boxed from T0 to its wrapper class, + * which is then widened as needed to T1.) + *
  • If T0 is a reference and T1 a primitive, an unboxing + * conversion will be applied at runtime, possibly followed + * by a Java method invocation conversion (JLS 5.3) + * on the primitive value. (These are the primitive widening conversions.) + * T0 must be a wrapper class or a supertype of one. + * (In the case where T0 is Object, these are the conversions + * allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.) + * The unboxing conversion must have a possibility of success, which means that + * if T0 is not itself a wrapper class, there must exist at least one + * wrapper class TW which is a subtype of T0 and whose unboxed + * primitive value can be widened to T1. + *
  • If the return type T1 is marked as void, any returned value is discarded + *
  • If the return type T0 is void and T1 a reference, a null value is introduced. + *
  • If the return type T0 is void and T1 a primitive, + * a zero value is introduced. + *
+ * (Note: Both T0 and T1 may be regarded as static types, + * because neither corresponds specifically to the dynamic type of any + * actual argument or return value.) + *

+ * The method handle conversion cannot be made if any one of the required + * pairwise conversions cannot be made. + *

+ * At runtime, the conversions applied to reference arguments + * or return values may require additional runtime checks which can fail. + * An unboxing operation may fail because the original reference is null, + * causing a {@link java.lang.NullPointerException NullPointerException}. + * An unboxing operation or a reference cast may also fail on a reference + * to an object of the wrong type, + * causing a {@link java.lang.ClassCastException ClassCastException}. + * Although an unboxing operation may accept several kinds of wrappers, + * if none are available, a {@code ClassCastException} will be thrown. + * + * @param newType the expected type of the new method handle + * @return a method handle which delegates to {@code this} after performing + * any necessary argument conversions, and arranges for any + * necessary return value conversions + * @throws NullPointerException if {@code newType} is a null reference + * @throws WrongMethodTypeException if the conversion cannot be made + * @see MethodHandles#explicitCastArguments + */ + public MethodHandle asType(MethodType newType) { + // Fast path alternative to a heavyweight {@code asType} call. + // Return 'this' if the conversion will be a no-op. + if (newType == type) { + return this; + } + // Return 'this.asTypeCache' if the conversion is already memoized. + MethodHandle atc = asTypeCache; + if (atc != null && newType == atc.type) { + return atc; + } + return asTypeUncached(newType); + } + + /** Override this to change asType behavior. */ + /*non-public*/ MethodHandle asTypeUncached(MethodType newType) { + if (!type.isConvertibleTo(newType)) + throw new WrongMethodTypeException("cannot convert "+this+" to "+newType); + return asTypeCache = convertArguments(newType); + } + + /** + * Makes an array-spreading method handle, which accepts a trailing array argument + * and spreads its elements as positional arguments. + * The new method handle adapts, as its target, + * the current method handle. The type of the adapter will be + * the same as the type of the target, except that the final + * {@code arrayLength} parameters of the target's type are replaced + * by a single array parameter of type {@code arrayType}. + *

+ * If the array element type differs from any of the corresponding + * argument types on the original target, + * the original target is adapted to take the array elements directly, + * as if by a call to {@link #asType asType}. + *

+ * When called, the adapter replaces a trailing array argument + * by the array's elements, each as its own argument to the target. + * (The order of the arguments is preserved.) + * They are converted pairwise by casting and/or unboxing + * to the types of the trailing parameters of the target. + * Finally the target is called. + * What the target eventually returns is returned unchanged by the adapter. + *

+ * Before calling the target, the adapter verifies that the array + * contains exactly enough elements to provide a correct argument count + * to the target method handle. + * (The array may also be null when zero elements are required.) + *

+ * If, when the adapter is called, the supplied array argument does + * not have the correct number of elements, the adapter will throw + * an {@link IllegalArgumentException} instead of invoking the target. + *

+ * Here are some simple examples of array-spreading method handles: + *

{@code
+MethodHandle equals = publicLookup()
+  .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
+assert( (boolean) equals.invokeExact("me", (Object)"me"));
+assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
+// spread both arguments from a 2-array:
+MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
+assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
+assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
+// try to spread from anything but a 2-array:
+for (int n = 0; n <= 10; n++) {
+  Object[] badArityArgs = (n == 2 ? null : new Object[n]);
+  try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
+  catch (IllegalArgumentException ex) { } // OK
+}
+// spread both arguments from a String array:
+MethodHandle eq2s = equals.asSpreader(String[].class, 2);
+assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
+assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
+// spread second arguments from a 1-array:
+MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
+assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
+assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
+// spread no arguments from a 0-array or null:
+MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
+assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
+assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
+// asSpreader and asCollector are approximate inverses:
+for (int n = 0; n <= 2; n++) {
+    for (Class a : new Class[]{Object[].class, String[].class, CharSequence[].class}) {
+        MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
+        assert( (boolean) equals2.invokeWithArguments("me", "me"));
+        assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
+    }
+}
+MethodHandle caToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
+assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
+MethodHandle caString3 = caToString.asCollector(char[].class, 3);
+assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
+MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
+assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
+     * }
+ * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments + * @param arrayLength the number of arguments to spread from an incoming array argument + * @return a new method handle which spreads its final array argument, + * before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type, + * or if target does not have at least + * {@code arrayLength} parameter types, + * or if {@code arrayLength} is negative, + * or if the resulting method handle's type would have + * too many parameters + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * @see #asCollector + */ + public MethodHandle asSpreader(Class arrayType, int arrayLength) { + asSpreaderChecks(arrayType, arrayLength); + int spreadArgPos = type.parameterCount() - arrayLength; + return MethodHandleImpl.makeSpreadArguments(this, arrayType, spreadArgPos, arrayLength); + } + + private void asSpreaderChecks(Class arrayType, int arrayLength) { + spreadArrayChecks(arrayType, arrayLength); + int nargs = type().parameterCount(); + if (nargs < arrayLength || arrayLength < 0) + throw newIllegalArgumentException("bad spread array length"); + if (arrayType != Object[].class && arrayLength != 0) { + boolean sawProblem = false; + Class arrayElement = arrayType.getComponentType(); + for (int i = nargs - arrayLength; i < nargs; i++) { + if (!MethodType.canConvert(arrayElement, type().parameterType(i))) { + sawProblem = true; + break; + } + } + if (sawProblem) { + ArrayList> ptypes = new ArrayList<>(type().parameterList()); + for (int i = nargs - arrayLength; i < nargs; i++) { + ptypes.set(i, arrayElement); + } + // elicit an error: + this.asType(MethodType.methodType(type().returnType(), ptypes)); + } + } + } + + private void spreadArrayChecks(Class arrayType, int arrayLength) { + Class arrayElement = arrayType.getComponentType(); + if (arrayElement == null) + throw newIllegalArgumentException("not an array type", arrayType); + if ((arrayLength & 0x7F) != arrayLength) { + if ((arrayLength & 0xFF) != arrayLength) + throw newIllegalArgumentException("array length is not legal", arrayLength); + assert(arrayLength >= 128); + if (arrayElement == long.class || + arrayElement == double.class) + throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength); + } + } + + /** + * Makes an array-collecting method handle, which accepts a given number of trailing + * positional arguments and collects them into an array argument. + * The new method handle adapts, as its target, + * the current method handle. The type of the adapter will be + * the same as the type of the target, except that a single trailing + * parameter (usually of type {@code arrayType}) is replaced by + * {@code arrayLength} parameters whose type is element type of {@code arrayType}. + *

+ * If the array type differs from the final argument type on the original target, + * the original target is adapted to take the array type directly, + * as if by a call to {@link #asType asType}. + *

+ * When called, the adapter replaces its trailing {@code arrayLength} + * arguments by a single new array of type {@code arrayType}, whose elements + * comprise (in order) the replaced arguments. + * Finally the target is called. + * What the target eventually returns is returned unchanged by the adapter. + *

+ * (The array may also be a shared constant when {@code arrayLength} is zero.) + *

+ * (Note: The {@code arrayType} is often identical to the last + * parameter type of the original target. + * It is an explicit argument for symmetry with {@code asSpreader}, and also + * to allow the target to use a simple {@code Object} as its last parameter type.) + *

+ * In order to create a collecting adapter which is not restricted to a particular + * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead. + *

+ * Here are some examples of array-collecting method handles: + *

{@code
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+assertEquals("[won]",   (String) deepToString.invokeExact(new Object[]{"won"}));
+MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
+assertEquals(methodType(String.class, Object.class), ts1.type());
+//assertEquals("[won]", (String) ts1.invokeExact(         new Object[]{"won"})); //FAIL
+assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
+// arrayType can be a subtype of Object[]
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals(methodType(String.class, String.class, String.class), ts2.type());
+assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
+MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
+assertEquals("[]", (String) ts0.invokeExact());
+// collectors can be nested, Lisp-style
+MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
+assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
+// arrayType can be any primitive array type
+MethodHandle bytesToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
+  .asCollector(byte[].class, 3);
+assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
+MethodHandle longsToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
+  .asCollector(long[].class, 1);
+assertEquals("[123]", (String) longsToString.invokeExact((long)123));
+     * }
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments + * @param arrayLength the number of arguments to collect into a new array argument + * @return a new method handle which collects some trailing argument + * into an array, before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type + * or {@code arrayType} is not assignable to this method handle's trailing parameter type, + * or {@code arrayLength} is not a legal array size, + * or the resulting method handle's type would have + * too many parameters + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * @see #asSpreader + * @see #asVarargsCollector + */ + public MethodHandle asCollector(Class arrayType, int arrayLength) { + asCollectorChecks(arrayType, arrayLength); + int collectArgPos = type().parameterCount()-1; + MethodHandle target = this; + if (arrayType != type().parameterType(collectArgPos)) + target = convertArguments(type().changeParameterType(collectArgPos, arrayType)); + MethodHandle collector = ValueConversions.varargsArray(arrayType, arrayLength); + return MethodHandles.collectArguments(target, collectArgPos, collector); + } + + // private API: return true if last param exactly matches arrayType + private boolean asCollectorChecks(Class arrayType, int arrayLength) { + spreadArrayChecks(arrayType, arrayLength); + int nargs = type().parameterCount(); + if (nargs != 0) { + Class lastParam = type().parameterType(nargs-1); + if (lastParam == arrayType) return true; + if (lastParam.isAssignableFrom(arrayType)) return false; + } + throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType); + } + + /** + * Makes a variable arity adapter which is able to accept + * any number of trailing positional arguments and collect them + * into an array argument. + *

+ * The type and behavior of the adapter will be the same as + * the type and behavior of the target, except that certain + * {@code invoke} and {@code asType} requests can lead to + * trailing positional arguments being collected into target's + * trailing parameter. + * Also, the last parameter type of the adapter will be + * {@code arrayType}, even if the target has a different + * last parameter type. + *

+ * This transformation may return {@code this} if the method handle is + * already of variable arity and its trailing parameter type + * is identical to {@code arrayType}. + *

+ * When called with {@link #invokeExact invokeExact}, the adapter invokes + * the target with no argument changes. + * (Note: This behavior is different from a + * {@linkplain #asCollector fixed arity collector}, + * since it accepts a whole array of indeterminate length, + * rather than a fixed number of arguments.) + *

+ * When called with plain, inexact {@link #invoke invoke}, if the caller + * type is the same as the adapter, the adapter invokes the target as with + * {@code invokeExact}. + * (This is the normal behavior for {@code invoke} when types match.) + *

+ * Otherwise, if the caller and adapter arity are the same, and the + * trailing parameter type of the caller is a reference type identical to + * or assignable to the trailing parameter type of the adapter, + * the arguments and return values are converted pairwise, + * as if by {@link #asType asType} on a fixed arity + * method handle. + *

+ * Otherwise, the arities differ, or the adapter's trailing parameter + * type is not assignable from the corresponding caller type. + * In this case, the adapter replaces all trailing arguments from + * the original trailing argument position onward, by + * a new array of type {@code arrayType}, whose elements + * comprise (in order) the replaced arguments. + *

+ * The caller type must provides as least enough arguments, + * and of the correct type, to satisfy the target's requirement for + * positional arguments before the trailing array argument. + * Thus, the caller must supply, at a minimum, {@code N-1} arguments, + * where {@code N} is the arity of the target. + * Also, there must exist conversions from the incoming arguments + * to the target's arguments. + * As with other uses of plain {@code invoke}, if these basic + * requirements are not fulfilled, a {@code WrongMethodTypeException} + * may be thrown. + *

+ * In all cases, what the target eventually returns is returned unchanged by the adapter. + *

+ * In the final case, it is exactly as if the target method handle were + * temporarily adapted with a {@linkplain #asCollector fixed arity collector} + * to the arity required by the caller type. + * (As with {@code asCollector}, if the array length is zero, + * a shared constant may be used instead of a new array. + * If the implied call to {@code asCollector} would throw + * an {@code IllegalArgumentException} or {@code WrongMethodTypeException}, + * the call to the variable arity adapter must throw + * {@code WrongMethodTypeException}.) + *

+ * The behavior of {@link #asType asType} is also specialized for + * variable arity adapters, to maintain the invariant that + * plain, inexact {@code invoke} is always equivalent to an {@code asType} + * call to adjust the target type, followed by {@code invokeExact}. + * Therefore, a variable arity adapter responds + * to an {@code asType} request by building a fixed arity collector, + * if and only if the adapter and requested type differ either + * in arity or trailing argument type. + * The resulting fixed arity collector has its type further adjusted + * (if necessary) to the requested type by pairwise conversion, + * as if by another application of {@code asType}. + *

+ * When a method handle is obtained by executing an {@code ldc} instruction + * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked + * as a variable arity method (with the modifier bit {@code 0x0080}), + * the method handle will accept multiple arities, as if the method handle + * constant were created by means of a call to {@code asVarargsCollector}. + *

+ * In order to create a collecting adapter which collects a predetermined + * number of arguments, and whose type reflects this predetermined number, + * use {@link #asCollector asCollector} instead. + *

+ * No method handle transformations produce new method handles with + * variable arity, unless they are documented as doing so. + * Therefore, besides {@code asVarargsCollector}, + * all methods in {@code MethodHandle} and {@code MethodHandles} + * will return a method handle with fixed arity, + * except in the cases where they are specified to return their original + * operand (e.g., {@code asType} of the method handle's own type). + *

+ * Calling {@code asVarargsCollector} on a method handle which is already + * of variable arity will produce a method handle with the same type and behavior. + * It may (or may not) return the original variable arity method handle. + *

+ * Here is an example, of a list-making variable arity method handle: + *

{@code
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
+assertEquals("[won]",   (String) ts1.invokeExact(    new Object[]{"won"}));
+assertEquals("[won]",   (String) ts1.invoke(         new Object[]{"won"}));
+assertEquals("[won]",   (String) ts1.invoke(                      "won" ));
+assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
+// findStatic of Arrays.asList(...) produces a variable arity method handle:
+MethodHandle asList = publicLookup()
+  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
+assertEquals(methodType(List.class, Object[].class), asList.type());
+assert(asList.isVarargsCollector());
+assertEquals("[]", asList.invoke().toString());
+assertEquals("[1]", asList.invoke(1).toString());
+assertEquals("[two, too]", asList.invoke("two", "too").toString());
+String[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
+List ls = (List) asList.invoke((Object)argv);
+assertEquals(1, ls.size());
+assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
+     * }
+ *

+ * Discussion: + * These rules are designed as a dynamically-typed variation + * of the Java rules for variable arity methods. + * In both cases, callers to a variable arity method or method handle + * can either pass zero or more positional arguments, or else pass + * pre-collected arrays of any length. Users should be aware of the + * special role of the final argument, and of the effect of a + * type match on that final argument, which determines whether + * or not a single trailing argument is interpreted as a whole + * array or a single element of an array to be collected. + * Note that the dynamic type of the trailing argument has no + * effect on this decision, only a comparison between the symbolic + * type descriptor of the call site and the type descriptor of the method handle.) + * + * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments + * @return a new method handle which can collect any number of trailing arguments + * into an array, before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type + * or {@code arrayType} is not assignable to this method handle's trailing parameter type + * @see #asCollector + * @see #isVarargsCollector + * @see #asFixedArity + */ + public MethodHandle asVarargsCollector(Class arrayType) { + Class arrayElement = arrayType.getComponentType(); + boolean lastMatch = asCollectorChecks(arrayType, 0); + if (isVarargsCollector() && lastMatch) + return this; + return MethodHandleImpl.makeVarargsCollector(this, arrayType); + } + + /** + * Determines if this method handle + * supports {@linkplain #asVarargsCollector variable arity} calls. + * Such method handles arise from the following sources: + *

    + *
  • a call to {@linkplain #asVarargsCollector asVarargsCollector} + *
  • a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method} + * which resolves to a variable arity Java method or constructor + *
  • an {@code ldc} instruction of a {@code CONSTANT_MethodHandle} + * which resolves to a variable arity Java method or constructor + *
+ * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls + * @see #asVarargsCollector + * @see #asFixedArity + */ + public boolean isVarargsCollector() { + return false; + } + + /** + * Makes a fixed arity method handle which is otherwise + * equivalent to the current method handle. + *

+ * If the current method handle is not of + * {@linkplain #asVarargsCollector variable arity}, + * the current method handle is returned. + * This is true even if the current method handle + * could not be a valid input to {@code asVarargsCollector}. + *

+ * Otherwise, the resulting fixed-arity method handle has the same + * type and behavior of the current method handle, + * except that {@link #isVarargsCollector isVarargsCollector} + * will be false. + * The fixed-arity method handle may (or may not) be the + * a previous argument to {@code asVarargsCollector}. + *

+ * Here is an example, of a list-making variable arity method handle: + *

{@code
+MethodHandle asListVar = publicLookup()
+  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
+  .asVarargsCollector(Object[].class);
+MethodHandle asListFix = asListVar.asFixedArity();
+assertEquals("[1]", asListVar.invoke(1).toString());
+Exception caught = null;
+try { asListFix.invoke((Object)1); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof ClassCastException);
+assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
+try { asListFix.invoke("two", "too"); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof WrongMethodTypeException);
+Object[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
+assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
+assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
+     * }
+ * + * @return a new method handle which accepts only a fixed number of arguments + * @see #asVarargsCollector + * @see #isVarargsCollector + */ + public MethodHandle asFixedArity() { + assert(!isVarargsCollector()); + return this; + } + + /** + * Binds a value {@code x} to the first argument of a method handle, without invoking it. + * The new method handle adapts, as its target, + * the current method handle by binding it to the given argument. + * The type of the bound handle will be + * the same as the type of the target, except that a single leading + * reference parameter will be omitted. + *

+ * When called, the bound handle inserts the given value {@code x} + * as a new leading argument to the target. The other arguments are + * also passed unchanged. + * What the target eventually returns is returned unchanged by the bound handle. + *

+ * The reference {@code x} must be convertible to the first parameter + * type of the target. + *

+ * (Note: Because method handles are immutable, the target method handle + * retains its original type and behavior.) + * @param x the value to bind to the first argument of the target + * @return a new method handle which prepends the given value to the incoming + * argument list, before calling the original method handle + * @throws IllegalArgumentException if the target does not have a + * leading parameter type that is a reference type + * @throws ClassCastException if {@code x} cannot be converted + * to the leading parameter type of the target + * @see MethodHandles#insertArguments + */ + public MethodHandle bindTo(Object x) { + Class ptype; + @SuppressWarnings("LocalVariableHidesMemberVariable") + MethodType type = type(); + if (type.parameterCount() == 0 || + (ptype = type.parameterType(0)).isPrimitive()) + throw newIllegalArgumentException("no leading reference parameter", x); + x = ptype.cast(x); // throw CCE if needed + return bindReceiver(x); + } + + /** + * Returns a string representation of the method handle, + * starting with the string {@code "MethodHandle"} and + * ending with the string representation of the method handle's type. + * In other words, this method returns a string equal to the value of: + *

{@code
+     * "MethodHandle" + type().toString()
+     * }
+ *

+ * (Note: Future releases of this API may add further information + * to the string representation. + * Therefore, the present syntax should not be parsed by applications.) + * + * @return a string representation of the method handle + */ + @Override + public String toString() { + if (DEBUG_METHOD_HANDLE_NAMES) return debugString(); + return standardString(); + } + String standardString() { + return "MethodHandle"+type; + } + String debugString() { + return standardString()+"/LF="+internalForm()+internalProperties(); + } + + //// Implementation methods. + //// Sub-classes can override these default implementations. + //// All these methods assume arguments are already validated. + + // Other transforms to do: convert, explicitCast, permute, drop, filter, fold, GWT, catch + + /*non-public*/ + MethodHandle setVarargs(MemberName member) throws IllegalAccessException { + if (!member.isVarargs()) return this; + int argc = type().parameterCount(); + if (argc != 0) { + Class arrayType = type().parameterType(argc-1); + if (arrayType.isArray()) { + return MethodHandleImpl.makeVarargsCollector(this, arrayType); + } + } + throw member.makeAccessException("cannot make variable arity", null); + } + /*non-public*/ + MethodHandle viewAsType(MethodType newType) { + // No actual conversions, just a new view of the same method. + return MethodHandleImpl.makePairwiseConvert(this, newType, 0); + } + + // Decoding + + /*non-public*/ + LambdaForm internalForm() { + return form; + } + + /*non-public*/ + MemberName internalMemberName() { + return null; // DMH returns DMH.member + } + + /*non-public*/ + Class internalCallerClass() { + return null; // caller-bound MH for @CallerSensitive method returns caller + } + + /*non-public*/ + MethodHandle withInternalMemberName(MemberName member) { + if (member != null) { + return MethodHandleImpl.makeWrappedMember(this, member); + } else if (internalMemberName() == null) { + // The required internaMemberName is null, and this MH (like most) doesn't have one. + return this; + } else { + // The following case is rare. Mask the internalMemberName by wrapping the MH in a BMH. + MethodHandle result = rebind(); + assert (result.internalMemberName() == null); + return result; + } + } + + /*non-public*/ + boolean isInvokeSpecial() { + return false; // DMH.Special returns true + } + + /*non-public*/ + Object internalValues() { + return null; + } + + /*non-public*/ + Object internalProperties() { + // Override to something like "/FOO=bar" + return ""; + } + + //// Method handle implementation methods. + //// Sub-classes can override these default implementations. + //// All these methods assume arguments are already validated. + + /*non-public*/ MethodHandle convertArguments(MethodType newType) { + // Override this if it can be improved. + return MethodHandleImpl.makePairwiseConvert(this, newType, 1); + } + + /*non-public*/ + MethodHandle bindArgument(int pos, char basicType, Object value) { + // Override this if it can be improved. + return rebind().bindArgument(pos, basicType, value); + } + + /*non-public*/ + MethodHandle bindReceiver(Object receiver) { + // Override this if it can be improved. + return bindArgument(0, 'L', receiver); + } + + /*non-public*/ + MethodHandle bindImmediate(int pos, char basicType, Object value) { + // Bind an immediate value to a position in the arguments. + // This means, elide the respective argument, + // and replace all references to it in NamedFunction args with the specified value. + + // CURRENT RESTRICTIONS + // * only for pos 0 and UNSAFE (position is adjusted in MHImpl to make API usable for others) + assert pos == 0 && basicType == 'L' && value instanceof Unsafe; + MethodType type2 = type.dropParameterTypes(pos, pos + 1); // adjustment: ignore receiver! + LambdaForm form2 = form.bindImmediate(pos + 1, basicType, value); // adjust pos to form-relative pos + return copyWith(type2, form2); + } + + /*non-public*/ + MethodHandle copyWith(MethodType mt, LambdaForm lf) { + throw new InternalError("copyWith: " + this.getClass()); + } + + /*non-public*/ + MethodHandle dropArguments(MethodType srcType, int pos, int drops) { + // Override this if it can be improved. + return rebind().dropArguments(srcType, pos, drops); + } + + /*non-public*/ + MethodHandle permuteArguments(MethodType newType, int[] reorder) { + // Override this if it can be improved. + return rebind().permuteArguments(newType, reorder); + } + + /*non-public*/ + MethodHandle rebind() { + // Bind 'this' into a new invoker, of the known class BMH. + MethodType type2 = type(); + LambdaForm form2 = reinvokerForm(this); + // form2 = lambda (bmh, arg*) { thismh = bmh[0]; invokeBasic(thismh, arg*) } + return BoundMethodHandle.bindSingle(type2, form2, this); + } + + /*non-public*/ + MethodHandle reinvokerTarget() { + throw new InternalError("not a reinvoker MH: "+this.getClass().getName()+": "+this); + } + + /** Create a LF which simply reinvokes a target of the given basic type. + * The target MH must override {@link #reinvokerTarget} to provide the target. + */ + static LambdaForm reinvokerForm(MethodHandle target) { + MethodType mtype = target.type().basicType(); + LambdaForm reinvoker = mtype.form().cachedLambdaForm(MethodTypeForm.LF_REINVOKE); + if (reinvoker != null) return reinvoker; + if (mtype.parameterSlotCount() >= MethodType.MAX_MH_ARITY) + return makeReinvokerForm(target.type(), target); // cannot cache this + reinvoker = makeReinvokerForm(mtype, null); + return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_REINVOKE, reinvoker); + } + private static LambdaForm makeReinvokerForm(MethodType mtype, MethodHandle customTargetOrNull) { + boolean customized = (customTargetOrNull != null); + MethodHandle MH_invokeBasic = customized ? null : MethodHandles.basicInvoker(mtype); + final int THIS_BMH = 0; + final int ARG_BASE = 1; + final int ARG_LIMIT = ARG_BASE + mtype.parameterCount(); + int nameCursor = ARG_LIMIT; + final int NEXT_MH = customized ? -1 : nameCursor++; + final int REINVOKE = nameCursor++; + LambdaForm.Name[] names = LambdaForm.arguments(nameCursor - ARG_LIMIT, mtype.invokerType()); + Object[] targetArgs; + MethodHandle targetMH; + if (customized) { + targetArgs = Arrays.copyOfRange(names, ARG_BASE, ARG_LIMIT, Object[].class); + targetMH = customTargetOrNull; + } else { + names[NEXT_MH] = new LambdaForm.Name(NF_reinvokerTarget, names[THIS_BMH]); + targetArgs = Arrays.copyOfRange(names, THIS_BMH, ARG_LIMIT, Object[].class); + targetArgs[0] = names[NEXT_MH]; // overwrite this MH with next MH + targetMH = MethodHandles.basicInvoker(mtype); + } + names[REINVOKE] = new LambdaForm.Name(targetMH, targetArgs); + return new LambdaForm("BMH.reinvoke", ARG_LIMIT, names); + } + + private static final LambdaForm.NamedFunction NF_reinvokerTarget; + static { + try { + NF_reinvokerTarget = new LambdaForm.NamedFunction(MethodHandle.class + .getDeclaredMethod("reinvokerTarget")); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } + + /** + * Replace the old lambda form of this method handle with a new one. + * The new one must be functionally equivalent to the old one. + * Threads may continue running the old form indefinitely, + * but it is likely that the new one will be preferred for new executions. + * Use with discretion. + */ + /*non-public*/ + void updateForm(LambdaForm newForm) { + if (form == newForm) return; + // ISSUE: Should we have a memory fence here? + UNSAFE.putObject(this, FORM_OFFSET, newForm); + this.form.prepare(); // as in MethodHandle. + } + + private static final long FORM_OFFSET; + static { + try { + FORM_OFFSET = UNSAFE.objectFieldOffset(MethodHandle.class.getDeclaredField("form")); + } catch (ReflectiveOperationException ex) { + throw newInternalError(ex); + } + } +} diff -r 0101d10bd2e0 -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); + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleInfo.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2012, 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.lang.reflect.*; +import java.util.*; +import java.lang.invoke.MethodHandleNatives.Constants; +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandleStatics.*; + +/** + * A symbolic reference obtained by cracking a direct method handle + * into its consitutent symbolic parts. + * To crack a direct method handle, call {@link Lookup#revealDirect Lookup.revealDirect}. + *

Direct Method Handles

+ * A direct method handle represents a method, constructor, or field without + * any intervening argument bindings or other transformations. + * The method, constructor, or field referred to by a direct method handle is called + * its underlying member. + * Direct method handles may be obtained in any of these ways: + *
    + *
  • By executing an {@code ldc} instruction on a {@code CONSTANT_MethodHandle} constant. + * (See the Java Virtual Machine Specification, sections 4.4.8 and 5.4.3.) + *
  • By calling one of the Lookup Factory Methods, + * such as {@link Lookup#findVirtual Lookup.findVirtual}, + * to resolve a symbolic reference into a method handle. + * A symbolic reference consists of a class, name string, and type. + *
  • By calling the factory method {@link Lookup#unreflect Lookup.unreflect} + * or {@link Lookup#unreflectSpecial Lookup.unreflectSpecial} + * to convert a {@link Method} into a method handle. + *
  • By calling the factory method {@link Lookup#unreflectConstructor Lookup.unreflectConstructor} + * to convert a {@link Constructor} into a method handle. + *
  • By calling the factory method {@link Lookup#unreflectGetter Lookup.unreflectGetter} + * or {@link Lookup#unreflectSetter Lookup.unreflectSetter} + * to convert a {@link Field} into a method handle. + *
+ * + *

Restrictions on Cracking

+ * Given a suitable {@code Lookup} object, it is possible to crack any direct method handle + * to recover a symbolic reference for the underlying method, constructor, or field. + * Cracking must be done via a {@code Lookup} object equivalent to that which created + * the target method handle, or which has enough access permissions to recreate + * an equivalent method handle. + *

+ * If the underlying method is caller sensitive, + * the direct method handle will have been "bound" to a particular caller class, the + * {@linkplain java.lang.invoke.MethodHandles.Lookup#lookupClass() lookup class} + * of the lookup object used to create it. + * Cracking this method handle with a different lookup class will fail + * even if the underlying method is public (like {@code Class.forName}). + *

+ * The requirement of lookup object matching provides a "fast fail" behavior + * for programs which may otherwise trust erroneous revelation of a method + * handle with symbolic information (or caller binding) from an unexpected scope. + * Use {@link java.lang.invoke.MethodHandles#reflectAs} to override this limitation. + * + *

Reference kinds

+ * The Lookup Factory Methods + * correspond to all major use cases for methods, constructors, and fields. + * These use cases may be distinguished using small integers as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
reference kinddescriptive namescopememberbehavior
{@code 1}{@code REF_getField}{@code class}{@code FT f;}{@code (T) this.f;}
{@code 2}{@code REF_getStatic}{@code class} or {@code interface}{@code static}
{@code FT f;}
{@code (T) C.f;}
{@code 3}{@code REF_putField}{@code class}{@code FT f;}{@code this.f = x;}
{@code 4}{@code REF_putStatic}{@code class}{@code static}
{@code FT f;}
{@code C.f = arg;}
{@code 5}{@code REF_invokeVirtual}{@code class}{@code T m(A*);}{@code (T) this.m(arg*);}
{@code 6}{@code REF_invokeStatic}{@code class} or {@code interface}{@code static}
{@code T m(A*);}
{@code (T) C.m(arg*);}
{@code 7}{@code REF_invokeSpecial}{@code class} or {@code interface}{@code T m(A*);}{@code (T) super.m(arg*);}
{@code 8}{@code REF_newInvokeSpecial}{@code class}{@code C(A*);}{@code new C(arg*);}
{@code 9}{@code REF_invokeInterface}{@code interface}{@code T m(A*);}{@code (T) this.m(arg*);}
+ * @since 1.8 + */ +public +interface MethodHandleInfo { + /** + * A direct method handle reference kind, + * as defined in the table above. + */ + public static final int + REF_getField = Constants.REF_getField, + REF_getStatic = Constants.REF_getStatic, + REF_putField = Constants.REF_putField, + REF_putStatic = Constants.REF_putStatic, + REF_invokeVirtual = Constants.REF_invokeVirtual, + REF_invokeStatic = Constants.REF_invokeStatic, + REF_invokeSpecial = Constants.REF_invokeSpecial, + REF_newInvokeSpecial = Constants.REF_newInvokeSpecial, + REF_invokeInterface = Constants.REF_invokeInterface; + + /** + * Returns the reference kind of the cracked method handle, which in turn + * determines whether the method handle's underlying member was a constructor, method, or field. + * See the table above for definitions. + * @return the integer code for the kind of reference used to access the underlying member + */ + public int getReferenceKind(); + + /** + * Returns the class in which the cracked method handle's underlying member was defined. + * @return the declaring class of the underlying member + */ + public Class getDeclaringClass(); + + /** + * Returns the name of the cracked method handle's underlying member. + * This is {@code "<init>"} if the underlying member was a constructor, + * else it is a simple method name or field name. + * @return the simple name of the underlying member + */ + public String getName(); + + /** + * Returns the nominal type of the cracked symbolic reference, expressed as a method type. + * If the reference is to a constructor, the return type will be {@code void}. + * If it is to a non-static method, the method type will not mention the {@code this} parameter. + * If it is to a field and the requested access is to read the field, + * the method type will have no parameters and return the field type. + * If it is to a field and the requested access is to write the field, + * the method type will have one parameter of the field type and return {@code void}. + *

+ * Note that original direct method handle may include a leading {@code this} parameter, + * or (in the case of a constructor) will replace the {@code void} return type + * with the constructed class. + * The nominal type does not include any {@code this} parameter, + * and (in the case of a constructor) will return {@code void}. + * @return the type of the underlying member, expressed as a method type + */ + public MethodType getMethodType(); + + // Utility methods. + // NOTE: class/name/type and reference kind constitute a symbolic reference + // member and modifiers are an add-on, derived from Core Reflection (or the equivalent) + + /** + * Reflects the underlying member as a method, constructor, or field object. + * If the underlying member is public, it is reflected as if by + * {@code getMethod}, {@code getConstructor}, or {@code getField}. + * Otherwise, it is reflected as if by + * {@code getDeclaredMethod}, {@code getDeclaredConstructor}, or {@code getDeclaredField}. + * The underlying member must be accessible to the given lookup object. + * @param the desired type of the result, either {@link Member} or a subtype + * @param expected a class object representing the desired result type {@code T} + * @param lookup the lookup object that created this MethodHandleInfo, or one with equivalent access privileges + * @return a reference to the method, constructor, or field object + * @exception ClassCastException if the member is not of the expected type + * @exception NullPointerException if either argument is {@code null} + * @exception IllegalArgumentException if the underlying member is not accessible to the given lookup object + */ + public T reflectAs(Class expected, Lookup lookup); + + /** + * Returns the access modifiers of the underlying member. + * @return the Java language modifiers for underlying member, + * or -1 if the member cannot be accessed + * @see Modifier + * @see #reflectAs + */ + public int getModifiers(); + + /** + * Determines if the underlying member was a variable arity method or constructor. + * Such members are represented by method handles that are varargs collectors. + * @implSpec + * This produces a result equivalent to: + *

{@code
+     *     getReferenceKind() >= REF_invokeVirtual && Modifier.isTransient(getModifiers())
+     * }
+ * + * + * @return {@code true} if and only if the underlying member was declared with variable arity. + */ + // spelling derived from java.lang.reflect.Executable, not MethodHandle.isVarargsCollector + public default boolean isVarArgs() { + // fields are never varargs: + if (MethodHandleNatives.refKindIsField((byte) getReferenceKind())) + return false; + // not in the public API: Modifier.VARARGS + final int ACC_VARARGS = 0x00000080; // from JVMS 4.6 (Table 4.20) + assert(ACC_VARARGS == Modifier.TRANSIENT); + return Modifier.isTransient(getModifiers()); + } + + /** + * Returns the descriptive name of the given reference kind, + * as defined in the table above. + * The conventional prefix "REF_" is omitted. + * @param referenceKind an integer code for a kind of reference used to access a class member + * @return a mixed-case string such as {@code "getField"} + * @exception IllegalArgumentException if the argument is not a valid + * reference kind number + */ + public static String referenceKindToString(int referenceKind) { + if (!MethodHandleNatives.refKindIsValid(referenceKind)) + throw newIllegalArgumentException("invalid reference kind", referenceKind); + return MethodHandleNatives.refKindName((byte)referenceKind); + } + + /** + * Returns a string representation for a {@code MethodHandleInfo}, + * given the four parts of its symbolic reference. + * This is defined to be of the form {@code "RK C.N:MT"}, where {@code RK} is the + * {@linkplain #referenceKindToString reference kind string} for {@code kind}, + * {@code C} is the {@linkplain java.lang.Class#getName name} of {@code defc} + * {@code N} is the {@code name}, and + * {@code MT} is the {@code type}. + * These four values may be obtained from the + * {@linkplain #getReferenceKind reference kind}, + * {@linkplain #getDeclaringClass declaring class}, + * {@linkplain #getName member name}, + * and {@linkplain #getMethodType method type} + * of a {@code MethodHandleInfo} object. + * + * @implSpec + * This produces a result equivalent to: + *
{@code
+     *     String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type)
+     * }
+ * + * @param kind the {@linkplain #getReferenceKind reference kind} part of the symbolic reference + * @param defc the {@linkplain #getDeclaringClass declaring class} part of the symbolic reference + * @param name the {@linkplain #getName member name} part of the symbolic reference + * @param type the {@linkplain #getMethodType method type} part of the symbolic reference + * @return a string of the form {@code "RK C.N:MT"} + * @exception IllegalArgumentException if the first argument is not a valid + * reference kind number + * @exception NullPointerException if any reference argument is {@code null} + */ + public static String toString(int kind, Class defc, String name, MethodType type) { + Objects.requireNonNull(name); Objects.requireNonNull(type); + return String.format("%s %s.%s:%s", referenceKindToString(kind), defc.getName(), name, type); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleNatives.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleNatives.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,505 @@ +/* + * 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.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Field; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; + +/** + * The JVM interface for the method handles package is all here. + * This is an interface internal and private to an implementation of JSR 292. + * This class is not part of the JSR 292 standard. + * @author jrose + */ +class MethodHandleNatives { + + private MethodHandleNatives() { } // static only + + /// MemberName support + + static native void init(MemberName self, Object ref); + static native void expand(MemberName self); + static native MemberName resolve(MemberName self, Class caller) throws LinkageError; + static native int getMembers(Class defc, String matchName, String matchSig, + int matchFlags, Class caller, int skip, MemberName[] results); + + /// Field layout queries parallel to sun.misc.Unsafe: + static native long objectFieldOffset(MemberName self); // e.g., returns vmindex + static native long staticFieldOffset(MemberName self); // e.g., returns vmindex + static native Object staticFieldBase(MemberName self); // e.g., returns clazz + static native Object getMemberVMInfo(MemberName self); // returns {vmindex,vmtarget} + + /// MethodHandle support + + /** Fetch MH-related JVM parameter. + * which=0 retrieves MethodHandlePushLimit + * which=1 retrieves stack slot push size (in address units) + */ + static native int getConstant(int which); + + static final boolean COUNT_GWT; + + /// CallSite support + + /** Tell the JVM that we need to change the target of a CallSite. */ + static native void setCallSiteTargetNormal(CallSite site, MethodHandle target); + static native void setCallSiteTargetVolatile(CallSite site, MethodHandle target); + + private static native void registerNatives(); + static { + registerNatives(); + COUNT_GWT = getConstant(Constants.GC_COUNT_GWT) != 0; + + // The JVM calls MethodHandleNatives.. Cascade the calls as needed: + MethodHandleImpl.initStatics(); +} + + // All compile-time constants go here. + // There is an opportunity to check them against the JVM's idea of them. + static class Constants { + Constants() { } // static only + // MethodHandleImpl + static final int // for getConstant + GC_COUNT_GWT = 4, + GC_LAMBDA_SUPPORT = 5; + + // MemberName + // The JVM uses values of -2 and above for vtable indexes. + // Field values are simple positive offsets. + // Ref: src/share/vm/oops/methodOop.hpp + // This value is negative enough to avoid such numbers, + // but not too negative. + static final int + MN_IS_METHOD = 0x00010000, // method (not constructor) + MN_IS_CONSTRUCTOR = 0x00020000, // constructor + MN_IS_FIELD = 0x00040000, // field + MN_IS_TYPE = 0x00080000, // nested type + MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected + MN_REFERENCE_KIND_SHIFT = 24, // refKind + MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, + // The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers: + MN_SEARCH_SUPERCLASSES = 0x00100000, + MN_SEARCH_INTERFACES = 0x00200000; + + /** + * Basic types as encoded in the JVM. These code values are not + * intended for use outside this class. They are used as part of + * a private interface between the JVM and this class. + */ + static final int + T_BOOLEAN = 4, + T_CHAR = 5, + T_FLOAT = 6, + T_DOUBLE = 7, + T_BYTE = 8, + T_SHORT = 9, + T_INT = 10, + T_LONG = 11, + T_OBJECT = 12, + //T_ARRAY = 13 + T_VOID = 14, + //T_ADDRESS = 15 + T_ILLEGAL = 99; + + /** + * Constant pool entry types. + */ + static final byte + CONSTANT_Utf8 = 1, + CONSTANT_Integer = 3, + CONSTANT_Float = 4, + CONSTANT_Long = 5, + CONSTANT_Double = 6, + CONSTANT_Class = 7, + CONSTANT_String = 8, + CONSTANT_Fieldref = 9, + CONSTANT_Methodref = 10, + CONSTANT_InterfaceMethodref = 11, + CONSTANT_NameAndType = 12, + CONSTANT_MethodHandle = 15, // JSR 292 + CONSTANT_MethodType = 16, // JSR 292 + CONSTANT_InvokeDynamic = 18, + CONSTANT_LIMIT = 19; // Limit to tags found in classfiles + + /** + * Access modifier flags. + */ + static final char + ACC_PUBLIC = 0x0001, + ACC_PRIVATE = 0x0002, + ACC_PROTECTED = 0x0004, + ACC_STATIC = 0x0008, + ACC_FINAL = 0x0010, + ACC_SYNCHRONIZED = 0x0020, + ACC_VOLATILE = 0x0040, + ACC_TRANSIENT = 0x0080, + ACC_NATIVE = 0x0100, + ACC_INTERFACE = 0x0200, + ACC_ABSTRACT = 0x0400, + ACC_STRICT = 0x0800, + ACC_SYNTHETIC = 0x1000, + ACC_ANNOTATION = 0x2000, + ACC_ENUM = 0x4000, + // aliases: + ACC_SUPER = ACC_SYNCHRONIZED, + ACC_BRIDGE = ACC_VOLATILE, + ACC_VARARGS = ACC_TRANSIENT; + + /** + * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. + */ + static final byte + REF_NONE = 0, // null value + REF_getField = 1, + REF_getStatic = 2, + REF_putField = 3, + REF_putStatic = 4, + REF_invokeVirtual = 5, + REF_invokeStatic = 6, + REF_invokeSpecial = 7, + REF_newInvokeSpecial = 8, + REF_invokeInterface = 9, + REF_LIMIT = 10; + } + + static boolean refKindIsValid(int refKind) { + return (refKind > REF_NONE && refKind < REF_LIMIT); + } + static boolean refKindIsField(byte refKind) { + assert(refKindIsValid(refKind)); + return (refKind <= REF_putStatic); + } + static boolean refKindIsGetter(byte refKind) { + assert(refKindIsValid(refKind)); + return (refKind <= REF_getStatic); + } + static boolean refKindIsSetter(byte refKind) { + return refKindIsField(refKind) && !refKindIsGetter(refKind); + } + static boolean refKindIsMethod(byte refKind) { + return !refKindIsField(refKind) && (refKind != REF_newInvokeSpecial); + } + static boolean refKindIsConstructor(byte refKind) { + return (refKind == REF_newInvokeSpecial); + } + static boolean refKindHasReceiver(byte refKind) { + assert(refKindIsValid(refKind)); + return (refKind & 1) != 0; + } + static boolean refKindIsStatic(byte refKind) { + return !refKindHasReceiver(refKind) && (refKind != REF_newInvokeSpecial); + } + static boolean refKindDoesDispatch(byte refKind) { + assert(refKindIsValid(refKind)); + return (refKind == REF_invokeVirtual || + refKind == REF_invokeInterface); + } + static { + final int HR_MASK = ((1 << REF_getField) | + (1 << REF_putField) | + (1 << REF_invokeVirtual) | + (1 << REF_invokeSpecial) | + (1 << REF_invokeInterface) + ); + for (byte refKind = REF_NONE+1; refKind < REF_LIMIT; refKind++) { + assert(refKindHasReceiver(refKind) == (((1< caller = (Class)callerObj; + String name = nameObj.toString().intern(); + MethodType type = (MethodType)typeObj; + CallSite callSite = CallSite.makeSite(bootstrapMethod, + name, + type, + staticArguments, + caller); + if (callSite instanceof ConstantCallSite) { + appendixResult[0] = callSite.dynamicInvoker(); + return Invokers.linkToTargetMethod(type); + } else { + appendixResult[0] = callSite; + return Invokers.linkToCallSiteMethod(type); + } + } + + /** + * The JVM wants a pointer to a MethodType. Oblige it by finding or creating one. + */ + static MethodType findMethodHandleType(Class rtype, Class[] ptypes) { + return MethodType.makeImpl(rtype, ptypes, true); + } + + /** + * The JVM wants to link a call site that requires a dynamic type check. + * Name is a type-checking invoker, invokeExact or invoke. + * Return a JVM method (MemberName) to handle the invoking. + * The method assumes the following arguments on the stack: + * 0: the method handle being invoked + * 1-N: the arguments to the method handle invocation + * N+1: an optional, implicitly added argument (typically the given MethodType) + *

+ * The nominal method at such a call site is an instance of + * a signature-polymorphic method (see @PolymorphicSignature). + * Such method instances are user-visible entities which are + * "split" from the generic placeholder method in {@code MethodHandle}. + * (Note that the placeholder method is not identical with any of + * its instances. If invoked reflectively, is guaranteed to throw an + * {@code UnsupportedOperationException}.) + * If the signature-polymorphic method instance is ever reified, + * it appears as a "copy" of the original placeholder + * (a native final member of {@code MethodHandle}) except + * that its type descriptor has shape required by the instance, + * and the method instance is not varargs. + * The method instance is also marked synthetic, since the + * method (by definition) does not appear in Java source code. + *

+ * The JVM is allowed to reify this method as instance metadata. + * For example, {@code invokeBasic} is always reified. + * But the JVM may instead call {@code linkMethod}. + * If the result is an * ordered pair of a {@code (method, appendix)}, + * the method gets all the arguments (0..N inclusive) + * plus the appendix (N+1), and uses the appendix to complete the call. + * In this way, one reusable method (called a "linker method") + * can perform the function of any number of polymorphic instance + * methods. + *

+ * Linker methods are allowed to be weakly typed, with any or + * all references rewritten to {@code Object} and any primitives + * (except {@code long}/{@code float}/{@code double}) + * rewritten to {@code int}. + * A linker method is trusted to return a strongly typed result, + * according to the specific method type descriptor of the + * signature-polymorphic instance it is emulating. + * This can involve (as necessary) a dynamic check using + * data extracted from the appendix argument. + *

+ * The JVM does not inspect the appendix, other than to pass + * it verbatim to the linker method at every call. + * This means that the JDK runtime has wide latitude + * for choosing the shape of each linker method and its + * corresponding appendix. + * Linker methods should be generated from {@code LambdaForm}s + * so that they do not become visible on stack traces. + *

+ * The {@code linkMethod} call is free to omit the appendix + * (returning null) and instead emulate the required function + * completely in the linker method. + * As a corner case, if N==255, no appendix is possible. + * In this case, the method returned must be custom-generated to + * to perform any needed type checking. + *

+ * If the JVM does not reify a method at a call site, but instead + * calls {@code linkMethod}, the corresponding call represented + * in the bytecodes may mention a valid method which is not + * representable with a {@code MemberName}. + * Therefore, use cases for {@code linkMethod} tend to correspond to + * special cases in reflective code such as {@code findVirtual} + * or {@code revealDirect}. + */ + static MemberName linkMethod(Class callerClass, int refKind, + Class defc, String name, Object type, + Object[] appendixResult) { + if (!TRACE_METHOD_LINKAGE) + return linkMethodImpl(callerClass, refKind, defc, name, type, appendixResult); + return linkMethodTracing(callerClass, refKind, defc, name, type, appendixResult); + } + static MemberName linkMethodImpl(Class callerClass, int refKind, + Class defc, String name, Object type, + Object[] appendixResult) { + try { + if (defc == MethodHandle.class && refKind == REF_invokeVirtual) { + return Invokers.methodHandleInvokeLinkerMethod(name, fixMethodType(callerClass, type), appendixResult); + } + } catch (Throwable ex) { + if (ex instanceof LinkageError) + throw (LinkageError) ex; + else + throw new LinkageError(ex.getMessage(), ex); + } + throw new LinkageError("no such method "+defc.getName()+"."+name+type); + } + private static MethodType fixMethodType(Class callerClass, Object type) { + if (type instanceof MethodType) + return (MethodType) type; + else + return MethodType.fromMethodDescriptorString((String)type, callerClass.getClassLoader()); + } + // Tracing logic: + static MemberName linkMethodTracing(Class callerClass, int refKind, + Class defc, String name, Object type, + Object[] appendixResult) { + System.out.println("linkMethod "+defc.getName()+"."+ + name+type+"/"+Integer.toHexString(refKind)); + try { + MemberName res = linkMethodImpl(callerClass, refKind, defc, name, type, appendixResult); + System.out.println("linkMethod => "+res+" + "+appendixResult[0]); + return res; + } catch (Throwable ex) { + System.out.println("linkMethod => throw "+ex); + throw ex; + } + } + + + /** + * The JVM is resolving a CONSTANT_MethodHandle CP entry. And it wants our help. + * It will make an up-call to this method. (Do not change the name or signature.) + * The type argument is a Class for field requests and a MethodType for non-fields. + *

+ * Recent versions of the JVM may also pass a resolved MemberName for the type. + * In that case, the name is ignored and may be null. + */ + static MethodHandle linkMethodHandleConstant(Class callerClass, int refKind, + Class defc, String name, Object type) { + try { + Lookup lookup = IMPL_LOOKUP.in(callerClass); + assert(refKindIsValid(refKind)); + return lookup.linkMethodHandleConstant((byte) refKind, defc, name, type); + } catch (IllegalAccessException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof AbstractMethodError) { + throw (AbstractMethodError) cause; + } else { + Error err = new IllegalAccessError(ex.getMessage()); + throw initCauseFrom(err, ex); + } + } catch (NoSuchMethodException ex) { + Error err = new NoSuchMethodError(ex.getMessage()); + throw initCauseFrom(err, ex); + } catch (NoSuchFieldException ex) { + Error err = new NoSuchFieldError(ex.getMessage()); + throw initCauseFrom(err, ex); + } catch (ReflectiveOperationException ex) { + Error err = new IncompatibleClassChangeError(); + throw initCauseFrom(err, ex); + } + } + + /** + * Use best possible cause for err.initCause(), substituting the + * cause for err itself if the cause has the same (or better) type. + */ + static private Error initCauseFrom(Error err, Exception ex) { + Throwable th = ex.getCause(); + if (err.getClass().isInstance(th)) + return (Error) th; + err.initCause(th == null ? ex : th); + return err; + } + + /** + * Is this method a caller-sensitive method? + * I.e., does it call Reflection.getCallerClass or a similer method + * to ask about the identity of its caller? + */ + static boolean isCallerSensitive(MemberName mem) { + if (!mem.isInvocable()) return false; // fields are not caller sensitive + + return mem.isCallerSensitive() || canBeCalledVirtual(mem); + } + + static boolean canBeCalledVirtual(MemberName mem) { + assert(mem.isInvocable()); + Class defc = mem.getDeclaringClass(); + switch (mem.getName()) { + case "checkMemberAccess": + return canBeCalledVirtual(mem, java.lang.SecurityManager.class); + case "getContextClassLoader": + return canBeCalledVirtual(mem, java.lang.Thread.class); + } + return false; + } + + static boolean canBeCalledVirtual(MemberName symbolicRef, Class definingClass) { + Class symbolicRefClass = symbolicRef.getDeclaringClass(); + if (symbolicRefClass == definingClass) return true; + if (symbolicRef.isStatic() || symbolicRef.isPrivate()) return false; + return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef + symbolicRefClass.isInterface()); // Mdef implements Msym + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleProxies.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleProxies.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,321 @@ +/* + * 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.lang.reflect.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import sun.invoke.WrapperInstance; +import java.util.ArrayList; +import sun.reflect.CallerSensitive; +import sun.reflect.Reflection; +import sun.reflect.misc.ReflectUtil; + +/** + * This class consists exclusively of static methods that help adapt + * method handles to other JVM types, such as interfaces. + */ +public class MethodHandleProxies { + + private MethodHandleProxies() { } // do not instantiate + + /** + * Produces an instance of the given single-method interface which redirects + * its calls to the given method handle. + *

+ * A single-method interface is an interface which declares a uniquely named method. + * When determining the uniquely named method of a single-method interface, + * the public {@code Object} methods ({@code toString}, {@code equals}, {@code hashCode}) + * are disregarded. For example, {@link java.util.Comparator} is a single-method interface, + * even though it re-declares the {@code Object.equals} method. + *

+ * The interface must be public. No additional access checks are performed. + *

+ * The resulting instance of the required type will respond to + * invocation of the type's uniquely named method by calling + * the given target on the incoming arguments, + * and returning or throwing whatever the target + * returns or throws. The invocation will be as if by + * {@code target.invoke}. + * The target's type will be checked before the + * instance is created, as if by a call to {@code asType}, + * which may result in a {@code WrongMethodTypeException}. + *

+ * The uniquely named method is allowed to be multiply declared, + * with distinct type descriptors. (E.g., it can be overloaded, + * or can possess bridge methods.) All such declarations are + * connected directly to the target method handle. + * Argument and return types are adjusted by {@code asType} + * for each individual declaration. + *

+ * The wrapper instance will implement the requested interface + * and its super-types, but no other single-method interfaces. + * This means that the instance will not unexpectedly + * pass an {@code instanceof} test for any unrequested type. + *

+ * Implementation Note: + * Therefore, each instance must implement a unique single-method interface. + * Implementations may not bundle together + * multiple single-method interfaces onto single implementation classes + * in the style of {@link java.awt.AWTEventMulticaster}. + *

+ * The method handle may throw an undeclared exception, + * which means any checked exception (or other checked throwable) + * not declared by the requested type's single abstract method. + * If this happens, the throwable will be wrapped in an instance of + * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException} + * and thrown in that wrapped form. + *

+ * Like {@link java.lang.Integer#valueOf Integer.valueOf}, + * {@code asInterfaceInstance} is a factory method whose results are defined + * by their behavior. + * It is not guaranteed to return a new instance for every call. + *

+ * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods} + * and other corner cases, the interface may also have several abstract methods + * with the same name but having distinct descriptors (types of returns and parameters). + * In this case, all the methods are bound in common to the one given target. + * The type check and effective {@code asType} conversion is applied to each + * method type descriptor, and all abstract methods are bound to the target in common. + * Beyond this type check, no further checks are made to determine that the + * abstract methods are related in any way. + *

+ * Future versions of this API may accept additional types, + * such as abstract classes with single abstract methods. + * Future versions of this API may also equip wrapper instances + * with one or more additional public "marker" interfaces. + *

+ * If a security manager is installed, this method is caller sensitive. + * During any invocation of the target method handle via the returned wrapper, + * the original creator of the wrapper (the caller) will be visible + * to context checks requested by the security manager. + * + * @param the desired type of the wrapper, a single-method interface + * @param intfc a class object representing {@code T} + * @param target the method handle to invoke from the wrapper + * @return a correctly-typed wrapper for the given target + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the {@code intfc} is not a + * valid argument to this method + * @throws WrongMethodTypeException if the target cannot + * be converted to the type required by the requested interface + */ + // Other notes to implementors: + //

+ // No stable mapping is promised between the single-method interface and + // the implementation class C. Over time, several implementation + // classes might be used for the same type. + //

+ // If the implementation is able + // to prove that a wrapper of the required type + // has already been created for a given + // method handle, or for another method handle with the + // same behavior, the implementation may return that wrapper in place of + // a new wrapper. + //

+ // This method is designed to apply to common use cases + // where a single method handle must interoperate with + // an interface that implements a function-like + // API. Additional variations, such as single-abstract-method classes with + // private constructors, or interfaces with multiple but related + // entry points, must be covered by hand-written or automatically + // generated adapter classes. + // + @CallerSensitive + public static + T asInterfaceInstance(final Class intfc, final MethodHandle target) { + if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers())) + throw new IllegalArgumentException("not a public interface: "+intfc.getName()); + final MethodHandle mh; + if (System.getSecurityManager() != null) { + final Class caller = Reflection.getCallerClass(); + final ClassLoader ccl = caller != null ? caller.getClassLoader() : null; + ReflectUtil.checkProxyPackageAccess(ccl, intfc); + mh = ccl != null ? bindCaller(target, caller) : target; + } else { + mh = target; + } + ClassLoader proxyLoader = intfc.getClassLoader(); + if (proxyLoader == null) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP + proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader(); + } + final Method[] methods = getSingleNameMethods(intfc); + if (methods == null) + throw new IllegalArgumentException("not a single-method interface: "+intfc.getName()); + final MethodHandle[] vaTargets = new MethodHandle[methods.length]; + for (int i = 0; i < methods.length; i++) { + Method sm = methods[i]; + MethodType smMT = MethodType.methodType(sm.getReturnType(), sm.getParameterTypes()); + MethodHandle checkTarget = mh.asType(smMT); // make throw WMT + checkTarget = checkTarget.asType(checkTarget.type().changeReturnType(Object.class)); + vaTargets[i] = checkTarget.asSpreader(Object[].class, smMT.parameterCount()); + } + final InvocationHandler ih = new InvocationHandler() { + private Object getArg(String name) { + if ((Object)name == "getWrapperInstanceTarget") return target; + if ((Object)name == "getWrapperInstanceType") return intfc; + throw new AssertionError(); + } + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + for (int i = 0; i < methods.length; i++) { + if (method.equals(methods[i])) + return vaTargets[i].invokeExact(args); + } + if (method.getDeclaringClass() == WrapperInstance.class) + return getArg(method.getName()); + if (isObjectMethod(method)) + return callObjectMethod(proxy, method, args); + throw new InternalError("bad proxy method: "+method); + } + }; + + final Object proxy; + if (System.getSecurityManager() != null) { + // sun.invoke.WrapperInstance is a restricted interface not accessible + // by any non-null class loader. + final ClassLoader loader = proxyLoader; + proxy = AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return Proxy.newProxyInstance( + loader, + new Class[]{ intfc, WrapperInstance.class }, + ih); + } + }); + } else { + proxy = Proxy.newProxyInstance(proxyLoader, + new Class[]{ intfc, WrapperInstance.class }, + ih); + } + return intfc.cast(proxy); + } + + private static MethodHandle bindCaller(MethodHandle target, Class hostClass) { + MethodHandle cbmh = MethodHandleImpl.bindCaller(target, hostClass); + if (target.isVarargsCollector()) { + MethodType type = cbmh.type(); + int arity = type.parameterCount(); + return cbmh.asVarargsCollector(type.parameterType(arity-1)); + } + return cbmh; + } + + /** + * Determines if the given object was produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. + * @param x any reference + * @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance} + */ + public static + boolean isWrapperInstance(Object x) { + return x instanceof WrapperInstance; + } + + private static WrapperInstance asWrapperInstance(Object x) { + try { + if (x != null) + return (WrapperInstance) x; + } catch (ClassCastException ex) { + } + throw new IllegalArgumentException("not a wrapper instance"); + } + + /** + * Produces or recovers a target method handle which is behaviorally + * equivalent to the unique method of this wrapper instance. + * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. + * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}. + * @param x any reference + * @return a method handle implementing the unique method + * @throws IllegalArgumentException if the reference x is not to a wrapper instance + */ + public static + MethodHandle wrapperInstanceTarget(Object x) { + return asWrapperInstance(x).getWrapperInstanceTarget(); + } + + /** + * Recovers the unique single-method interface type for which this wrapper instance was created. + * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}. + * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}. + * @param x any reference + * @return the single-method interface type for which the wrapper was created + * @throws IllegalArgumentException if the reference x is not to a wrapper instance + */ + public static + Class wrapperInstanceType(Object x) { + return asWrapperInstance(x).getWrapperInstanceType(); + } + + private static + boolean isObjectMethod(Method m) { + switch (m.getName()) { + case "toString": + return (m.getReturnType() == String.class + && m.getParameterTypes().length == 0); + case "hashCode": + return (m.getReturnType() == int.class + && m.getParameterTypes().length == 0); + case "equals": + return (m.getReturnType() == boolean.class + && m.getParameterTypes().length == 1 + && m.getParameterTypes()[0] == Object.class); + } + return false; + } + + private static + Object callObjectMethod(Object self, Method m, Object[] args) { + assert(isObjectMethod(m)) : m; + switch (m.getName()) { + case "toString": + return self.getClass().getName() + "@" + Integer.toHexString(self.hashCode()); + case "hashCode": + return System.identityHashCode(self); + case "equals": + return (self == args[0]); + } + return null; + } + + private static + Method[] getSingleNameMethods(Class intfc) { + ArrayList methods = new ArrayList(); + String uniqueName = null; + for (Method m : intfc.getMethods()) { + if (isObjectMethod(m)) continue; + if (!Modifier.isAbstract(m.getModifiers())) continue; + String mname = m.getName(); + if (uniqueName == null) + uniqueName = mname; + else if (!uniqueName.equals(mname)) + return null; // too many abstract methods + methods.add(m); + } + if (uniqueName == null) return null; + return methods.toArray(new Method[methods.size()]); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleStatics.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandleStatics.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011, 2012, 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 sun.misc.Unsafe; + +/** + * This class consists exclusively of static names internal to the + * method handle implementation. + * Usage: {@code import static java.lang.invoke.MethodHandleStatics.*} + * @author John Rose, JSR 292 EG + */ +/*non-public*/ class MethodHandleStatics { + + private MethodHandleStatics() { } // do not instantiate + + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + static final boolean DEBUG_METHOD_HANDLE_NAMES; + static final boolean DUMP_CLASS_FILES; + static final boolean TRACE_INTERPRETER; + static final boolean TRACE_METHOD_LINKAGE; + static final Integer COMPILE_THRESHOLD; + static { + final Object[] values = { false, false, false, false, null }; + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); + values[1] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DUMP_CLASS_FILES"); + values[2] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_INTERPRETER"); + values[3] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE"); + values[4] = Integer.getInteger("java.lang.invoke.MethodHandle.COMPILE_THRESHOLD"); + return null; + } + }); + DEBUG_METHOD_HANDLE_NAMES = (Boolean) values[0]; + DUMP_CLASS_FILES = (Boolean) values[1]; + TRACE_INTERPRETER = (Boolean) values[2]; + TRACE_METHOD_LINKAGE = (Boolean) values[3]; + COMPILE_THRESHOLD = (Integer) values[4]; + } + + /*non-public*/ static String getNameString(MethodHandle target, MethodType type) { + if (type == null) + type = target.type(); + MemberName name = null; + if (target != null) + name = target.internalMemberName(); + if (name == null) + return "invoke" + type; + return name.getName() + type; + } + + /*non-public*/ static String getNameString(MethodHandle target, MethodHandle typeHolder) { + return getNameString(target, typeHolder == null ? (MethodType) null : typeHolder.type()); + } + + /*non-public*/ static String getNameString(MethodHandle target) { + return getNameString(target, (MethodType) null); + } + + /*non-public*/ static String addTypeString(Object obj, MethodHandle target) { + String str = String.valueOf(obj); + if (target == null) return str; + int paren = str.indexOf('('); + if (paren >= 0) str = str.substring(0, paren); + return str + target.type(); + } + + // handy shared exception makers (they simplify the common case code) + /*non-public*/ static InternalError newInternalError(String message, Throwable cause) { + return new InternalError(message, cause); + } + /*non-public*/ static InternalError newInternalError(Throwable cause) { + return new InternalError(cause); + } + /*non-public*/ static RuntimeException newIllegalStateException(String message) { + return new IllegalStateException(message); + } + /*non-public*/ static RuntimeException newIllegalStateException(String message, Object obj) { + return new IllegalStateException(message(message, obj)); + } + /*non-public*/ static RuntimeException newIllegalArgumentException(String message) { + return new IllegalArgumentException(message); + } + /*non-public*/ static RuntimeException newIllegalArgumentException(String message, Object obj) { + return new IllegalArgumentException(message(message, obj)); + } + /*non-public*/ static RuntimeException newIllegalArgumentException(String message, Object obj, Object obj2) { + return new IllegalArgumentException(message(message, obj, obj2)); + } + /*non-public*/ static Error uncaughtException(Throwable ex) { + throw newInternalError("uncaught exception", ex); + } + static Error NYI() { + throw new AssertionError("NYI"); + } + private static String message(String message, Object obj) { + if (obj != null) message = message + ": " + obj; + return message; + } + private static String message(String message, Object obj, Object obj2) { + if (obj != null || obj2 != null) message = message + ": " + obj + ", " + obj2; + return message; + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodHandles.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodHandles.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,2848 @@ +/* + * 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.lang.reflect.*; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyAccess; +import sun.invoke.util.Wrapper; +import sun.reflect.CallerSensitive; +import sun.reflect.Reflection; +import sun.reflect.misc.ReflectUtil; +import sun.security.util.SecurityConstants; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import java.util.concurrent.ConcurrentHashMap; +import sun.security.util.SecurityConstants; + +/** + * This class consists exclusively of static methods that operate on or return + * method handles. They fall into several categories: + *
    + *
  • Lookup methods which help create method handles for methods and fields. + *
  • Combinator methods, which combine or transform pre-existing method handles into new ones. + *
  • Other factory methods to create method handles that emulate other common JVM operations or control flow patterns. + *
+ *

+ * @author John Rose, JSR 292 EG + * @since 1.7 + */ +public class MethodHandles { + + private MethodHandles() { } // do not instantiate + + private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory(); + static { MethodHandleImpl.initStatics(); } + // See IMPL_LOOKUP below. + + //// Method handle creation from ordinary methods. + + /** + * Returns a {@link Lookup lookup object} with + * full capabilities to emulate all supported bytecode behaviors of the caller. + * These capabilities include private access to the caller. + * Factory methods on the lookup object can create + * direct method handles + * for any member that the caller has access to via bytecodes, + * including protected and private fields and methods. + * This lookup object is a capability which may be delegated to trusted agents. + * Do not store it in place where untrusted code can access it. + *

+ * This method is caller sensitive, which means that it may return different + * values to different callers. + *

+ * For any given caller class {@code C}, the lookup object returned by this call + * has equivalent capabilities to any lookup object + * supplied by the JVM to the bootstrap method of an + * invokedynamic instruction + * executing in the same caller class {@code C}. + * @return a lookup object for the caller of this method, with private access + */ + @CallerSensitive + public static Lookup lookup() { + return new Lookup(Reflection.getCallerClass()); + } + + /** + * Returns a {@link Lookup lookup object} which is trusted minimally. + * It can only be used to create method handles to + * publicly accessible fields and methods. + *

+ * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class} + * of this lookup object will be {@link java.lang.Object}. + * + *

+ * Discussion: + * The lookup class can be changed to any other class {@code C} using an expression of the form + * {@link Lookup#in publicLookup().in(C.class)}. + * Since all classes have equal access to public names, + * such a change would confer no new access rights. + * A public lookup object is always subject to + * security manager checks. + * Also, it cannot access + * caller sensitive methods. + * @return a lookup object which is trusted minimally + */ + public static Lookup publicLookup() { + return Lookup.PUBLIC_LOOKUP; + } + + /** + * Performs an unchecked "crack" of a + * direct method handle. + * The result is as if the user had obtained a lookup object capable enough + * to crack the target method handle, called + * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect} + * on the target to obtain its symbolic reference, and then called + * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs} + * to resolve the symbolic reference to a member. + *

+ * If there is a security manager, its {@code checkPermission} method + * is called with a {@code ReflectPermission("suppressAccessChecks")} permission. + * @param the desired type of the result, either {@link Member} or a subtype + * @param target a direct method handle to crack into symbolic reference components + * @param expected a class object representing the desired result type {@code T} + * @return a reference to the method, constructor, or field object + * @exception SecurityException if the caller is not privileged to call {@code setAccessible} + * @exception NullPointerException if either argument is {@code null} + * @exception IllegalArgumentException if the target is not a direct method handle + * @exception ClassCastException if the member is not of the expected type + * @since 1.8 + */ + public static T + reflectAs(Class expected, MethodHandle target) { + SecurityManager smgr = System.getSecurityManager(); + if (smgr != null) smgr.checkPermission(ACCESS_PERMISSION); + Lookup lookup = Lookup.IMPL_LOOKUP; // use maximally privileged lookup + return lookup.revealDirect(target).reflectAs(expected, lookup); + } + // Copied from AccessibleObject, as used by Method.setAccessible, etc.: + static final private java.security.Permission ACCESS_PERMISSION = + new ReflectPermission("suppressAccessChecks"); + + /** + * A lookup object is a factory for creating method handles, + * when the creation requires access checking. + * Method handles do not perform + * access checks when they are called, but rather when they are created. + * Therefore, method handle access + * restrictions must be enforced when a method handle is created. + * The caller class against which those restrictions are enforced + * is known as the {@linkplain #lookupClass lookup class}. + *

+ * A lookup class which needs to create method handles will call + * {@link MethodHandles#lookup MethodHandles.lookup} to create a factory for itself. + * When the {@code Lookup} factory object is created, the identity of the lookup class is + * determined, and securely stored in the {@code Lookup} object. + * The lookup class (or its delegates) may then use factory methods + * on the {@code Lookup} object to create method handles for access-checked members. + * This includes all methods, constructors, and fields which are allowed to the lookup class, + * even private ones. + * + *

Lookup Factory Methods

+ * The factory methods on a {@code Lookup} object correspond to all major + * use cases for methods, constructors, and fields. + * Each method handle created by a factory method is the functional + * equivalent of a particular bytecode behavior. + * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.) + * Here is a summary of the correspondence between these factory methods and + * the behavior the resulting method handles: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
lookup expressionmemberbytecode behavior
{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}{@code FT f;}{@code (T) this.f;}
{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}{@code static}
{@code FT f;}
{@code (T) C.f;}
{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}{@code FT f;}{@code this.f = x;}
{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}{@code static}
{@code FT f;}
{@code C.f = arg;}
{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}{@code T m(A*);}{@code (T) this.m(arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}{@code static}
{@code T m(A*);}
{@code (T) C.m(arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}{@code T m(A*);}{@code (T) super.m(arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}{@code C(A*);}{@code new C(arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}({@code static})?
{@code FT f;}
{@code (FT) aField.get(thisOrNull);}
{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}({@code static})?
{@code FT f;}
{@code aField.set(thisOrNull, arg);}
{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}({@code static})?
{@code T m(A*);}
{@code (T) aMethod.invoke(thisOrNull, arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}{@code C(A*);}{@code (C) aConstructor.newInstance(arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}({@code static})?
{@code T m(A*);}
{@code (T) aMethod.invoke(thisOrNull, arg*);}
+ * + * Here, the type {@code C} is the class or interface being searched for a member, + * documented as a parameter named {@code refc} in the lookup methods. + * The method type {@code MT} is composed from the return type {@code T} + * and the sequence of argument types {@code A*}. + * The constructor also has a sequence of argument types {@code A*} and + * is deemed to return the newly-created object of type {@code C}. + * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}. + * The formal parameter {@code this} stands for the self-reference of type {@code C}; + * if it is present, it is always the leading argument to the method handle invocation. + * (In the case of some {@code protected} members, {@code this} may be + * restricted in type to the lookup class; see below.) + * The name {@code arg} stands for all the other method handle arguments. + * In the code examples for the Core Reflection API, the name {@code thisOrNull} + * stands for a null reference if the accessed method or field is static, + * and {@code this} otherwise. + * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand + * for reflective objects corresponding to the given members. + *

+ * In cases where the given member is of variable arity (i.e., a method or constructor) + * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}. + * In all other cases, the returned method handle will be of fixed arity. + *

+ * Discussion: + * The equivalence between looked-up method handles and underlying + * class members and bytecode behaviors + * can break down in a few ways: + *

    + *
  • If {@code C} is not symbolically accessible from the lookup class's loader, + * the lookup can still succeed, even when there is no equivalent + * Java expression or bytecoded constant. + *
  • Likewise, if {@code T} or {@code MT} + * is not symbolically accessible from the lookup class's loader, + * the lookup can still succeed. + * For example, lookups for {@code MethodHandle.invokeExact} and + * {@code MethodHandle.invoke} will always succeed, regardless of requested type. + *
  • If there is a security manager installed, it can forbid the lookup + * on various grounds (see below). + * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle} + * constant is not subject to security manager checks. + *
  • If the looked-up method has a + * very large arity, + * the method handle creation may fail, due to the method handle + * type having too many parameters. + *
+ * + *

Access checking

+ * Access checks are applied in the factory methods of {@code Lookup}, + * when a method handle is created. + * This is a key difference from the Core Reflection API, since + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * performs access checking against every caller, on every call. + *

+ * All access checks start from a {@code Lookup} object, which + * compares its recorded lookup class against all requests to + * create method handles. + * A single {@code Lookup} object can be used to create any number + * of access-checked method handles, all checked against a single + * lookup class. + *

+ * A {@code Lookup} object can be shared with other trusted code, + * such as a metaobject protocol. + * A shared {@code Lookup} object delegates the capability + * to create method handles on private members of the lookup class. + * Even if privileged code uses the {@code Lookup} object, + * the access checking is confined to the privileges of the + * original lookup class. + *

+ * A lookup can fail, because + * the containing class is not accessible to the lookup class, or + * because the desired class member is missing, or because the + * desired class member is not accessible to the lookup class, or + * because the lookup object is not trusted enough to access the member. + * In any of these cases, a {@code ReflectiveOperationException} will be + * thrown from the attempted lookup. The exact class will be one of + * the following: + *

    + *
  • NoSuchMethodException — if a method is requested but does not exist + *
  • NoSuchFieldException — if a field is requested but does not exist + *
  • IllegalAccessException — if the member exists but an access check fails + *
+ *

+ * In general, the conditions under which a method handle may be + * looked up for a method {@code M} are no more restrictive than the conditions + * under which the lookup class could have compiled, verified, and resolved a call to {@code M}. + * Where the JVM would raise exceptions like {@code NoSuchMethodError}, + * a method handle lookup will generally raise a corresponding + * checked exception, such as {@code NoSuchMethodException}. + * And the effect of invoking the method handle resulting from the lookup + * is exactly equivalent + * to executing the compiled, verified, and resolved call to {@code M}. + * The same point is true of fields and constructors. + *

+ * Discussion: + * Access checks only apply to named and reflected methods, + * constructors, and fields. + * Other method handle creation methods, such as + * {@link MethodHandle#asType MethodHandle.asType}, + * do not require any access checks, and are used + * independently of any {@code Lookup} object. + *

+ * If the desired member is {@code protected}, the usual JVM rules apply, + * including the requirement that the lookup class must be either be in the + * same package as the desired member, or must inherit that member. + * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.) + * In addition, if the desired member is a non-static field or method + * in a different package, the resulting method handle may only be applied + * to objects of the lookup class or one of its subclasses. + * This requirement is enforced by narrowing the type of the leading + * {@code this} parameter from {@code C} + * (which will necessarily be a superclass of the lookup class) + * to the lookup class itself. + *

+ * The JVM imposes a similar requirement on {@code invokespecial} instruction, + * that the receiver argument must match both the resolved method and + * the current class. Again, this requirement is enforced by narrowing the + * type of the leading parameter to the resulting method handle. + * (See the Java Virtual Machine Specification, section 4.10.1.9.) + *

+ * The JVM represents constructors and static initializer blocks as internal methods + * with special names ({@code ""} and {@code ""}). + * The internal syntax of invocation instructions allows them to refer to such internal + * methods as if they were normal methods, but the JVM bytecode verifier rejects them. + * A lookup of such an internal method will produce a {@code NoSuchMethodException}. + *

+ * In some cases, access between nested classes is obtained by the Java compiler by creating + * an wrapper method to access a private method of another class + * in the same top-level declaration. + * For example, a nested class {@code C.D} + * can access private members within other related classes such as + * {@code C}, {@code C.D.E}, or {@code C.B}, + * but the Java compiler may need to generate wrapper methods in + * those related classes. In such cases, a {@code Lookup} object on + * {@code C.E} would be unable to those private members. + * A workaround for this limitation is the {@link Lookup#in Lookup.in} method, + * which can transform a lookup on {@code C.E} into one on any of those other + * classes, without special elevation of privilege. + *

+ * The accesses permitted to a given lookup object may be limited, + * according to its set of {@link #lookupModes lookupModes}, + * to a subset of members normally accessible to the lookup class. + * For example, the {@link MethodHandles#publicLookup publicLookup} + * method produces a lookup object which is only allowed to access + * public members in public classes. + * The caller sensitive method {@link MethodHandles#lookup lookup} + * produces a lookup object with full capabilities relative to + * its caller class, to emulate all supported bytecode behaviors. + * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object + * with fewer access modes than the original lookup object. + * + *

+ * + * Discussion of private access: + * We say that a lookup has private access + * if its {@linkplain #lookupModes lookup modes} + * include the possibility of accessing {@code private} members. + * As documented in the relevant methods elsewhere, + * only lookups with private access possess the following capabilities: + *

    + *
  • access private fields, methods, and constructors of the lookup class + *
  • create method handles which invoke caller sensitive methods, + * such as {@code Class.forName} + *
  • create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions + *
  • avoid package access checks + * for classes accessible to the lookup class + *
  • create {@link Lookup#in delegated lookup objects} which have private access to other classes + * within the same package member + *
+ *

+ * Each of these permissions is a consequence of the fact that a lookup object + * with private access can be securely traced back to an originating class, + * whose bytecode behaviors and Java language access permissions + * can be reliably determined and emulated by method handles. + * + *

Security manager interactions

+ * Although bytecode instructions can only refer to classes in + * a related class loader, this API can search for methods in any + * class, as long as a reference to its {@code Class} object is + * available. Such cross-loader references are also possible with the + * Core Reflection API, and are impossible to bytecode instructions + * such as {@code invokestatic} or {@code getfield}. + * There is a {@linkplain java.lang.SecurityManager security manager API} + * to allow applications to check such cross-loader references. + * These checks apply to both the {@code MethodHandles.Lookup} API + * and the Core Reflection API + * (as found on {@link java.lang.Class Class}). + *

+ * If a security manager is present, member lookups are subject to + * additional checks. + * From one to three calls are made to the security manager. + * Any of these calls can refuse access by throwing a + * {@link java.lang.SecurityException SecurityException}. + * Define {@code smgr} as the security manager, + * {@code lookc} as the lookup class of the current lookup object, + * {@code refc} as the containing class in which the member + * is being sought, and {@code defc} as the class in which the + * member is actually defined. + * The value {@code lookc} is defined as not present + * if the current lookup object does not have + * private access. + * The calls are made according to the following rules: + *

    + *
  • Step 1: + * If {@code lookc} is not present, or if its class loader is not + * the same as or an ancestor of the class loader of {@code refc}, + * then {@link SecurityManager#checkPackageAccess + * smgr.checkPackageAccess(refcPkg)} is called, + * where {@code refcPkg} is the package of {@code refc}. + *
  • Step 2: + * If the retrieved member is not public and + * {@code lookc} is not present, then + * {@link SecurityManager#checkPermission smgr.checkPermission} + * with {@code RuntimePermission("accessDeclaredMembers")} is called. + *
  • Step 3: + * If the retrieved member is not public, + * and if {@code lookc} is not present, + * and if {@code defc} and {@code refc} are different, + * then {@link SecurityManager#checkPackageAccess + * smgr.checkPackageAccess(defcPkg)} is called, + * where {@code defcPkg} is the package of {@code defc}. + *
+ * Security checks are performed after other access checks have passed. + * Therefore, the above rules presuppose a member that is public, + * or else that is being accessed from a lookup class that has + * rights to access the member. + * + *

Caller sensitive methods

+ * A small number of Java methods have a special property called caller sensitivity. + * A caller-sensitive method can behave differently depending on the + * identity of its immediate caller. + *

+ * If a method handle for a caller-sensitive method is requested, + * the general rules for bytecode behaviors apply, + * but they take account of the lookup class in a special way. + * The resulting method handle behaves as if it were called + * from an instruction contained in the lookup class, + * so that the caller-sensitive method detects the lookup class. + * (By contrast, the invoker of the method handle is disregarded.) + * Thus, in the case of caller-sensitive methods, + * different lookup classes may give rise to + * differently behaving method handles. + *

+ * In cases where the lookup object is + * {@link MethodHandles#publicLookup() publicLookup()}, + * or some other lookup object without + * private access, + * the lookup class is disregarded. + * In such cases, no caller-sensitive method handle can be created, + * access is forbidden, and the lookup fails with an + * {@code IllegalAccessException}. + *

+ * Discussion: + * For example, the caller-sensitive method + * {@link java.lang.Class#forName(String) Class.forName(x)} + * can return varying classes or throw varying exceptions, + * depending on the class loader of the class that calls it. + * A public lookup of {@code Class.forName} will fail, because + * there is no reasonable way to determine its bytecode behavior. + *

+ * If an application caches method handles for broad sharing, + * it should use {@code publicLookup()} to create them. + * If there is a lookup of {@code Class.forName}, it will fail, + * and the application must take appropriate action in that case. + * It may be that a later lookup, perhaps during the invocation of a + * bootstrap method, can incorporate the specific identity + * of the caller, making the method accessible. + *

+ * The function {@code MethodHandles.lookup} is caller sensitive + * so that there can be a secure foundation for lookups. + * Nearly all other methods in the JSR 292 API rely on lookup + * objects to check access requests. + */ + public static final + class Lookup { + /** The class on behalf of whom the lookup is being performed. */ + private final Class lookupClass; + + /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */ + private final int allowedModes; + + /** A single-bit mask representing {@code public} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x01}, happens to be the same as the value of the + * {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}. + */ + public static final int PUBLIC = Modifier.PUBLIC; + + /** A single-bit mask representing {@code private} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x02}, happens to be the same as the value of the + * {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}. + */ + public static final int PRIVATE = Modifier.PRIVATE; + + /** A single-bit mask representing {@code protected} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x04}, happens to be the same as the value of the + * {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}. + */ + public static final int PROTECTED = Modifier.PROTECTED; + + /** A single-bit mask representing {@code package} access (default access), + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value is {@code 0x08}, which does not correspond meaningfully to + * any particular {@linkplain java.lang.reflect.Modifier modifier bit}. + */ + public static final int PACKAGE = Modifier.STATIC; + + private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE); + private static final int TRUSTED = -1; + + private static int fixmods(int mods) { + mods &= (ALL_MODES - PACKAGE); + return (mods != 0) ? mods : PACKAGE; + } + + /** Tells which class is performing the lookup. It is this class against + * which checks are performed for visibility and access permissions. + *

+ * The class implies a maximum level of access permission, + * but the permissions may be additionally limited by the bitmask + * {@link #lookupModes lookupModes}, which controls whether non-public members + * can be accessed. + * @return the lookup class, on behalf of which this lookup object finds members + */ + public Class lookupClass() { + return lookupClass; + } + + // This is just for calling out to MethodHandleImpl. + private Class lookupClassOrNull() { + return (allowedModes == TRUSTED) ? null : lookupClass; + } + + /** Tells which access-protection classes of members this lookup object can produce. + * The result is a bit-mask of the bits + * {@linkplain #PUBLIC PUBLIC (0x01)}, + * {@linkplain #PRIVATE PRIVATE (0x02)}, + * {@linkplain #PROTECTED PROTECTED (0x04)}, + * and {@linkplain #PACKAGE PACKAGE (0x08)}. + *

+ * A freshly-created lookup object + * on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class} + * has all possible bits set, since the caller class can access all its own members. + * A lookup object on a new lookup class + * {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object} + * may have some mode bits set to zero. + * The purpose of this is to restrict access via the new lookup object, + * so that it can access only names which can be reached by the original + * lookup object, and also by the new lookup class. + * @return the lookup modes, which limit the kinds of access performed by this lookup object + */ + public int lookupModes() { + return allowedModes & ALL_MODES; + } + + /** Embody the current class (the lookupClass) as a lookup class + * for method handle creation. + * Must be called by from a method in this package, + * which in turn is called by a method not in this package. + */ + Lookup(Class lookupClass) { + this(lookupClass, ALL_MODES); + // make sure we haven't accidentally picked up a privileged class: + checkUnprivilegedlookupClass(lookupClass, ALL_MODES); + } + + private Lookup(Class lookupClass, int allowedModes) { + this.lookupClass = lookupClass; + this.allowedModes = allowedModes; + } + + /** + * Creates a lookup on the specified new lookup class. + * The resulting object will report the specified + * class as its own {@link #lookupClass lookupClass}. + *

+ * However, the resulting {@code Lookup} object is guaranteed + * to have no more access capabilities than the original. + * In particular, access capabilities can be lost as follows:

    + *
  • If the new lookup class differs from the old one, + * protected members will not be accessible by virtue of inheritance. + * (Protected members may continue to be accessible because of package sharing.) + *
  • If the new lookup class is in a different package + * than the old one, protected and default (package) members will not be accessible. + *
  • If the new lookup class is not within the same package member + * as the old one, private members will not be accessible. + *
  • If the new lookup class is not accessible to the old lookup class, + * then no members, not even public members, will be accessible. + * (In all other cases, public members will continue to be accessible.) + *
+ * + * @param requestedLookupClass the desired lookup class for the new lookup object + * @return a lookup object which reports the desired lookup class + * @throws NullPointerException if the argument is null + */ + public Lookup in(Class requestedLookupClass) { + requestedLookupClass.getClass(); // null check + if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all + return new Lookup(requestedLookupClass, ALL_MODES); + if (requestedLookupClass == this.lookupClass) + return this; // keep same capabilities + int newModes = (allowedModes & (ALL_MODES & ~PROTECTED)); + if ((newModes & PACKAGE) != 0 + && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) { + newModes &= ~(PACKAGE|PRIVATE); + } + // Allow nestmate lookups to be created without special privilege: + if ((newModes & PRIVATE) != 0 + && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { + newModes &= ~PRIVATE; + } + if ((newModes & PUBLIC) != 0 + && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) { + // The requested class it not accessible from the lookup class. + // No permissions. + newModes = 0; + } + checkUnprivilegedlookupClass(requestedLookupClass, newModes); + return new Lookup(requestedLookupClass, newModes); + } + + // Make sure outer class is initialized first. + static { IMPL_NAMES.getClass(); } + + /** Version of lookup which is trusted minimally. + * It can only be used to create method handles to + * publicly accessible members. + */ + static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC); + + /** Package-private version of lookup which is trusted. */ + static final Lookup IMPL_LOOKUP = new Lookup(Object.class, TRUSTED); + + private static void checkUnprivilegedlookupClass(Class lookupClass, int allowedModes) { + String name = lookupClass.getName(); + if (name.startsWith("java.lang.invoke.")) + throw newIllegalArgumentException("illegal lookupClass: "+lookupClass); + + // For caller-sensitive MethodHandles.lookup() + // disallow lookup more restricted packages + if (allowedModes == ALL_MODES && lookupClass.getClassLoader() == null) { + if (name.startsWith("java.") || + (name.startsWith("sun.") && !name.startsWith("sun.invoke."))) { + throw newIllegalArgumentException("illegal lookupClass: " + lookupClass); + } + } + } + + /** + * Displays the name of the class from which lookups are to be made. + * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.) + * If there are restrictions on the access permitted to this lookup, + * this is indicated by adding a suffix to the class name, consisting + * of a slash and a keyword. The keyword represents the strongest + * allowed access, and is chosen as follows: + *
    + *
  • If no access is allowed, the suffix is "/noaccess". + *
  • If only public access is allowed, the suffix is "/public". + *
  • If only public and package access are allowed, the suffix is "/package". + *
  • If only public, package, and private access are allowed, the suffix is "/private". + *
+ * If none of the above cases apply, it is the case that full + * access (public, package, private, and protected) is allowed. + * In this case, no suffix is added. + * This is true only of an object obtained originally from + * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. + * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in} + * always have restricted access, and will display a suffix. + *

+ * (It may seem strange that protected access should be + * stronger than private access. Viewed independently from + * package access, protected access is the first to be lost, + * because it requires a direct subclass relationship between + * caller and callee.) + * @see #in + */ + @Override + public String toString() { + String cname = lookupClass.getName(); + switch (allowedModes) { + case 0: // no privileges + return cname + "/noaccess"; + case PUBLIC: + return cname + "/public"; + case PUBLIC|PACKAGE: + return cname + "/package"; + case ALL_MODES & ~PROTECTED: + return cname + "/private"; + case ALL_MODES: + return cname; + case TRUSTED: + return "/trusted"; // internal only; not exported + default: // Should not happen, but it's a bitfield... + cname = cname + "/" + Integer.toHexString(allowedModes); + assert(false) : cname; + return cname; + } + } + + /** + * Produces a method handle for a static method. + * The type of the method handle will be that of the method. + * (Since static methods do not take receivers, there is no + * additional receiver argument inserted into the method handle type, + * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.) + * The method and all its argument types must be accessible to the lookup object. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * If the returned method handle is invoked, the method's class will + * be initialized, if it has not already been initialized. + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
+  "asList", methodType(List.class, Object[].class));
+assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
+         * }
+ * @param refc the class from which the method is accessed + * @param name the name of the method + * @param type the type of the method + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails, + * or if the method is not {@code static}, + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public + MethodHandle findStatic(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + MemberName method = resolveOrFail(REF_invokeStatic, refc, name, type); + return getDirectMethod(REF_invokeStatic, refc, method, findBoundCallerClass(method)); + } + + /** + * Produces a method handle for a virtual method. + * The type of the method handle will be that of the method, + * with the receiver type (usually {@code refc}) prepended. + * The method and all its argument types must be accessible to the lookup object. + *

+ * When called, the handle will treat the first argument as a receiver + * and dispatch on the receiver's type to determine which method + * implementation to enter. + * (The dispatching action is identical with that performed by an + * {@code invokevirtual} or {@code invokeinterface} instruction.) + *

+ * The first argument will be of type {@code refc} if the lookup + * class has full privileges to access the member. Otherwise + * the member must be {@code protected} and the first argument + * will be restricted in type to the lookup class. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * Because of the general equivalence between {@code invokevirtual} + * instructions and method handles produced by {@code findVirtual}, + * if the class is {@code MethodHandle} and the name string is + * {@code invokeExact} or {@code invoke}, the resulting + * method handle is equivalent to one produced by + * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or + * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker} + * with the same {@code type} argument. + * + * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_concat = publicLookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
+  "hashCode", methodType(int.class));
+MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
+  "hashCode", methodType(int.class));
+assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
+assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
+assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
+// interface method:
+MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
+  "subSequence", methodType(CharSequence.class, int.class, int.class));
+assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
+// constructor "internal method" must be accessed differently:
+MethodType MT_newString = methodType(void.class); //()V for new String()
+try { assertEquals("impossible", lookup()
+        .findVirtual(String.class, "", MT_newString));
+ } catch (NoSuchMethodException ex) { } // OK
+MethodHandle MH_newString = publicLookup()
+  .findConstructor(String.class, MT_newString);
+assertEquals("", (String) MH_newString.invokeExact());
+         * }
+ * + * @param refc the class or interface from which the method is accessed + * @param name the name of the method + * @param type the type of the method, with the receiver argument omitted + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails, + * or if the method is {@code static} + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findVirtual(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + if (refc == MethodHandle.class) { + MethodHandle mh = findVirtualForMH(name, type); + if (mh != null) return mh; + } + byte refKind = (refc.isInterface() ? REF_invokeInterface : REF_invokeVirtual); + MemberName method = resolveOrFail(refKind, refc, name, type); + return getDirectMethod(refKind, refc, method, findBoundCallerClass(method)); + } + private MethodHandle findVirtualForMH(String name, MethodType type) { + // these names require special lookups because of the implicit MethodType argument + if ("invoke".equals(name)) + return invoker(type); + if ("invokeExact".equals(name)) + return exactInvoker(type); + assert(!MemberName.isMethodHandleInvokeName(name)); + return null; + } + + /** + * Produces a method handle which creates an object and initializes it, using + * the constructor of the specified type. + * The parameter types of the method handle will be those of the constructor, + * while the return type will be a reference to the constructor's class. + * The constructor and all its argument types must be accessible to the lookup object. + *

+ * The requested type must have a return type of {@code void}. + * (This is consistent with the JVM's treatment of constructor type descriptors.) + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the constructor's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * If the returned method handle is invoked, the constructor's class will + * be initialized, if it has not already been initialized. + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_newArrayList = publicLookup().findConstructor(
+  ArrayList.class, methodType(void.class, Collection.class));
+Collection orig = Arrays.asList("x", "y");
+Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
+assert(orig != copy);
+assertEquals(orig, copy);
+// a variable-arity constructor:
+MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
+  ProcessBuilder.class, methodType(void.class, String[].class));
+ProcessBuilder pb = (ProcessBuilder)
+  MH_newProcessBuilder.invoke("x", "y", "z");
+assertEquals("[x, y, z]", pb.command().toString());
+         * }
+ * @param refc the class or interface from which the method is accessed + * @param type the type of the method, with the receiver argument omitted, and a void return type + * @return the desired method handle + * @throws NoSuchMethodException if the constructor does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findConstructor(Class refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { + String name = ""; + MemberName ctor = resolveOrFail(REF_newInvokeSpecial, refc, name, type); + return getDirectConstructor(refc, ctor); + } + + /** + * Produces an early-bound method handle for a virtual method. + * It will bypass checks for overriding methods on the receiver, + * as if called from an {@code invokespecial} + * instruction from within the explicitly specified {@code specialCaller}. + * The type of the method handle will be that of the method, + * with a suitably restricted receiver type prepended. + * (The receiver type will be {@code specialCaller} or a subtype.) + * The method and all its argument types must be accessible + * to the lookup object. + *

+ * Before method resolution, + * if the explicitly specified caller class is not identical with the + * lookup class, or if this lookup object does not have + * private access + * privileges, the access fails. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * (Note: JVM internal methods named {@code ""} are not visible to this API, + * even though the {@code invokespecial} instruction can refer to them + * in special circumstances. Use {@link #findConstructor findConstructor} + * to access instance initialization methods in a safe manner.) + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+static class Listie extends ArrayList {
+  public String toString() { return "[wee Listie]"; }
+  static Lookup lookup() { return MethodHandles.lookup(); }
+}
+...
+// no access to constructor via invokeSpecial:
+MethodHandle MH_newListie = Listie.lookup()
+  .findConstructor(Listie.class, methodType(void.class));
+Listie l = (Listie) MH_newListie.invokeExact();
+try { assertEquals("impossible", Listie.lookup().findSpecial(
+        Listie.class, "", methodType(void.class), Listie.class));
+ } catch (NoSuchMethodException ex) { } // OK
+// access to super and self methods via invokeSpecial:
+MethodHandle MH_super = Listie.lookup().findSpecial(
+  ArrayList.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_this = Listie.lookup().findSpecial(
+  Listie.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_duper = Listie.lookup().findSpecial(
+  Object.class, "toString" , methodType(String.class), Listie.class);
+assertEquals("[]", (String) MH_super.invokeExact(l));
+assertEquals(""+l, (String) MH_this.invokeExact(l));
+assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
+try { assertEquals("inaccessible", Listie.lookup().findSpecial(
+        String.class, "toString", methodType(String.class), Listie.class));
+ } catch (IllegalAccessException ex) { } // OK
+Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
+assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
+         * }
+ * + * @param refc the class or interface from which the method is accessed + * @param name the name of the method (which must not be "<init>") + * @param type the type of the method, with the receiver argument omitted + * @param specialCaller the proposed calling class to perform the {@code invokespecial} + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findSpecial(Class refc, String name, MethodType type, + Class specialCaller) throws NoSuchMethodException, IllegalAccessException { + checkSpecialCaller(specialCaller); + Lookup specialLookup = this.in(specialCaller); + MemberName method = specialLookup.resolveOrFail(REF_invokeSpecial, refc, name, type); + return specialLookup.getDirectMethod(REF_invokeSpecial, refc, method, findBoundCallerClass(method)); + } + + /** + * Produces a method handle giving read access to a non-static field. + * The type of the method handle will have a return type of the field's + * value type. + * The method handle's single argument will be the instance containing + * the field. + * Access checking is performed immediately on behalf of the lookup class. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can load values from the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is {@code static} + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findGetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(REF_getField, refc, name, type); + return getDirectField(REF_getField, refc, field); + } + + /** + * Produces a method handle giving write access to a non-static field. + * The type of the method handle will have a void return type. + * The method handle will take two arguments, the instance containing + * the field, and the value to be stored. + * The second argument will be of the field's value type. + * Access checking is performed immediately on behalf of the lookup class. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can store values into the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is {@code static} + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findSetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(REF_putField, refc, name, type); + return getDirectField(REF_putField, refc, field); + } + + /** + * Produces a method handle giving read access to a static field. + * The type of the method handle will have a return type of the field's + * value type. + * The method handle will take no arguments. + * Access checking is performed immediately on behalf of the lookup class. + *

+ * If the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can load values from the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is not {@code static} + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findStaticGetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(REF_getStatic, refc, name, type); + return getDirectField(REF_getStatic, refc, field); + } + + /** + * Produces a method handle giving write access to a static field. + * The type of the method handle will have a void return type. + * The method handle will take a single + * argument, of the field's value type, the value to be stored. + * Access checking is performed immediately on behalf of the lookup class. + *

+ * If the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can store values into the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is not {@code static} + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + */ + public MethodHandle findStaticSetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(REF_putStatic, refc, name, type); + return getDirectField(REF_putStatic, refc, field); + } + + /** + * Produces an early-bound method handle for a non-static method. + * The receiver must have a supertype {@code defc} in which a method + * of the given name and type is accessible to the lookup class. + * The method and all its argument types must be accessible to the lookup object. + * The type of the method handle will be that of the method, + * without any insertion of an additional receiver parameter. + * The given receiver will be bound into the method handle, + * so that every call to the method handle will invoke the + * requested method on the given receiver. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set + * and the trailing array argument is not the only argument. + * (If the trailing array argument is the only argument, + * the given receiver value will be bound to it.) + *

+ * This is equivalent to the following code: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle mh0 = lookup().findVirtual(defc, name, type);
+MethodHandle mh1 = mh0.bindTo(receiver);
+MethodType mt1 = mh1.type();
+if (mh0.isVarargsCollector())
+  mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1));
+return mh1;
+         * }
+ * where {@code defc} is either {@code receiver.getClass()} or a super + * type of that class, in which the requested method is accessible + * to the lookup class. + * (Note that {@code bindTo} does not preserve variable arity.) + * @param receiver the object from which the method is accessed + * @param name the name of the method + * @param type the type of the method, with the receiver argument omitted + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws NullPointerException if any argument is null + * @see MethodHandle#bindTo + * @see #findVirtual + */ + public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + Class refc = receiver.getClass(); // may get NPE + MemberName method = resolveOrFail(REF_invokeSpecial, refc, name, type); + MethodHandle mh = getDirectMethodNoRestrict(REF_invokeSpecial, refc, method, findBoundCallerClass(method)); + return mh.bindReceiver(receiver).setVarargs(method); + } + + /** + * Makes a direct method handle + * to m, if the lookup class has permission. + * If m is non-static, the receiver argument is treated as an initial argument. + * If m is virtual, overriding is respected on every call. + * Unlike the Core Reflection API, exceptions are not wrapped. + * The type of the method handle will be that of the method, + * with the receiver type prepended (but only if it is non-static). + * If the method's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + * If m is not public, do not share the resulting handle with untrusted parties. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * If m is static, and + * if the returned method handle is invoked, the method's class will + * be initialized, if it has not already been initialized. + * @param m the reflected method + * @return a method handle which can invoke the reflected method + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflect(Method m) throws IllegalAccessException { + if (m.getDeclaringClass() == MethodHandle.class) { + MethodHandle mh = unreflectForMH(m); + if (mh != null) return mh; + } + MemberName method = new MemberName(m); + byte refKind = method.getReferenceKind(); + if (refKind == REF_invokeSpecial) + refKind = REF_invokeVirtual; + assert(method.isMethod()); + Lookup lookup = m.isAccessible() ? IMPL_LOOKUP : this; + return lookup.getDirectMethodNoSecurityManager(refKind, method.getDeclaringClass(), method, findBoundCallerClass(method)); + } + private MethodHandle unreflectForMH(Method m) { + // these names require special lookups because they throw UnsupportedOperationException + if (MemberName.isMethodHandleInvokeName(m.getName())) + return MethodHandleImpl.fakeMethodHandleInvoke(new MemberName(m)); + return null; + } + + /** + * Produces a method handle for a reflected method. + * It will bypass checks for overriding methods on the receiver, + * as if called from an {@code invokespecial} + * instruction from within the explicitly specified {@code specialCaller}. + * The type of the method handle will be that of the method, + * with a suitably restricted receiver type prepended. + * (The receiver type will be {@code specialCaller} or a subtype.) + * If the method's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class, + * as if {@code invokespecial} instruction were being linked. + *

+ * Before method resolution, + * if the explicitly specified caller class is not identical with the + * lookup class, or if this lookup object does not have + * private access + * privileges, the access fails. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * @param m the reflected method + * @param specialCaller the class nominally calling the method + * @return a method handle which can invoke the reflected method + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if any argument is null + */ + public MethodHandle unreflectSpecial(Method m, Class specialCaller) throws IllegalAccessException { + checkSpecialCaller(specialCaller); + Lookup specialLookup = this.in(specialCaller); + MemberName method = new MemberName(m, true); + assert(method.isMethod()); + // ignore m.isAccessible: this is a new kind of access + return specialLookup.getDirectMethodNoSecurityManager(REF_invokeSpecial, method.getDeclaringClass(), method, findBoundCallerClass(method)); + } + + /** + * Produces a method handle for a reflected constructor. + * The type of the method handle will be that of the constructor, + * with the return type changed to the declaring class. + * The method handle will perform a {@code newInstance} operation, + * creating a new instance of the constructor's class on the + * arguments passed to the method handle. + *

+ * If the constructor's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + *

+ * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the constructor's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * If the returned method handle is invoked, the constructor's class will + * be initialized, if it has not already been initialized. + * @param c the reflected constructor + * @return a method handle which can invoke the reflected constructor + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectConstructor(Constructor c) throws IllegalAccessException { + MemberName ctor = new MemberName(c); + assert(ctor.isConstructor()); + Lookup lookup = c.isAccessible() ? IMPL_LOOKUP : this; + return lookup.getDirectConstructorNoSecurityManager(ctor.getDeclaringClass(), ctor); + } + + /** + * Produces a method handle giving read access to a reflected field. + * The type of the method handle will have a return type of the field's + * value type. + * If the field is static, the method handle will take no arguments. + * Otherwise, its single argument will be the instance containing + * the field. + * If the field's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + *

+ * If the field is static, and + * if the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param f the reflected field + * @return a method handle which can load values from the reflected field + * @throws IllegalAccessException if access checking fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { + return unreflectField(f, false); + } + private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException { + MemberName field = new MemberName(f, isSetter); + assert(isSetter + ? MethodHandleNatives.refKindIsSetter(field.getReferenceKind()) + : MethodHandleNatives.refKindIsGetter(field.getReferenceKind())); + Lookup lookup = f.isAccessible() ? IMPL_LOOKUP : this; + return lookup.getDirectFieldNoSecurityManager(field.getReferenceKind(), f.getDeclaringClass(), field); + } + + /** + * Produces a method handle giving write access to a reflected field. + * The type of the method handle will have a void return type. + * If the field is static, the method handle will take a single + * argument, of the field's value type, the value to be stored. + * Otherwise, the two arguments will be the instance containing + * the field, and the value to be stored. + * If the field's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + *

+ * If the field is static, and + * if the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param f the reflected field + * @return a method handle which can store values into the reflected field + * @throws IllegalAccessException if access checking fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return unreflectField(f, true); + } + + /** + * Cracks a direct method handle + * created by this lookup object or a similar one. + * Security and access checks are performed to ensure that this lookup object + * is capable of reproducing the target method handle. + * This means that the cracking may fail if target is a direct method handle + * but was created by an unrelated lookup object. + * This can happen if the method handle is caller sensitive + * and was created by a lookup object for a different class. + * @param target a direct method handle to crack into symbolic reference components + * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails + * @exception NullPointerException if the target is {@code null} + * @see MethodHandleInfo + * @since 1.8 + */ + public MethodHandleInfo revealDirect(MethodHandle target) { + MemberName member = target.internalMemberName(); + if (member == null || (!member.isResolved() && !member.isMethodHandleInvoke())) + throw newIllegalArgumentException("not a direct method handle"); + Class defc = member.getDeclaringClass(); + byte refKind = member.getReferenceKind(); + assert(MethodHandleNatives.refKindIsValid(refKind)); + if (refKind == REF_invokeSpecial && !target.isInvokeSpecial()) + // Devirtualized method invocation is usually formally virtual. + // To avoid creating extra MemberName objects for this common case, + // we encode this extra degree of freedom using MH.isInvokeSpecial. + refKind = REF_invokeVirtual; + if (refKind == REF_invokeVirtual && defc.isInterface()) + // Symbolic reference is through interface but resolves to Object method (toString, etc.) + refKind = REF_invokeInterface; + // Check SM permissions and member access before cracking. + try { + checkAccess(refKind, defc, member); + checkSecurityManager(defc, member); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } + if (allowedModes != TRUSTED && member.isCallerSensitive()) { + Class callerClass = target.internalCallerClass(); + if (!hasPrivateAccess() || callerClass != lookupClass()) + throw new IllegalArgumentException("method handle is caller sensitive: "+callerClass); + } + // Produce the handle to the results. + return new InfoFromMemberName(this, member, refKind); + } + + /// Helper methods, all package-private. + + MemberName resolveOrFail(byte refKind, Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + checkSymbolicClass(refc); // do this before attempting to resolve + name.getClass(); // NPE + type.getClass(); // NPE + return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(), + NoSuchFieldException.class); + } + + MemberName resolveOrFail(byte refKind, Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + checkSymbolicClass(refc); // do this before attempting to resolve + name.getClass(); // NPE + type.getClass(); // NPE + checkMethodName(refKind, name); // NPE check on name + return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(), + NoSuchMethodException.class); + } + + MemberName resolveOrFail(byte refKind, MemberName member) throws ReflectiveOperationException { + checkSymbolicClass(member.getDeclaringClass()); // do this before attempting to resolve + member.getName().getClass(); // NPE + member.getType().getClass(); // NPE + return IMPL_NAMES.resolveOrFail(refKind, member, lookupClassOrNull(), + ReflectiveOperationException.class); + } + + void checkSymbolicClass(Class refc) throws IllegalAccessException { + refc.getClass(); // NPE + Class caller = lookupClassOrNull(); + if (caller != null && !VerifyAccess.isClassAccessible(refc, caller, allowedModes)) + throw new MemberName(refc).makeAccessException("symbolic reference class is not public", this); + } + + /** Check name for an illegal leading "<" character. */ + void checkMethodName(byte refKind, String name) throws NoSuchMethodException { + if (name.startsWith("<") && refKind != REF_newInvokeSpecial) + throw new NoSuchMethodException("illegal method name: "+name); + } + + + /** + * Find my trustable caller class if m is a caller sensitive method. + * If this lookup object has private access, then the caller class is the lookupClass. + * Otherwise, if m is caller-sensitive, throw IllegalAccessException. + */ + Class findBoundCallerClass(MemberName m) throws IllegalAccessException { + Class callerClass = null; + if (MethodHandleNatives.isCallerSensitive(m)) { + // Only lookups with private access are allowed to resolve caller-sensitive methods + if (hasPrivateAccess()) { + callerClass = lookupClass; + } else { + throw new IllegalAccessException("Attempt to lookup caller-sensitive method using restricted lookup object"); + } + } + return callerClass; + } + + private boolean hasPrivateAccess() { + return (allowedModes & PRIVATE) != 0; + } + + /** + * Perform necessary access checks. + * Determines a trustable caller class to compare with refc, the symbolic reference class. + * If this lookup object has private access, then the caller class is the lookupClass. + */ + void checkSecurityManager(Class refc, MemberName m) { + SecurityManager smgr = System.getSecurityManager(); + if (smgr == null) return; + if (allowedModes == TRUSTED) return; + + // Step 1: + boolean fullPowerLookup = hasPrivateAccess(); + if (!fullPowerLookup || + !VerifyAccess.classLoaderIsAncestor(lookupClass, refc)) { + ReflectUtil.checkPackageAccess(refc); + } + + // Step 2: + if (m.isPublic()) return; + if (!fullPowerLookup) { + smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); + } + + // Step 3: + Class defc = m.getDeclaringClass(); + if (!fullPowerLookup && defc != refc) { + ReflectUtil.checkPackageAccess(defc); + } + } + + void checkMethod(byte refKind, Class refc, MemberName m) throws IllegalAccessException { + boolean wantStatic = (refKind == REF_invokeStatic); + String message; + if (m.isConstructor()) + message = "expected a method, not a constructor"; + else if (!m.isMethod()) + message = "expected a method"; + else if (wantStatic != m.isStatic()) + message = wantStatic ? "expected a static method" : "expected a non-static method"; + else + { checkAccess(refKind, refc, m); return; } + throw m.makeAccessException(message, this); + } + + void checkField(byte refKind, Class refc, MemberName m) throws IllegalAccessException { + boolean wantStatic = !MethodHandleNatives.refKindHasReceiver(refKind); + String message; + if (wantStatic != m.isStatic()) + message = wantStatic ? "expected a static field" : "expected a non-static field"; + else + { checkAccess(refKind, refc, m); return; } + throw m.makeAccessException(message, this); + } + + /** Check public/protected/private bits on the symbolic reference class and its member. */ + void checkAccess(byte refKind, Class refc, MemberName m) throws IllegalAccessException { + assert(m.referenceKindIsConsistentWith(refKind) && + MethodHandleNatives.refKindIsValid(refKind) && + (MethodHandleNatives.refKindIsField(refKind) == m.isField())); + int allowedModes = this.allowedModes; + if (allowedModes == TRUSTED) return; + int mods = m.getModifiers(); + if (Modifier.isProtected(mods) && + refKind == REF_invokeVirtual && + m.getDeclaringClass() == Object.class && + m.getName().equals("clone") && + refc.isArray()) { + // The JVM does this hack also. + // (See ClassVerifier::verify_invoke_instructions + // and LinkResolver::check_method_accessability.) + // Because the JVM does not allow separate methods on array types, + // there is no separate method for int[].clone. + // All arrays simply inherit Object.clone. + // But for access checking logic, we make Object.clone + // (normally protected) appear to be public. + // Later on, when the DirectMethodHandle is created, + // its leading argument will be restricted to the + // requested array type. + // N.B. The return type is not adjusted, because + // that is *not* the bytecode behavior. + mods ^= Modifier.PROTECTED | Modifier.PUBLIC; + } + if (Modifier.isFinal(mods) && + MethodHandleNatives.refKindIsSetter(refKind)) + throw m.makeAccessException("unexpected set of a final field", this); + if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0) + return; // common case + int requestedModes = fixmods(mods); // adjust 0 => PACKAGE + if ((requestedModes & allowedModes) != 0) { + if (VerifyAccess.isMemberAccessible(refc, m.getDeclaringClass(), + mods, lookupClass(), allowedModes)) + return; + } else { + // Protected members can also be checked as if they were package-private. + if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0 + && VerifyAccess.isSamePackage(m.getDeclaringClass(), lookupClass())) + return; + } + throw m.makeAccessException(accessFailedMessage(refc, m), this); + } + + String accessFailedMessage(Class refc, MemberName m) { + Class defc = m.getDeclaringClass(); + int mods = m.getModifiers(); + // check the class first: + boolean classOK = (Modifier.isPublic(defc.getModifiers()) && + (defc == refc || + Modifier.isPublic(refc.getModifiers()))); + if (!classOK && (allowedModes & PACKAGE) != 0) { + classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) && + (defc == refc || + VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES))); + } + if (!classOK) + return "class is not public"; + if (Modifier.isPublic(mods)) + return "access to public member failed"; // (how?) + if (Modifier.isPrivate(mods)) + return "member is private"; + if (Modifier.isProtected(mods)) + return "member is protected"; + return "member is private to package"; + } + + private static final boolean ALLOW_NESTMATE_ACCESS = false; + + private void checkSpecialCaller(Class specialCaller) throws IllegalAccessException { + int allowedModes = this.allowedModes; + if (allowedModes == TRUSTED) return; + if (!hasPrivateAccess() + || (specialCaller != lookupClass() + && !(ALLOW_NESTMATE_ACCESS && + VerifyAccess.isSamePackageMember(specialCaller, lookupClass())))) + throw new MemberName(specialCaller). + makeAccessException("no private access for invokespecial", this); + } + + private boolean restrictProtectedReceiver(MemberName method) { + // The accessing class only has the right to use a protected member + // on itself or a subclass. Enforce that restriction, from JVMS 5.4.4, etc. + if (!method.isProtected() || method.isStatic() + || allowedModes == TRUSTED + || method.getDeclaringClass() == lookupClass() + || VerifyAccess.isSamePackage(method.getDeclaringClass(), lookupClass()) + || (ALLOW_NESTMATE_ACCESS && + VerifyAccess.isSamePackageMember(method.getDeclaringClass(), lookupClass()))) + return false; + return true; + } + private MethodHandle restrictReceiver(MemberName method, MethodHandle mh, Class caller) throws IllegalAccessException { + assert(!method.isStatic()); + // receiver type of mh is too wide; narrow to caller + if (!method.getDeclaringClass().isAssignableFrom(caller)) { + throw method.makeAccessException("caller class must be a subclass below the method", caller); + } + MethodType rawType = mh.type(); + if (rawType.parameterType(0) == caller) return mh; + MethodType narrowType = rawType.changeParameterType(0, caller); + return mh.viewAsType(narrowType); + } + + /** Check access and get the requested method. */ + private MethodHandle getDirectMethod(byte refKind, Class refc, MemberName method, Class callerClass) throws IllegalAccessException { + final boolean doRestrict = true; + final boolean checkSecurity = true; + return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, callerClass); + } + /** Check access and get the requested method, eliding receiver narrowing rules. */ + private MethodHandle getDirectMethodNoRestrict(byte refKind, Class refc, MemberName method, Class callerClass) throws IllegalAccessException { + final boolean doRestrict = false; + final boolean checkSecurity = true; + return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, callerClass); + } + /** Check access and get the requested method, eliding security manager checks. */ + private MethodHandle getDirectMethodNoSecurityManager(byte refKind, Class refc, MemberName method, Class callerClass) throws IllegalAccessException { + final boolean doRestrict = true; + final boolean checkSecurity = false; // not needed for reflection or for linking CONSTANT_MH constants + return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, callerClass); + } + /** Common code for all methods; do not call directly except from immediately above. */ + private MethodHandle getDirectMethodCommon(byte refKind, Class refc, MemberName method, + boolean checkSecurity, + boolean doRestrict, Class callerClass) throws IllegalAccessException { + checkMethod(refKind, refc, method); + // Optionally check with the security manager; this isn't needed for unreflect* calls. + if (checkSecurity) + checkSecurityManager(refc, method); + assert(!method.isMethodHandleInvoke()); + + Class refcAsSuper; + if (refKind == REF_invokeSpecial && + refc != lookupClass() && + !refc.isInterface() && + refc != (refcAsSuper = lookupClass().getSuperclass()) && + refc.isAssignableFrom(lookupClass())) { + assert(!method.getName().equals("")); // not this code path + // Per JVMS 6.5, desc. of invokespecial instruction: + // If the method is in a superclass of the LC, + // and if our original search was above LC.super, + // repeat the search (symbolic lookup) from LC.super. + // FIXME: MemberName.resolve should handle this instead. + MemberName m2 = new MemberName(refcAsSuper, + method.getName(), + method.getMethodType(), + REF_invokeSpecial); + m2 = IMPL_NAMES.resolveOrNull(refKind, m2, lookupClassOrNull()); + if (m2 == null) throw new InternalError(method.toString()); + method = m2; + refc = refcAsSuper; + // redo basic checks + checkMethod(refKind, refc, method); + } + + MethodHandle mh = DirectMethodHandle.make(refKind, refc, method); + mh = maybeBindCaller(method, mh, callerClass); + mh = mh.setVarargs(method); + // Optionally narrow the receiver argument to refc using restrictReceiver. + if (doRestrict && + (refKind == REF_invokeSpecial || + (MethodHandleNatives.refKindHasReceiver(refKind) && + restrictProtectedReceiver(method)))) + mh = restrictReceiver(method, mh, lookupClass()); + return mh; + } + private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh, + Class callerClass) + throws IllegalAccessException { + if (allowedModes == TRUSTED || !MethodHandleNatives.isCallerSensitive(method)) + return mh; + Class hostClass = lookupClass; + if (!hasPrivateAccess()) // caller must have private access + hostClass = callerClass; // callerClass came from a security manager style stack walk + MethodHandle cbmh = MethodHandleImpl.bindCaller(mh, hostClass); + // Note: caller will apply varargs after this step happens. + return cbmh; + } + /** Check access and get the requested field. */ + private MethodHandle getDirectField(byte refKind, Class refc, MemberName field) throws IllegalAccessException { + final boolean checkSecurity = true; + return getDirectFieldCommon(refKind, refc, field, checkSecurity); + } + /** Check access and get the requested field, eliding security manager checks. */ + private MethodHandle getDirectFieldNoSecurityManager(byte refKind, Class refc, MemberName field) throws IllegalAccessException { + final boolean checkSecurity = false; // not needed for reflection or for linking CONSTANT_MH constants + return getDirectFieldCommon(refKind, refc, field, checkSecurity); + } + /** Common code for all fields; do not call directly except from immediately above. */ + private MethodHandle getDirectFieldCommon(byte refKind, Class refc, MemberName field, + boolean checkSecurity) throws IllegalAccessException { + checkField(refKind, refc, field); + // Optionally check with the security manager; this isn't needed for unreflect* calls. + if (checkSecurity) + checkSecurityManager(refc, field); + MethodHandle mh = DirectMethodHandle.make(refc, field); + boolean doRestrict = (MethodHandleNatives.refKindHasReceiver(refKind) && + restrictProtectedReceiver(field)); + if (doRestrict) + mh = restrictReceiver(field, mh, lookupClass()); + return mh; + } + /** Check access and get the requested constructor. */ + private MethodHandle getDirectConstructor(Class refc, MemberName ctor) throws IllegalAccessException { + final boolean checkSecurity = true; + return getDirectConstructorCommon(refc, ctor, checkSecurity); + } + /** Check access and get the requested constructor, eliding security manager checks. */ + private MethodHandle getDirectConstructorNoSecurityManager(Class refc, MemberName ctor) throws IllegalAccessException { + final boolean checkSecurity = false; // not needed for reflection or for linking CONSTANT_MH constants + return getDirectConstructorCommon(refc, ctor, checkSecurity); + } + /** Common code for all constructors; do not call directly except from immediately above. */ + private MethodHandle getDirectConstructorCommon(Class refc, MemberName ctor, + boolean checkSecurity) throws IllegalAccessException { + assert(ctor.isConstructor()); + checkAccess(REF_newInvokeSpecial, refc, ctor); + // Optionally check with the security manager; this isn't needed for unreflect* calls. + if (checkSecurity) + checkSecurityManager(refc, ctor); + assert(!MethodHandleNatives.isCallerSensitive(ctor)); // maybeBindCaller not relevant here + return DirectMethodHandle.make(ctor).setVarargs(ctor); + } + + /** Hook called from the JVM (via MethodHandleNatives) to link MH constants: + */ + /*non-public*/ + MethodHandle linkMethodHandleConstant(byte refKind, Class defc, String name, Object type) throws ReflectiveOperationException { + if (!(type instanceof Class || type instanceof MethodType)) + throw new InternalError("unresolved MemberName"); + MemberName member = new MemberName(refKind, defc, name, type); + MethodHandle mh = LOOKASIDE_TABLE.get(member); + if (mh != null) { + checkSymbolicClass(defc); + return mh; + } + // Treat MethodHandle.invoke and invokeExact specially. + if (defc == MethodHandle.class && refKind == REF_invokeVirtual) { + mh = findVirtualForMH(member.getName(), member.getMethodType()); + if (mh != null) { + return mh; + } + } + MemberName resolved = resolveOrFail(refKind, member); + mh = getDirectMethodForConstant(refKind, defc, resolved); + if (mh instanceof DirectMethodHandle + && canBeCached(refKind, defc, resolved)) { + MemberName key = mh.internalMemberName(); + if (key != null) { + key = key.asNormalOriginal(); + } + if (member.equals(key)) { // better safe than sorry + LOOKASIDE_TABLE.put(key, (DirectMethodHandle) mh); + } + } + return mh; + } + private + boolean canBeCached(byte refKind, Class defc, MemberName member) { + if (refKind == REF_invokeSpecial) { + return false; + } + if (!Modifier.isPublic(defc.getModifiers()) || + !Modifier.isPublic(member.getDeclaringClass().getModifiers()) || + !member.isPublic() || + member.isCallerSensitive()) { + return false; + } + ClassLoader loader = defc.getClassLoader(); + if (!sun.misc.VM.isSystemDomainLoader(loader)) { + ClassLoader sysl = ClassLoader.getSystemClassLoader(); + boolean found = false; + while (sysl != null) { + if (loader == sysl) { found = true; break; } + sysl = sysl.getParent(); + } + if (!found) { + return false; + } + } + try { + MemberName resolved2 = publicLookup().resolveOrFail(refKind, + new MemberName(refKind, defc, member.getName(), member.getType())); + checkSecurityManager(defc, resolved2); + } catch (ReflectiveOperationException | SecurityException ex) { + return false; + } + return true; + } + private + MethodHandle getDirectMethodForConstant(byte refKind, Class defc, MemberName member) + throws ReflectiveOperationException { + if (MethodHandleNatives.refKindIsField(refKind)) { + return getDirectFieldNoSecurityManager(refKind, defc, member); + } else if (MethodHandleNatives.refKindIsMethod(refKind)) { + return getDirectMethodNoSecurityManager(refKind, defc, member, lookupClass); + } else if (refKind == REF_newInvokeSpecial) { + return getDirectConstructorNoSecurityManager(defc, member); + } + // oops + throw newIllegalArgumentException("bad MethodHandle constant #"+member); + } + + static ConcurrentHashMap LOOKASIDE_TABLE = new ConcurrentHashMap<>(); + } + + /** + * Produces a method handle giving read access to elements of an array. + * The type of the method handle will have a return type of the array's + * element type. Its first argument will be the array type, + * and the second will be {@code int}. + * @param arrayClass an array type + * @return a method handle which can load values from the given array type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if arrayClass is not an array type + */ + public static + MethodHandle arrayElementGetter(Class arrayClass) throws IllegalArgumentException { + return MethodHandleImpl.makeArrayElementAccessor(arrayClass, false); + } + + /** + * Produces a method handle giving write access to elements of an array. + * The type of the method handle will have a void return type. + * Its last argument will be the array's element type. + * The first and second arguments will be the array type and int. + * @param arrayClass the class of an array + * @return a method handle which can store values into the array type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if arrayClass is not an array type + */ + public static + MethodHandle arrayElementSetter(Class arrayClass) throws IllegalArgumentException { + return MethodHandleImpl.makeArrayElementAccessor(arrayClass, true); + } + + /// method handle invocation (reflective style) + + /** + * Produces a method handle which will invoke any method handle of the + * given {@code type}, with a given number of trailing arguments replaced by + * a single trailing {@code Object[]} array. + * The resulting invoker will be a method handle with the following + * arguments: + *

    + *
  • a single {@code MethodHandle} target + *
  • zero or more leading values (counted by {@code leadingArgCount}) + *
  • an {@code Object[]} array containing trailing arguments + *
+ *

+ * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with + * the indicated {@code type}. + * That is, if the target is exactly of the given {@code type}, it will behave + * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType} + * is used to convert the target to the required {@code type}. + *

+ * The type of the returned invoker will not be the given {@code type}, but rather + * will have all parameters except the first {@code leadingArgCount} + * replaced by a single array of type {@code Object[]}, which will be + * the final parameter. + *

+ * Before invoking its target, the invoker will spread the final array, apply + * reference casts as necessary, and unbox and widen primitive arguments. + * If, when the invoker is called, the supplied array argument does + * not have the correct number of elements, the invoker will throw + * an {@link IllegalArgumentException} instead of invoking the target. + *

+ * This method is equivalent to the following code (though it may be more efficient): + *

{@code
+MethodHandle invoker = MethodHandles.invoker(type);
+int spreadArgCount = type.parameterCount() - leadingArgCount;
+invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+return invoker;
+     * }
+ * This method throws no reflective or security exceptions. + * @param type the desired target type + * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target + * @return a method handle suitable for invoking any method handle of the given type + * @throws NullPointerException if {@code type} is null + * @throws IllegalArgumentException if {@code leadingArgCount} is not in + * the range from 0 to {@code type.parameterCount()} inclusive, + * or if the resulting method handle's type would have + * too many parameters + */ + static public + MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { + if (leadingArgCount < 0 || leadingArgCount > type.parameterCount()) + throw new IllegalArgumentException("bad argument count "+leadingArgCount); + return type.invokers().spreadInvoker(leadingArgCount); + } + + /** + * Produces a special invoker method handle which can be used to + * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}. + * The resulting invoker will have a type which is + * exactly equal to the desired type, except that it will accept + * an additional leading argument of type {@code MethodHandle}. + *

+ * This method is equivalent to the following code (though it may be more efficient): + * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)} + * + *

+ * Discussion: + * Invoker method handles can be useful when working with variable method handles + * of unknown types. + * For example, to emulate an {@code invokeExact} call to a variable method + * handle {@code M}, extract its type {@code T}, + * look up the invoker method {@code X} for {@code T}, + * and call the invoker method, as {@code X.invoke(T, A...)}. + * (It would not work to call {@code X.invokeExact}, since the type {@code T} + * is unknown.) + * If spreading, collecting, or other argument transformations are required, + * they can be applied once to the invoker {@code X} and reused on many {@code M} + * method handle values, as long as they are compatible with the type of {@code X}. + *

+ * (Note: The invoker method is not available via the Core Reflection API. + * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * on the declared {@code invokeExact} or {@code invoke} method will raise an + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.) + *

+ * This method throws no reflective or security exceptions. + * @param type the desired target type + * @return a method handle suitable for invoking any method handle of the given type + * @throws IllegalArgumentException if the resulting method handle's type would have + * too many parameters + */ + static public + MethodHandle exactInvoker(MethodType type) { + return type.invokers().exactInvoker(); + } + + /** + * Produces a special invoker method handle which can be used to + * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}. + * The resulting invoker will have a type which is + * exactly equal to the desired type, except that it will accept + * an additional leading argument of type {@code MethodHandle}. + *

+ * Before invoking its target, if the target differs from the expected type, + * the invoker will apply reference casts as + * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}. + * Similarly, the return value will be converted as necessary. + * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle}, + * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}. + *

+ * This method is equivalent to the following code (though it may be more efficient): + * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)} + *

+ * Discussion: + * A {@linkplain MethodType#genericMethodType general method type} is one which + * mentions only {@code Object} arguments and return values. + * An invoker for such a type is capable of calling any method handle + * of the same arity as the general type. + *

+ * (Note: The invoker method is not available via the Core Reflection API. + * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * on the declared {@code invokeExact} or {@code invoke} method will raise an + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.) + *

+ * This method throws no reflective or security exceptions. + * @param type the desired target type + * @return a method handle suitable for invoking any method handle convertible to the given type + * @throws IllegalArgumentException if the resulting method handle's type would have + * too many parameters + */ + static public + MethodHandle invoker(MethodType type) { + return type.invokers().generalInvoker(); + } + + static /*non-public*/ + MethodHandle basicInvoker(MethodType type) { + return type.form().basicInvoker(); + } + + /// method handle modification (creation from other method handles) + + /** + * Produces a method handle which adapts the type of the + * given method handle to a new type by pairwise argument and return type conversion. + * The original type and new type must have the same number of arguments. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + *

+ * If the original type and new type are equal, returns target. + *

+ * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType}, + * and some additional conversions are also applied if those conversions fail. + * Given types T0, T1, one of the following conversions is applied + * if possible, before or instead of any conversions done by {@code asType}: + *

    + *
  • If T0 and T1 are references, and T1 is an interface type, + * then the value of type T0 is passed as a T1 without a cast. + * (This treatment of interfaces follows the usage of the bytecode verifier.) + *
  • If T0 is boolean and T1 is another primitive, + * the boolean is converted to a byte value, 1 for true, 0 for false. + * (This treatment follows the usage of the bytecode verifier.) + *
  • If T1 is boolean and T0 is another primitive, + * T0 is converted to byte via Java casting conversion (JLS 5.5), + * and the low order bit of the result is tested, as if by {@code (x & 1) != 0}. + *
  • If T0 and T1 are primitives other than boolean, + * then a Java casting conversion (JLS 5.5) is applied. + * (Specifically, T0 will convert to T1 by + * widening and/or narrowing.) + *
  • If T0 is a reference and T1 a primitive, an unboxing + * conversion will be applied at runtime, possibly followed + * by a Java casting conversion (JLS 5.5) on the primitive value, + * possibly followed by a conversion from byte to boolean by testing + * the low-order bit. + *
  • If T0 is a reference and T1 a primitive, + * and if the reference is null at runtime, a zero value is introduced. + *
+ * @param target the method handle to invoke after arguments are retyped + * @param newType the expected type of the new method handle + * @return a method handle which delegates to the target after performing + * any necessary argument conversions, and arranges for any + * necessary return value conversions + * @throws NullPointerException if either argument is null + * @throws WrongMethodTypeException if the conversion cannot be made + * @see MethodHandle#asType + */ + public static + MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { + if (!target.type().isCastableTo(newType)) { + throw new WrongMethodTypeException("cannot explicitly cast "+target+" to "+newType); + } + return MethodHandleImpl.makePairwiseConvert(target, newType, 2); + } + + /** + * Produces a method handle which adapts the calling sequence of the + * given method handle to a new type, by reordering the arguments. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + *

+ * The given array controls the reordering. + * Call {@code #I} the number of incoming parameters (the value + * {@code newType.parameterCount()}, and call {@code #O} the number + * of outgoing parameters (the value {@code target.type().parameterCount()}). + * Then the length of the reordering array must be {@code #O}, + * and each element must be a non-negative number less than {@code #I}. + * For every {@code N} less than {@code #O}, the {@code N}-th + * outgoing argument will be taken from the {@code I}-th incoming + * argument, where {@code I} is {@code reorder[N]}. + *

+ * No argument or return value conversions are applied. + * The type of each incoming argument, as determined by {@code newType}, + * must be identical to the type of the corresponding outgoing parameter + * or parameters in the target method handle. + * The return type of {@code newType} must be identical to the return + * type of the original target. + *

+ * The reordering array need not specify an actual permutation. + * An incoming argument will be duplicated if its index appears + * more than once in the array, and an incoming argument will be dropped + * if its index does not appear in the array. + * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments}, + * incoming arguments which are not mentioned in the reordering array + * are may be any type, as determined only by {@code newType}. + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodType intfn1 = methodType(int.class, int.class);
+MethodType intfn2 = methodType(int.class, int.class, int.class);
+MethodHandle sub = ... (int x, int y) -> (x-y) ...;
+assert(sub.type().equals(intfn2));
+MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
+MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
+assert((int)rsub.invokeExact(1, 100) == 99);
+MethodHandle add = ... (int x, int y) -> (x+y) ...;
+assert(add.type().equals(intfn2));
+MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
+assert(twice.type().equals(intfn1));
+assert((int)twice.invokeExact(21) == 42);
+     * }
+ * @param target the method handle to invoke after arguments are reordered + * @param newType the expected type of the new method handle + * @param reorder an index array which controls the reordering + * @return a method handle which delegates to the target after it + * drops unused arguments and moves and/or duplicates the other arguments + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if the index array length is not equal to + * the arity of the target, or if any index array element + * not a valid index for a parameter of {@code newType}, + * or if two corresponding parameter types in + * {@code target.type()} and {@code newType} are not identical, + */ + public static + MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { + checkReorder(reorder, newType, target.type()); + return target.permuteArguments(newType, reorder); + } + + private static void checkReorder(int[] reorder, MethodType newType, MethodType oldType) { + if (newType.returnType() != oldType.returnType()) + throw newIllegalArgumentException("return types do not match", + oldType, newType); + if (reorder.length == oldType.parameterCount()) { + int limit = newType.parameterCount(); + boolean bad = false; + for (int j = 0; j < reorder.length; j++) { + int i = reorder[j]; + if (i < 0 || i >= limit) { + bad = true; break; + } + Class src = newType.parameterType(i); + Class dst = oldType.parameterType(j); + if (src != dst) + throw newIllegalArgumentException("parameter types do not match after reorder", + oldType, newType); + } + if (!bad) return; + } + throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder)); + } + + /** + * Produces a method handle of the requested return type which returns the given + * constant value every time it is invoked. + *

+ * Before the method handle is returned, the passed-in value is converted to the requested type. + * If the requested type is primitive, widening primitive conversions are attempted, + * else reference conversions are attempted. + *

The returned method handle is equivalent to {@code identity(type).bindTo(value)}. + * @param type the return type of the desired method handle + * @param value the value to return + * @return a method handle of the given return type and no arguments, which always returns the given value + * @throws NullPointerException if the {@code type} argument is null + * @throws ClassCastException if the value cannot be converted to the required return type + * @throws IllegalArgumentException if the given type is {@code void.class} + */ + public static + MethodHandle constant(Class type, Object value) { + if (type.isPrimitive()) { + if (type == void.class) + throw newIllegalArgumentException("void type"); + Wrapper w = Wrapper.forPrimitiveType(type); + return insertArguments(identity(type), 0, w.convert(value, type)); + } else { + return identity(type).bindTo(type.cast(value)); + } + } + + /** + * Produces a method handle which returns its sole argument when invoked. + * @param type the type of the sole parameter and return value of the desired method handle + * @return a unary method handle which accepts and returns the given type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if the given type is {@code void.class} + */ + public static + MethodHandle identity(Class type) { + if (type == void.class) + throw newIllegalArgumentException("void type"); + else if (type == Object.class) + return ValueConversions.identity(); + else if (type.isPrimitive()) + return ValueConversions.identity(Wrapper.forPrimitiveType(type)); + else + return MethodHandleImpl.makeReferenceIdentity(type); + } + + /** + * Provides a target method handle with one or more bound arguments + * in advance of the method handle's invocation. + * The formal parameters to the target corresponding to the bound + * arguments are called bound parameters. + * Returns a new method handle which saves away the bound arguments. + * When it is invoked, it receives arguments for any non-bound parameters, + * binds the saved arguments to their corresponding parameters, + * and calls the original target. + *

+ * The type of the new method handle will drop the types for the bound + * parameters from the original target type, since the new method handle + * will no longer require those arguments to be supplied by its callers. + *

+ * Each given argument object must match the corresponding bound parameter type. + * If a bound parameter type is a primitive, the argument object + * must be a wrapper, and will be unboxed to produce the primitive value. + *

+ * The {@code pos} argument selects which parameters are to be bound. + * It may range between zero and N-L (inclusively), + * where N is the arity of the target method handle + * and L is the length of the values array. + * @param target the method handle to invoke after the argument is inserted + * @param pos where to insert the argument (zero for the first) + * @param values the series of arguments to insert + * @return a method handle which inserts an additional argument, + * before calling the original method handle + * @throws NullPointerException if the target or the {@code values} array is null + * @see MethodHandle#bindTo + */ + public static + MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { + int insCount = values.length; + MethodType oldType = target.type(); + int outargs = oldType.parameterCount(); + int inargs = outargs - insCount; + if (inargs < 0) + throw newIllegalArgumentException("too many values to insert"); + if (pos < 0 || pos > inargs) + throw newIllegalArgumentException("no argument type to append"); + MethodHandle result = target; + for (int i = 0; i < insCount; i++) { + Object value = values[i]; + Class ptype = oldType.parameterType(pos+i); + if (ptype.isPrimitive()) { + char btype = 'I'; + Wrapper w = Wrapper.forPrimitiveType(ptype); + switch (w) { + case LONG: btype = 'J'; break; + case FLOAT: btype = 'F'; break; + case DOUBLE: btype = 'D'; break; + } + // perform unboxing and/or primitive conversion + value = w.convert(value, ptype); + result = result.bindArgument(pos, btype, value); + continue; + } + value = ptype.cast(value); // throw CCE if needed + if (pos == 0) { + result = result.bindReceiver(value); + } else { + result = result.bindArgument(pos, 'L', value); + } + } + return result; + } + + /** + * Produces a method handle which will discard some dummy arguments + * before calling some other specified target method handle. + * The type of the new method handle will be the same as the target's type, + * except it will also include the dummy argument types, + * at some given position. + *

+ * The {@code pos} argument may range between zero and N, + * where N is the arity of the target. + * If {@code pos} is zero, the dummy arguments will precede + * the target's real arguments; if {@code pos} is N + * they will come after. + *

+ * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
+MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2));
+assertEquals(bigType, d0.type());
+assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
+     * }
+ *

+ * This method is also equivalent to the following code: + *

+     * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))}
+     * 
+ * @param target the method handle to invoke after the arguments are dropped + * @param valueTypes the type(s) of the argument(s) to drop + * @param pos position of first argument to drop (zero for the leftmost) + * @return a method handle which drops arguments of the given types, + * before calling the original method handle + * @throws NullPointerException if the target is null, + * or if the {@code valueTypes} list or any of its elements is null + * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class}, + * or if {@code pos} is negative or greater than the arity of the target, + * or if the new method handle's type would have too many parameters + */ + public static + MethodHandle dropArguments(MethodHandle target, int pos, List> valueTypes) { + MethodType oldType = target.type(); // get NPE + int dropped = valueTypes.size(); + MethodType.checkSlotCount(dropped); + if (dropped == 0) return target; + int outargs = oldType.parameterCount(); + int inargs = outargs + dropped; + if (pos < 0 || pos >= inargs) + throw newIllegalArgumentException("no argument type to remove"); + ArrayList> ptypes = new ArrayList<>(oldType.parameterList()); + ptypes.addAll(pos, valueTypes); + MethodType newType = MethodType.methodType(oldType.returnType(), ptypes); + return target.dropArguments(newType, pos, dropped); + } + + /** + * Produces a method handle which will discard some dummy arguments + * before calling some other specified target method handle. + * The type of the new method handle will be the same as the target's type, + * except it will also include the dummy argument types, + * at some given position. + *

+ * The {@code pos} argument may range between zero and N, + * where N is the arity of the target. + * If {@code pos} is zero, the dummy arguments will precede + * the target's real arguments; if {@code pos} is N + * they will come after. + *

+ * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle d0 = dropArguments(cat, 0, String.class);
+assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
+MethodHandle d1 = dropArguments(cat, 1, String.class);
+assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
+MethodHandle d2 = dropArguments(cat, 2, String.class);
+assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
+MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
+assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
+     * }
+ *

+ * This method is also equivalent to the following code: + *

+     * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))}
+     * 
+ * @param target the method handle to invoke after the arguments are dropped + * @param valueTypes the type(s) of the argument(s) to drop + * @param pos position of first argument to drop (zero for the leftmost) + * @return a method handle which drops arguments of the given types, + * before calling the original method handle + * @throws NullPointerException if the target is null, + * or if the {@code valueTypes} array or any of its elements is null + * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class}, + * or if {@code pos} is negative or greater than the arity of the target, + * or if the new method handle's type would have + * too many parameters + */ + public static + MethodHandle dropArguments(MethodHandle target, int pos, Class... valueTypes) { + return dropArguments(target, pos, Arrays.asList(valueTypes)); + } + + /** + * Adapts a target method handle by pre-processing + * one or more of its arguments, each with its own unary filter function, + * and then calling the target with each pre-processed argument + * replaced by the result of its corresponding filter function. + *

+ * The pre-processing is performed by one or more method handles, + * specified in the elements of the {@code filters} array. + * The first element of the filter array corresponds to the {@code pos} + * argument of the target, and so on in sequence. + *

+ * Null arguments in the array are treated as identity functions, + * and the corresponding arguments left unchanged. + * (If there are no non-null elements in the array, the original target is returned.) + * Each filter is applied to the corresponding argument of the adapter. + *

+ * If a filter {@code F} applies to the {@code N}th argument of + * the target, then {@code F} must be a method handle which + * takes exactly one argument. The type of {@code F}'s sole argument + * replaces the corresponding argument type of the target + * in the resulting adapted method handle. + * The return type of {@code F} must be identical to the corresponding + * parameter type of the target. + *

+ * It is an error if there are elements of {@code filters} + * (null or not) + * which do not correspond to argument positions in the target. + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle upcase = lookup().findVirtual(String.class,
+  "toUpperCase", methodType(String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle f0 = filterArguments(cat, 0, upcase);
+assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
+MethodHandle f1 = filterArguments(cat, 1, upcase);
+assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
+MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
+assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
+     * }
+ *

Here is pseudocode for the resulting adapter: + *

{@code
+     * V target(P... p, A[i]... a[i], B... b);
+     * A[i] filter[i](V[i]);
+     * T adapter(P... p, V[i]... v[i], B... b) {
+     *   return target(p..., f[i](v[i])..., b...);
+     * }
+     * }
+ * + * @param target the method handle to invoke after arguments are filtered + * @param pos the position of the first argument to filter + * @param filters method handles to call initially on filtered arguments + * @return method handle which incorporates the specified argument filtering logic + * @throws NullPointerException if the target is null + * or if the {@code filters} array is null + * @throws IllegalArgumentException if a non-null element of {@code filters} + * does not match a corresponding argument type of target as described above, + * or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()}, + * or if the resulting method handle's type would have + * too many parameters + */ + public static + MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { + MethodType targetType = target.type(); + MethodHandle adapter = target; + MethodType adapterType = null; + assert((adapterType = targetType) != null); + int maxPos = targetType.parameterCount(); + if (pos + filters.length > maxPos) + throw newIllegalArgumentException("too many filters"); + int curPos = pos-1; // pre-incremented + for (MethodHandle filter : filters) { + curPos += 1; + if (filter == null) continue; // ignore null elements of filters + adapter = filterArgument(adapter, curPos, filter); + assert((adapterType = adapterType.changeParameterType(curPos, filter.type().parameterType(0))) != null); + } + assert(adapterType.equals(adapter.type())); + return adapter; + } + + /*non-public*/ static + MethodHandle filterArgument(MethodHandle target, int pos, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + if (filterType.parameterCount() != 1 + || filterType.returnType() != targetType.parameterType(pos)) + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + return MethodHandleImpl.makeCollectArguments(target, filter, pos, false); + } + + /** + * Adapts a target method handle by pre-processing + * a sub-sequence of its arguments with a filter (another method handle). + * The pre-processed arguments are replaced by the result (if any) of the + * filter function. + * The target is then called on the modified (usually shortened) argument list. + *

+ * If the filter returns a value, the target must accept that value as + * its argument in position {@code pos}, preceded and/or followed by + * any arguments not passed to the filter. + * If the filter returns void, the target must accept all arguments + * not passed to the filter. + * No arguments are reordered, and a result returned from the filter + * replaces (in order) the whole subsequence of arguments originally + * passed to the adapter. + *

+ * The argument types (if any) of the filter + * replace zero or one argument types of the target, at position {@code pos}, + * in the resulting adapted method handle. + * The return type of the filter (if any) must be identical to the + * argument type of the target at position {@code pos}, and that target argument + * is supplied by the return value of the filter. + *

+ * In all cases, {@code pos} must be greater than or equal to zero, and + * {@code pos} must also be less than or equal to the target's arity. + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+
+MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
+assertEquals("[strange]", (String) ts1.invokeExact("strange"));
+
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
+
+MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
+MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
+assertEquals("[top, [up, down], strange]",
+             (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
+assertEquals("[top, [up, down], [strange]]",
+             (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
+assertEquals("[top, [[up, down, strange], charm], bottom]",
+             (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
+     * }
+ *

Here is pseudocode for the resulting adapter: + *

{@code
+     * T target(A...,V,C...);
+     * V filter(B...);
+     * T adapter(A... a,B... b,C... c) {
+     *   V v = filter(b...);
+     *   return target(a...,v,c...);
+     * }
+     * // and if the filter has no arguments:
+     * T target2(A...,V,C...);
+     * V filter2();
+     * T adapter2(A... a,C... c) {
+     *   V v = filter2();
+     *   return target2(a...,v,c...);
+     * }
+     * // and if the filter has a void return:
+     * T target3(A...,C...);
+     * void filter3(B...);
+     * void adapter3(A... a,B... b,C... c) {
+     *   filter3(b...);
+     *   return target3(a...,c...);
+     * }
+     * }
+ *

+ * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to + * one which first "folds" the affected arguments, and then drops them, in separate + * steps as follows: + *

{@code
+     * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
+     * mh = MethodHandles.foldArguments(mh, coll); //step 1
+     * }
+ * If the target method handle consumes no arguments besides than the result + * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)} + * is equivalent to {@code filterReturnValue(coll, mh)}. + * If the filter method handle {@code coll} consumes one argument and produces + * a non-void result, then {@code collectArguments(mh, N, coll)} + * is equivalent to {@code filterArguments(mh, N, coll)}. + * Other equivalences are possible but would require argument permutation. + * + * @param target the method handle to invoke after filtering the subsequence of arguments + * @param pos the position of the first adapter argument to pass to the filter, + * and/or the target argument which receives the result of the filter + * @param filter method handle to call on the subsequence of arguments + * @return method handle which incorporates the specified argument subsequence filtering logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the return type of {@code filter} + * is non-void and is not the same as the {@code pos} argument of the target, + * or if {@code pos} is not between 0 and the target's arity, inclusive, + * or if the resulting method handle's type would have + * too many parameters + * @see MethodHandles#foldArguments + * @see MethodHandles#filterArguments + * @see MethodHandles#filterReturnValue + */ + public static + MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + if (filterType.returnType() != void.class && + filterType.returnType() != targetType.parameterType(pos)) + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + return MethodHandleImpl.makeCollectArguments(target, filter, pos, false); + } + + /** + * Adapts a target method handle by post-processing + * its return value (if any) with a filter (another method handle). + * The result of the filter is returned from the adapter. + *

+ * If the target returns a value, the filter must accept that value as + * its only argument. + * If the target returns void, the filter must accept no arguments. + *

+ * The return type of the filter + * replaces the return type of the target + * in the resulting adapted method handle. + * The argument type of the filter (if any) must be identical to the + * return type of the target. + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle length = lookup().findVirtual(String.class,
+  "length", methodType(int.class));
+System.out.println((String) cat.invokeExact("x", "y")); // xy
+MethodHandle f0 = filterReturnValue(cat, length);
+System.out.println((int) f0.invokeExact("x", "y")); // 2
+     * }
+ *

Here is pseudocode for the resulting adapter: + *

{@code
+     * V target(A...);
+     * T filter(V);
+     * T adapter(A... a) {
+     *   V v = target(a...);
+     *   return filter(v);
+     * }
+     * // and if the target has a void return:
+     * void target2(A...);
+     * T filter2();
+     * T adapter2(A... a) {
+     *   target2(a...);
+     *   return filter2();
+     * }
+     * // and if the filter has a void return:
+     * V target3(A...);
+     * void filter3(V);
+     * void adapter3(A... a) {
+     *   V v = target3(a...);
+     *   filter3(v);
+     * }
+     * }
+ * @param target the method handle to invoke before filtering the return value + * @param filter method handle to call on the return value + * @return method handle which incorporates the specified return value filtering logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the argument list of {@code filter} + * does not match the return type of target as described above + */ + public static + MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + Class rtype = targetType.returnType(); + int filterValues = filterType.parameterCount(); + if (filterValues == 0 + ? (rtype != void.class) + : (rtype != filterType.parameterType(0))) + throw newIllegalArgumentException("target and filter types do not match", target, filter); + // result = fold( lambda(retval, arg...) { filter(retval) }, + // lambda( arg...) { target(arg...) } ) + return MethodHandleImpl.makeCollectArguments(filter, target, 0, false); + } + + /** + * Adapts a target method handle by pre-processing + * some of its arguments, and then calling the target with + * the result of the pre-processing, inserted into the original + * sequence of arguments. + *

+ * The pre-processing is performed by {@code combiner}, a second method handle. + * Of the arguments passed to the adapter, the first {@code N} arguments + * are copied to the combiner, which is then called. + * (Here, {@code N} is defined as the parameter count of the combiner.) + * After this, control passes to the target, with any result + * from the combiner inserted before the original {@code N} incoming + * arguments. + *

+ * If the combiner returns a value, the first parameter type of the target + * must be identical with the return type of the combiner, and the next + * {@code N} parameter types of the target must exactly match the parameters + * of the combiner. + *

+ * If the combiner has a void return, no result will be inserted, + * and the first {@code N} parameter types of the target + * must exactly match the parameters of the combiner. + *

+ * The resulting adapter is the same type as the target, except that the + * first parameter type is dropped, + * if it corresponds to the result of the combiner. + *

+ * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments + * that either the combiner or the target does not wish to receive. + * If some of the incoming arguments are destined only for the combiner, + * consider using {@link MethodHandle#asCollector asCollector} instead, since those + * arguments will not need to be live on the stack on entry to the + * target.) + *

Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+  "println", methodType(void.class, String.class))
+    .bindTo(System.out);
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+MethodHandle catTrace = foldArguments(cat, trace);
+// also prints "boo":
+assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+     * }
+ *

Here is pseudocode for the resulting adapter: + *

{@code
+     * // there are N arguments in A...
+     * T target(V, A[N]..., B...);
+     * V combiner(A...);
+     * T adapter(A... a, B... b) {
+     *   V v = combiner(a...);
+     *   return target(v, a..., b...);
+     * }
+     * // and if the combiner has a void return:
+     * T target2(A[N]..., B...);
+     * void combiner2(A...);
+     * T adapter2(A... a, B... b) {
+     *   combiner2(a...);
+     *   return target2(a..., b...);
+     * }
+     * }
+ * @param target the method handle to invoke after arguments are combined + * @param combiner method handle to call initially on the incoming arguments + * @return method handle which incorporates the specified argument folding logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if {@code combiner}'s return type + * is non-void and not the same as the first argument type of + * the target, or if the initial {@code N} argument types + * of the target + * (skipping one matching the {@code combiner}'s return type) + * are not identical with the argument types of {@code combiner} + */ + public static + MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { + int pos = 0; + MethodType targetType = target.type(); + MethodType combinerType = combiner.type(); + int foldPos = pos; + int foldArgs = combinerType.parameterCount(); + int foldVals = combinerType.returnType() == void.class ? 0 : 1; + int afterInsertPos = foldPos + foldVals; + boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs); + if (ok && !(combinerType.parameterList() + .equals(targetType.parameterList().subList(afterInsertPos, + afterInsertPos + foldArgs)))) + ok = false; + if (ok && foldVals != 0 && !combinerType.returnType().equals(targetType.parameterType(0))) + ok = false; + if (!ok) + throw misMatchedTypes("target and combiner types", targetType, combinerType); + MethodType newType = targetType.dropParameterTypes(foldPos, afterInsertPos); + return MethodHandleImpl.makeCollectArguments(target, combiner, foldPos, true); + } + + /** + * Makes a method handle which adapts a target method handle, + * by guarding it with a test, a boolean-valued method handle. + * If the guard fails, a fallback handle is called instead. + * All three method handles must have the same corresponding + * argument and return types, except that the return type + * of the test must be boolean, and the test is allowed + * to have fewer arguments than the other two method handles. + *

Here is pseudocode for the resulting adapter: + *

{@code
+     * boolean test(A...);
+     * T target(A...,B...);
+     * T fallback(A...,B...);
+     * T adapter(A... a,B... b) {
+     *   if (test(a...))
+     *     return target(a..., b...);
+     *   else
+     *     return fallback(a..., b...);
+     * }
+     * }
+ * Note that the test arguments ({@code a...} in the pseudocode) cannot + * be modified by execution of the test, and so are passed unchanged + * from the caller to the target or fallback as appropriate. + * @param test method handle used for test, must return boolean + * @param target method handle to call if test passes + * @param fallback method handle to call if test fails + * @return method handle which incorporates the specified if/then/else logic + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if {@code test} does not return boolean, + * or if all three method types do not match (with the return + * type of {@code test} changed to match that of the target). + */ + public static + MethodHandle guardWithTest(MethodHandle test, + MethodHandle target, + MethodHandle fallback) { + MethodType gtype = test.type(); + MethodType ttype = target.type(); + MethodType ftype = fallback.type(); + if (!ttype.equals(ftype)) + throw misMatchedTypes("target and fallback types", ttype, ftype); + if (gtype.returnType() != boolean.class) + throw newIllegalArgumentException("guard type is not a predicate "+gtype); + List> targs = ttype.parameterList(); + List> gargs = gtype.parameterList(); + if (!targs.equals(gargs)) { + int gpc = gargs.size(), tpc = targs.size(); + if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs)) + throw misMatchedTypes("target and test types", ttype, gtype); + test = dropArguments(test, gpc, targs.subList(gpc, tpc)); + gtype = test.type(); + } + return MethodHandleImpl.makeGuardWithTest(test, target, fallback); + } + + static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) { + return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2); + } + + /** + * Makes a method handle which adapts a target method handle, + * by running it inside an exception handler. + * If the target returns normally, the adapter returns that value. + * If an exception matching the specified type is thrown, the fallback + * handle is called instead on the exception, plus the original arguments. + *

+ * The target and handler must have the same corresponding + * argument and return types, except that handler may omit trailing arguments + * (similarly to the predicate in {@link #guardWithTest guardWithTest}). + * Also, the handler must have an extra leading parameter of {@code exType} or a supertype. + *

Here is pseudocode for the resulting adapter: + *

{@code
+     * T target(A..., B...);
+     * T handler(ExType, A...);
+     * T adapter(A... a, B... b) {
+     *   try {
+     *     return target(a..., b...);
+     *   } catch (ExType ex) {
+     *     return handler(ex, a...);
+     *   }
+     * }
+     * }
+ * Note that the saved arguments ({@code a...} in the pseudocode) cannot + * be modified by execution of the target, and so are passed unchanged + * from the caller to the handler, if the handler is invoked. + *

+ * The target and handler must return the same type, even if the handler + * always throws. (This might happen, for instance, because the handler + * is simulating a {@code finally} clause). + * To create such a throwing handler, compose the handler creation logic + * with {@link #throwException throwException}, + * in order to create a method handle of the correct return type. + * @param target method handle to call + * @param exType the type of exception which the handler will catch + * @param handler method handle to call if a matching exception is thrown + * @return method handle which incorporates the specified try/catch logic + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if {@code handler} does not accept + * the given exception type, or if the method handle types do + * not match in their return types and their + * corresponding parameters + */ + public static + MethodHandle catchException(MethodHandle target, + Class exType, + MethodHandle handler) { + MethodType ttype = target.type(); + MethodType htype = handler.type(); + if (htype.parameterCount() < 1 || + !htype.parameterType(0).isAssignableFrom(exType)) + throw newIllegalArgumentException("handler does not accept exception type "+exType); + if (htype.returnType() != ttype.returnType()) + throw misMatchedTypes("target and handler return types", ttype, htype); + List> targs = ttype.parameterList(); + List> hargs = htype.parameterList(); + hargs = hargs.subList(1, hargs.size()); // omit leading parameter from handler + if (!targs.equals(hargs)) { + int hpc = hargs.size(), tpc = targs.size(); + if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs)) + throw misMatchedTypes("target and handler types", ttype, htype); + handler = dropArguments(handler, 1+hpc, targs.subList(hpc, tpc)); + htype = handler.type(); + } + return MethodHandleImpl.makeGuardWithCatch(target, exType, handler); + } + + /** + * Produces a method handle which will throw exceptions of the given {@code exType}. + * The method handle will accept a single argument of {@code exType}, + * and immediately throw it as an exception. + * The method type will nominally specify a return of {@code returnType}. + * The return type may be anything convenient: It doesn't matter to the + * method handle's behavior, since it will never return normally. + * @param returnType the return type of the desired method handle + * @param exType the parameter type of the desired method handle + * @return method handle which can throw the given exceptions + * @throws NullPointerException if either argument is null + */ + public static + MethodHandle throwException(Class returnType, Class exType) { + if (!Throwable.class.isAssignableFrom(exType)) + throw new ClassCastException(exType.getName()); + return MethodHandleImpl.throwException(MethodType.methodType(returnType, exType)); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodType.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1149 @@ +/* + * 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 sun.invoke.util.Wrapper; +import java.lang.ref.WeakReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import sun.invoke.util.BytecodeDescriptor; +import static java.lang.invoke.MethodHandleStatics.*; +import sun.invoke.util.VerifyType; + +/** + * A method type represents the arguments and return type accepted and + * returned by a method handle, or the arguments and return type passed + * and expected by a method handle caller. Method types must be properly + * matched between a method handle and all its callers, + * and the JVM's operations enforce this matching at, specifically + * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact} + * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution + * of {@code invokedynamic} instructions. + *

+ * The structure is a return type accompanied by any number of parameter types. + * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects. + * (For ease of exposition, we treat {@code void} as if it were a type. + * In fact, it denotes the absence of a return type.) + *

+ * All instances of {@code MethodType} are immutable. + * Two instances are completely interchangeable if they compare equal. + * Equality depends on pairwise correspondence of the return and parameter types and on nothing else. + *

+ * This type can be created only by factory methods. + * All factory methods may cache values, though caching is not guaranteed. + * Some factory methods are static, while others are virtual methods which + * modify precursor method types, e.g., by changing a selected parameter. + *

+ * Factory methods which operate on groups of parameter types + * are systematically presented in two versions, so that both Java arrays and + * Java lists can be used to work with groups of parameter types. + * The query methods {@code parameterArray} and {@code parameterList} + * also provide a choice between arrays and lists. + *

+ * {@code MethodType} objects are sometimes derived from bytecode instructions + * such as {@code invokedynamic}, specifically from the type descriptor strings associated + * with the instructions in a class file's constant pool. + *

+ * Like classes and strings, method types can also be represented directly + * in a class file's constant pool as constants. + * A method type may be loaded by an {@code ldc} instruction which refers + * to a suitable {@code CONSTANT_MethodType} constant pool entry. + * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string. + * (For full details on method type constants, + * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.) + *

+ * When the JVM materializes a {@code MethodType} from a descriptor string, + * all classes named in the descriptor must be accessible, and will be loaded. + * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.) + * This loading may occur at any time before the {@code MethodType} object is first derived. + * @author John Rose, JSR 292 EG + */ +public final +class MethodType implements java.io.Serializable { + private static final long serialVersionUID = 292L; // {rtype, {ptype...}} + + // The rtype and ptypes fields define the structural identity of the method type: + private final Class rtype; + private final Class[] ptypes; + + // The remaining fields are caches of various sorts: + private @Stable MethodTypeForm form; // erased form, plus cached data about primitives + private @Stable MethodType wrapAlt; // alternative wrapped/unwrapped version + private @Stable Invokers invokers; // cache of handy higher-order adapters + private @Stable String methodDescriptor; // cache for toMethodDescriptorString + + /** + * Check the given parameters for validity and store them into the final fields. + */ + private MethodType(Class rtype, Class[] ptypes, boolean trusted) { + checkRtype(rtype); + checkPtypes(ptypes); + this.rtype = rtype; + // defensively copy the array passed in by the user + this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length); + } + + /** + * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table. + * Does not check the given parameters for validity, and must be discarded after it is used as a searching key. + * The parameters are reversed for this constructor, so that is is not accidentally used. + */ + private MethodType(Class[] ptypes, Class rtype) { + this.rtype = rtype; + this.ptypes = ptypes; + } + + /*trusted*/ MethodTypeForm form() { return form; } + /*trusted*/ Class rtype() { return rtype; } + /*trusted*/ Class[] ptypes() { return ptypes; } + + void setForm(MethodTypeForm f) { form = f; } + + /** This number, mandated by the JVM spec as 255, + * is the maximum number of slots + * that any Java method can receive in its argument list. + * It limits both JVM signatures and method type objects. + * The longest possible invocation will look like + * {@code staticMethod(arg1, arg2, ..., arg255)} or + * {@code x.virtualMethod(arg1, arg2, ..., arg254)}. + */ + /*non-public*/ static final int MAX_JVM_ARITY = 255; // this is mandated by the JVM spec. + + /** This number is the maximum arity of a method handle, 254. + * It is derived from the absolute JVM-imposed arity by subtracting one, + * which is the slot occupied by the method handle itself at the + * beginning of the argument list used to invoke the method handle. + * The longest possible invocation will look like + * {@code mh.invoke(arg1, arg2, ..., arg254)}. + */ + // Issue: Should we allow MH.invokeWithArguments to go to the full 255? + /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1; // deduct one for mh receiver + + /** This number is the maximum arity of a method handle invoker, 253. + * It is derived from the absolute JVM-imposed arity by subtracting two, + * which are the slots occupied by invoke method handle, and the + * target method handle, which are both at the beginning of the argument + * list used to invoke the target method handle. + * The longest possible invocation will look like + * {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}. + */ + /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1; // deduct one more for invoker + + private static void checkRtype(Class rtype) { + Objects.requireNonNull(rtype); + } + private static void checkPtype(Class ptype) { + Objects.requireNonNull(ptype); + if (ptype == void.class) + throw newIllegalArgumentException("parameter type cannot be void"); + } + /** Return number of extra slots (count of long/double args). */ + private static int checkPtypes(Class[] ptypes) { + int slots = 0; + for (Class ptype : ptypes) { + checkPtype(ptype); + if (ptype == double.class || ptype == long.class) { + slots++; + } + } + checkSlotCount(ptypes.length + slots); + return slots; + } + static void checkSlotCount(int count) { + assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0); + // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work: + if ((count & MAX_JVM_ARITY) != count) + throw newIllegalArgumentException("bad parameter count "+count); + } + private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) { + if (num instanceof Integer) num = "bad index: "+num; + return new IndexOutOfBoundsException(num.toString()); + } + + static final ConcurrentWeakInternSet internTable = new ConcurrentWeakInternSet<>(); + + static final Class[] NO_PTYPES = {}; + + /** + * Finds or creates an instance of the given method type. + * @param rtype the return type + * @param ptypes the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class} + */ + public static + MethodType methodType(Class rtype, Class[] ptypes) { + return makeImpl(rtype, ptypes, false); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param rtype the return type + * @param ptypes the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class} + */ + public static + MethodType methodType(Class rtype, List> ptypes) { + boolean notrust = false; // random List impl. could return evil ptypes array + return makeImpl(rtype, listToArray(ptypes), notrust); + } + + private static Class[] listToArray(List> ptypes) { + // sanity check the size before the toArray call, since size might be huge + checkSlotCount(ptypes.size()); + return ptypes.toArray(NO_PTYPES); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The leading parameter type is prepended to the remaining array. + * @param rtype the return type + * @param ptype0 the first parameter type + * @param ptypes the remaining parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class} + */ + public static + MethodType methodType(Class rtype, Class ptype0, Class... ptypes) { + Class[] ptypes1 = new Class[1+ptypes.length]; + ptypes1[0] = ptype0; + System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length); + return makeImpl(rtype, ptypes1, true); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has no parameter types. + * @param rtype the return type + * @return a method type with the given return value + * @throws NullPointerException if {@code rtype} is null + */ + public static + MethodType methodType(Class rtype) { + return makeImpl(rtype, NO_PTYPES, true); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has the single given parameter type. + * @param rtype the return type + * @param ptype0 the parameter type + * @return a method type with the given return value and parameter type + * @throws NullPointerException if {@code rtype} or {@code ptype0} is null + * @throws IllegalArgumentException if {@code ptype0} is {@code void.class} + */ + public static + MethodType methodType(Class rtype, Class ptype0) { + return makeImpl(rtype, new Class[]{ ptype0 }, true); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has the same parameter types as {@code ptypes}, + * and the specified return type. + * @param rtype the return type + * @param ptypes the method type which supplies the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} is null + */ + public static + MethodType methodType(Class rtype, MethodType ptypes) { + return makeImpl(rtype, ptypes.ptypes, true); + } + + /** + * Sole factory method to find or create an interned method type. + * @param rtype desired return type + * @param ptypes desired parameter types + * @param trusted whether the ptypes can be used without cloning + * @return the unique method type of the desired structure + */ + /*trusted*/ static + MethodType makeImpl(Class rtype, Class[] ptypes, boolean trusted) { + MethodType mt = internTable.get(new MethodType(ptypes, rtype)); + if (mt != null) + return mt; + if (ptypes.length == 0) { + ptypes = NO_PTYPES; trusted = true; + } + mt = new MethodType(rtype, ptypes, trusted); + // promote the object to the Real Thing, and reprobe + mt.form = MethodTypeForm.findForm(mt); + return internTable.add(mt); + } + private static final MethodType[] objectOnlyTypes = new MethodType[20]; + + /** + * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All parameters and the return type will be {@code Object}, + * except the final array parameter if any, which will be {@code Object[]}. + * @param objectArgCount number of parameters (excluding the final array parameter if any) + * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]} + * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments + * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true) + * @see #genericMethodType(int) + */ + public static + MethodType genericMethodType(int objectArgCount, boolean finalArray) { + MethodType mt; + checkSlotCount(objectArgCount); + int ivarargs = (!finalArray ? 0 : 1); + int ootIndex = objectArgCount*2 + ivarargs; + if (ootIndex < objectOnlyTypes.length) { + mt = objectOnlyTypes[ootIndex]; + if (mt != null) return mt; + } + Class[] ptypes = new Class[objectArgCount + ivarargs]; + Arrays.fill(ptypes, Object.class); + if (ivarargs != 0) ptypes[objectArgCount] = Object[].class; + mt = makeImpl(Object.class, ptypes, true); + if (ootIndex < objectOnlyTypes.length) { + objectOnlyTypes[ootIndex] = mt; // cache it here also! + } + return mt; + } + + /** + * Finds or creates a method type whose components are all {@code Object}. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All parameters and the return type will be Object. + * @param objectArgCount number of parameters + * @return a generally applicable method type, for all calls of the given argument count + * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 + * @see #genericMethodType(int, boolean) + */ + public static + MethodType genericMethodType(int objectArgCount) { + return genericMethodType(objectArgCount, false); + } + + /** + * Finds or creates a method type with a single different parameter type. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the index (zero-based) of the parameter type to change + * @param nptype a new parameter type to replace the old one with + * @return the same type, except with the selected parameter changed + * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()} + * @throws IllegalArgumentException if {@code nptype} is {@code void.class} + * @throws NullPointerException if {@code nptype} is null + */ + public MethodType changeParameterType(int num, Class nptype) { + if (parameterType(num) == nptype) return this; + checkPtype(nptype); + Class[] nptypes = ptypes.clone(); + nptypes[num] = nptype; + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the position (zero-based) of the inserted parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) inserted + * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType insertParameterTypes(int num, Class... ptypesToInsert) { + int len = ptypes.length; + if (num < 0 || num > len) + throw newIndexOutOfBoundsException(num); + int ins = checkPtypes(ptypesToInsert); + checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins); + int ilen = ptypesToInsert.length; + if (ilen == 0) return this; + Class[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen); + System.arraycopy(nptypes, num, nptypes, num+ilen, len-num); + System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen); + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list + * @return the same type, except with the selected parameter(s) appended + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType appendParameterTypes(Class... ptypesToInsert) { + return insertParameterTypes(parameterCount(), ptypesToInsert); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the position (zero-based) of the inserted parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) inserted + * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType insertParameterTypes(int num, List> ptypesToInsert) { + return insertParameterTypes(num, listToArray(ptypesToInsert)); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list + * @return the same type, except with the selected parameter(s) appended + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType appendParameterTypes(List> ptypesToInsert) { + return insertParameterTypes(parameterCount(), ptypesToInsert); + } + + /** + * Finds or creates a method type with modified parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param start the position (zero-based) of the first replaced parameter type(s) + * @param end the position (zero-based) after the last replaced parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) replaced + * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()} + * or if {@code end} is negative or greater than {@code parameterCount()} + * or if {@code start} is greater than {@code end} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class... ptypesToInsert) { + if (start == end) + return insertParameterTypes(start, ptypesToInsert); + int len = ptypes.length; + if (!(0 <= start && start <= end && end <= len)) + throw newIndexOutOfBoundsException("start="+start+" end="+end); + int ilen = ptypesToInsert.length; + if (ilen == 0) + return dropParameterTypes(start, end); + return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert); + } + + /** + * Finds or creates a method type with some parameter types omitted. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param start the index (zero-based) of the first parameter type to remove + * @param end the index (greater than {@code start}) of the first parameter type after not to remove + * @return the same type, except with the selected parameter(s) removed + * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()} + * or if {@code end} is negative or greater than {@code parameterCount()} + * or if {@code start} is greater than {@code end} + */ + public MethodType dropParameterTypes(int start, int end) { + int len = ptypes.length; + if (!(0 <= start && start <= end && end <= len)) + throw newIndexOutOfBoundsException("start="+start+" end="+end); + if (start == end) return this; + Class[] nptypes; + if (start == 0) { + if (end == len) { + // drop all parameters + nptypes = NO_PTYPES; + } else { + // drop initial parameter(s) + nptypes = Arrays.copyOfRange(ptypes, end, len); + } + } else { + if (end == len) { + // drop trailing parameter(s) + nptypes = Arrays.copyOfRange(ptypes, 0, start); + } else { + int tail = len - end; + nptypes = Arrays.copyOfRange(ptypes, 0, start + tail); + System.arraycopy(ptypes, end, nptypes, start, tail); + } + } + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with a different return type. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param nrtype a return parameter type to replace the old one with + * @return the same type, except with the return type change + * @throws NullPointerException if {@code nrtype} is null + */ + public MethodType changeReturnType(Class nrtype) { + if (returnType() == nrtype) return this; + return makeImpl(nrtype, ptypes, true); + } + + /** + * Reports if this type contains a primitive argument or return value. + * The return type {@code void} counts as a primitive. + * @return true if any of the types are primitives + */ + public boolean hasPrimitives() { + return form.hasPrimitives(); + } + + /** + * Reports if this type contains a wrapper argument or return value. + * Wrappers are types which box primitive values, such as {@link Integer}. + * The reference type {@code java.lang.Void} counts as a wrapper, + * if it occurs as a return type. + * @return true if any of the types are wrappers + */ + public boolean hasWrappers() { + return unwrap() != this; + } + + /** + * Erases all reference types to {@code Object}. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All primitive types (including {@code void}) will remain unchanged. + * @return a version of the original type with all reference types replaced + */ + public MethodType erase() { + return form.erasedType(); + } + + /** + * Erases all reference types to {@code Object}, and all subword types to {@code int}. + * This is the reduced type polymorphism used by private methods + * such as {@link MethodHandle#invokeBasic invokeBasic}. + * @return a version of the original type with all reference and subword types replaced + */ + /*non-public*/ MethodType basicType() { + return form.basicType(); + } + + /** + * @return a version of the original type with MethodHandle prepended as the first argument + */ + /*non-public*/ MethodType invokerType() { + return insertParameterTypes(0, MethodHandle.class); + } + + /** + * Converts all types, both reference and primitive, to {@code Object}. + * Convenience method for {@link #genericMethodType(int) genericMethodType}. + * The expression {@code type.wrap().erase()} produces the same value + * as {@code type.generic()}. + * @return a version of the original type with all types replaced + */ + public MethodType generic() { + return genericMethodType(parameterCount()); + } + + /** + * Converts all primitive types to their corresponding wrapper types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All reference types (including wrapper types) will remain unchanged. + * A {@code void} return type is changed to the type {@code java.lang.Void}. + * The expression {@code type.wrap().erase()} produces the same value + * as {@code type.generic()}. + * @return a version of the original type with all primitive types replaced + */ + public MethodType wrap() { + return hasPrimitives() ? wrapWithPrims(this) : this; + } + + /** + * Converts all wrapper types to their corresponding primitive types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All primitive types (including {@code void}) will remain unchanged. + * A return type of {@code java.lang.Void} is changed to {@code void}. + * @return a version of the original type with all wrapper types replaced + */ + public MethodType unwrap() { + MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this); + return unwrapWithNoPrims(noprims); + } + + private static MethodType wrapWithPrims(MethodType pt) { + assert(pt.hasPrimitives()); + MethodType wt = pt.wrapAlt; + if (wt == null) { + // fill in lazily + wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP); + assert(wt != null); + pt.wrapAlt = wt; + } + return wt; + } + + private static MethodType unwrapWithNoPrims(MethodType wt) { + assert(!wt.hasPrimitives()); + MethodType uwt = wt.wrapAlt; + if (uwt == null) { + // fill in lazily + uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP); + if (uwt == null) + uwt = wt; // type has no wrappers or prims at all + wt.wrapAlt = uwt; + } + return uwt; + } + + /** + * Returns the parameter type at the specified index, within this method type. + * @param num the index (zero-based) of the desired parameter type + * @return the selected parameter type + * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()} + */ + public Class parameterType(int num) { + return ptypes[num]; + } + /** + * Returns the number of parameter types in this method type. + * @return the number of parameter types + */ + public int parameterCount() { + return ptypes.length; + } + /** + * Returns the return type of this method type. + * @return the return type + */ + public Class returnType() { + return rtype; + } + + /** + * Presents the parameter types as a list (a convenience method). + * The list will be immutable. + * @return the parameter types (as an immutable list) + */ + public List> parameterList() { + return Collections.unmodifiableList(Arrays.asList(ptypes)); + } + + /*non-public*/ Class lastParameterType() { + int len = ptypes.length; + return len == 0 ? void.class : ptypes[len-1]; + } + + /** + * Presents the parameter types as an array (a convenience method). + * Changes to the array will not result in changes to the type. + * @return the parameter types (as a fresh copy if necessary) + */ + public Class[] parameterArray() { + return ptypes.clone(); + } + + /** + * Compares the specified object with this type for equality. + * That is, it returns true if and only if the specified object + * is also a method type with exactly the same parameters and return type. + * @param x object to compare + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object x) { + return this == x || x instanceof MethodType && equals((MethodType)x); + } + + private boolean equals(MethodType that) { + return this.rtype == that.rtype + && Arrays.equals(this.ptypes, that.ptypes); + } + + /** + * Returns the hash code value for this method type. + * It is defined to be the same as the hashcode of a List + * whose elements are the return type followed by the + * parameter types. + * @return the hash code value for this method type + * @see Object#hashCode() + * @see #equals(Object) + * @see List#hashCode() + */ + @Override + public int hashCode() { + int hashCode = 31 + rtype.hashCode(); + for (Class ptype : ptypes) + hashCode = 31*hashCode + ptype.hashCode(); + return hashCode; + } + + /** + * Returns a string representation of the method type, + * of the form {@code "(PT0,PT1...)RT"}. + * The string representation of a method type is a + * parenthesis enclosed, comma separated list of type names, + * followed immediately by the return type. + *

+ * Each type is represented by its + * {@link java.lang.Class#getSimpleName simple name}. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < ptypes.length; i++) { + if (i > 0) sb.append(","); + sb.append(ptypes[i].getSimpleName()); + } + sb.append(")"); + sb.append(rtype.getSimpleName()); + return sb.toString(); + } + + + /*non-public*/ + boolean isViewableAs(MethodType newType) { + if (!VerifyType.isNullConversion(returnType(), newType.returnType())) + return false; + int argc = parameterCount(); + if (argc != newType.parameterCount()) + return false; + for (int i = 0; i < argc; i++) { + if (!VerifyType.isNullConversion(newType.parameterType(i), parameterType(i))) + return false; + } + return true; + } + /*non-public*/ + boolean isCastableTo(MethodType newType) { + int argc = parameterCount(); + if (argc != newType.parameterCount()) + return false; + return true; + } + /*non-public*/ + boolean isConvertibleTo(MethodType newType) { + if (!canConvert(returnType(), newType.returnType())) + return false; + int argc = parameterCount(); + if (argc != newType.parameterCount()) + return false; + for (int i = 0; i < argc; i++) { + if (!canConvert(newType.parameterType(i), parameterType(i))) + return false; + } + return true; + } + /*non-public*/ + static boolean canConvert(Class src, Class dst) { + // short-circuit a few cases: + if (src == dst || dst == Object.class) return true; + // the remainder of this logic is documented in MethodHandle.asType + if (src.isPrimitive()) { + // can force void to an explicit null, a la reflect.Method.invoke + // can also force void to a primitive zero, by analogy + if (src == void.class) return true; //or !dst.isPrimitive()? + Wrapper sw = Wrapper.forPrimitiveType(src); + if (dst.isPrimitive()) { + // P->P must widen + return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw); + } else { + // P->R must box and widen + return dst.isAssignableFrom(sw.wrapperType()); + } + } else if (dst.isPrimitive()) { + // any value can be dropped + if (dst == void.class) return true; + Wrapper dw = Wrapper.forPrimitiveType(dst); + // R->P must be able to unbox (from a dynamically chosen type) and widen + // For example: + // Byte/Number/Comparable/Object -> dw:Byte -> byte. + // Character/Comparable/Object -> dw:Character -> char + // Boolean/Comparable/Object -> dw:Boolean -> boolean + // This means that dw must be cast-compatible with src. + if (src.isAssignableFrom(dw.wrapperType())) { + return true; + } + // The above does not work if the source reference is strongly typed + // to a wrapper whose primitive must be widened. For example: + // Byte -> unbox:byte -> short/int/long/float/double + // Character -> unbox:char -> int/long/float/double + if (Wrapper.isWrapperType(src) && + dw.isConvertibleFrom(Wrapper.forWrapperType(src))) { + // can unbox from src and then widen to dst + return true; + } + // We have already covered cases which arise due to runtime unboxing + // of a reference type which covers several wrapper types: + // Object -> cast:Integer -> unbox:int -> long/float/double + // Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double + // An marginal case is Number -> dw:Character -> char, which would be OK if there were a + // subclass of Number which wraps a value that can convert to char. + // Since there is none, we don't need an extra check here to cover char or boolean. + return false; + } else { + // R->R always works, since null is always valid dynamically + return true; + } + } + + /// Queries which have to do with the bytecode architecture + + /** Reports the number of JVM stack slots required to invoke a method + * of this type. Note that (for historical reasons) the JVM requires + * a second stack slot to pass long and double arguments. + * So this method returns {@link #parameterCount() parameterCount} plus the + * number of long and double parameters (if any). + *

+ * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and invokedynamic. + * @return the number of JVM stack slots for this type's parameters + */ + /*non-public*/ int parameterSlotCount() { + return form.parameterSlotCount(); + } + + /*non-public*/ Invokers invokers() { + Invokers inv = invokers; + if (inv != null) return inv; + invokers = inv = new Invokers(this); + return inv; + } + + /** Reports the number of JVM stack slots which carry all parameters including and after + * the given position, which must be in the range of 0 to + * {@code parameterCount} inclusive. Successive parameters are + * more shallowly stacked, and parameters are indexed in the bytecodes + * according to their trailing edge. Thus, to obtain the depth + * in the outgoing call stack of parameter {@code N}, obtain + * the {@code parameterSlotDepth} of its trailing edge + * at position {@code N+1}. + *

+ * Parameters of type {@code long} and {@code double} occupy + * two stack slots (for historical reasons) and all others occupy one. + * Therefore, the number returned is the number of arguments + * including and after the given parameter, + * plus the number of long or double arguments + * at or after after the argument for the given parameter. + *

+ * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and invokedynamic. + * @param num an index (zero-based, inclusive) within the parameter types + * @return the index of the (shallowest) JVM stack slot transmitting the + * given parameter + * @throws IllegalArgumentException if {@code num} is negative or greater than {@code parameterCount()} + */ + /*non-public*/ int parameterSlotDepth(int num) { + if (num < 0 || num > ptypes.length) + parameterType(num); // force a range check + return form.parameterToArgSlot(num-1); + } + + /** Reports the number of JVM stack slots required to receive a return value + * from a method of this type. + * If the {@link #returnType() return type} is void, it will be zero, + * else if the return type is long or double, it will be two, else one. + *

+ * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and invokedynamic. + * @return the number of JVM stack slots (0, 1, or 2) for this type's return value + * Will be removed for PFD. + */ + /*non-public*/ int returnSlotCount() { + return form.returnSlotCount(); + } + + /** + * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * Any class or interface name embedded in the descriptor string + * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)} + * on the given loader (or if it is null, on the system class loader). + *

+ * Note that it is possible to encounter method types which cannot be + * constructed by this method, because their component types are + * not all reachable from a common class loader. + *

+ * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and {@code invokedynamic}. + * @param descriptor a bytecode-level type descriptor string "(T...)T" + * @param loader the class loader in which to look up the types + * @return a method type matching the bytecode-level type descriptor + * @throws NullPointerException if the string is null + * @throws IllegalArgumentException if the string is not well-formed + * @throws TypeNotPresentException if a named type cannot be found + */ + public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader) + throws IllegalArgumentException, TypeNotPresentException + { + if (!descriptor.startsWith("(") || // also generates NPE if needed + descriptor.indexOf(')') < 0 || + descriptor.indexOf('.') >= 0) + throw new IllegalArgumentException("not a method descriptor: "+descriptor); + List> types = BytecodeDescriptor.parseMethod(descriptor, loader); + Class rtype = types.remove(types.size() - 1); + checkSlotCount(types.size()); + Class[] ptypes = listToArray(types); + return makeImpl(rtype, ptypes, true); + } + + /** + * Produces a bytecode descriptor representation of the method type. + *

+ * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}. + * Two distinct classes which share a common name but have different class loaders + * will appear identical when viewed within descriptor strings. + *

+ * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and {@code invokedynamic}. + * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString}, + * because the latter requires a suitable class loader argument. + * @return the bytecode type descriptor representation + */ + public String toMethodDescriptorString() { + String desc = methodDescriptor; + if (desc == null) { + desc = BytecodeDescriptor.unparse(this); + methodDescriptor = desc; + } + return desc; + } + + /*non-public*/ static String toFieldDescriptorString(Class cls) { + return BytecodeDescriptor.unparse(cls); + } + + /// Serialization. + + /** + * There are no serializable fields for {@code MethodType}. + */ + private static final java.io.ObjectStreamField[] serialPersistentFields = { }; + + /** + * Save the {@code MethodType} instance to a stream. + * + * @serialData + * For portability, the serialized format does not refer to named fields. + * Instead, the return type and parameter type arrays are written directly + * from the {@code writeObject} method, using two calls to {@code s.writeObject} + * as follows: + *

{@code
+s.writeObject(this.returnType());
+s.writeObject(this.parameterArray());
+     * }
+ *

+ * The deserialized field values are checked as if they were + * provided to the factory method {@link #methodType(Class,Class[]) methodType}. + * For example, null values, or {@code void} parameter types, + * will lead to exceptions during deserialization. + * @param s the stream to write the object to + * @throws java.io.IOException if there is a problem writing the object + */ + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + s.defaultWriteObject(); // requires serialPersistentFields to be an empty array + s.writeObject(returnType()); + s.writeObject(parameterArray()); + } + + /** + * Reconstitute the {@code MethodType} instance from a stream (that is, + * deserialize it). + * This instance is a scratch object with bogus final fields. + * It provides the parameters to the factory method called by + * {@link #readResolve readResolve}. + * After that call it is discarded. + * @param s the stream to read the object from + * @throws java.io.IOException if there is a problem reading the object + * @throws ClassNotFoundException if one of the component classes cannot be resolved + * @see #MethodType() + * @see #readResolve + * @see #writeObject + */ + private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); // requires serialPersistentFields to be an empty array + + Class returnType = (Class) s.readObject(); + Class[] parameterArray = (Class[]) s.readObject(); + + // Probably this object will never escape, but let's check + // the field values now, just to be sure. + checkRtype(returnType); + checkPtypes(parameterArray); + + parameterArray = parameterArray.clone(); // make sure it is unshared + MethodType_init(returnType, parameterArray); + } + + /** + * For serialization only. + * Sets the final fields to null, pending {@code Unsafe.putObject}. + */ + private MethodType() { + this.rtype = null; + this.ptypes = null; + } + private void MethodType_init(Class rtype, Class[] ptypes) { + // In order to communicate these values to readResolve, we must + // store them into the implementation-specific final fields. + checkRtype(rtype); + checkPtypes(ptypes); + UNSAFE.putObject(this, rtypeOffset, rtype); + UNSAFE.putObject(this, ptypesOffset, ptypes); + } + + // Support for resetting final fields while deserializing + private static final long rtypeOffset, ptypesOffset; + static { + try { + rtypeOffset = UNSAFE.objectFieldOffset + (MethodType.class.getDeclaredField("rtype")); + ptypesOffset = UNSAFE.objectFieldOffset + (MethodType.class.getDeclaredField("ptypes")); + } catch (Exception ex) { + throw new Error(ex); + } + } + + /** + * Resolves and initializes a {@code MethodType} object + * after serialization. + * @return the fully initialized {@code MethodType} object + */ + private Object readResolve() { + // Do not use a trusted path for deserialization: + //return makeImpl(rtype, ptypes, true); + // Verify all operands, and make sure ptypes is unshared: + return methodType(rtype, ptypes); + } + + /** + * Simple implementation of weak concurrent intern set. + * + * @param interned type + */ + private static class ConcurrentWeakInternSet { + + private final ConcurrentMap, WeakEntry> map; + private final ReferenceQueue stale; + + public ConcurrentWeakInternSet() { + this.map = new ConcurrentHashMap<>(); + this.stale = new ReferenceQueue<>(); + } + + /** + * Get the existing interned element. + * This method returns null if no element is interned. + * + * @param elem element to look up + * @return the interned element + */ + public T get(T elem) { + if (elem == null) throw new NullPointerException(); + expungeStaleElements(); + + WeakEntry value = map.get(new WeakEntry<>(elem)); + if (value != null) { + T res = value.get(); + if (res != null) { + return res; + } + } + return null; + } + + /** + * Interns the element. + * Always returns non-null element, matching the one in the intern set. + * Under the race against another add(), it can return different + * element, if another thread beats us to interning it. + * + * @param elem element to add + * @return element that was actually added + */ + public T add(T elem) { + if (elem == null) throw new NullPointerException(); + + // Playing double race here, and so spinloop is required. + // First race is with two concurrent updaters. + // Second race is with GC purging weak ref under our feet. + // Hopefully, we almost always end up with a single pass. + T interned; + WeakEntry e = new WeakEntry<>(elem, stale); + do { + expungeStaleElements(); + WeakEntry exist = map.putIfAbsent(e, e); + interned = (exist == null) ? elem : exist.get(); + } while (interned == null); + return interned; + } + + private void expungeStaleElements() { + Reference reference; + while ((reference = stale.poll()) != null) { + map.remove(reference); + } + } + + private static class WeakEntry extends WeakReference { + + public final int hashcode; + + public WeakEntry(T key, ReferenceQueue queue) { + super(key, queue); + hashcode = key.hashCode(); + } + + public WeakEntry(T key) { + super(key); + hashcode = key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeakEntry) { + Object that = ((WeakEntry) obj).get(); + Object mine = get(); + return (that == null || mine == null) ? (this == obj) : mine.equals(that); + } + return false; + } + + @Override + public int hashCode() { + return hashcode; + } + + } + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MethodTypeForm.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MethodTypeForm.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,388 @@ +/* + * 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 sun.invoke.util.Wrapper; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; + import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; + +/** + * Shared information for a group of method types, which differ + * only by reference types, and therefore share a common erasure + * and wrapping. + *

+ * For an empirical discussion of the structure of method types, + * see + * the thread "Avoiding Boxing" on jvm-languages. + * There are approximately 2000 distinct erased method types in the JDK. + * There are a little over 10 times that number of unerased types. + * No more than half of these are likely to be loaded at once. + * @author John Rose + */ +final class MethodTypeForm { + final int[] argToSlotTable, slotToArgTable; + final long argCounts; // packed slot & value counts + final long primCounts; // packed prim & double counts + final int vmslots; // total number of parameter slots + final MethodType erasedType; // the canonical erasure + final MethodType basicType; // the canonical erasure, with primitives simplified + + // Cached adapter information: + @Stable String typeString; // argument type signature characters + @Stable MethodHandle genericInvoker; // JVM hook for inexact invoke + @Stable MethodHandle basicInvoker; // cached instance of MH.invokeBasic + @Stable MethodHandle namedFunctionInvoker; // cached helper for LF.NamedFunction + + // Cached lambda form information, for basic types only: + final @Stable LambdaForm[] lambdaForms; + // Indexes into lambdaForms: + static final int + LF_INVVIRTUAL = 0, // DMH invokeVirtual + LF_INVSTATIC = 1, + LF_INVSPECIAL = 2, + LF_NEWINVSPECIAL = 3, + LF_INVINTERFACE = 4, + LF_INVSTATIC_INIT = 5, // DMH invokeStatic with barrier + LF_INTERPRET = 6, // LF interpreter + LF_COUNTER = 7, // CMH wrapper + LF_REINVOKE = 8, // other wrapper + LF_EX_LINKER = 9, // invokeExact_MT + LF_EX_INVOKER = 10, // invokeExact MH + LF_GEN_LINKER = 11, + LF_GEN_INVOKER = 12, + LF_CS_LINKER = 13, // linkToCallSite_CS + LF_MH_LINKER = 14, // linkToCallSite_MH + LF_LIMIT = 15; + + public MethodType erasedType() { + return erasedType; + } + + public MethodType basicType() { + return basicType; + } + + public LambdaForm cachedLambdaForm(int which) { + return lambdaForms[which]; + } + + public LambdaForm setCachedLambdaForm(int which, LambdaForm form) { + // Should we perform some sort of CAS, to avoid racy duplication? + return lambdaForms[which] = form; + } + + public MethodHandle basicInvoker() { + assert(erasedType == basicType) : "erasedType: " + erasedType + " != basicType: " + basicType; // primitives must be flattened also + MethodHandle invoker = basicInvoker; + if (invoker != null) return invoker; + invoker = DirectMethodHandle.make(invokeBasicMethod(basicType)); + basicInvoker = invoker; + return invoker; + } + + // This next one is called from LambdaForm.NamedFunction.. + /*non-public*/ static MemberName invokeBasicMethod(MethodType basicType) { + assert(basicType == basicType.basicType()); + try { + // Do approximately the same as this public API call: + // Lookup.findVirtual(MethodHandle.class, name, type); + // But bypass access and corner case checks, since we know exactly what we need. + return IMPL_LOOKUP.resolveOrFail(REF_invokeVirtual, MethodHandle.class, "invokeBasic", basicType); + } catch (ReflectiveOperationException ex) { + throw newInternalError("JVM cannot find invoker for "+basicType, ex); + } + } + + /** + * Build an MTF for a given type, which must have all references erased to Object. + * This MTF will stand for that type and all un-erased variations. + * Eagerly compute some basic properties of the type, common to all variations. + */ + protected MethodTypeForm(MethodType erasedType) { + this.erasedType = erasedType; + + Class[] ptypes = erasedType.ptypes(); + int ptypeCount = ptypes.length; + int pslotCount = ptypeCount; // temp. estimate + int rtypeCount = 1; // temp. estimate + int rslotCount = 1; // temp. estimate + + int[] argToSlotTab = null, slotToArgTab = null; + + // Walk the argument types, looking for primitives. + int pac = 0, lac = 0, prc = 0, lrc = 0; + Class[] epts = ptypes; + Class[] bpts = epts; + for (int i = 0; i < epts.length; i++) { + Class pt = epts[i]; + if (pt != Object.class) { + ++pac; + Wrapper w = Wrapper.forPrimitiveType(pt); + if (w.isDoubleWord()) ++lac; + if (w.isSubwordOrInt() && pt != int.class) { + if (bpts == epts) + bpts = bpts.clone(); + bpts[i] = int.class; + } + } + } + pslotCount += lac; // #slots = #args + #longs + Class rt = erasedType.returnType(); + Class bt = rt; + if (rt != Object.class) { + ++prc; // even void.class counts as a prim here + Wrapper w = Wrapper.forPrimitiveType(rt); + if (w.isDoubleWord()) ++lrc; + if (w.isSubwordOrInt() && rt != int.class) + bt = int.class; + // adjust #slots, #args + if (rt == void.class) + rtypeCount = rslotCount = 0; + else + rslotCount += lrc; + } + if (epts == bpts && bt == rt) { + this.basicType = erasedType; + } else { + this.basicType = MethodType.makeImpl(bt, bpts, true); + } + if (lac != 0) { + int slot = ptypeCount + lac; + slotToArgTab = new int[slot+1]; + argToSlotTab = new int[1+ptypeCount]; + argToSlotTab[0] = slot; // argument "-1" is past end of slots + for (int i = 0; i < epts.length; i++) { + Class pt = epts[i]; + Wrapper w = Wrapper.forBasicType(pt); + if (w.isDoubleWord()) --slot; + --slot; + slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note + argToSlotTab[1+i] = slot; + } + assert(slot == 0); // filled the table + } + this.primCounts = pack(lrc, prc, lac, pac); + this.argCounts = pack(rslotCount, rtypeCount, pslotCount, ptypeCount); + if (slotToArgTab == null) { + int slot = ptypeCount; // first arg is deepest in stack + slotToArgTab = new int[slot+1]; + argToSlotTab = new int[1+ptypeCount]; + argToSlotTab[0] = slot; // argument "-1" is past end of slots + for (int i = 0; i < ptypeCount; i++) { + --slot; + slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note + argToSlotTab[1+i] = slot; + } + } + this.argToSlotTable = argToSlotTab; + this.slotToArgTable = slotToArgTab; + + if (pslotCount >= 256) throw newIllegalArgumentException("too many arguments"); + + // send a few bits down to the JVM: + this.vmslots = parameterSlotCount(); + + if (basicType == erasedType) { + lambdaForms = new LambdaForm[LF_LIMIT]; + } else { + lambdaForms = null; // could be basicType.form().lambdaForms; + } + } + + private static long pack(int a, int b, int c, int d) { + assert(((a|b|c|d) & ~0xFFFF) == 0); + long hw = ((a << 16) | b), lw = ((c << 16) | d); + return (hw << 32) | lw; + } + private static char unpack(long packed, int word) { // word==0 => return a, ==3 => return d + assert(word <= 3); + return (char)(packed >> ((3-word) * 16)); + } + + public int parameterCount() { // # outgoing values + return unpack(argCounts, 3); + } + public int parameterSlotCount() { // # outgoing interpreter slots + return unpack(argCounts, 2); + } + public int returnCount() { // = 0 (V), or 1 + return unpack(argCounts, 1); + } + public int returnSlotCount() { // = 0 (V), 2 (J/D), or 1 + return unpack(argCounts, 0); + } + public int primitiveParameterCount() { + return unpack(primCounts, 3); + } + public int longPrimitiveParameterCount() { + return unpack(primCounts, 2); + } + public int primitiveReturnCount() { // = 0 (obj), or 1 + return unpack(primCounts, 1); + } + public int longPrimitiveReturnCount() { // = 1 (J/D), or 0 + return unpack(primCounts, 0); + } + public boolean hasPrimitives() { + return primCounts != 0; + } + public boolean hasNonVoidPrimitives() { + if (primCounts == 0) return false; + if (primitiveParameterCount() != 0) return true; + return (primitiveReturnCount() != 0 && returnCount() != 0); + } + public boolean hasLongPrimitives() { + return (longPrimitiveParameterCount() | longPrimitiveReturnCount()) != 0; + } + public int parameterToArgSlot(int i) { + return argToSlotTable[1+i]; + } + public int argSlotToParameter(int argSlot) { + // Note: Empty slots are represented by zero in this table. + // Valid arguments slots contain incremented entries, so as to be non-zero. + // We return -1 the caller to mean an empty slot. + return slotToArgTable[argSlot] - 1; + } + + static MethodTypeForm findForm(MethodType mt) { + MethodType erased = canonicalize(mt, ERASE, ERASE); + if (erased == null) { + // It is already erased. Make a new MethodTypeForm. + return new MethodTypeForm(mt); + } else { + // Share the MethodTypeForm with the erased version. + return erased.form(); + } + } + + /** Codes for {@link #canonicalize(java.lang.Class, int)}. + * ERASE means change every reference to {@code Object}. + * WRAP means convert primitives (including {@code void} to their + * corresponding wrapper types. UNWRAP means the reverse of WRAP. + * INTS means convert all non-void primitive types to int or long, + * according to size. LONGS means convert all non-void primitives + * to long, regardless of size. RAW_RETURN means convert a type + * (assumed to be a return type) to int if it is smaller than an int, + * or if it is void. + */ + public static final int NO_CHANGE = 0, ERASE = 1, WRAP = 2, UNWRAP = 3, INTS = 4, LONGS = 5, RAW_RETURN = 6; + + /** Canonicalize the types in the given method type. + * If any types change, intern the new type, and return it. + * Otherwise return null. + */ + public static MethodType canonicalize(MethodType mt, int howRet, int howArgs) { + Class[] ptypes = mt.ptypes(); + Class[] ptc = MethodTypeForm.canonicalizes(ptypes, howArgs); + Class rtype = mt.returnType(); + Class rtc = MethodTypeForm.canonicalize(rtype, howRet); + if (ptc == null && rtc == null) { + // It is already canonical. + return null; + } + // Find the erased version of the method type: + if (rtc == null) rtc = rtype; + if (ptc == null) ptc = ptypes; + return MethodType.makeImpl(rtc, ptc, true); + } + + /** Canonicalize the given return or param type. + * Return null if the type is already canonicalized. + */ + static Class canonicalize(Class t, int how) { + Class ct; + if (t == Object.class) { + // no change, ever + } else if (!t.isPrimitive()) { + switch (how) { + case UNWRAP: + ct = Wrapper.asPrimitiveType(t); + if (ct != t) return ct; + break; + case RAW_RETURN: + case ERASE: + return Object.class; + } + } else if (t == void.class) { + // no change, usually + switch (how) { + case RAW_RETURN: + return int.class; + case WRAP: + return Void.class; + } + } else { + // non-void primitive + switch (how) { + case WRAP: + return Wrapper.asWrapperType(t); + case INTS: + if (t == int.class || t == long.class) + return null; // no change + if (t == double.class) + return long.class; + return int.class; + case LONGS: + if (t == long.class) + return null; // no change + return long.class; + case RAW_RETURN: + if (t == int.class || t == long.class || + t == float.class || t == double.class) + return null; // no change + // everything else returns as an int + return int.class; + } + } + // no change; return null to signify + return null; + } + + /** Canonicalize each param type in the given array. + * Return null if all types are already canonicalized. + */ + static Class[] canonicalizes(Class[] ts, int how) { + Class[] cs = null; + for (int imax = ts.length, i = 0; i < imax; i++) { + Class c = canonicalize(ts[i], how); + if (c == void.class) + c = null; // a Void parameter was unwrapped to void; ignore + if (c != null) { + if (cs == null) + cs = ts.clone(); + cs[i] = c; + } + } + return cs; + } + + @Override + public String toString() { + return "Form"+erasedType; + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/MutableCallSite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/MutableCallSite.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,283 @@ +/* + * 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.util.concurrent.atomic.AtomicInteger; + +/** + * A {@code MutableCallSite} is a {@link CallSite} whose target variable + * behaves like an ordinary field. + * An {@code invokedynamic} instruction linked to a {@code MutableCallSite} delegates + * all calls to the site's current target. + * The {@linkplain CallSite#dynamicInvoker dynamic invoker} of a mutable call site + * also delegates each call to the site's current target. + *

+ * Here is an example of a mutable call site which introduces a + * state variable into a method handle chain. + * + *

{@code
+MutableCallSite name = new MutableCallSite(MethodType.methodType(String.class));
+MethodHandle MH_name = name.dynamicInvoker();
+MethodType MT_str1 = MethodType.methodType(String.class);
+MethodHandle MH_upcase = MethodHandles.lookup()
+    .findVirtual(String.class, "toUpperCase", MT_str1);
+MethodHandle worker1 = MethodHandles.filterReturnValue(MH_name, MH_upcase);
+name.setTarget(MethodHandles.constant(String.class, "Rocky"));
+assertEquals("ROCKY", (String) worker1.invokeExact());
+name.setTarget(MethodHandles.constant(String.class, "Fred"));
+assertEquals("FRED", (String) worker1.invokeExact());
+// (mutation can be continued indefinitely)
+ * }
+ *

+ * The same call site may be used in several places at once. + *

{@code
+MethodType MT_str2 = MethodType.methodType(String.class, String.class);
+MethodHandle MH_cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle MH_dear = MethodHandles.insertArguments(MH_cat, 1, ", dear?");
+MethodHandle worker2 = MethodHandles.filterReturnValue(MH_name, MH_dear);
+assertEquals("Fred, dear?", (String) worker2.invokeExact());
+name.setTarget(MethodHandles.constant(String.class, "Wilma"));
+assertEquals("WILMA", (String) worker1.invokeExact());
+assertEquals("Wilma, dear?", (String) worker2.invokeExact());
+ * }
+ *

+ * Non-synchronization of target values: + * A write to a mutable call site's target does not force other threads + * to become aware of the updated value. Threads which do not perform + * suitable synchronization actions relative to the updated call site + * may cache the old target value and delay their use of the new target + * value indefinitely. + * (This is a normal consequence of the Java Memory Model as applied + * to object fields.) + *

+ * The {@link #syncAll syncAll} operation provides a way to force threads + * to accept a new target value, even if there is no other synchronization. + *

+ * For target values which will be frequently updated, consider using + * a {@linkplain VolatileCallSite volatile call site} instead. + * @author John Rose, JSR 292 EG + */ +public class MutableCallSite extends CallSite { + /** + * Creates a blank call site object with the given method type. + * The initial target is set to a method handle of the given type + * which will throw an {@link IllegalStateException} if called. + *

+ * The type of the call site is permanently set to the given type. + *

+ * Before this {@code CallSite} object is returned from a bootstrap method, + * or invoked in some other manner, + * it is usually provided with a more useful target method, + * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}. + * @param type the method type that this call site will have + * @throws NullPointerException if the proposed type is null + */ + public MutableCallSite(MethodType type) { + super(type); + } + + /** + * Creates a call site object with an initial target method handle. + * The type of the call site is permanently set to the initial target's type. + * @param target the method handle that will be the initial target of the call site + * @throws NullPointerException if the proposed target is null + */ + public MutableCallSite(MethodHandle target) { + super(target); + } + + /** + * Returns the target method of the call site, which behaves + * like a normal field of the {@code MutableCallSite}. + *

+ * The interactions of {@code getTarget} with memory are the same + * as of a read from an ordinary variable, such as an array element or a + * non-volatile, non-final field. + *

+ * In particular, the current thread may choose to reuse the result + * of a previous read of the target from memory, and may fail to see + * a recent update to the target by another thread. + * + * @return the linkage state of this call site, a method handle which can change over time + * @see #setTarget + */ + @Override public final MethodHandle getTarget() { + return target; + } + + /** + * Updates the target method of this call site, as a normal variable. + * The type of the new target must agree with the type of the old target. + *

+ * The interactions with memory are the same + * as of a write to an ordinary variable, such as an array element or a + * non-volatile, non-final field. + *

+ * In particular, unrelated threads may fail to see the updated target + * until they perform a read from memory. + * Stronger guarantees can be created by putting appropriate operations + * into the bootstrap method and/or the target methods used + * at any given call site. + * + * @param newTarget the new target + * @throws NullPointerException if the proposed new target is null + * @throws WrongMethodTypeException if the proposed new target + * has a method type that differs from the previous target + * @see #getTarget + */ + @Override public void setTarget(MethodHandle newTarget) { + checkTargetChange(this.target, newTarget); + setTargetNormal(newTarget); + } + + /** + * {@inheritDoc} + */ + @Override + public final MethodHandle dynamicInvoker() { + return makeDynamicInvoker(); + } + + /** + * Performs a synchronization operation on each call site in the given array, + * forcing all other threads to throw away any cached values previously + * loaded from the target of any of the call sites. + *

+ * This operation does not reverse any calls that have already started + * on an old target value. + * (Java supports {@linkplain java.lang.Object#wait() forward time travel} only.) + *

+ * The overall effect is to force all future readers of each call site's target + * to accept the most recently stored value. + * ("Most recently" is reckoned relative to the {@code syncAll} itself.) + * Conversely, the {@code syncAll} call may block until all readers have + * (somehow) decached all previous versions of each call site's target. + *

+ * To avoid race conditions, calls to {@code setTarget} and {@code syncAll} + * should generally be performed under some sort of mutual exclusion. + * Note that reader threads may observe an updated target as early + * as the {@code setTarget} call that install the value + * (and before the {@code syncAll} that confirms the value). + * On the other hand, reader threads may observe previous versions of + * the target until the {@code syncAll} call returns + * (and after the {@code setTarget} that attempts to convey the updated version). + *

+ * This operation is likely to be expensive and should be used sparingly. + * If possible, it should be buffered for batch processing on sets of call sites. + *

+ * If {@code sites} contains a null element, + * a {@code NullPointerException} will be raised. + * In this case, some non-null elements in the array may be + * processed before the method returns abnormally. + * Which elements these are (if any) is implementation-dependent. + * + *

Java Memory Model details

+ * In terms of the Java Memory Model, this operation performs a synchronization + * action which is comparable in effect to the writing of a volatile variable + * by the current thread, and an eventual volatile read by every other thread + * that may access one of the affected call sites. + *

+ * The following effects are apparent, for each individual call site {@code S}: + *

    + *
  • A new volatile variable {@code V} is created, and written by the current thread. + * As defined by the JMM, this write is a global synchronization event. + *
  • As is normal with thread-local ordering of write events, + * every action already performed by the current thread is + * taken to happen before the volatile write to {@code V}. + * (In some implementations, this means that the current thread + * performs a global release operation.) + *
  • Specifically, the write to the current target of {@code S} is + * taken to happen before the volatile write to {@code V}. + *
  • The volatile write to {@code V} is placed + * (in an implementation specific manner) + * in the global synchronization order. + *
  • Consider an arbitrary thread {@code T} (other than the current thread). + * If {@code T} executes a synchronization action {@code A} + * after the volatile write to {@code V} (in the global synchronization order), + * it is therefore required to see either the current target + * of {@code S}, or a later write to that target, + * if it executes a read on the target of {@code S}. + * (This constraint is called "synchronization-order consistency".) + *
  • The JMM specifically allows optimizing compilers to elide + * reads or writes of variables that are known to be useless. + * Such elided reads and writes have no effect on the happens-before + * relation. Regardless of this fact, the volatile {@code V} + * will not be elided, even though its written value is + * indeterminate and its read value is not used. + *
+ * Because of the last point, the implementation behaves as if a + * volatile read of {@code V} were performed by {@code T} + * immediately after its action {@code A}. In the local ordering + * of actions in {@code T}, this read happens before any future + * read of the target of {@code S}. It is as if the + * implementation arbitrarily picked a read of {@code S}'s target + * by {@code T}, and forced a read of {@code V} to precede it, + * thereby ensuring communication of the new target value. + *

+ * As long as the constraints of the Java Memory Model are obeyed, + * implementations may delay the completion of a {@code syncAll} + * operation while other threads ({@code T} above) continue to + * use previous values of {@code S}'s target. + * However, implementations are (as always) encouraged to avoid + * livelock, and to eventually require all threads to take account + * of the updated target. + * + *

+ * Discussion: + * For performance reasons, {@code syncAll} is not a virtual method + * on a single call site, but rather applies to a set of call sites. + * Some implementations may incur a large fixed overhead cost + * for processing one or more synchronization operations, + * but a small incremental cost for each additional call site. + * In any case, this operation is likely to be costly, since + * other threads may have to be somehow interrupted + * in order to make them notice the updated target value. + * However, it may be observed that a single call to synchronize + * several sites has the same formal effect as many calls, + * each on just one of the sites. + * + *

+ * Implementation Note: + * Simple implementations of {@code MutableCallSite} may use + * a volatile variable for the target of a mutable call site. + * In such an implementation, the {@code syncAll} method can be a no-op, + * and yet it will conform to the JMM behavior documented above. + * + * @param sites an array of call sites to be synchronized + * @throws NullPointerException if the {@code sites} array reference is null + * or the array contains a null + */ + public static void syncAll(MutableCallSite[] sites) { + if (sites.length == 0) return; + STORE_BARRIER.lazySet(0); + for (int i = 0; i < sites.length; i++) { + sites[i].getClass(); // trigger NPE on first null + } + // FIXME: NYI + } + private static final AtomicInteger STORE_BARRIER = new AtomicInteger(); +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/ProxyClassesDumper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/ProxyClassesDumper.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,147 @@ +/* + * Copyright (c) 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 sun.util.logging.PlatformLogger; + +import java.io.FilePermission; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Helper class used by InnerClassLambdaMetafactory to log generated classes + * + * @implNote + *

Because this class is called by LambdaMetafactory, make use + * of lambda lead to recursive calls cause stack overflow. + */ +final class ProxyClassesDumper { + private static final char[] HEX = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + private static final char[] BAD_CHARS = { + '\\', ':', '*', '?', '"', '<', '>', '|' + }; + private static final String[] REPLACEMENT = { + "%5C", "%3A", "%2A", "%3F", "%22", "%3C", "%3E", "%7C" + }; + + private final Path dumpDir; + + public static ProxyClassesDumper getInstance(String path) { + if (null == path) { + return null; + } + try { + path = path.trim(); + final Path dir = Paths.get(path.length() == 0 ? "." : path); + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + validateDumpDir(dir); + return null; + } + }, null, new FilePermission("<>", "read, write")); + return new ProxyClassesDumper(dir); + } catch (InvalidPathException ex) { + PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) + .warning("Path " + path + " is not valid - dumping disabled", ex); + } catch (IllegalArgumentException iae) { + PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) + .warning(iae.getMessage() + " - dumping disabled"); + } + return null; + } + + private ProxyClassesDumper(Path path) { + dumpDir = Objects.requireNonNull(path); + } + + private static void validateDumpDir(Path path) { + if (!Files.exists(path)) { + throw new IllegalArgumentException("Directory " + path + " does not exist"); + } else if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Path " + path + " is not a directory"); + } else if (!Files.isWritable(path)) { + throw new IllegalArgumentException("Directory " + path + " is not writable"); + } + } + + public static String encodeForFilename(String className) { + final int len = className.length(); + StringBuilder sb = new StringBuilder(len); + + for (int i = 0; i < len; i++) { + char c = className.charAt(i); + // control characters + if (c <= 31) { + sb.append('%'); + sb.append(HEX[c >> 4 & 0x0F]); + sb.append(HEX[c & 0x0F]); + } else { + int j = 0; + for (; j < BAD_CHARS.length; j++) { + if (c == BAD_CHARS[j]) { + sb.append(REPLACEMENT[j]); + break; + } + } + if (j >= BAD_CHARS.length) { + sb.append(c); + } + } + } + + return sb.toString(); + } + + public void dumpClass(String className, final byte[] classBytes) { + Path file; + try { + file = dumpDir.resolve(encodeForFilename(className) + ".class"); + } catch (InvalidPathException ex) { + PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) + .warning("Invalid path for class " + className); + return; + } + + try { + Path dir = file.getParent(); + Files.createDirectories(dir); + Files.write(file, classBytes); + } catch (Exception ignore) { + PlatformLogger.getLogger(ProxyClassesDumper.class.getName()) + .warning("Exception writing to path at " + file.toString()); + // simply don't care if this operation failed + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/SerializedLambda.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/SerializedLambda.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2012, 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.io.Serializable; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; + +/** + * Serialized form of a lambda expression. The properties of this class + * represent the information that is present at the lambda factory site, including + * static metafactory arguments such as the identity of the primary functional + * interface method and the identity of the implementation method, as well as + * dynamic metafactory arguments such as values captured from the lexical scope + * at the time of lambda capture. + * + *

Implementors of serializable lambdas, such as compilers or language + * runtime libraries, are expected to ensure that instances deserialize properly. + * One means to do so is to ensure that the {@code writeReplace} method returns + * an instance of {@code SerializedLambda}, rather than allowing default + * serialization to proceed. + * + *

{@code SerializedLambda} has a {@code readResolve} method that looks for + * a (possibly private) static method called + * {@code $deserializeLambda$(SerializedLambda)} in the capturing class, invokes + * that with itself as the first argument, and returns the result. Lambda classes + * implementing {@code $deserializeLambda$} are responsible for validating + * that the properties of the {@code SerializedLambda} are consistent with a + * lambda actually captured by that class. + * + * @see LambdaMetafactory + */ +public final class SerializedLambda implements Serializable { + private static final long serialVersionUID = 8025925345765570181L; + private final Class capturingClass; + private final String functionalInterfaceClass; + private final String functionalInterfaceMethodName; + private final String functionalInterfaceMethodSignature; + private final String implClass; + private final String implMethodName; + private final String implMethodSignature; + private final int implMethodKind; + private final String instantiatedMethodType; + private final Object[] capturedArgs; + + /** + * Create a {@code SerializedLambda} from the low-level information present + * at the lambda factory site. + * + * @param capturingClass The class in which the lambda expression appears + * @param functionalInterfaceClass Name, in slash-delimited form, of static + * type of the returned lambda object + * @param functionalInterfaceMethodName Name of the functional interface + * method for the present at the + * lambda factory site + * @param functionalInterfaceMethodSignature Signature of the functional + * interface method present at + * the lambda factory site + * @param implMethodKind Method handle kind for the implementation method + * @param implClass Name, in slash-delimited form, for the class holding + * the implementation method + * @param implMethodName Name of the implementation method + * @param implMethodSignature Signature of the implementation method + * @param instantiatedMethodType The signature of the primary functional + * interface method after type variables + * are substituted with their instantiation + * from the capture site + * @param capturedArgs The dynamic arguments to the lambda factory site, + * which represent variables captured by + * the lambda + */ + public SerializedLambda(Class capturingClass, + String functionalInterfaceClass, + String functionalInterfaceMethodName, + String functionalInterfaceMethodSignature, + int implMethodKind, + String implClass, + String implMethodName, + String implMethodSignature, + String instantiatedMethodType, + Object[] capturedArgs) { + this.capturingClass = capturingClass; + this.functionalInterfaceClass = functionalInterfaceClass; + this.functionalInterfaceMethodName = functionalInterfaceMethodName; + this.functionalInterfaceMethodSignature = functionalInterfaceMethodSignature; + this.implMethodKind = implMethodKind; + this.implClass = implClass; + this.implMethodName = implMethodName; + this.implMethodSignature = implMethodSignature; + this.instantiatedMethodType = instantiatedMethodType; + this.capturedArgs = Objects.requireNonNull(capturedArgs).clone(); + } + + /** + * Get the name of the class that captured this lambda. + * @return the name of the class that captured this lambda + */ + public String getCapturingClass() { + return capturingClass.getName().replace('.', '/'); + } + + /** + * Get the name of the invoked type to which this + * lambda has been converted + * @return the name of the functional interface class to which + * this lambda has been converted + */ + public String getFunctionalInterfaceClass() { + return functionalInterfaceClass; + } + + /** + * Get the name of the primary method for the functional interface + * to which this lambda has been converted. + * @return the name of the primary methods of the functional interface + */ + public String getFunctionalInterfaceMethodName() { + return functionalInterfaceMethodName; + } + + /** + * Get the signature of the primary method for the functional + * interface to which this lambda has been converted. + * @return the signature of the primary method of the functional + * interface + */ + public String getFunctionalInterfaceMethodSignature() { + return functionalInterfaceMethodSignature; + } + + /** + * Get the name of the class containing the implementation + * method. + * @return the name of the class containing the implementation + * method + */ + public String getImplClass() { + return implClass; + } + + /** + * Get the name of the implementation method. + * @return the name of the implementation method + */ + public String getImplMethodName() { + return implMethodName; + } + + /** + * Get the signature of the implementation method. + * @return the signature of the implementation method + */ + public String getImplMethodSignature() { + return implMethodSignature; + } + + /** + * Get the method handle kind (see {@link MethodHandleInfo}) of + * the implementation method. + * @return the method handle kind of the implementation method + */ + public int getImplMethodKind() { + return implMethodKind; + } + + /** + * Get the signature of the primary functional interface method + * after type variables are substituted with their instantiation + * from the capture site. + * @return the signature of the primary functional interface method + * after type variable processing + */ + public final String getInstantiatedMethodType() { + return instantiatedMethodType; + } + + /** + * Get the count of dynamic arguments to the lambda capture site. + * @return the count of dynamic arguments to the lambda capture site + */ + public int getCapturedArgCount() { + return capturedArgs.length; + } + + /** + * Get a dynamic argument to the lambda capture site. + * @param i the argument to capture + * @return a dynamic argument to the lambda capture site + */ + public Object getCapturedArg(int i) { + return capturedArgs[i]; + } + + private Object readResolve() throws ReflectiveOperationException { + try { + Method deserialize = AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class); + m.setAccessible(true); + return m; + } + }); + + return deserialize.invoke(null, this); + } + catch (PrivilegedActionException e) { + Exception cause = e.getException(); + if (cause instanceof ReflectiveOperationException) + throw (ReflectiveOperationException) cause; + else if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + else + throw new RuntimeException("Exception in SerializedLambda.readResolve", e); + } + } + + @Override + public String toString() { + String implKind=MethodHandleInfo.referenceKindToString(implMethodKind); + return String.format("SerializedLambda[%s=%s, %s=%s.%s:%s, " + + "%s=%s %s.%s:%s, %s=%s, %s=%d]", + "capturingClass", capturingClass, + "functionalInterfaceMethod", functionalInterfaceClass, + functionalInterfaceMethodName, + functionalInterfaceMethodSignature, + "implementation", + implKind, + implClass, implMethodName, implMethodSignature, + "instantiatedMethodType", instantiatedMethodType, + "numCaptured", capturedArgs.length); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/SimpleMethodHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/SimpleMethodHandle.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008, 2012, 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 static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A method handle whose behavior is determined only by its LambdaForm. + * @author jrose + */ +final class SimpleMethodHandle extends MethodHandle { + private SimpleMethodHandle(MethodType type, LambdaForm form) { + super(type, form); + } + + /*non-public*/ static SimpleMethodHandle make(MethodType type, LambdaForm form) { + return new SimpleMethodHandle(type, form); + } + + @Override + MethodHandle bindArgument(int pos, char basicType, Object value) { + MethodType type2 = type().dropParameterTypes(pos, pos+1); + LambdaForm form2 = internalForm().bind(1+pos, BoundMethodHandle.SpeciesData.EMPTY); + return BoundMethodHandle.bindSingle(type2, form2, basicType, value); + } + + @Override + MethodHandle dropArguments(MethodType srcType, int pos, int drops) { + LambdaForm newForm = internalForm().addArguments(pos, srcType.parameterList().subList(pos, pos+drops)); + return new SimpleMethodHandle(srcType, newForm); + } + + @Override + MethodHandle permuteArguments(MethodType newType, int[] reorder) { + LambdaForm form2 = internalForm().permuteArguments(1, reorder, basicTypes(newType.parameterList())); + return new SimpleMethodHandle(newType, form2); + } + + @Override + MethodHandle copyWith(MethodType mt, LambdaForm lf) { + return new SimpleMethodHandle(mt, lf); + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/Stable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/Stable.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012, 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.lang.annotation.*; + +/** + * A field may be annotated as stable if all of its component variables + * changes value at most once. + * A field's value counts as its component value. + * If the field is typed as an array, then all the non-null components + * of the array, of depth up to the rank of the field's array type, + * also count as component values. + * By extension, any variable (either array or field) which has annotated + * as stable is called a stable variable, and its non-null or non-zero + * value is called a stable value. + *

+ * Since all fields begin with a default value of null for references + * (resp., zero for primitives), it follows that this annotation indicates + * that the first non-null (resp., non-zero) value stored in the field + * will never be changed. + *

+ * If the field is not of an array type, there are no array elements, + * then the value indicated as stable is simply the value of the field. + * If the dynamic type of the field value is an array but the static type + * is not, the components of the array are not regarded as stable. + *

+ * If the field is an array type, then both the field value and + * all the components of the field value (if the field value is non-null) + * are indicated to be stable. + * If the field type is an array type with rank {@code N > 1}, + * then each component of the field value (if the field value is non-null), + * is regarded as a stable array of rank {@code N-1}. + *

+ * Fields which are declared {@code final} may also be annotated as stable. + * Since final fields already behave as stable values, such an annotation + * indicates no additional information, unless the type of the field is + * an array type. + *

+ * It is (currently) undefined what happens if a field annotated as stable + * is given a third value. In practice, if the JVM relies on this annotation + * to promote a field reference to a constant, it may be that the Java memory + * model would appear to be broken, if such a constant (the second value of the field) + * is used as the value of the field even after the field value has changed. + */ +/* package-private */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@interface Stable { +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/SwitchPoint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/SwitchPoint.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2010, 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; + +/** + *

+ * A {@code SwitchPoint} is an object which can publish state transitions to other threads. + * A switch point is initially in the valid state, but may at any time be + * changed to the invalid state. Invalidation cannot be reversed. + * A switch point can combine a guarded pair of method handles into a + * guarded delegator. + * The guarded delegator is a method handle which delegates to one of the old method handles. + * The state of the switch point determines which of the two gets the delegation. + *

+ * A single switch point may be used to control any number of method handles. + * (Indirectly, therefore, it can control any number of call sites.) + * This is done by using the single switch point as a factory for combining + * any number of guarded method handle pairs into guarded delegators. + *

+ * When a guarded delegator is created from a guarded pair, the pair + * is wrapped in a new method handle {@code M}, + * which is permanently associated with the switch point that created it. + * Each pair consists of a target {@code T} and a fallback {@code F}. + * While the switch point is valid, invocations to {@code M} are delegated to {@code T}. + * After it is invalidated, invocations are delegated to {@code F}. + *

+ * Invalidation is global and immediate, as if the switch point contained a + * volatile boolean variable consulted on every call to {@code M}. + * The invalidation is also permanent, which means the switch point + * can change state only once. + * The switch point will always delegate to {@code F} after being invalidated. + * At that point {@code guardWithTest} may ignore {@code T} and return {@code F}. + *

+ * Here is an example of a switch point in action: + *

{@code
+MethodHandle MH_strcat = MethodHandles.lookup()
+    .findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class));
+SwitchPoint spt = new SwitchPoint();
+assert(!spt.hasBeenInvalidated());
+// the following steps may be repeated to re-use the same switch point:
+MethodHandle worker1 = MH_strcat;
+MethodHandle worker2 = MethodHandles.permuteArguments(MH_strcat, MH_strcat.type(), 1, 0);
+MethodHandle worker = spt.guardWithTest(worker1, worker2);
+assertEquals("method", (String) worker.invokeExact("met", "hod"));
+SwitchPoint.invalidateAll(new SwitchPoint[]{ spt });
+assert(spt.hasBeenInvalidated());
+assertEquals("hodmet", (String) worker.invokeExact("met", "hod"));
+ * }
+ *

+ * Discussion: + * Switch points are useful without subclassing. They may also be subclassed. + * This may be useful in order to associate application-specific invalidation logic + * with the switch point. + * Notice that there is no permanent association between a switch point and + * the method handles it produces and consumes. + * The garbage collector may collect method handles produced or consumed + * by a switch point independently of the lifetime of the switch point itself. + *

+ * Implementation Note: + * A switch point behaves as if implemented on top of {@link MutableCallSite}, + * approximately as follows: + *

{@code
+public class SwitchPoint {
+  private static final MethodHandle
+    K_true  = MethodHandles.constant(boolean.class, true),
+    K_false = MethodHandles.constant(boolean.class, false);
+  private final MutableCallSite mcs;
+  private final MethodHandle mcsInvoker;
+  public SwitchPoint() {
+    this.mcs = new MutableCallSite(K_true);
+    this.mcsInvoker = mcs.dynamicInvoker();
+  }
+  public MethodHandle guardWithTest(
+                MethodHandle target, MethodHandle fallback) {
+    // Note:  mcsInvoker is of type ()boolean.
+    // Target and fallback may take any arguments, but must have the same type.
+    return MethodHandles.guardWithTest(this.mcsInvoker, target, fallback);
+  }
+  public static void invalidateAll(SwitchPoint[] spts) {
+    List<MutableCallSite> mcss = new ArrayList<>();
+    for (SwitchPoint spt : spts)  mcss.add(spt.mcs);
+    for (MutableCallSite mcs : mcss)  mcs.setTarget(K_false);
+    MutableCallSite.syncAll(mcss.toArray(new MutableCallSite[0]));
+  }
+}
+ * }
+ * @author Remi Forax, JSR 292 EG + */ +public class SwitchPoint { + private static final MethodHandle + K_true = MethodHandles.constant(boolean.class, true), + K_false = MethodHandles.constant(boolean.class, false); + + private final MutableCallSite mcs; + private final MethodHandle mcsInvoker; + + /** + * Creates a new switch point. + */ + public SwitchPoint() { + this.mcs = new MutableCallSite(K_true); + this.mcsInvoker = mcs.dynamicInvoker(); + } + + /** + * Determines if this switch point has been invalidated yet. + * + *

+ * Discussion: + * Because of the one-way nature of invalidation, once a switch point begins + * to return true for {@code hasBeenInvalidated}, + * it will always do so in the future. + * On the other hand, a valid switch point visible to other threads may + * be invalidated at any moment, due to a request by another thread. + *

+ * Since invalidation is a global and immediate operation, + * the execution of this query, on a valid switchpoint, + * must be internally sequenced with any + * other threads that could cause invalidation. + * This query may therefore be expensive. + * The recommended way to build a boolean-valued method handle + * which queries the invalidation state of a switch point {@code s} is + * to call {@code s.guardWithTest} on + * {@link MethodHandles#constant constant} true and false method handles. + * + * @return true if this switch point has been invalidated + */ + public boolean hasBeenInvalidated() { + return (mcs.getTarget() != K_true); + } + + /** + * Returns a method handle which always delegates either to the target or the fallback. + * The method handle will delegate to the target exactly as long as the switch point is valid. + * After that, it will permanently delegate to the fallback. + *

+ * The target and fallback must be of exactly the same method type, + * and the resulting combined method handle will also be of this type. + * + * @param target the method handle selected by the switch point as long as it is valid + * @param fallback the method handle selected by the switch point after it is invalidated + * @return a combined method handle which always calls either the target or fallback + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the two method types do not match + * @see MethodHandles#guardWithTest + */ + public MethodHandle guardWithTest(MethodHandle target, MethodHandle fallback) { + if (mcs.getTarget() == K_false) + return fallback; // already invalid + return MethodHandles.guardWithTest(mcsInvoker, target, fallback); + } + + /** + * Sets all of the given switch points into the invalid state. + * After this call executes, no thread will observe any of the + * switch points to be in a valid state. + *

+ * This operation is likely to be expensive and should be used sparingly. + * If possible, it should be buffered for batch processing on sets of switch points. + *

+ * If {@code switchPoints} contains a null element, + * a {@code NullPointerException} will be raised. + * In this case, some non-null elements in the array may be + * processed before the method returns abnormally. + * Which elements these are (if any) is implementation-dependent. + * + *

+ * Discussion: + * For performance reasons, {@code invalidateAll} is not a virtual method + * on a single switch point, but rather applies to a set of switch points. + * Some implementations may incur a large fixed overhead cost + * for processing one or more invalidation operations, + * but a small incremental cost for each additional invalidation. + * In any case, this operation is likely to be costly, since + * other threads may have to be somehow interrupted + * in order to make them notice the updated switch point state. + * However, it may be observed that a single call to invalidate + * several switch points has the same formal effect as many calls, + * each on just one of the switch points. + * + *

+ * Implementation Note: + * Simple implementations of {@code SwitchPoint} may use + * a private {@link MutableCallSite} to publish the state of a switch point. + * In such an implementation, the {@code invalidateAll} method can + * simply change the call site's target, and issue one call to + * {@linkplain MutableCallSite#syncAll synchronize} all the + * private call sites. + * + * @param switchPoints an array of call sites to be synchronized + * @throws NullPointerException if the {@code switchPoints} array reference is null + * or the array contains a null + */ + public static void invalidateAll(SwitchPoint[] switchPoints) { + if (switchPoints.length == 0) return; + MutableCallSite[] sites = new MutableCallSite[switchPoints.length]; + for (int i = 0; i < switchPoints.length; i++) { + SwitchPoint spt = switchPoints[i]; + if (spt == null) break; // MSC.syncAll will trigger a NPE + sites[i] = spt.mcs; + spt.mcs.setTarget(K_false); + } + MutableCallSite.syncAll(sites); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/TypeConvertingMethodAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/TypeConvertingMethodAdapter.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2012, 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 jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; +import sun.invoke.util.BytecodeDescriptor; +import sun.invoke.util.Wrapper; +import static sun.invoke.util.Wrapper.*; + +class TypeConvertingMethodAdapter extends MethodVisitor { + + TypeConvertingMethodAdapter(MethodVisitor mv) { + super(Opcodes.ASM5, mv); + } + + private static final int NUM_WRAPPERS = Wrapper.values().length; + + private static final String NAME_OBJECT = "java/lang/Object"; + private static final String WRAPPER_PREFIX = "Ljava/lang/"; + + // Same for all primitives; name of the boxing method + private static final String NAME_BOX_METHOD = "valueOf"; + + // Table of opcodes for widening primitive conversions; NOP = no conversion + private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS]; + + private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16]; + + // Table of wrappers for primitives, indexed by ASM type sorts + private static final Wrapper[] FROM_TYPE_SORT = new Wrapper[16]; + + static { + for (Wrapper w : Wrapper.values()) { + if (w.basicTypeChar() != 'L') { + int wi = hashWrapperName(w.wrapperSimpleName()); + assert (FROM_WRAPPER_NAME[wi] == null); + FROM_WRAPPER_NAME[wi] = w; + } + } + + for (int i = 0; i < NUM_WRAPPERS; i++) { + for (int j = 0; j < NUM_WRAPPERS; j++) { + wideningOpcodes[i][j] = Opcodes.NOP; + } + } + + initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR); + initWidening(LONG, Opcodes.F2L, FLOAT); + initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR); + initWidening(FLOAT, Opcodes.L2F, LONG); + initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR); + initWidening(DOUBLE, Opcodes.F2D, FLOAT); + initWidening(DOUBLE, Opcodes.L2D, LONG); + + FROM_TYPE_SORT[Type.BYTE] = Wrapper.BYTE; + FROM_TYPE_SORT[Type.SHORT] = Wrapper.SHORT; + FROM_TYPE_SORT[Type.INT] = Wrapper.INT; + FROM_TYPE_SORT[Type.LONG] = Wrapper.LONG; + FROM_TYPE_SORT[Type.CHAR] = Wrapper.CHAR; + FROM_TYPE_SORT[Type.FLOAT] = Wrapper.FLOAT; + FROM_TYPE_SORT[Type.DOUBLE] = Wrapper.DOUBLE; + FROM_TYPE_SORT[Type.BOOLEAN] = Wrapper.BOOLEAN; + } + + private static void initWidening(Wrapper to, int opcode, Wrapper... from) { + for (Wrapper f : from) { + wideningOpcodes[f.ordinal()][to.ordinal()] = opcode; + } + } + + /** + * Class name to Wrapper hash, derived from Wrapper.hashWrap() + * @param xn + * @return The hash code 0-15 + */ + private static int hashWrapperName(String xn) { + if (xn.length() < 3) { + return 0; + } + return (3 * xn.charAt(1) + xn.charAt(2)) % 16; + } + + private Wrapper wrapperOrNullFromDescriptor(String desc) { + if (!desc.startsWith(WRAPPER_PREFIX)) { + // Not a class type (array or method), so not a boxed type + // or not in the right package + return null; + } + // Pare it down to the simple class name + String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1); + // Hash to a Wrapper + Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)]; + if (w == null || w.wrapperSimpleName().equals(cname)) { + return w; + } else { + return null; + } + } + + private static String wrapperName(Wrapper w) { + return "java/lang/" + w.wrapperSimpleName(); + } + + private static String unboxMethod(Wrapper w) { + return w.primitiveSimpleName() + "Value"; + } + + private static String boxingDescriptor(Wrapper w) { + return String.format("(%s)L%s;", w.basicTypeChar(), wrapperName(w)); + } + + private static String unboxingDescriptor(Wrapper w) { + return "()" + w.basicTypeChar(); + } + + void boxIfTypePrimitive(Type t) { + Wrapper w = FROM_TYPE_SORT[t.getSort()]; + if (w != null) { + box(w); + } + } + + void widen(Wrapper ws, Wrapper wt) { + if (ws != wt) { + int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()]; + if (opcode != Opcodes.NOP) { + visitInsn(opcode); + } + } + } + + void box(Wrapper w) { + visitMethodInsn(Opcodes.INVOKESTATIC, + wrapperName(w), + NAME_BOX_METHOD, + boxingDescriptor(w)); + } + + /** + * Convert types by unboxing. The source type is known to be a primitive wrapper. + * @param ws A primitive wrapper corresponding to wrapped reference source type + * @param wt A primitive wrapper being converted to + */ + void unbox(String sname, Wrapper wt) { + visitMethodInsn(Opcodes.INVOKEVIRTUAL, + sname, + unboxMethod(wt), + unboxingDescriptor(wt)); + } + + private String descriptorToName(String desc) { + int last = desc.length() - 1; + if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') { + // In descriptor form + return desc.substring(1, last); + } else { + // Already in internal name form + return desc; + } + } + + void cast(String ds, String dt) { + String ns = descriptorToName(ds); + String nt = descriptorToName(dt); + if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) { + visitTypeInsn(Opcodes.CHECKCAST, nt); + } + } + + private boolean isPrimitive(Wrapper w) { + return w != OBJECT; + } + + private Wrapper toWrapper(String desc) { + char first = desc.charAt(0); + if (first == '[' || first == '(') { + first = 'L'; + } + return Wrapper.forBasicType(first); + } + + /** + * Convert an argument of type 'arg' to be passed to 'target' assuring that it is 'functional'. + * Insert the needed conversion instructions in the method code. + * @param arg + * @param target + * @param functional + */ + void convertType(Class arg, Class target, Class functional) { + if (arg.equals(target) && arg.equals(functional)) { + return; + } + if (arg == Void.TYPE || target == Void.TYPE) { + return; + } + if (arg.isPrimitive()) { + Wrapper wArg = Wrapper.forPrimitiveType(arg); + if (target.isPrimitive()) { + // Both primitives: widening + widen(wArg, Wrapper.forPrimitiveType(target)); + } else { + // Primitive argument to reference target + String dTarget = BytecodeDescriptor.unparse(target); + Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget); + if (wPrimTarget != null) { + // The target is a boxed primitive type, widen to get there before boxing + widen(wArg, wPrimTarget); + box(wPrimTarget); + } else { + // Otherwise, box and cast + box(wArg); + cast(wrapperName(wArg), dTarget); + } + } + } else { + String dArg = BytecodeDescriptor.unparse(arg); + String dSrc; + if (functional.isPrimitive()) { + dSrc = dArg; + } else { + // Cast to convert to possibly more specific type, and generate CCE for invalid arg + dSrc = BytecodeDescriptor.unparse(functional); + cast(dArg, dSrc); + } + String dTarget = BytecodeDescriptor.unparse(target); + if (target.isPrimitive()) { + Wrapper wTarget = toWrapper(dTarget); + // Reference argument to primitive target + Wrapper wps = wrapperOrNullFromDescriptor(dSrc); + if (wps != null) { + if (wps.isSigned() || wps.isFloating()) { + // Boxed number to primitive + unbox(wrapperName(wps), wTarget); + } else { + // Character or Boolean + unbox(wrapperName(wps), wps); + widen(wps, wTarget); + } + } else { + // Source type is reference type, but not boxed type, + // assume it is super type of target type + String intermediate; + if (wTarget.isSigned() || wTarget.isFloating()) { + // Boxed number to primitive + intermediate = "java/lang/Number"; + } else { + // Character or Boolean + intermediate = wrapperName(wTarget); + } + cast(dSrc, intermediate); + unbox(intermediate, wTarget); + } + } else { + // Both reference types: just case to target type + cast(dSrc, dTarget); + } + } + } + + /** + * The following method is copied from + * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small + * and fast Java bytecode manipulation framework. + * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. + */ + void iconst(final int cst) { + if (cst >= -1 && cst <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + cst); + } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { + mv.visitIntInsn(Opcodes.BIPUSH, cst); + } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, cst); + } else { + mv.visitLdcInsn(cst); + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/VolatileCallSite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/VolatileCallSite.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2010, 2011, 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; + +/** + * A {@code VolatileCallSite} is a {@link CallSite} whose target acts like a volatile variable. + * An {@code invokedynamic} instruction linked to a {@code VolatileCallSite} sees updates + * to its call site target immediately, even if the update occurs in another thread. + * There may be a performance penalty for such tight coupling between threads. + *

+ * Unlike {@code MutableCallSite}, there is no + * {@linkplain MutableCallSite#syncAll syncAll operation} on volatile + * call sites, since every write to a volatile variable is implicitly + * synchronized with reader threads. + *

+ * In other respects, a {@code VolatileCallSite} is interchangeable + * with {@code MutableCallSite}. + * @see MutableCallSite + * @author John Rose, JSR 292 EG + */ +public class VolatileCallSite extends CallSite { + /** + * Creates a call site with a volatile binding to its target. + * The initial target is set to a method handle + * of the given type which will throw an {@code IllegalStateException} if called. + * @param type the method type that this call site will have + * @throws NullPointerException if the proposed type is null + */ + public VolatileCallSite(MethodType type) { + super(type); + } + + /** + * Creates a call site with a volatile binding to its target. + * The target is set to the given value. + * @param target the method handle that will be the initial target of the call site + * @throws NullPointerException if the proposed target is null + */ + public VolatileCallSite(MethodHandle target) { + super(target); + } + + /** + * Returns the target method of the call site, which behaves + * like a {@code volatile} field of the {@code VolatileCallSite}. + *

+ * The interactions of {@code getTarget} with memory are the same + * as of a read from a {@code volatile} field. + *

+ * In particular, the current thread is required to issue a fresh + * read of the target from memory, and must not fail to see + * a recent update to the target by another thread. + * + * @return the linkage state of this call site, a method handle which can change over time + * @see #setTarget + */ + @Override public final MethodHandle getTarget() { + return getTargetVolatile(); + } + + /** + * Updates the target method of this call site, as a volatile variable. + * The type of the new target must agree with the type of the old target. + *

+ * The interactions with memory are the same as of a write to a volatile field. + * In particular, any threads is guaranteed to see the updated target + * the next time it calls {@code getTarget}. + * @param newTarget the new target + * @throws NullPointerException if the proposed new target is null + * @throws WrongMethodTypeException if the proposed new target + * has a method type that differs from the previous target + * @see #getTarget + */ + @Override public void setTarget(MethodHandle newTarget) { + checkTargetChange(getTargetVolatile(), newTarget); + setTargetVolatile(newTarget); + } + + /** + * {@inheritDoc} + */ + @Override + public final MethodHandle dynamicInvoker() { + return makeDynamicInvoker(); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/WrongMethodTypeException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/WrongMethodTypeException.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2008, 2012, 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; + +/** + * Thrown to indicate that code has attempted to call a method handle + * via the wrong method type. As with the bytecode representation of + * normal Java method calls, method handle calls are strongly typed + * to a specific type descriptor associated with a call site. + *

+ * This exception may also be thrown when two method handles are + * composed, and the system detects that their types cannot be + * matched up correctly. This amounts to an early evaluation + * of the type mismatch, at method handle construction time, + * instead of when the mismatched method handle is called. + * + * @author John Rose, JSR 292 EG + * @since 1.7 + */ +public class WrongMethodTypeException extends RuntimeException { + private static final long serialVersionUID = 292L; + + /** + * Constructs a {@code WrongMethodTypeException} with no detail message. + */ + public WrongMethodTypeException() { + super(); + } + + /** + * Constructs a {@code WrongMethodTypeException} with the specified + * detail message. + * + * @param s the detail message. + */ + public WrongMethodTypeException(String s) { + super(s); + } + + /** + * Constructs a {@code WrongMethodTypeException} with the specified + * detail message and cause. + * + * @param s the detail message. + * @param cause the cause of the exception, or null. + */ + //FIXME: make this public in MR1 + /*non-public*/ WrongMethodTypeException(String s, Throwable cause) { + super(s, cause); + } + + /** + * Constructs a {@code WrongMethodTypeException} with the specified + * cause. + * + * @param cause the cause of the exception, or null. + */ + //FIXME: make this public in MR1 + /*non-public*/ WrongMethodTypeException(Throwable cause) { + super(cause); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/package-info.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,211 @@ +/* + * 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. + */ + +/** + * The {@code java.lang.invoke} package contains dynamic language support provided directly by + * the Java core class libraries and virtual machine. + * + *

+ * As described in the Java Virtual Machine Specification, + * certain types in this package have special relations to dynamic + * language support in the virtual machine: + *

    + *
  • The class {@link java.lang.invoke.MethodHandle MethodHandle} contains + * signature polymorphic methods + * which can be linked regardless of their type descriptor. + * Normally, method linkage requires exact matching of type descriptors. + *
  • + * + *
  • The JVM bytecode format supports immediate constants of + * the classes {@link java.lang.invoke.MethodHandle MethodHandle} and {@link java.lang.invoke.MethodType MethodType}. + *
  • + *
+ * + *

Summary of relevant Java Virtual Machine changes

+ * The following low-level information summarizes relevant parts of the + * Java Virtual Machine specification. For full details, please see the + * current version of that specification. + * + * Each occurrence of an {@code invokedynamic} instruction is called a dynamic call site. + *

{@code invokedynamic} instructions

+ * A dynamic call site is originally in an unlinked state. In this state, there is + * no target method for the call site to invoke. + *

+ * Before the JVM can execute a dynamic call site (an {@code invokedynamic} instruction), + * the call site must first be linked. + * Linking is accomplished by calling a bootstrap method + * which is given the static information content of the call site, + * and which must produce a {@link java.lang.invoke.MethodHandle method handle} + * that gives the behavior of the call site. + *

+ * Each {@code invokedynamic} instruction statically specifies its own + * bootstrap method as a constant pool reference. + * The constant pool reference also specifies the call site's name and type descriptor, + * just like {@code invokevirtual} and the other invoke instructions. + *

+ * Linking starts with resolving the constant pool entry for the + * bootstrap method, and resolving a {@link java.lang.invoke.MethodType MethodType} object for + * the type descriptor of the dynamic call site. + * This resolution process may trigger class loading. + * It may therefore throw an error if a class fails to load. + * This error becomes the abnormal termination of the dynamic + * call site execution. + * Linkage does not trigger class initialization. + *

+ * The bootstrap method is invoked on at least three values: + *

    + *
  • a {@code MethodHandles.Lookup}, a lookup object on the caller class in which dynamic call site occurs
  • + *
  • a {@code String}, the method name mentioned in the call site
  • + *
  • a {@code MethodType}, the resolved type descriptor of the call
  • + *
  • optionally, between 1 and 251 additional static arguments taken from the constant pool
  • + *
+ * Invocation is as if by + * {@link java.lang.invoke.MethodHandle#invoke MethodHandle.invoke}. + * The returned result must be a {@link java.lang.invoke.CallSite CallSite} (or a subclass). + * The type of the call site's target must be exactly equal to the type + * derived from the dynamic call site's type descriptor and passed to + * the bootstrap method. + * The call site then becomes permanently linked to the dynamic call site. + *

+ * As documented in the JVM specification, all failures arising from + * the linkage of a dynamic call site are reported + * by a {@link java.lang.BootstrapMethodError BootstrapMethodError}, + * which is thrown as the abnormal termination of the dynamic call + * site execution. + * If this happens, the same error will the thrown for all subsequent + * attempts to execute the dynamic call site. + * + *

timing of linkage

+ * A dynamic call site is linked just before its first execution. + * The bootstrap method call implementing the linkage occurs within + * a thread that is attempting a first execution. + *

+ * If there are several such threads, the bootstrap method may be + * invoked in several threads concurrently. + * Therefore, bootstrap methods which access global application + * data must take the usual precautions against race conditions. + * In any case, every {@code invokedynamic} instruction is either + * unlinked or linked to a unique {@code CallSite} object. + *

+ * In an application which requires dynamic call sites with individually + * mutable behaviors, their bootstrap methods should produce distinct + * {@link java.lang.invoke.CallSite CallSite} objects, one for each linkage request. + * Alternatively, an application can link a single {@code CallSite} object + * to several {@code invokedynamic} instructions, in which case + * a change to the target method will become visible at each of + * the instructions. + *

+ * If several threads simultaneously execute a bootstrap method for a single dynamic + * call site, the JVM must choose one {@code CallSite} object and install it visibly to + * all threads. Any other bootstrap method calls are allowed to complete, but their + * results are ignored, and their dynamic call site invocations proceed with the originally + * chosen target object. + + *

+ * Discussion: + * These rules do not enable the JVM to duplicate dynamic call sites, + * or to issue “causeless” bootstrap method calls. + * Every dynamic call site transitions at most once from unlinked to linked, + * just before its first invocation. + * There is no way to undo the effect of a completed bootstrap method call. + * + *

types of bootstrap methods

+ * As long as each bootstrap method can be correctly invoked + * by {@code MethodHandle.invoke}, its detailed type is arbitrary. + * For example, the first argument could be {@code Object} + * instead of {@code MethodHandles.Lookup}, and the return type + * could also be {@code Object} instead of {@code CallSite}. + * (Note that the types and number of the stacked arguments limit + * the legal kinds of bootstrap methods to appropriately typed + * static methods and constructors of {@code CallSite} subclasses.) + *

+ * If a given {@code invokedynamic} instruction specifies no static arguments, + * the instruction's bootstrap method will be invoked on three arguments, + * conveying the instruction's caller class, name, and method type. + * If the {@code invokedynamic} instruction specifies one or more static arguments, + * those values will be passed as additional arguments to the method handle. + * (Note that because there is a limit of 255 arguments to any method, + * at most 251 extra arguments can be supplied, since the bootstrap method + * handle itself and its first three arguments must also be stacked.) + * The bootstrap method will be invoked as if by either {@code MethodHandle.invoke} + * or {@code invokeWithArguments}. (There is no way to tell the difference.) + *

+ * The normal argument conversion rules for {@code MethodHandle.invoke} apply to all stacked arguments. + * For example, if a pushed value is a primitive type, it may be converted to a reference by boxing conversion. + * If the bootstrap method is a variable arity method (its modifier bit {@code 0x0080} is set), + * then some or all of the arguments specified here may be collected into a trailing array parameter. + * (This is not a special rule, but rather a useful consequence of the interaction + * between {@code CONSTANT_MethodHandle} constants, the modifier bit for variable arity methods, + * and the {@link java.lang.invoke.MethodHandle#asVarargsCollector asVarargsCollector} transformation.) + *

+ * Given these rules, here are examples of legal bootstrap method declarations, + * given various numbers {@code N} of extra arguments. + * The first rows (marked {@code *}) will work for any number of extra arguments. + * + * + * + * + * + * + * + * + * + * + * + *
Nsample bootstrap method
*CallSite bootstrap(Lookup caller, String name, MethodType type, Object... args)
*CallSite bootstrap(Object... args)
*CallSite bootstrap(Object caller, Object... nameAndTypeWithArgs)
0CallSite bootstrap(Lookup caller, String name, MethodType type)
0CallSite bootstrap(Lookup caller, Object... nameAndType)
1CallSite bootstrap(Lookup caller, String name, MethodType type, Object arg)
2CallSite bootstrap(Lookup caller, String name, MethodType type, Object... args)
2CallSite bootstrap(Lookup caller, String name, MethodType type, String... args)
2CallSite bootstrap(Lookup caller, String name, MethodType type, String x, int y)
+ * The last example assumes that the extra arguments are of type + * {@code CONSTANT_String} and {@code CONSTANT_Integer}, respectively. + * The second-to-last example assumes that all extra arguments are of type + * {@code CONSTANT_String}. + * The other examples work with all types of extra arguments. + *

+ * As noted above, the actual method type of the bootstrap method can vary. + * For example, the fourth argument could be {@code MethodHandle}, + * if that is the type of the corresponding constant in + * the {@code CONSTANT_InvokeDynamic} entry. + * In that case, the {@code MethodHandle.invoke} call will pass the extra method handle + * constant as an {@code Object}, but the type matching machinery of {@code MethodHandle.invoke} + * will cast the reference back to {@code MethodHandle} before invoking the bootstrap method. + * (If a string constant were passed instead, by badly generated code, that cast would then fail, + * resulting in a {@code BootstrapMethodError}.) + *

+ * Note that, as a consequence of the above rules, the bootstrap method may accept a primitive + * argument, if it can be represented by a constant pool entry. + * However, arguments of type {@code boolean}, {@code byte}, {@code short}, or {@code char} + * cannot be created for bootstrap methods, since such constants cannot be directly + * represented in the constant pool, and the invocation of the bootstrap method will + * not perform the necessary narrowing primitive conversions. + *

+ * Extra bootstrap method arguments are intended to allow language implementors + * to safely and compactly encode metadata. + * In principle, the name and extra arguments are redundant, + * since each call site could be given its own unique bootstrap method. + * Such a practice is likely to produce large class files and constant pools. + * + * @author John Rose, JSR 292 EG + * @since 1.7 + */ + +package java.lang.invoke; diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/WrapperInstance.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/WrapperInstance.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011, 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 sun.invoke; + +import java.lang.invoke.MethodHandle; + +/** + * Private API used inside of java.lang.invoke.MethodHandles. + * Interface implemented by every object which is produced by + * {@link java.lang.invoke.MethodHandleProxies#asInterfaceInstance MethodHandleProxies.asInterfaceInstance}. + * The methods of this interface allow a caller to recover the parameters + * to {@code asInstance}. + * This allows applications to repeatedly convert between method handles + * and SAM objects, without the risk of creating unbounded delegation chains. + */ +public interface WrapperInstance { + /** Produce or recover a target method handle which is behaviorally + * equivalent to the SAM method of this object. + */ + public MethodHandle getWrapperInstanceTarget(); + /** Recover the SAM type for which this object was created. + */ + public Class getWrapperInstanceType(); +} + diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/anon/AnonymousClassLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/AnonymousClassLoader.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2008, 2012, 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 sun.invoke.anon; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import sun.misc.IOUtils; + +/** + * Anonymous class loader. Will load any valid classfile, producing + * a {@link Class} metaobject, without installing that class in the + * system dictionary. Therefore, {@link Class#forName(String)} will never + * produce a reference to an anonymous class. + *

+ * The access permissions of the anonymous class are borrowed from + * a host class. The new class behaves as if it were an + * inner class of the host class. It can access the host's private + * members, if the creator of the class loader has permission to + * do so (or to create accessible reflective objects). + *

+ * When the anonymous class is loaded, elements of its constant pool + * can be patched to new values. This provides a hook to pre-resolve + * named classes in the constant pool to other classes, including + * anonymous ones. Also, string constants can be pre-resolved to + * any reference. (The verifier treats non-string, non-class reference + * constants as plain objects.) + *

+ * Why include the patching function? It makes some use cases much easier. + * Second, the constant pool needed some internal patching anyway, + * to anonymize the loaded class itself. Finally, if you are going + * to use this seriously, you'll want to build anonymous classes + * on top of pre-existing anonymous classes, and that requires patching. + * + *

%%% TO-DO: + *

    + *
  • needs better documentation
  • + *
  • needs more security work (for safe delegation)
  • + *
  • needs a clearer story about error processing
  • + *
  • patch member references also (use ';' as delimiter char)
  • + *
  • patch method references to (conforming) method handles
  • + *
+ * + * @author jrose + * @author Remi Forax + * @see + * http://blogs.sun.com/jrose/entry/anonymous_classes_in_the_vm + */ + +public class AnonymousClassLoader { + final Class hostClass; + + // Privileged constructor. + private AnonymousClassLoader(Class hostClass) { + this.hostClass = hostClass; + } + + public static AnonymousClassLoader make(sun.misc.Unsafe unsafe, Class hostClass) { + if (unsafe == null) throw new NullPointerException(); + return new AnonymousClassLoader(hostClass); + } + + public Class loadClass(byte[] classFile) { + if (defineAnonymousClass == null) { + // no JVM support; try to fake an approximation + try { + return fakeLoadClass(new ConstantPoolParser(classFile).createPatch()); + } catch (InvalidConstantPoolFormatException ee) { + throw new IllegalArgumentException(ee); + } + } + return loadClass(classFile, null); + } + + public Class loadClass(ConstantPoolPatch classPatch) { + if (defineAnonymousClass == null) { + // no JVM support; try to fake an approximation + return fakeLoadClass(classPatch); + } + Object[] patches = classPatch.patchArray; + // Convert class names (this late in the game) + // to use slash '/' instead of dot '.'. + // Java likes dots, but the JVM likes slashes. + for (int i = 0; i < patches.length; i++) { + Object value = patches[i]; + if (value != null) { + byte tag = classPatch.getTag(i); + switch (tag) { + case ConstantPoolVisitor.CONSTANT_Class: + if (value instanceof String) { + if (patches == classPatch.patchArray) + patches = patches.clone(); + patches[i] = ((String)value).replace('.', '/'); + } + break; + case ConstantPoolVisitor.CONSTANT_Fieldref: + case ConstantPoolVisitor.CONSTANT_Methodref: + case ConstantPoolVisitor.CONSTANT_InterfaceMethodref: + case ConstantPoolVisitor.CONSTANT_NameAndType: + // When/if the JVM supports these patches, + // we'll probably need to reformat them also. + // Meanwhile, let the class loader create the error. + break; + } + } + } + return loadClass(classPatch.outer.classFile, classPatch.patchArray); + } + + private Class loadClass(byte[] classFile, Object[] patchArray) { + try { + return (Class) + defineAnonymousClass.invoke(unsafe, + hostClass, classFile, patchArray); + } catch (Exception ex) { + throwReflectedException(ex); + throw new RuntimeException("error loading into "+hostClass, ex); + } + } + + private static void throwReflectedException(Exception ex) { + if (ex instanceof InvocationTargetException) { + Throwable tex = ((InvocationTargetException)ex).getTargetException(); + if (tex instanceof Error) + throw (Error) tex; + ex = (Exception) tex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + } + + private Class fakeLoadClass(ConstantPoolPatch classPatch) { + // Implementation: + // 1. Make up a new name nobody has used yet. + // 2. Inspect the tail-header of the class to find the this_class index. + // 3. Patch the CONSTANT_Class for this_class to the new name. + // 4. Add other CP entries required by (e.g.) string patches. + // 5. Flatten Class constants down to their names, making sure that + // the host class loader can pick them up again accurately. + // 6. Generate the edited class file bytes. + // + // Potential limitations: + // * The class won't be truly anonymous, and may interfere with others. + // * Flattened class constants might not work, because of loader issues. + // * Pseudo-string constants will not flatten down to real strings. + // * Method handles will (of course) fail to flatten to linkage strings. + if (true) throw new UnsupportedOperationException("NYI"); + Object[] cpArray; + try { + cpArray = classPatch.getOriginalCP(); + } catch (InvalidConstantPoolFormatException ex) { + throw new RuntimeException(ex); + } + int thisClassIndex = classPatch.getParser().getThisClassIndex(); + String thisClassName = (String) cpArray[thisClassIndex]; + synchronized (AnonymousClassLoader.class) { + thisClassName = thisClassName+"\\|"+(++fakeNameCounter); + } + classPatch.putUTF8(thisClassIndex, thisClassName); + byte[] classFile = null; + return unsafe.defineClass(null, classFile, 0, classFile.length, + hostClass.getClassLoader(), + hostClass.getProtectionDomain()); + } + private static int fakeNameCounter = 99999; + + // ignore two warnings on this line: + private static sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe(); + // preceding line requires that this class be on the boot class path + + static private final Method defineAnonymousClass; + static { + Method dac = null; + Class unsafeClass = unsafe.getClass(); + try { + dac = unsafeClass.getMethod("defineAnonymousClass", + Class.class, + byte[].class, + Object[].class); + } catch (Exception ee) { + dac = null; + } + defineAnonymousClass = dac; + } + + private static void noJVMSupport() { + throw new UnsupportedOperationException("no JVM support for anonymous classes"); + } + + + private static native Class loadClassInternal(Class hostClass, + byte[] classFile, + Object[] patchArray); + + public static byte[] readClassFile(Class templateClass) throws IOException { + String templateName = templateClass.getName(); + int lastDot = templateName.lastIndexOf('.'); + java.net.URL url = templateClass.getResource(templateName.substring(lastDot+1)+".class"); + java.net.URLConnection connection = url.openConnection(); + int contentLength = connection.getContentLength(); + if (contentLength < 0) + throw new IOException("invalid content length "+contentLength); + + return IOUtils.readFully(connection.getInputStream(), contentLength, true); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolParser.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2008, 2011, 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 sun.invoke.anon; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +import static sun.invoke.anon.ConstantPoolVisitor.*; + +/** A constant pool parser. + */ +public class ConstantPoolParser { + final byte[] classFile; + final byte[] tags; + final char[] firstHeader; // maghi, maglo, minor, major, cplen + + // these are filled in on first parse: + int endOffset; + char[] secondHeader; // flags, this_class, super_class, intlen + + // used to decode UTF8 array + private char[] charArray = new char[80]; + + /** Creates a constant pool parser. + * @param classFile an array of bytes containing a class. + * @throws InvalidConstantPoolFormatException if the header of the class has errors. + */ + public ConstantPoolParser(byte[] classFile) throws InvalidConstantPoolFormatException { + this.classFile = classFile; + this.firstHeader = parseHeader(classFile); + this.tags = new byte[firstHeader[4]]; + } + + /** Create a constant pool parser by loading the bytecodes of the + * class taken as argument. + * + * @param templateClass the class to parse. + * + * @throws IOException raised if an I/O occurs when loading + * the bytecode of the template class. + * @throws InvalidConstantPoolFormatException if the header of the class has errors. + * + * @see #ConstantPoolParser(byte[]) + * @see AnonymousClassLoader#readClassFile(Class) + */ + public ConstantPoolParser(Class templateClass) throws IOException, InvalidConstantPoolFormatException { + this(AnonymousClassLoader.readClassFile(templateClass)); + } + + /** Creates an empty patch to patch the class file + * used by the current parser. + * @return a new class patch. + */ + public ConstantPoolPatch createPatch() { + return new ConstantPoolPatch(this); + } + + /** Report the tag of the indicated CP entry. + * @param index + * @return one of {@link ConstantPoolVisitor#CONSTANT_Utf8}, etc. + */ + public byte getTag(int index) { + getEndOffset(); // trigger an exception if we haven't parsed yet + return tags[index]; + } + + /** Report the length of the constant pool. */ + public int getLength() { + return firstHeader[4]; + } + + /** Report the offset, within the class file, of the start of the constant pool. */ + public int getStartOffset() { + return firstHeader.length * 2; + } + + /** Report the offset, within the class file, of the end of the constant pool. */ + public int getEndOffset() { + if (endOffset == 0) + throw new IllegalStateException("class file has not yet been parsed"); + return endOffset; + } + + /** Report the CP index of this class's own name. */ + public int getThisClassIndex() { + getEndOffset(); // provoke exception if not yet parsed + return secondHeader[1]; + } + + /** Report the total size of the class file. */ + public int getTailLength() { + return classFile.length - getEndOffset(); + } + + /** Write the head (header plus constant pool) + * of the class file to the indicated stream. + */ + public void writeHead(OutputStream out) throws IOException { + out.write(classFile, 0, getEndOffset()); + } + + /** Write the head (header plus constant pool) + * of the class file to the indicated stream, + * incorporating the non-null entries of the given array + * as patches. + */ + void writePatchedHead(OutputStream out, Object[] patchArray) { + // this will be useful to partially emulate the class loader on old JVMs + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** Write the tail (everything after the constant pool) + * of the class file to the indicated stream. + */ + public void writeTail(OutputStream out) throws IOException { + out.write(classFile, getEndOffset(), getTailLength()); + } + + private static char[] parseHeader(byte[] classFile) throws InvalidConstantPoolFormatException { + char[] result = new char[5]; + ByteBuffer buffer = ByteBuffer.wrap(classFile); + for (int i = 0; i < result.length; i++) + result[i] = (char) getUnsignedShort(buffer); + int magic = result[0] << 16 | result[1] << 0; + if (magic != 0xCAFEBABE) + throw new InvalidConstantPoolFormatException("invalid magic number "+magic); + // skip major, minor version + int len = result[4]; + if (len < 1) + throw new InvalidConstantPoolFormatException("constant pool length < 1"); + return result; + } + + /** Parse the constant pool of the class + * calling a method visit* each time a constant pool entry is parsed. + * + * The order of the calls to visit* is not guaranteed to be the same + * than the order of the constant pool entry in the bytecode array. + * + * @param visitor + * @throws InvalidConstantPoolFormatException + */ + public void parse(ConstantPoolVisitor visitor) throws InvalidConstantPoolFormatException { + ByteBuffer buffer = ByteBuffer.wrap(classFile); + buffer.position(getStartOffset()); //skip header + + Object[] values = new Object[getLength()]; + try { + parseConstantPool(buffer, values, visitor); + } catch(BufferUnderflowException e) { + throw new InvalidConstantPoolFormatException(e); + } + if (endOffset == 0) { + endOffset = buffer.position(); + secondHeader = new char[4]; + for (int i = 0; i < secondHeader.length; i++) { + secondHeader[i] = (char) getUnsignedShort(buffer); + } + } + resolveConstantPool(values, visitor); + } + + private char[] getCharArray(int utfLength) { + if (utfLength <= charArray.length) + return charArray; + return charArray = new char[utfLength]; + } + + private void parseConstantPool(ByteBuffer buffer, Object[] values, ConstantPoolVisitor visitor) throws InvalidConstantPoolFormatException { + for (int i = 1; i < tags.length; ) { + byte tag = (byte) getUnsignedByte(buffer); + assert(tags[i] == 0 || tags[i] == tag); + tags[i] = tag; + switch (tag) { + case CONSTANT_Utf8: + int utfLen = getUnsignedShort(buffer); + String value = getUTF8(buffer, utfLen, getCharArray(utfLen)); + visitor.visitUTF8(i, CONSTANT_Utf8, value); + tags[i] = tag; + values[i++] = value; + break; + case CONSTANT_Integer: + visitor.visitConstantValue(i, tag, buffer.getInt()); + i++; + break; + case CONSTANT_Float: + visitor.visitConstantValue(i, tag, buffer.getFloat()); + i++; + break; + case CONSTANT_Long: + visitor.visitConstantValue(i, tag, buffer.getLong()); + i+=2; + break; + case CONSTANT_Double: + visitor.visitConstantValue(i, tag, buffer.getDouble()); + i+=2; + break; + + case CONSTANT_Class: // fall through: + case CONSTANT_String: + tags[i] = tag; + values[i++] = new int[] { getUnsignedShort(buffer) }; + break; + + case CONSTANT_Fieldref: // fall through: + case CONSTANT_Methodref: // fall through: + case CONSTANT_InterfaceMethodref: // fall through: + case CONSTANT_NameAndType: + tags[i] = tag; + values[i++] = new int[] { getUnsignedShort(buffer), getUnsignedShort(buffer) }; + break; + default: + throw new AssertionError("invalid constant "+tag); + } + } + } + + private void resolveConstantPool(Object[] values, ConstantPoolVisitor visitor) { + // clean out the int[] values, which are temporary + for (int beg = 1, end = values.length-1, beg2, end2; + beg <= end; + beg = beg2, end = end2) { + beg2 = end; end2 = beg-1; + //System.out.println("CP resolve pass: "+beg+".."+end); + for (int i = beg; i <= end; i++) { + Object value = values[i]; + if (!(value instanceof int[])) + continue; + int[] array = (int[]) value; + byte tag = tags[i]; + switch (tag) { + case CONSTANT_String: + String stringBody = (String) values[array[0]]; + visitor.visitConstantString(i, tag, stringBody, array[0]); + values[i] = null; + break; + case CONSTANT_Class: { + String className = (String) values[array[0]]; + // use the external form favored by Class.forName: + className = className.replace('/', '.'); + visitor.visitConstantString(i, tag, className, array[0]); + values[i] = className; + break; + } + case CONSTANT_NameAndType: { + String memberName = (String) values[array[0]]; + String signature = (String) values[array[1]]; + visitor.visitDescriptor(i, tag, memberName, signature, + array[0], array[1]); + values[i] = new String[] {memberName, signature}; + break; + } + case CONSTANT_Fieldref: // fall through: + case CONSTANT_Methodref: // fall through: + case CONSTANT_InterfaceMethodref: { + Object className = values[array[0]]; + Object nameAndType = values[array[1]]; + if (!(className instanceof String) || + !(nameAndType instanceof String[])) { + // one more pass is needed + if (beg2 > i) beg2 = i; + if (end2 < i) end2 = i; + continue; + } + String[] nameAndTypeArray = (String[]) nameAndType; + visitor.visitMemberRef(i, tag, + (String)className, + nameAndTypeArray[0], + nameAndTypeArray[1], + array[0], array[1]); + values[i] = null; + } + break; + default: + continue; + } + } + } + } + + private static int getUnsignedByte(ByteBuffer buffer) { + return buffer.get() & 0xFF; + } + + private static int getUnsignedShort(ByteBuffer buffer) { + int b1 = getUnsignedByte(buffer); + int b2 = getUnsignedByte(buffer); + return (b1 << 8) + (b2 << 0); + } + + private static String getUTF8(ByteBuffer buffer, int utfLen, char[] charArray) throws InvalidConstantPoolFormatException { + int utfLimit = buffer.position() + utfLen; + int index = 0; + while (buffer.position() < utfLimit) { + int c = buffer.get() & 0xff; + if (c > 127) { + buffer.position(buffer.position() - 1); + return getUTF8Extended(buffer, utfLimit, charArray, index); + } + charArray[index++] = (char)c; + } + return new String(charArray, 0, index); + } + + private static String getUTF8Extended(ByteBuffer buffer, int utfLimit, char[] charArray, int index) throws InvalidConstantPoolFormatException { + int c, c2, c3; + while (buffer.position() < utfLimit) { + c = buffer.get() & 0xff; + switch (c >> 4) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + /* 0xxxxxxx*/ + charArray[index++] = (char)c; + break; + case 12: case 13: + /* 110x xxxx 10xx xxxx*/ + c2 = buffer.get(); + if ((c2 & 0xC0) != 0x80) + throw new InvalidConstantPoolFormatException( + "malformed input around byte " + buffer.position()); + charArray[index++] = (char)(((c & 0x1F) << 6) | + (c2 & 0x3F)); + break; + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + c2 = buffer.get(); + c3 = buffer.get(); + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) + throw new InvalidConstantPoolFormatException( + "malformed input around byte " + (buffer.position())); + charArray[index++] = (char)(((c & 0x0F) << 12) | + ((c2 & 0x3F) << 6) | + ((c3 & 0x3F) << 0)); + break; + default: + /* 10xx xxxx, 1111 xxxx */ + throw new InvalidConstantPoolFormatException( + "malformed input around byte " + buffer.position()); + } + } + // The number of chars produced may be less than utflen + return new String(charArray, 0, index); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolPatch.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolPatch.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,503 @@ +/* + * 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 sun.invoke.anon; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; + +import static sun.invoke.anon.ConstantPoolVisitor.*; + +/** A class and its patched constant pool. + * + * This class allow to modify (patch) a constant pool + * by changing the value of its entry. + * Entry are referenced using index that can be get + * by parsing the constant pool using + * {@link ConstantPoolParser#parse(ConstantPoolVisitor)}. + * + * @see ConstantPoolVisitor + * @see ConstantPoolParser#createPatch() + */ +public class ConstantPoolPatch { + final ConstantPoolParser outer; + final Object[] patchArray; + + ConstantPoolPatch(ConstantPoolParser outer) { + this.outer = outer; + this.patchArray = new Object[outer.getLength()]; + } + + /** Create a {@link ConstantPoolParser} and + * a {@link ConstantPoolPatch} in one step. + * Equivalent to {@code new ConstantPoolParser(classFile).createPatch()}. + * + * @param classFile an array of bytes containing a class. + * @see #ConstantPoolParser(Class) + */ + public ConstantPoolPatch(byte[] classFile) throws InvalidConstantPoolFormatException { + this(new ConstantPoolParser(classFile)); + } + + /** Create a {@link ConstantPoolParser} and + * a {@link ConstantPoolPatch} in one step. + * Equivalent to {@code new ConstantPoolParser(templateClass).createPatch()}. + * + * @param templateClass the class to parse. + * @see #ConstantPoolParser(Class) + */ + public ConstantPoolPatch(Class templateClass) throws IOException, InvalidConstantPoolFormatException { + this(new ConstantPoolParser(templateClass)); + } + + + /** Creates a patch from an existing patch. + * All changes are copied from that patch. + * @param patch a patch + * + * @see ConstantPoolParser#createPatch() + */ + public ConstantPoolPatch(ConstantPoolPatch patch) { + outer = patch.outer; + patchArray = patch.patchArray.clone(); + } + + /** Which parser built this patch? */ + public ConstantPoolParser getParser() { + return outer; + } + + /** Report the tag at the given index in the constant pool. */ + public byte getTag(int index) { + return outer.getTag(index); + } + + /** Report the current patch at the given index of the constant pool. + * Null means no patch will be made. + * To observe the unpatched entry at the given index, use + * {@link #getParser()}{@code .}@link ConstantPoolParser#parse(ConstantPoolVisitor)} + */ + public Object getPatch(int index) { + Object value = patchArray[index]; + if (value == null) return null; + switch (getTag(index)) { + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + if (value instanceof String) + value = stripSemis(2, (String) value); + break; + case CONSTANT_NameAndType: + if (value instanceof String) + value = stripSemis(1, (String) value); + break; + } + return value; + } + + /** Clear all patches. */ + public void clear() { + Arrays.fill(patchArray, null); + } + + /** Clear one patch. */ + public void clear(int index) { + patchArray[index] = null; + } + + /** Produce the patches as an array. */ + public Object[] getPatches() { + return patchArray.clone(); + } + + /** Produce the original constant pool as an array. */ + public Object[] getOriginalCP() throws InvalidConstantPoolFormatException { + return getOriginalCP(0, patchArray.length, -1); + } + + /** Walk the constant pool, applying patches using the given map. + * + * @param utf8Map Utf8 strings to modify, if encountered + * @param classMap Classes (or their names) to modify, if encountered + * @param valueMap Constant values to modify, if encountered + * @param deleteUsedEntries if true, delete map entries that are used + */ + public void putPatches(final Map utf8Map, + final Map classMap, + final Map valueMap, + boolean deleteUsedEntries) throws InvalidConstantPoolFormatException { + final HashSet usedUtf8Keys; + final HashSet usedClassKeys; + final HashSet usedValueKeys; + if (deleteUsedEntries) { + usedUtf8Keys = (utf8Map == null) ? null : new HashSet(); + usedClassKeys = (classMap == null) ? null : new HashSet(); + usedValueKeys = (valueMap == null) ? null : new HashSet(); + } else { + usedUtf8Keys = null; + usedClassKeys = null; + usedValueKeys = null; + } + + outer.parse(new ConstantPoolVisitor() { + + @Override + public void visitUTF8(int index, byte tag, String utf8) { + putUTF8(index, utf8Map.get(utf8)); + if (usedUtf8Keys != null) usedUtf8Keys.add(utf8); + } + + @Override + public void visitConstantValue(int index, byte tag, Object value) { + putConstantValue(index, tag, valueMap.get(value)); + if (usedValueKeys != null) usedValueKeys.add(value); + } + + @Override + public void visitConstantString(int index, byte tag, String name, int nameIndex) { + if (tag == CONSTANT_Class) { + putConstantValue(index, tag, classMap.get(name)); + if (usedClassKeys != null) usedClassKeys.add(name); + } else { + assert(tag == CONSTANT_String); + visitConstantValue(index, tag, name); + } + } + }); + if (usedUtf8Keys != null) utf8Map.keySet().removeAll(usedUtf8Keys); + if (usedClassKeys != null) classMap.keySet().removeAll(usedClassKeys); + if (usedValueKeys != null) valueMap.keySet().removeAll(usedValueKeys); + } + + Object[] getOriginalCP(final int startIndex, + final int endIndex, + final int tagMask) throws InvalidConstantPoolFormatException { + final Object[] cpArray = new Object[endIndex - startIndex]; + outer.parse(new ConstantPoolVisitor() { + + void show(int index, byte tag, Object value) { + if (index < startIndex || index >= endIndex) return; + if (((1 << tag) & tagMask) == 0) return; + cpArray[index - startIndex] = value; + } + + @Override + public void visitUTF8(int index, byte tag, String utf8) { + show(index, tag, utf8); + } + + @Override + public void visitConstantValue(int index, byte tag, Object value) { + assert(tag != CONSTANT_String); + show(index, tag, value); + } + + @Override + public void visitConstantString(int index, byte tag, + String value, int j) { + show(index, tag, value); + } + + @Override + public void visitMemberRef(int index, byte tag, + String className, String memberName, + String signature, + int j, int k) { + show(index, tag, new String[]{ className, memberName, signature }); + } + + @Override + public void visitDescriptor(int index, byte tag, + String memberName, String signature, + int j, int k) { + show(index, tag, new String[]{ memberName, signature }); + } + }); + return cpArray; + } + + /** Write the head (header plus constant pool) + * of the patched class file to the indicated stream. + */ + void writeHead(OutputStream out) throws IOException { + outer.writePatchedHead(out, patchArray); + } + + /** Write the tail (everything after the constant pool) + * of the patched class file to the indicated stream. + */ + void writeTail(OutputStream out) throws IOException { + outer.writeTail(out); + } + + private void checkConstantTag(byte tag, Object value) { + if (value == null) + throw new IllegalArgumentException( + "invalid null constant value"); + if (classForTag(tag) != value.getClass()) + throw new IllegalArgumentException( + "invalid constant value" + + (tag == CONSTANT_None ? "" + : " for tag "+tagName(tag)) + + " of class "+value.getClass()); + } + + private void checkTag(int index, byte putTag) { + byte tag = outer.tags[index]; + if (tag != putTag) + throw new IllegalArgumentException( + "invalid put operation" + + " for " + tagName(putTag) + + " at index " + index + " found " + tagName(tag)); + } + + private void checkTagMask(int index, int tagBitMask) { + byte tag = outer.tags[index]; + int tagBit = ((tag & 0x1F) == tag) ? (1 << tag) : 0; + if ((tagBit & tagBitMask) == 0) + throw new IllegalArgumentException( + "invalid put operation" + + " at index " + index + " found " + tagName(tag)); + } + + private static void checkMemberName(String memberName) { + if (memberName.indexOf(';') >= 0) + throw new IllegalArgumentException("memberName " + memberName + " contains a ';'"); + } + + /** Set the entry of the constant pool indexed by index to + * a new string. + * + * @param index an index to a constant pool entry containing a + * {@link ConstantPoolVisitor#CONSTANT_Utf8} value. + * @param utf8 a string + * + * @see ConstantPoolVisitor#visitUTF8(int, byte, String) + */ + public void putUTF8(int index, String utf8) { + if (utf8 == null) { clear(index); return; } + checkTag(index, CONSTANT_Utf8); + patchArray[index] = utf8; + } + + /** Set the entry of the constant pool indexed by index to + * a new value, depending on its dynamic type. + * + * @param index an index to a constant pool entry containing a + * one of the following structures: + * {@link ConstantPoolVisitor#CONSTANT_Integer}, + * {@link ConstantPoolVisitor#CONSTANT_Float}, + * {@link ConstantPoolVisitor#CONSTANT_Long}, + * {@link ConstantPoolVisitor#CONSTANT_Double}, + * {@link ConstantPoolVisitor#CONSTANT_String}, or + * {@link ConstantPoolVisitor#CONSTANT_Class} + * @param value a boxed int, float, long or double; or a string or class object + * @throws IllegalArgumentException if the type of the constant does not + * match the constant pool entry type, + * as reported by {@link #getTag(int)} + * + * @see #putConstantValue(int, byte, Object) + * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) + * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) + */ + public void putConstantValue(int index, Object value) { + if (value == null) { clear(index); return; } + byte tag = tagForConstant(value.getClass()); + checkConstantTag(tag, value); + checkTag(index, tag); + patchArray[index] = value; + } + + /** Set the entry of the constant pool indexed by index to + * a new value. + * + * @param index an index to a constant pool entry matching the given tag + * @param tag one of the following values: + * {@link ConstantPoolVisitor#CONSTANT_Integer}, + * {@link ConstantPoolVisitor#CONSTANT_Float}, + * {@link ConstantPoolVisitor#CONSTANT_Long}, + * {@link ConstantPoolVisitor#CONSTANT_Double}, + * {@link ConstantPoolVisitor#CONSTANT_String}, or + * {@link ConstantPoolVisitor#CONSTANT_Class} + * @param value a boxed number, string, or class object + * @throws IllegalArgumentException if the type of the constant does not + * match the constant pool entry type, or if a class name contains + * '/' or ';' + * + * @see #putConstantValue(int, Object) + * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) + * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) + */ + public void putConstantValue(int index, byte tag, Object value) { + if (value == null) { clear(index); return; } + checkTag(index, tag); + if (tag == CONSTANT_Class && value instanceof String) { + checkClassName((String) value); + } else if (tag == CONSTANT_String) { + // the JVM accepts any object as a patch for a string + } else { + // make sure the incoming value is the right type + checkConstantTag(tag, value); + } + checkTag(index, tag); + patchArray[index] = value; + } + + /** Set the entry of the constant pool indexed by index to + * a new {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. + * + * @param index an index to a constant pool entry containing a + * {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. + * @param memberName a memberName + * @param signature a signature + * @throws IllegalArgumentException if memberName contains the character ';' + * + * @see ConstantPoolVisitor#visitDescriptor(int, byte, String, String, int, int) + */ + public void putDescriptor(int index, String memberName, String signature) { + checkTag(index, CONSTANT_NameAndType); + checkMemberName(memberName); + patchArray[index] = addSemis(memberName, signature); + } + + /** Set the entry of the constant pool indexed by index to + * a new {@link ConstantPoolVisitor#CONSTANT_Fieldref}, + * {@link ConstantPoolVisitor#CONSTANT_Methodref}, or + * {@link ConstantPoolVisitor#CONSTANT_InterfaceMethodref} value. + * + * @param index an index to a constant pool entry containing a member reference + * @param className a class name + * @param memberName a field or method name + * @param signature a field or method signature + * @throws IllegalArgumentException if memberName contains the character ';' + * or signature is not a correct signature + * + * @see ConstantPoolVisitor#visitMemberRef(int, byte, String, String, String, int, int) + */ + public void putMemberRef(int index, byte tag, + String className, String memberName, String signature) { + checkTagMask(tag, CONSTANT_MemberRef_MASK); + checkTag(index, tag); + checkClassName(className); + checkMemberName(memberName); + if (signature.startsWith("(") == (tag == CONSTANT_Fieldref)) + throw new IllegalArgumentException("bad signature: "+signature); + patchArray[index] = addSemis(className, memberName, signature); + } + + static private final int CONSTANT_MemberRef_MASK = + CONSTANT_Fieldref + | CONSTANT_Methodref + | CONSTANT_InterfaceMethodref; + + private static final Map, Byte> CONSTANT_VALUE_CLASS_TAG + = new IdentityHashMap, Byte>(); + private static final Class[] CONSTANT_VALUE_CLASS = new Class[16]; + static { + Object[][] values = { + {Integer.class, CONSTANT_Integer}, + {Long.class, CONSTANT_Long}, + {Float.class, CONSTANT_Float}, + {Double.class, CONSTANT_Double}, + {String.class, CONSTANT_String}, + {Class.class, CONSTANT_Class} + }; + for (Object[] value : values) { + Class cls = (Class)value[0]; + Byte tag = (Byte) value[1]; + CONSTANT_VALUE_CLASS_TAG.put(cls, tag); + CONSTANT_VALUE_CLASS[(byte)tag] = cls; + } + } + + static Class classForTag(byte tag) { + if ((tag & 0xFF) >= CONSTANT_VALUE_CLASS.length) + return null; + return CONSTANT_VALUE_CLASS[tag]; + } + + static byte tagForConstant(Class cls) { + Byte tag = CONSTANT_VALUE_CLASS_TAG.get(cls); + return (tag == null) ? CONSTANT_None : (byte)tag; + } + + private static void checkClassName(String className) { + if (className.indexOf('/') >= 0 || className.indexOf(';') >= 0) + throw new IllegalArgumentException("invalid class name " + className); + } + + static String addSemis(String name, String... names) { + StringBuilder buf = new StringBuilder(name.length() * 5); + buf.append(name); + for (String name2 : names) { + buf.append(';').append(name2); + } + String res = buf.toString(); + assert(stripSemis(names.length, res)[0].equals(name)); + assert(stripSemis(names.length, res)[1].equals(names[0])); + assert(names.length == 1 || + stripSemis(names.length, res)[2].equals(names[1])); + return res; + } + + static String[] stripSemis(int count, String string) { + String[] res = new String[count+1]; + int pos = 0; + for (int i = 0; i < count; i++) { + int pos2 = string.indexOf(';', pos); + if (pos2 < 0) pos2 = string.length(); // yuck + res[i] = string.substring(pos, pos2); + pos = pos2; + } + res[count] = string.substring(pos); + return res; + } + + public String toString() { + StringBuilder buf = new StringBuilder(this.getClass().getName()); + buf.append("{"); + Object[] origCP = null; + for (int i = 0; i < patchArray.length; i++) { + if (patchArray[i] == null) continue; + if (origCP != null) { + buf.append(", "); + } else { + try { + origCP = getOriginalCP(); + } catch (InvalidConstantPoolFormatException ee) { + origCP = new Object[0]; + } + } + Object orig = (i < origCP.length) ? origCP[i] : "?"; + buf.append(orig).append("=").append(patchArray[i]); + } + buf.append("}"); + return buf.toString(); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolVisitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolVisitor.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2008, 2011, 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 sun.invoke.anon; + +/** + * A visitor called by {@link ConstantPoolParser#parse(ConstantPoolVisitor)} + * when a constant pool entry is parsed. + *

+ * A visit* method is called when a constant pool entry is parsed. + * The first argument is always the constant pool index. + * The second argument is always the constant pool tag, + * even for methods like {@link #visitUTF8(int, byte, String)} which only apply to one tag. + * String arguments refer to Utf8 or NameAndType entries declared elsewhere, + * and are always accompanied by the indexes of those entries. + *

+ * The order of the calls to the visit* methods is not necessarily related + * to the order of the entries in the constant pool. + * If one entry has a reference to another entry, the latter (lower-level) + * entry will be visited first. + *

+ * The following table shows the relation between constant pool entry + * types and the corresponding visit* methods: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Tag(s)Method
{@link #CONSTANT_Utf8}{@link #visitUTF8(int, byte, String)}
{@link #CONSTANT_Integer}, {@link #CONSTANT_Float}, + * {@link #CONSTANT_Long}, {@link #CONSTANT_Double}{@link #visitConstantValue(int, byte, Object)}
{@link #CONSTANT_String}, {@link #CONSTANT_Class}{@link #visitConstantString(int, byte, String, int)}
{@link #CONSTANT_NameAndType}{@link #visitDescriptor(int, byte, String, String, int, int)}
{@link #CONSTANT_Fieldref}, + * {@link #CONSTANT_Methodref}, + * {@link #CONSTANT_InterfaceMethodref}{@link #visitMemberRef(int, byte, String, String, String, int, int)}
+ * + * @see ConstantPoolPatch + * @author Remi Forax + * @author jrose + */ +public class ConstantPoolVisitor { + /** Called each time an UTF8 constant pool entry is found. + * @param index the constant pool index + * @param tag always {@link #CONSTANT_Utf8} + * @param utf8 string encoded in modified UTF-8 format passed as a {@code String} + * + * @see ConstantPoolPatch#putUTF8(int, String) + */ + public void visitUTF8(int index, byte tag, String utf8) { + // do nothing + } + + /** Called for each constant pool entry that encodes an integer, + * a float, a long, or a double. + * Constant strings and classes are not managed by this method but + * by {@link #visitConstantString(int, byte, String, int)}. + * + * @param index the constant pool index + * @param tag one of {@link #CONSTANT_Integer}, + * {@link #CONSTANT_Float}, + * {@link #CONSTANT_Long}, + * or {@link #CONSTANT_Double} + * @param value encoded value + * + * @see ConstantPoolPatch#putConstantValue(int, Object) + */ + public void visitConstantValue(int index, byte tag, Object value) { + // do nothing + } + + /** Called for each constant pool entry that encodes a string or a class. + * @param index the constant pool index + * @param tag one of {@link #CONSTANT_String}, + * {@link #CONSTANT_Class}, + * @param name string body or class name (using dot separator) + * @param nameIndex the index of the Utf8 string for the name + * + * @see ConstantPoolPatch#putConstantValue(int, byte, Object) + */ + public void visitConstantString(int index, byte tag, + String name, int nameIndex) { + // do nothing + } + + /** Called for each constant pool entry that encodes a name and type. + * @param index the constant pool index + * @param tag always {@link #CONSTANT_NameAndType} + * @param memberName a field or method name + * @param signature the member signature + * @param memberNameIndex index of the Utf8 string for the member name + * @param signatureIndex index of the Utf8 string for the signature + * + * @see ConstantPoolPatch#putDescriptor(int, String, String) + */ + public void visitDescriptor(int index, byte tag, + String memberName, String signature, + int memberNameIndex, int signatureIndex) { + // do nothing + } + + /** Called for each constant pool entry that encodes a field or method. + * @param index the constant pool index + * @param tag one of {@link #CONSTANT_Fieldref}, + * or {@link #CONSTANT_Methodref}, + * or {@link #CONSTANT_InterfaceMethodref} + * @param className the class name (using dot separator) + * @param memberName name of the field or method + * @param signature the field or method signature + * @param classNameIndex index of the Utf8 string for the class name + * @param descriptorIndex index of the NameAndType descriptor constant + * + * @see ConstantPoolPatch#putMemberRef(int, byte, String, String, String) + */ + public void visitMemberRef(int index, byte tag, + String className, String memberName, String signature, + int classNameIndex, int descriptorIndex) { + // do nothing + } + + public static final byte + CONSTANT_None = 0, + CONSTANT_Utf8 = 1, + //CONSTANT_Unicode = 2, /* unused */ + CONSTANT_Integer = 3, + CONSTANT_Float = 4, + CONSTANT_Long = 5, + CONSTANT_Double = 6, + CONSTANT_Class = 7, + CONSTANT_String = 8, + CONSTANT_Fieldref = 9, + CONSTANT_Methodref = 10, + CONSTANT_InterfaceMethodref = 11, + CONSTANT_NameAndType = 12; + + private static String[] TAG_NAMES = { + "Empty", + "Utf8", + null, //"Unicode", + "Integer", + "Float", + "Long", + "Double", + "Class", + "String", + "Fieldref", + "Methodref", + "InterfaceMethodref", + "NameAndType" + }; + + public static String tagName(byte tag) { + String name = null; + if ((tag & 0xFF) < TAG_NAMES.length) + name = TAG_NAMES[tag]; + if (name == null) + name = "Unknown#"+(tag&0xFF); + return name; + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/anon/InvalidConstantPoolFormatException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/InvalidConstantPoolFormatException.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008, 2011, 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 sun.invoke.anon; + +/** Exception used when there is an error in the constant pool + * format. + */ +public class InvalidConstantPoolFormatException extends Exception { + private static final long serialVersionUID=-6103888330523770949L; + + public InvalidConstantPoolFormatException(String message,Throwable cause) { + super(message,cause); + } + + public InvalidConstantPoolFormatException(String message) { + super(message); + } + + public InvalidConstantPoolFormatException(Throwable cause) { + super(cause); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/empty/Empty.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/empty/Empty.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009, 2011, 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 sun.invoke.empty; + +/** + * An empty class in an empty package. + * Used as a proxy for unprivileged code, since making access checks + * against it will only succeed against public methods in public types. + *

+ * This class also stands (internally to sun.invoke) for the type of a + * value that cannot be produced, because the expression of this type + * always returns abnormally. (Cf. Nothing in the closures proposal.) + * @author jrose + */ +public class Empty { + private Empty() { throw new InternalError(); } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/package-info.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2008, 2011, 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. + */ + +/** + * Implementation details for JSR 292 RI, package java.lang.invoke. + * @author jrose + */ + +package sun.invoke; diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/BytecodeDescriptor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/BytecodeDescriptor.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2008, 2011, 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 sun.invoke.util; + +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility routines for dealing with bytecode-level signatures. + * @author jrose + */ +public class BytecodeDescriptor { + + private BytecodeDescriptor() { } // cannot instantiate + + public static List> parseMethod(String bytecodeSignature, ClassLoader loader) { + return parseMethod(bytecodeSignature, 0, bytecodeSignature.length(), loader); + } + + static List> parseMethod(String bytecodeSignature, + int start, int end, ClassLoader loader) { + if (loader == null) + loader = ClassLoader.getSystemClassLoader(); + String str = bytecodeSignature; + int[] i = {start}; + ArrayList> ptypes = new ArrayList>(); + if (i[0] < end && str.charAt(i[0]) == '(') { + ++i[0]; // skip '(' + while (i[0] < end && str.charAt(i[0]) != ')') { + Class pt = parseSig(str, i, end, loader); + if (pt == null || pt == void.class) + parseError(str, "bad argument type"); + ptypes.add(pt); + } + ++i[0]; // skip ')' + } else { + parseError(str, "not a method type"); + } + Class rtype = parseSig(str, i, end, loader); + if (rtype == null || i[0] != end) + parseError(str, "bad return type"); + ptypes.add(rtype); + return ptypes; + } + + static private void parseError(String str, String msg) { + throw new IllegalArgumentException("bad signature: "+str+": "+msg); + } + + static private Class parseSig(String str, int[] i, int end, ClassLoader loader) { + if (i[0] == end) return null; + char c = str.charAt(i[0]++); + if (c == 'L') { + int begc = i[0], endc = str.indexOf(';', begc); + if (endc < 0) return null; + i[0] = endc+1; + String name = str.substring(begc, endc).replace('/', '.'); + try { + return loader.loadClass(name); + } catch (ClassNotFoundException ex) { + throw new TypeNotPresentException(name, ex); + } + } else if (c == '[') { + Class t = parseSig(str, i, end, loader); + if (t != null) + t = java.lang.reflect.Array.newInstance(t, 0).getClass(); + return t; + } else { + return Wrapper.forBasicType(c).primitiveType(); + } + } + + public static String unparse(Class type) { + StringBuilder sb = new StringBuilder(); + unparseSig(type, sb); + return sb.toString(); + } + + public static String unparse(MethodType type) { + return unparseMethod(type.returnType(), type.parameterList()); + } + + public static String unparse(Object type) { + if (type instanceof Class) + return unparse((Class) type); + if (type instanceof MethodType) + return unparse((MethodType) type); + return (String) type; + } + + public static String unparseMethod(Class rtype, List> ptypes) { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (Class pt : ptypes) + unparseSig(pt, sb); + sb.append(')'); + unparseSig(rtype, sb); + return sb.toString(); + } + + static private void unparseSig(Class t, StringBuilder sb) { + char c = Wrapper.forBasicType(t).basicTypeChar(); + if (c != 'L') { + sb.append(c); + } else { + boolean lsemi = (!t.isArray()); + if (lsemi) sb.append('L'); + sb.append(t.getName().replace('.', '/')); + if (lsemi) sb.append(';'); + } + } + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/BytecodeName.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/BytecodeName.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2007, 2011, 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 sun.invoke.util; + +/** + * Utility routines for dealing with bytecode-level names. + * Includes universal mangling rules for the JVM. + * + *

Avoiding Dangerous Characters

+ * + *

+ * The JVM defines a very small set of characters which are illegal + * in name spellings. We will slightly extend and regularize this set + * into a group of dangerous characters. + * These characters will then be replaced, in mangled names, by escape sequences. + * In addition, accidental escape sequences must be further escaped. + * Finally, a special prefix will be applied if and only if + * the mangling would otherwise fail to begin with the escape character. + * This happens to cover the corner case of the null string, + * and also clearly marks symbols which need demangling. + *

+ *

+ * Dangerous characters are the union of all characters forbidden + * or otherwise restricted by the JVM specification, + * plus their mates, if they are brackets + * ([ and ], + * < and >), + * plus, arbitrarily, the colon character :. + * There is no distinction between type, method, and field names. + * This makes it easier to convert between mangled names of different + * types, since they do not need to be decoded (demangled). + *

+ *

+ * The escape character is backslash \ + * (also known as reverse solidus). + * This character is, until now, unheard of in bytecode names, + * but traditional in the proposed role. + * + *

+ *

Replacement Characters

+ * + * + *

+ * Every escape sequence is two characters + * (in fact, two UTF8 bytes) beginning with + * the escape character and followed by a + * replacement character. + * (Since the replacement character is never a backslash, + * iterated manglings do not double in size.) + *

+ *

+ * Each dangerous character has some rough visual similarity + * to its corresponding replacement character. + * This makes mangled symbols easier to recognize by sight. + *

+ *

+ * The dangerous characters are + * / (forward slash, used to delimit package components), + * . (dot, also a package delimiter), + * ; (semicolon, used in signatures), + * $ (dollar, used in inner classes and synthetic members), + * < (left angle), + * > (right angle), + * [ (left square bracket, used in array types), + * ] (right square bracket, reserved in this scheme for language use), + * and : (colon, reserved in this scheme for language use). + * Their replacements are, respectively, + * | (vertical bar), + * , (comma), + * ? (question mark), + * % (percent), + * ^ (caret), + * _ (underscore), and + * { (left curly bracket), + * } (right curly bracket), + * ! (exclamation mark). + * In addition, the replacement character for the escape character itself is + * - (hyphen), + * and the replacement character for the null prefix is + * = (equal sign). + *

+ *

+ * An escape character \ + * followed by any of these replacement characters + * is an escape sequence, and there are no other escape sequences. + * An equal sign is only part of an escape sequence + * if it is the second character in the whole string, following a backslash. + * Two consecutive backslashes do not form an escape sequence. + *

+ *

+ * Each escape sequence replaces a so-called original character + * which is either one of the dangerous characters or the escape character. + * A null prefix replaces an initial null string, not a character. + *

+ *

+ * All this implies that escape sequences cannot overlap and may be + * determined all at once for a whole string. Note that a spelling + * string can contain accidental escapes, apparent escape + * sequences which must not be interpreted as manglings. + * These are disabled by replacing their leading backslash with an + * escape sequence (\-). To mangle a string, three logical steps + * are required, though they may be carried out in one pass: + *

+ *
    + *
  1. In each accidental escape, replace the backslash with an escape sequence + * (\-).
  2. + *
  3. Replace each dangerous character with an escape sequence + * (\| for /, etc.).
  4. + *
  5. If the first two steps introduced any change, and + * if the string does not already begin with a backslash, prepend a null prefix (\=).
  6. + *
+ * + * To demangle a mangled string that begins with an escape, + * remove any null prefix, and then replace (in parallel) + * each escape sequence by its original character. + *

Spelling strings which contain accidental + * escapes must have them replaced, even if those + * strings do not contain dangerous characters. + * This restriction means that mangling a string always + * requires a scan of the string for escapes. + * But then, a scan would be required anyway, + * to check for dangerous characters. + * + *

+ *

Nice Properties

+ * + *

+ * If a bytecode name does not contain any escape sequence, + * demangling is a no-op: The string demangles to itself. + * Such a string is called self-mangling. + * Almost all strings are self-mangling. + * In practice, to demangle almost any name “found in nature”, + * simply verify that it does not begin with a backslash. + *

+ *

+ * Mangling is a one-to-one function, while demangling + * is a many-to-one function. + * A mangled string is defined as validly mangled if + * it is in fact the unique mangling of its spelling string. + * Three examples of invalidly mangled strings are \=foo, + * \-bar, and baz\!, which demangle to foo, \bar, and + * baz\!, but then remangle to foo, \bar, and \=baz\-!. + * If a language back-end or runtime is using mangled names, + * it should never present an invalidly mangled bytecode + * name to the JVM. If the runtime encounters one, + * it should also report an error, since such an occurrence + * probably indicates a bug in name encoding which + * will lead to errors in linkage. + * However, this note does not propose that the JVM verifier + * detect invalidly mangled names. + *

+ *

+ * As a result of these rules, it is a simple matter to + * compute validly mangled substrings and concatenations + * of validly mangled strings, and (with a little care) + * these correspond to corresponding operations on their + * spelling strings. + *

+ *
    + *
  • Any prefix of a validly mangled string is also validly mangled, + * although a null prefix may need to be removed.
  • + *
  • Any suffix of a validly mangled string is also validly mangled, + * although a null prefix may need to be added.
  • + *
  • Two validly mangled strings, when concatenated, + * are also validly mangled, although any null prefix + * must be removed from the second string, + * and a trailing backslash on the first string may need escaping, + * if it would participate in an accidental escape when followed + * by the first character of the second string.
  • + *
+ *

If languages that include non-Java symbol spellings use this + * mangling convention, they will enjoy the following advantages: + *

+ *
    + *
  • They can interoperate via symbols they share in common.
  • + *
  • Low-level tools, such as backtrace printers, will have readable displays.
  • + *
  • Future JVM and language extensions can safely use the dangerous characters + * for structuring symbols, but will never interfere with valid spellings.
  • + *
  • Runtimes and compilers can use standard libraries for mangling and demangling.
  • + *
  • Occasional transliterations and name composition will be simple and regular, + * for classes, methods, and fields.
  • + *
  • Bytecode names will continue to be compact. + * When mangled, spellings will at most double in length, either in + * UTF8 or UTF16 format, and most will not change at all.
  • + *
+ * + * + *

Suggestions for Human Readable Presentations

+ * + * + *

+ * For human readable displays of symbols, + * it will be better to present a string-like quoted + * representation of the spelling, because JVM users + * are generally familiar with such tokens. + * We suggest using single or double quotes before and after + * mangled symbols which are not valid Java identifiers, + * with quotes, backslashes, and non-printing characters + * escaped as if for literals in the Java language. + *

+ *

+ * For example, an HTML-like spelling + * <pre> mangles to + * \^pre\_ and could + * display more cleanly as + * '<pre>', + * with the quotes included. + * Such string-like conventions are not suitable + * for mangled bytecode names, in part because + * dangerous characters must be eliminated, rather + * than just quoted. Otherwise internally structured + * strings like package prefixes and method signatures + * could not be reliably parsed. + *

+ *

+ * In such human-readable displays, invalidly mangled + * names should not be demangled and quoted, + * for this would be misleading. Likewise, JVM symbols + * which contain dangerous characters (like dots in field + * names or brackets in method names) should not be + * simply quoted. The bytecode names + * \=phase\,1 and + * phase.1 are distinct, + * and in demangled displays they should be presented as + * 'phase.1' and something like + * 'phase'.1, respectively. + *

+ * + * @author John Rose + * @version 1.2, 02/06/2008 + * @see http://blogs.sun.com/jrose/entry/symbolic_freedom_in_the_vm + */ +public class BytecodeName { + private BytecodeName() { } // static only class + + /** Given a source name, produce the corresponding bytecode name. + * The source name should not be qualified, because any syntactic + * markers (dots, slashes, dollar signs, colons, etc.) will be mangled. + * @param s the source name + * @return a valid bytecode name which represents the source name + */ + public static String toBytecodeName(String s) { + String bn = mangle(s); + assert((Object)bn == s || looksMangled(bn)) : bn; + assert(s.equals(toSourceName(bn))) : s; + return bn; + } + + /** Given an unqualified bytecode name, produce the corresponding source name. + * The bytecode name must not contain dangerous characters. + * In particular, it must not be qualified or segmented by colon {@code ':'}. + * @param s the bytecode name + * @return the source name, which may possibly have unsafe characters + * @throws IllegalArgumentException if the bytecode name is not {@link #isSafeBytecodeName safe} + * @see #isSafeBytecodeName(java.lang.String) + */ + public static String toSourceName(String s) { + checkSafeBytecodeName(s); + String sn = s; + if (looksMangled(s)) { + sn = demangle(s); + assert(s.equals(mangle(sn))) : s+" => "+sn+" => "+mangle(sn); + } + return sn; + } + + /** + * Given a bytecode name from a classfile, separate it into + * components delimited by dangerous characters. + * Each resulting array element will be either a dangerous character, + * or else a safe bytecode name. + * (The safe name might possibly be mangled to hide further dangerous characters.) + * For example, the qualified class name {@code java/lang/String} + * will be parsed into the array {@code {"java", '/', "lang", '/', "String"}}. + * The name {@code <init>} will be parsed into { '<', "init", '>'}} + * The name {@code foo/bar$:baz} will be parsed into + * {@code {"foo", '/', "bar", '$', ':', "baz"}}. + * The name {@code ::\=:foo:\=bar\!baz} will be parsed into + * {@code {':', ':', "", ':', "foo", ':', "bar:baz"}}. + */ + public static Object[] parseBytecodeName(String s) { + int slen = s.length(); + Object[] res = null; + for (int pass = 0; pass <= 1; pass++) { + int fillp = 0; + int lasti = 0; + for (int i = 0; i <= slen; i++) { + int whichDC = -1; + if (i < slen) { + whichDC = DANGEROUS_CHARS.indexOf(s.charAt(i)); + if (whichDC < DANGEROUS_CHAR_FIRST_INDEX) continue; + } + // got to end of string or next dangerous char + if (lasti < i) { + // normal component + if (pass != 0) + res[fillp] = toSourceName(s.substring(lasti, i)); + fillp++; + lasti = i+1; + } + if (whichDC >= DANGEROUS_CHAR_FIRST_INDEX) { + if (pass != 0) + res[fillp] = DANGEROUS_CHARS_CA[whichDC]; + fillp++; + lasti = i+1; + } + } + if (pass != 0) break; + // between passes, build the result array + res = new Object[fillp]; + if (fillp <= 1 && lasti == 0) { + if (fillp != 0) res[0] = toSourceName(s); + break; + } + } + return res; + } + + /** + * Given a series of components, create a bytecode name for a classfile. + * This is the inverse of {@link #parseBytecodeName(java.lang.String)}. + * Each component must either be an interned one-character string of + * a dangerous character, or else a safe bytecode name. + * @param components a series of name components + * @return the concatenation of all components + * @throws IllegalArgumentException if any component contains an unsafe + * character, and is not an interned one-character string + * @throws NullPointerException if any component is null + */ + public static String unparseBytecodeName(Object[] components) { + Object[] components0 = components; + for (int i = 0; i < components.length; i++) { + Object c = components[i]; + if (c instanceof String) { + String mc = toBytecodeName((String) c); + if (i == 0 && components.length == 1) + return mc; // usual case + if ((Object)mc != c) { + if (components == components0) + components = components.clone(); + components[i] = c = mc; + } + } + } + return appendAll(components); + } + private static String appendAll(Object[] components) { + if (components.length <= 1) { + if (components.length == 1) { + return String.valueOf(components[0]); + } + return ""; + } + int slen = 0; + for (Object c : components) { + if (c instanceof String) + slen += String.valueOf(c).length(); + else + slen += 1; + } + StringBuilder sb = new StringBuilder(slen); + for (Object c : components) { + sb.append(c); + } + return sb.toString(); + } + + /** + * Given a bytecode name, produce the corresponding display name. + * This is the source name, plus quotes if needed. + * If the bytecode name contains dangerous characters, + * assume that they are being used as punctuation, + * and pass them through unchanged. + * Non-empty runs of non-dangerous characters are demangled + * if necessary, and the resulting names are quoted if + * they are not already valid Java identifiers, or if + * they contain a dangerous character (i.e., dollar sign "$"). + * Single quotes are used when quoting. + * Within quoted names, embedded single quotes and backslashes + * are further escaped by prepended backslashes. + * + * @param s the original bytecode name (which may be qualified) + * @return a human-readable presentation + */ + public static String toDisplayName(String s) { + Object[] components = parseBytecodeName(s); + for (int i = 0; i < components.length; i++) { + if (!(components[i] instanceof String)) + continue; + String sn = (String) components[i]; + // note that the name is already demangled! + //sn = toSourceName(sn); + if (!isJavaIdent(sn) || sn.indexOf('$') >=0 ) { + components[i] = quoteDisplay(sn); + } + } + return appendAll(components); + } + private static boolean isJavaIdent(String s) { + int slen = s.length(); + if (slen == 0) return false; + if (!Character.isJavaIdentifierStart(s.charAt(0))) + return false; + for (int i = 1; i < slen; i++) { + if (!Character.isJavaIdentifierPart(s.charAt(i))) + return false; + } + return true; + } + private static String quoteDisplay(String s) { + // TO DO: Replace wierd characters in s by C-style escapes. + return "'"+s.replaceAll("['\\\\]", "\\\\$0")+"'"; + } + + private static void checkSafeBytecodeName(String s) + throws IllegalArgumentException { + if (!isSafeBytecodeName(s)) { + throw new IllegalArgumentException(s); + } + } + + /** + * Report whether a simple name is safe as a bytecode name. + * Such names are acceptable in class files as class, method, and field names. + * Additionally, they are free of "dangerous" characters, even if those + * characters are legal in some (or all) names in class files. + * @param s the proposed bytecode name + * @return true if the name is non-empty and all of its characters are safe + */ + public static boolean isSafeBytecodeName(String s) { + if (s.length() == 0) return false; + // check occurrences of each DANGEROUS char + for (char xc : DANGEROUS_CHARS_A) { + if (xc == ESCAPE_C) continue; // not really that dangerous + if (s.indexOf(xc) >= 0) return false; + } + return true; + } + + /** + * Report whether a character is safe in a bytecode name. + * This is true of any unicode character except the following + * dangerous characters: {@code ".;:$[]<>/"}. + * @param s the proposed character + * @return true if the character is safe to use in classfiles + */ + public static boolean isSafeBytecodeChar(char c) { + return DANGEROUS_CHARS.indexOf(c) < DANGEROUS_CHAR_FIRST_INDEX; + } + + private static boolean looksMangled(String s) { + return s.charAt(0) == ESCAPE_C; + } + + private static String mangle(String s) { + if (s.length() == 0) + return NULL_ESCAPE; + + // build this lazily, when we first need an escape: + StringBuilder sb = null; + + for (int i = 0, slen = s.length(); i < slen; i++) { + char c = s.charAt(i); + + boolean needEscape = false; + if (c == ESCAPE_C) { + if (i+1 < slen) { + char c1 = s.charAt(i+1); + if ((i == 0 && c1 == NULL_ESCAPE_C) + || c1 != originalOfReplacement(c1)) { + // an accidental escape + needEscape = true; + } + } + } else { + needEscape = isDangerous(c); + } + + if (!needEscape) { + if (sb != null) sb.append(c); + continue; + } + + // build sb if this is the first escape + if (sb == null) { + sb = new StringBuilder(s.length()+10); + // mangled names must begin with a backslash: + if (s.charAt(0) != ESCAPE_C && i > 0) + sb.append(NULL_ESCAPE); + // append the string so far, which is unremarkable: + sb.append(s.substring(0, i)); + } + + // rewrite \ to \-, / to \|, etc. + sb.append(ESCAPE_C); + sb.append(replacementOf(c)); + } + + if (sb != null) return sb.toString(); + + return s; + } + + private static String demangle(String s) { + // build this lazily, when we first meet an escape: + StringBuilder sb = null; + + int stringStart = 0; + if (s.startsWith(NULL_ESCAPE)) + stringStart = 2; + + for (int i = stringStart, slen = s.length(); i < slen; i++) { + char c = s.charAt(i); + + if (c == ESCAPE_C && i+1 < slen) { + // might be an escape sequence + char rc = s.charAt(i+1); + char oc = originalOfReplacement(rc); + if (oc != rc) { + // build sb if this is the first escape + if (sb == null) { + sb = new StringBuilder(s.length()); + // append the string so far, which is unremarkable: + sb.append(s.substring(stringStart, i)); + } + ++i; // skip both characters + c = oc; + } + } + + if (sb != null) + sb.append(c); + } + + if (sb != null) return sb.toString(); + + return s.substring(stringStart); + } + + static char ESCAPE_C = '\\'; + // empty escape sequence to avoid a null name or illegal prefix + static char NULL_ESCAPE_C = '='; + static String NULL_ESCAPE = ESCAPE_C+""+NULL_ESCAPE_C; + + static final String DANGEROUS_CHARS = "\\/.;:$[]<>"; // \\ must be first + static final String REPLACEMENT_CHARS = "-|,?!%{}^_"; + static final int DANGEROUS_CHAR_FIRST_INDEX = 1; // index after \\ + static char[] DANGEROUS_CHARS_A = DANGEROUS_CHARS.toCharArray(); + static char[] REPLACEMENT_CHARS_A = REPLACEMENT_CHARS.toCharArray(); + static final Character[] DANGEROUS_CHARS_CA; + static { + Character[] dcca = new Character[DANGEROUS_CHARS.length()]; + for (int i = 0; i < dcca.length; i++) + dcca[i] = Character.valueOf(DANGEROUS_CHARS.charAt(i)); + DANGEROUS_CHARS_CA = dcca; + } + + static final long[] SPECIAL_BITMAP = new long[2]; // 128 bits + static { + String SPECIAL = DANGEROUS_CHARS + REPLACEMENT_CHARS; + //System.out.println("SPECIAL = "+SPECIAL); + for (char c : SPECIAL.toCharArray()) { + SPECIAL_BITMAP[c >>> 6] |= 1L << c; + } + } + static boolean isSpecial(char c) { + if ((c >>> 6) < SPECIAL_BITMAP.length) + return ((SPECIAL_BITMAP[c >>> 6] >> c) & 1) != 0; + else + return false; + } + static char replacementOf(char c) { + if (!isSpecial(c)) return c; + int i = DANGEROUS_CHARS.indexOf(c); + if (i < 0) return c; + return REPLACEMENT_CHARS.charAt(i); + } + static char originalOfReplacement(char c) { + if (!isSpecial(c)) return c; + int i = REPLACEMENT_CHARS.indexOf(c); + if (i < 0) return c; + return DANGEROUS_CHARS.charAt(i); + } + static boolean isDangerous(char c) { + if (!isSpecial(c)) return false; + return (DANGEROUS_CHARS.indexOf(c) >= DANGEROUS_CHAR_FIRST_INDEX); + } + static int indexOfDangerousChar(String s, int from) { + for (int i = from, slen = s.length(); i < slen; i++) { + if (isDangerous(s.charAt(i))) + return i; + } + return -1; + } + static int lastIndexOfDangerousChar(String s, int from) { + for (int i = Math.min(from, s.length()-1); i >= 0; i--) { + if (isDangerous(s.charAt(i))) + return i; + } + return -1; + } + + +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/ValueConversions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/ValueConversions.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1188 @@ +/* + * 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 sun.invoke.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; + +public class ValueConversions { + private static final Class THIS_CLASS = ValueConversions.class; + // Do not adjust this except for special platforms: + private static final int MAX_ARITY; + static { + final Object[] values = { 255 }; + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + values[0] = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255); + return null; + } + }); + MAX_ARITY = (Integer) values[0]; + } + + private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); + + private static EnumMap[] newWrapperCaches(int n) { + @SuppressWarnings("unchecked") // generic array creation + EnumMap[] caches + = (EnumMap[]) new EnumMap[n]; + for (int i = 0; i < n; i++) + caches[i] = new EnumMap<>(Wrapper.class); + return caches; + } + + /// Converting references to values. + + // There are several levels of this unboxing conversions: + // no conversions: exactly Integer.valueOf, etc. + // implicit conversions sanctioned by JLS 5.1.2, etc. + // explicit conversions as allowed by explicitCastArguments + + static int unboxInteger(Object x, boolean cast) { + if (x instanceof Integer) + return ((Integer) x).intValue(); + return primitiveConversion(Wrapper.INT, x, cast).intValue(); + } + + static byte unboxByte(Object x, boolean cast) { + if (x instanceof Byte) + return ((Byte) x).byteValue(); + return primitiveConversion(Wrapper.BYTE, x, cast).byteValue(); + } + + static short unboxShort(Object x, boolean cast) { + if (x instanceof Short) + return ((Short) x).shortValue(); + return primitiveConversion(Wrapper.SHORT, x, cast).shortValue(); + } + + static boolean unboxBoolean(Object x, boolean cast) { + if (x instanceof Boolean) + return ((Boolean) x).booleanValue(); + return (primitiveConversion(Wrapper.BOOLEAN, x, cast).intValue() & 1) != 0; + } + + static char unboxCharacter(Object x, boolean cast) { + if (x instanceof Character) + return ((Character) x).charValue(); + return (char) primitiveConversion(Wrapper.CHAR, x, cast).intValue(); + } + + static long unboxLong(Object x, boolean cast) { + if (x instanceof Long) + return ((Long) x).longValue(); + return primitiveConversion(Wrapper.LONG, x, cast).longValue(); + } + + static float unboxFloat(Object x, boolean cast) { + if (x instanceof Float) + return ((Float) x).floatValue(); + return primitiveConversion(Wrapper.FLOAT, x, cast).floatValue(); + } + + static double unboxDouble(Object x, boolean cast) { + if (x instanceof Double) + return ((Double) x).doubleValue(); + return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); + } + + private static MethodType unboxType(Wrapper wrap) { + return MethodType.methodType(wrap.primitiveType(), Object.class, boolean.class); + } + + private static final EnumMap[] + UNBOX_CONVERSIONS = newWrapperCaches(2); + + private static MethodHandle unbox(Wrapper wrap, boolean cast) { + EnumMap cache = UNBOX_CONVERSIONS[(cast?1:0)]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + switch (wrap) { + case OBJECT: + mh = IDENTITY; break; + case VOID: + mh = IGNORE; break; + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + // look up the method + String name = "unbox" + wrap.wrapperSimpleName(); + MethodType type = unboxType(wrap); + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + if (mh != null) { + mh = MethodHandles.insertArguments(mh, 1, cast); + cache.put(wrap, mh); + return mh; + } + throw new IllegalArgumentException("cannot find unbox adapter for " + wrap + + (cast ? " (cast)" : "")); + } + + public static MethodHandle unboxCast(Wrapper type) { + return unbox(type, true); + } + + public static MethodHandle unbox(Class type) { + return unbox(Wrapper.forPrimitiveType(type), false); + } + + public static MethodHandle unboxCast(Class type) { + return unbox(Wrapper.forPrimitiveType(type), true); + } + + static private final Integer ZERO_INT = 0, ONE_INT = 1; + + /// Primitive conversions + /** + * Produce a Number which represents the given value {@code x} + * according to the primitive type of the given wrapper {@code wrap}. + * Caller must invoke intValue, byteValue, longValue (etc.) on the result + * to retrieve the desired primitive value. + */ + public static Number primitiveConversion(Wrapper wrap, Object x, boolean cast) { + // Maybe merge this code with Wrapper.convert/cast. + Number res; + if (x == null) { + if (!cast) return null; + return ZERO_INT; + } + if (x instanceof Number) { + res = (Number) x; + } else if (x instanceof Boolean) { + res = ((boolean)x ? ONE_INT : ZERO_INT); + } else if (x instanceof Character) { + res = (int)(char)x; + } else { + // this will fail with the required ClassCastException: + res = (Number) x; + } + Wrapper xwrap = Wrapper.findWrapperType(x.getClass()); + if (xwrap == null || !cast && !wrap.isConvertibleFrom(xwrap)) + // this will fail with the required ClassCastException: + return (Number) wrap.wrapperType().cast(x); + return res; + } + + /** + * The JVM verifier allows boolean, byte, short, or char to widen to int. + * Support exactly this conversion, from a boxed value type Boolean, + * Byte, Short, Character, or Integer. + */ + public static int widenSubword(Object x) { + if (x instanceof Integer) + return (int) x; + else if (x instanceof Boolean) + return fromBoolean((boolean) x); + else if (x instanceof Character) + return (char) x; + else if (x instanceof Short) + return (short) x; + else if (x instanceof Byte) + return (byte) x; + else + // Fail with a ClassCastException. + return (int) x; + } + + /// Converting primitives to references + + static Integer boxInteger(int x) { + return x; + } + + static Byte boxByte(byte x) { + return x; + } + + static Short boxShort(short x) { + return x; + } + + static Boolean boxBoolean(boolean x) { + return x; + } + + static Character boxCharacter(char x) { + return x; + } + + static Long boxLong(long x) { + return x; + } + + static Float boxFloat(float x) { + return x; + } + + static Double boxDouble(double x) { + return x; + } + + private static MethodType boxType(Wrapper wrap) { + // be exact, since return casts are hard to compose + Class boxType = wrap.wrapperType(); + return MethodType.methodType(boxType, wrap.primitiveType()); + } + + private static final EnumMap[] + BOX_CONVERSIONS = newWrapperCaches(2); + + private static MethodHandle box(Wrapper wrap, boolean exact) { + EnumMap cache = BOX_CONVERSIONS[(exact?1:0)]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + switch (wrap) { + case OBJECT: + mh = IDENTITY; break; + case VOID: + mh = ZERO_OBJECT; + break; + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + // look up the method + String name = "box" + wrap.wrapperSimpleName(); + MethodType type = boxType(wrap); + if (exact) { + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + } else { + mh = box(wrap, !exact).asType(type.erase()); + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + throw new IllegalArgumentException("cannot find box adapter for " + + wrap + (exact ? " (exact)" : "")); + } + + public static MethodHandle box(Class type) { + boolean exact = false; + // e.g., boxShort(short)Short if exact, + // e.g., boxShort(short)Object if !exact + return box(Wrapper.forPrimitiveType(type), exact); + } + + public static MethodHandle box(Wrapper type) { + boolean exact = false; + return box(type, exact); + } + + /// Constant functions + + static void ignore(Object x) { + // no value to return; this is an unbox of null + } + + static void empty() { + } + + static Object zeroObject() { + return null; + } + + static int zeroInteger() { + return 0; + } + + static long zeroLong() { + return 0; + } + + static float zeroFloat() { + return 0; + } + + static double zeroDouble() { + return 0; + } + + private static final EnumMap[] + CONSTANT_FUNCTIONS = newWrapperCaches(2); + + public static MethodHandle zeroConstantFunction(Wrapper wrap) { + EnumMap cache = CONSTANT_FUNCTIONS[0]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + MethodType type = MethodType.methodType(wrap.primitiveType()); + switch (wrap) { + case VOID: + mh = EMPTY; + break; + case OBJECT: + case INT: case LONG: case FLOAT: case DOUBLE: + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "zero"+wrap.wrapperSimpleName(), type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + break; + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + + // use zeroInt and cast the result + if (wrap.isSubwordOrInt() && wrap != Wrapper.INT) { + mh = MethodHandles.explicitCastArguments(zeroConstantFunction(Wrapper.INT), type); + cache.put(wrap, mh); + return mh; + } + throw new IllegalArgumentException("cannot find zero constant for " + wrap); + } + + /// Converting references to references. + + /** + * Identity function. + * @param x an arbitrary reference value + * @return the same value x + */ + static T identity(T x) { + return x; + } + + static T[] identity(T[] x) { + return x; + } + + /** + * Identity function on ints. + * @param x an arbitrary int value + * @return the same value x + */ + static int identity(int x) { + return x; + } + + static byte identity(byte x) { + return x; + } + + static short identity(short x) { + return x; + } + + static boolean identity(boolean x) { + return x; + } + + static char identity(char x) { + return x; + } + + /** + * Identity function on longs. + * @param x an arbitrary long value + * @return the same value x + */ + static long identity(long x) { + return x; + } + + static float identity(float x) { + return x; + } + + static double identity(double x) { + return x; + } + + /** + * Identity function, with reference cast. + * @param t an arbitrary reference type + * @param x an arbitrary reference value + * @return the same value x + */ + @SuppressWarnings("unchecked") + static T castReference(Class t, U x) { + // inlined Class.cast because we can't ForceInline it + if (x != null && !t.isInstance(x)) + throw newClassCastException(t, x); + return (T) x; + } + + private static ClassCastException newClassCastException(Class t, Object obj) { + return new ClassCastException("Cannot cast " + obj.getClass().getName() + " to " + t.getName()); + } + + private static final MethodHandle IDENTITY, CAST_REFERENCE, ZERO_OBJECT, IGNORE, EMPTY, + ARRAY_IDENTITY, FILL_NEW_TYPED_ARRAY, FILL_NEW_ARRAY; + static { + try { + MethodType idType = MethodType.genericMethodType(1); + MethodType castType = idType.insertParameterTypes(0, Class.class); + MethodType ignoreType = idType.changeReturnType(void.class); + MethodType zeroObjectType = MethodType.genericMethodType(0); + IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType); + //CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); + CAST_REFERENCE = IMPL_LOOKUP.findStatic(THIS_CLASS, "castReference", castType); + ZERO_OBJECT = IMPL_LOOKUP.findStatic(THIS_CLASS, "zeroObject", zeroObjectType); + IGNORE = IMPL_LOOKUP.findStatic(THIS_CLASS, "ignore", ignoreType); + EMPTY = IMPL_LOOKUP.findStatic(THIS_CLASS, "empty", ignoreType.dropParameterTypes(0, 1)); + ARRAY_IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", MethodType.methodType(Object[].class, Object[].class)); + FILL_NEW_ARRAY = IMPL_LOOKUP + .findStatic(THIS_CLASS, "fillNewArray", + MethodType.methodType(Object[].class, Integer.class, Object[].class)); + FILL_NEW_TYPED_ARRAY = IMPL_LOOKUP + .findStatic(THIS_CLASS, "fillNewTypedArray", + MethodType.methodType(Object[].class, Object[].class, Integer.class, Object[].class)); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw newInternalError("uncaught exception", ex); + } + } + + // Varargs methods need to be in a separately initialized class, to avoid bootstrapping problems. + static class LazyStatics { + private static final MethodHandle COPY_AS_REFERENCE_ARRAY, COPY_AS_PRIMITIVE_ARRAY, MAKE_LIST; + static { + try { + //MAKE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "makeArray", MethodType.methodType(Object[].class, Object[].class)); + COPY_AS_REFERENCE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "copyAsReferenceArray", MethodType.methodType(Object[].class, Class.class, Object[].class)); + COPY_AS_PRIMITIVE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "copyAsPrimitiveArray", MethodType.methodType(Object.class, Wrapper.class, Object[].class)); + MAKE_LIST = IMPL_LOOKUP.findStatic(THIS_CLASS, "makeList", MethodType.methodType(List.class, Object[].class)); + } catch (ReflectiveOperationException ex) { + throw newInternalError("uncaught exception", ex); + } + } + } + + private static final EnumMap[] WRAPPER_CASTS + = newWrapperCaches(1); + + /** Return a method that casts its sole argument (an Object) to the given type + * and returns it as the given type. + */ + public static MethodHandle cast(Class type) { + if (type.isPrimitive()) throw new IllegalArgumentException("cannot cast primitive type "+type); + MethodHandle mh; + Wrapper wrap = null; + EnumMap cache = null; + if (Wrapper.isWrapperType(type)) { + wrap = Wrapper.forWrapperType(type); + cache = WRAPPER_CASTS[0]; + mh = cache.get(wrap); + if (mh != null) return mh; + } + mh = MethodHandles.insertArguments(CAST_REFERENCE, 0, type); + if (cache != null) + cache.put(wrap, mh); + return mh; + } + + public static MethodHandle identity() { + return IDENTITY; + } + + public static MethodHandle identity(Class type) { + if (!type.isPrimitive()) + // Reference identity has been moved into MethodHandles: + return MethodHandles.identity(type); + return identity(Wrapper.findPrimitiveType(type)); + } + + public static MethodHandle identity(Wrapper wrap) { + EnumMap cache = CONSTANT_FUNCTIONS[1]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + MethodType type = MethodType.methodType(wrap.primitiveType()); + if (wrap != Wrapper.VOID) + type = type.appendParameterTypes(wrap.primitiveType()); + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + if (mh == null && wrap == Wrapper.VOID) { + mh = EMPTY; // #(){} : #()void + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + throw new IllegalArgumentException("cannot find identity for " + wrap); + } + + /// Primitive conversions. + // These are supported directly by the JVM, usually by a single instruction. + // In the case of narrowing to a subword, there may be a pair of instructions. + // In the case of booleans, there may be a helper routine to manage a 1-bit value. + // This is the full 8x8 matrix (minus the diagonal). + + // narrow double to all other types: + static float doubleToFloat(double x) { // bytecode: d2f + return (float) x; + } + static long doubleToLong(double x) { // bytecode: d2l + return (long) x; + } + static int doubleToInt(double x) { // bytecode: d2i + return (int) x; + } + static short doubleToShort(double x) { // bytecodes: d2i, i2s + return (short) x; + } + static char doubleToChar(double x) { // bytecodes: d2i, i2c + return (char) x; + } + static byte doubleToByte(double x) { // bytecodes: d2i, i2b + return (byte) x; + } + static boolean doubleToBoolean(double x) { + return toBoolean((byte) x); + } + + // widen float: + static double floatToDouble(float x) { // bytecode: f2d + return x; + } + // narrow float: + static long floatToLong(float x) { // bytecode: f2l + return (long) x; + } + static int floatToInt(float x) { // bytecode: f2i + return (int) x; + } + static short floatToShort(float x) { // bytecodes: f2i, i2s + return (short) x; + } + static char floatToChar(float x) { // bytecodes: f2i, i2c + return (char) x; + } + static byte floatToByte(float x) { // bytecodes: f2i, i2b + return (byte) x; + } + static boolean floatToBoolean(float x) { + return toBoolean((byte) x); + } + + // widen long: + static double longToDouble(long x) { // bytecode: l2d + return x; + } + static float longToFloat(long x) { // bytecode: l2f + return x; + } + // narrow long: + static int longToInt(long x) { // bytecode: l2i + return (int) x; + } + static short longToShort(long x) { // bytecodes: f2i, i2s + return (short) x; + } + static char longToChar(long x) { // bytecodes: f2i, i2c + return (char) x; + } + static byte longToByte(long x) { // bytecodes: f2i, i2b + return (byte) x; + } + static boolean longToBoolean(long x) { + return toBoolean((byte) x); + } + + // widen int: + static double intToDouble(int x) { // bytecode: i2d + return x; + } + static float intToFloat(int x) { // bytecode: i2f + return x; + } + static long intToLong(int x) { // bytecode: i2l + return x; + } + // narrow int: + static short intToShort(int x) { // bytecode: i2s + return (short) x; + } + static char intToChar(int x) { // bytecode: i2c + return (char) x; + } + static byte intToByte(int x) { // bytecode: i2b + return (byte) x; + } + static boolean intToBoolean(int x) { + return toBoolean((byte) x); + } + + // widen short: + static double shortToDouble(short x) { // bytecode: i2d (implicit 's2i') + return x; + } + static float shortToFloat(short x) { // bytecode: i2f (implicit 's2i') + return x; + } + static long shortToLong(short x) { // bytecode: i2l (implicit 's2i') + return x; + } + static int shortToInt(short x) { // (implicit 's2i') + return x; + } + // narrow short: + static char shortToChar(short x) { // bytecode: i2c (implicit 's2i') + return (char)x; + } + static byte shortToByte(short x) { // bytecode: i2b (implicit 's2i') + return (byte)x; + } + static boolean shortToBoolean(short x) { + return toBoolean((byte) x); + } + + // widen char: + static double charToDouble(char x) { // bytecode: i2d (implicit 'c2i') + return x; + } + static float charToFloat(char x) { // bytecode: i2f (implicit 'c2i') + return x; + } + static long charToLong(char x) { // bytecode: i2l (implicit 'c2i') + return x; + } + static int charToInt(char x) { // (implicit 'c2i') + return x; + } + // narrow char: + static short charToShort(char x) { // bytecode: i2s (implicit 'c2i') + return (short)x; + } + static byte charToByte(char x) { // bytecode: i2b (implicit 'c2i') + return (byte)x; + } + static boolean charToBoolean(char x) { + return toBoolean((byte) x); + } + + // widen byte: + static double byteToDouble(byte x) { // bytecode: i2d (implicit 'b2i') + return x; + } + static float byteToFloat(byte x) { // bytecode: i2f (implicit 'b2i') + return x; + } + static long byteToLong(byte x) { // bytecode: i2l (implicit 'b2i') + return x; + } + static int byteToInt(byte x) { // (implicit 'b2i') + return x; + } + static short byteToShort(byte x) { // bytecode: i2s (implicit 'b2i') + return (short)x; + } + static char byteToChar(byte x) { // bytecode: i2b (implicit 'b2i') + return (char)x; + } + // narrow byte to boolean: + static boolean byteToBoolean(byte x) { + return toBoolean(x); + } + + // widen boolean to all types: + static double booleanToDouble(boolean x) { + return fromBoolean(x); + } + static float booleanToFloat(boolean x) { + return fromBoolean(x); + } + static long booleanToLong(boolean x) { + return fromBoolean(x); + } + static int booleanToInt(boolean x) { + return fromBoolean(x); + } + static short booleanToShort(boolean x) { + return fromBoolean(x); + } + static char booleanToChar(boolean x) { + return (char)fromBoolean(x); + } + static byte booleanToByte(boolean x) { + return fromBoolean(x); + } + + // helpers to force boolean into the conversion scheme: + static boolean toBoolean(byte x) { + // see javadoc for MethodHandles.explicitCastArguments + return ((x & 1) != 0); + } + static byte fromBoolean(boolean x) { + // see javadoc for MethodHandles.explicitCastArguments + return (x ? (byte)1 : (byte)0); + } + + private static final EnumMap[] + CONVERT_PRIMITIVE_FUNCTIONS = newWrapperCaches(Wrapper.values().length); + + public static MethodHandle convertPrimitive(Wrapper wsrc, Wrapper wdst) { + EnumMap cache = CONVERT_PRIMITIVE_FUNCTIONS[wsrc.ordinal()]; + MethodHandle mh = cache.get(wdst); + if (mh != null) { + return mh; + } + // slow path + Class src = wsrc.primitiveType(); + Class dst = wdst.primitiveType(); + MethodType type = src == void.class ? MethodType.methodType(dst) : MethodType.methodType(dst, src); + if (wsrc == wdst) { + mh = identity(src); + } else if (wsrc == Wrapper.VOID) { + mh = zeroConstantFunction(wdst); + } else if (wdst == Wrapper.VOID) { + mh = MethodHandles.dropArguments(EMPTY, 0, src); // Defer back to MethodHandles. + } else if (wsrc == Wrapper.OBJECT) { + mh = unboxCast(dst); + } else if (wdst == Wrapper.OBJECT) { + mh = box(src); + } else { + assert(src.isPrimitive() && dst.isPrimitive()); + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + } + if (mh != null) { + assert(mh.type() == type) : mh; + cache.put(wdst, mh); + return mh; + } + + throw new IllegalArgumentException("cannot find primitive conversion function for " + + src.getSimpleName()+" -> "+dst.getSimpleName()); + } + + public static MethodHandle convertPrimitive(Class src, Class dst) { + return convertPrimitive(Wrapper.forPrimitiveType(src), Wrapper.forPrimitiveType(dst)); + } + + private static String capitalize(String x) { + return Character.toUpperCase(x.charAt(0))+x.substring(1); + } + + /// Collection of multiple arguments. + + public static Object convertArrayElements(Class arrayType, Object array) { + Class src = array.getClass().getComponentType(); + Class dst = arrayType.getComponentType(); + if (src == null || dst == null) throw new IllegalArgumentException("not array type"); + Wrapper sw = (src.isPrimitive() ? Wrapper.forPrimitiveType(src) : null); + Wrapper dw = (dst.isPrimitive() ? Wrapper.forPrimitiveType(dst) : null); + int length; + if (sw == null) { + Object[] a = (Object[]) array; + length = a.length; + if (dw == null) + return Arrays.copyOf(a, length, arrayType.asSubclass(Object[].class)); + Object res = dw.makeArray(length); + dw.copyArrayUnboxing(a, 0, res, 0, length); + return res; + } + length = java.lang.reflect.Array.getLength(array); + Object[] res; + if (dw == null) { + res = Arrays.copyOf(NO_ARGS_ARRAY, length, arrayType.asSubclass(Object[].class)); + } else { + res = new Object[length]; + } + sw.copyArrayBoxing(array, 0, res, 0, length); + if (dw == null) return res; + Object a = dw.makeArray(length); + dw.copyArrayUnboxing(res, 0, a, 0, length); + return a; + } + + private static MethodHandle findCollector(String name, int nargs, Class rtype, Class... ptypes) { + MethodType type = MethodType.genericMethodType(nargs) + .changeReturnType(rtype) + .insertParameterTypes(0, ptypes); + try { + return IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + } catch (ReflectiveOperationException ex) { + return null; + } + } + + private static final Object[] NO_ARGS_ARRAY = {}; + private static Object[] makeArray(Object... args) { return args; } + private static Object[] array() { return NO_ARGS_ARRAY; } + private static Object[] array(Object a0) + { return makeArray(a0); } + private static Object[] array(Object a0, Object a1) + { return makeArray(a0, a1); } + private static Object[] array(Object a0, Object a1, Object a2) + { return makeArray(a0, a1, a2); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3) + { return makeArray(a0, a1, a2, a3); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4) + { return makeArray(a0, a1, a2, a3, a4); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5) + { return makeArray(a0, a1, a2, a3, a4, a5); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6) + { return makeArray(a0, a1, a2, a3, a4, a5, a6); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7) + { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8) + { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8); } + private static Object[] array(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8, Object a9) + { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } + private static MethodHandle[] makeArrays() { + ArrayList mhs = new ArrayList<>(); + for (;;) { + MethodHandle mh = findCollector("array", mhs.size(), Object[].class); + if (mh == null) break; + mhs.add(mh); + } + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[MAX_ARITY+1]); + } + private static final MethodHandle[] ARRAYS = makeArrays(); + + // filling versions of the above: + // using Integer len instead of int len and no varargs to avoid bootstrapping problems + private static Object[] fillNewArray(Integer len, Object[] /*not ...*/ args) { + Object[] a = new Object[len]; + fillWithArguments(a, 0, args); + return a; + } + private static Object[] fillNewTypedArray(Object[] example, Integer len, Object[] /*not ...*/ args) { + Object[] a = Arrays.copyOf(example, len); + fillWithArguments(a, 0, args); + return a; + } + private static void fillWithArguments(Object[] a, int pos, Object... args) { + System.arraycopy(args, 0, a, pos, args.length); + } + // using Integer pos instead of int pos to avoid bootstrapping problems + private static Object[] fillArray(Integer pos, Object[] a, Object a0) + { fillWithArguments(a, pos, a0); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1) + { fillWithArguments(a, pos, a0, a1); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2) + { fillWithArguments(a, pos, a0, a1, a2); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3) + { fillWithArguments(a, pos, a0, a1, a2, a3); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7, a8); return a; } + private static Object[] fillArray(Integer pos, Object[] a, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8, Object a9) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); return a; } + private static MethodHandle[] makeFillArrays() { + ArrayList mhs = new ArrayList<>(); + mhs.add(null); // there is no empty fill; at least a0 is required + for (;;) { + MethodHandle mh = findCollector("fillArray", mhs.size(), Object[].class, Integer.class, Object[].class); + if (mh == null) break; + mhs.add(mh); + } + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[0]); + } + private static final MethodHandle[] FILL_ARRAYS = makeFillArrays(); + + private static Object[] copyAsReferenceArray(Class arrayType, Object... a) { + return Arrays.copyOf(a, a.length, arrayType); + } + private static Object copyAsPrimitiveArray(Wrapper w, Object... boxes) { + Object a = w.makeArray(boxes.length); + w.copyArrayUnboxing(boxes, 0, a, 0, boxes.length); + return a; + } + + /** Return a method handle that takes the indicated number of Object + * arguments and returns an Object array of them, as if for varargs. + */ + public static MethodHandle varargsArray(int nargs) { + MethodHandle mh = ARRAYS[nargs]; + if (mh != null) return mh; + mh = findCollector("array", nargs, Object[].class); + if (mh != null) return ARRAYS[nargs] = mh; + mh = buildVarargsArray(FILL_NEW_ARRAY, ARRAY_IDENTITY, nargs); + assert(assertCorrectArity(mh, nargs)); + return ARRAYS[nargs] = mh; + } + + private static boolean assertCorrectArity(MethodHandle mh, int arity) { + assert(mh.type().parameterCount() == arity) : "arity != "+arity+": "+mh; + return true; + } + + private static MethodHandle buildVarargsArray(MethodHandle newArray, MethodHandle finisher, int nargs) { + // Build up the result mh as a sequence of fills like this: + // finisher(fill(fill(newArrayWA(23,x1..x10),10,x11..x20),20,x21..x23)) + // The various fill(_,10*I,___*[J]) are reusable. + int leftLen = Math.min(nargs, LEFT_ARGS); // absorb some arguments immediately + int rightLen = nargs - leftLen; + MethodHandle leftCollector = newArray.bindTo(nargs); + leftCollector = leftCollector.asCollector(Object[].class, leftLen); + MethodHandle mh = finisher; + if (rightLen > 0) { + MethodHandle rightFiller = fillToRight(LEFT_ARGS + rightLen); + if (mh == ARRAY_IDENTITY) + mh = rightFiller; + else + mh = MethodHandles.collectArguments(mh, 0, rightFiller); + } + if (mh == ARRAY_IDENTITY) + mh = leftCollector; + else + mh = MethodHandles.collectArguments(mh, 0, leftCollector); + return mh; + } + + private static final int LEFT_ARGS = (FILL_ARRAYS.length - 1); + private static final MethodHandle[] FILL_ARRAY_TO_RIGHT = new MethodHandle[MAX_ARITY+1]; + /** fill_array_to_right(N).invoke(a, argL..arg[N-1]) + * fills a[L]..a[N-1] with corresponding arguments, + * and then returns a. The value L is a global constant (LEFT_ARGS). + */ + private static MethodHandle fillToRight(int nargs) { + MethodHandle filler = FILL_ARRAY_TO_RIGHT[nargs]; + if (filler != null) return filler; + filler = buildFiller(nargs); + assert(assertCorrectArity(filler, nargs - LEFT_ARGS + 1)); + return FILL_ARRAY_TO_RIGHT[nargs] = filler; + } + private static MethodHandle buildFiller(int nargs) { + if (nargs <= LEFT_ARGS) + return ARRAY_IDENTITY; // no args to fill; return the array unchanged + // we need room for both mh and a in mh.invoke(a, arg*[nargs]) + final int CHUNK = LEFT_ARGS; + int rightLen = nargs % CHUNK; + int midLen = nargs - rightLen; + if (rightLen == 0) { + midLen = nargs - (rightLen = CHUNK); + if (FILL_ARRAY_TO_RIGHT[midLen] == null) { + // build some precursors from left to right + for (int j = LEFT_ARGS % CHUNK; j < midLen; j += CHUNK) + if (j > LEFT_ARGS) fillToRight(j); + } + } + if (midLen < LEFT_ARGS) rightLen = nargs - (midLen = LEFT_ARGS); + assert(rightLen > 0); + MethodHandle midFill = fillToRight(midLen); // recursive fill + MethodHandle rightFill = FILL_ARRAYS[rightLen].bindTo(midLen); // [midLen..nargs-1] + assert(midFill.type().parameterCount() == 1 + midLen - LEFT_ARGS); + assert(rightFill.type().parameterCount() == 1 + rightLen); + + // Combine the two fills: + // right(mid(a, x10..x19), x20..x23) + // The final product will look like this: + // right(mid(newArrayLeft(24, x0..x9), x10..x19), x20..x23) + if (midLen == LEFT_ARGS) + return rightFill; + else + return MethodHandles.collectArguments(rightFill, 0, midFill); + } + + // Type-polymorphic version of varargs maker. + private static final ClassValue TYPED_COLLECTORS + = new ClassValue() { + @Override + protected MethodHandle[] computeValue(Class type) { + return new MethodHandle[256]; + } + }; + + static final int MAX_JVM_ARITY = 255; // limit imposed by the JVM + + /** Return a method handle that takes the indicated number of + * typed arguments and returns an array of them. + * The type argument is the array type. + */ + public static MethodHandle varargsArray(Class arrayType, int nargs) { + Class elemType = arrayType.getComponentType(); + if (elemType == null) throw new IllegalArgumentException("not an array: "+arrayType); + // FIXME: Need more special casing and caching here. + if (nargs >= MAX_JVM_ARITY/2 - 1) { + int slots = nargs; + final int MAX_ARRAY_SLOTS = MAX_JVM_ARITY - 1; // 1 for receiver MH + if (arrayType == double[].class || arrayType == long[].class) + slots *= 2; + if (slots > MAX_ARRAY_SLOTS) + throw new IllegalArgumentException("too many arguments: "+arrayType.getSimpleName()+", length "+nargs); + } + if (elemType == Object.class) + return varargsArray(nargs); + // other cases: primitive arrays, subtypes of Object[] + MethodHandle cache[] = TYPED_COLLECTORS.get(elemType); + MethodHandle mh = nargs < cache.length ? cache[nargs] : null; + if (mh != null) return mh; + if (elemType.isPrimitive()) { + MethodHandle builder = FILL_NEW_ARRAY; + MethodHandle producer = buildArrayProducer(arrayType); + mh = buildVarargsArray(builder, producer, nargs); + } else { + @SuppressWarnings("unchecked") + Class objArrayType = (Class) arrayType; + Object[] example = Arrays.copyOf(NO_ARGS_ARRAY, 0, objArrayType); + MethodHandle builder = FILL_NEW_TYPED_ARRAY.bindTo(example); + MethodHandle producer = ARRAY_IDENTITY; + mh = buildVarargsArray(builder, producer, nargs); + } + mh = mh.asType(MethodType.methodType(arrayType, Collections.>nCopies(nargs, elemType))); + assert(assertCorrectArity(mh, nargs)); + if (nargs < cache.length) + cache[nargs] = mh; + return mh; + } + + private static MethodHandle buildArrayProducer(Class arrayType) { + Class elemType = arrayType.getComponentType(); + if (elemType.isPrimitive()) + return LazyStatics.COPY_AS_PRIMITIVE_ARRAY.bindTo(Wrapper.forPrimitiveType(elemType)); + else + return LazyStatics.COPY_AS_REFERENCE_ARRAY.bindTo(arrayType); + } + + // List version of varargs maker. + + private static final List NO_ARGS_LIST = Arrays.asList(NO_ARGS_ARRAY); + private static List makeList(Object... args) { return Arrays.asList(args); } + private static List list() { return NO_ARGS_LIST; } + private static List list(Object a0) + { return makeList(a0); } + private static List list(Object a0, Object a1) + { return makeList(a0, a1); } + private static List list(Object a0, Object a1, Object a2) + { return makeList(a0, a1, a2); } + private static List list(Object a0, Object a1, Object a2, Object a3) + { return makeList(a0, a1, a2, a3); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4) + { return makeList(a0, a1, a2, a3, a4); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5) + { return makeList(a0, a1, a2, a3, a4, a5); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6) + { return makeList(a0, a1, a2, a3, a4, a5, a6); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7) + { return makeList(a0, a1, a2, a3, a4, a5, a6, a7); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8) + { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8); } + private static List list(Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8, Object a9) + { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } + private static MethodHandle[] makeLists() { + ArrayList mhs = new ArrayList<>(); + for (;;) { + MethodHandle mh = findCollector("list", mhs.size(), List.class); + if (mh == null) break; + mhs.add(mh); + } + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[MAX_ARITY+1]); + } + private static final MethodHandle[] LISTS = makeLists(); + + /** Return a method handle that takes the indicated number of Object + * arguments and returns a List. + */ + public static MethodHandle varargsList(int nargs) { + MethodHandle mh = LISTS[nargs]; + if (mh != null) return mh; + mh = findCollector("list", nargs, List.class); + if (mh != null) return LISTS[nargs] = mh; + return LISTS[nargs] = buildVarargsList(nargs); + } + private static MethodHandle buildVarargsList(int nargs) { + return MethodHandles.filterReturnValue(varargsArray(nargs), LazyStatics.MAKE_LIST); + } + + // handy shared exception makers (they simplify the common case code) + private static InternalError newInternalError(String message, Throwable cause) { + return new InternalError(message, cause); + } + private static InternalError newInternalError(Throwable cause) { + return new InternalError(cause); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/VerifyAccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/VerifyAccess.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,301 @@ +/* + * 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 sun.invoke.util; + +import java.lang.reflect.Modifier; +import static java.lang.reflect.Modifier.*; +import sun.reflect.Reflection; + +/** + * This class centralizes information about the JVM's linkage access control. + * @author jrose + */ +public class VerifyAccess { + + private VerifyAccess() { } // cannot instantiate + + private static final int PACKAGE_ONLY = 0; + private static final int PACKAGE_ALLOWED = java.lang.invoke.MethodHandles.Lookup.PACKAGE; + private static final int PROTECTED_OR_PACKAGE_ALLOWED = (PACKAGE_ALLOWED|PROTECTED); + private static final int ALL_ACCESS_MODES = (PUBLIC|PRIVATE|PROTECTED|PACKAGE_ONLY); + private static final boolean ALLOW_NESTMATE_ACCESS = false; + + /** + * Evaluate the JVM linkage rules for access to the given method + * on behalf of a caller class which proposes to perform the access. + * Return true if the caller class has privileges to invoke a method + * or access a field with the given properties. + * This requires an accessibility check of the referencing class, + * plus an accessibility check of the member within the class, + * which depends on the member's modifier flags. + *

+ * The relevant properties include the defining class ({@code defc}) + * of the member, and its modifier flags ({@code mods}). + * Also relevant is the class used to make the initial symbolic reference + * to the member ({@code refc}). If this latter class is not distinguished, + * the defining class should be passed for both arguments ({@code defc == refc}). + *

JVM Specification, 5.4.4 "Access Control"

+ * A field or method R is accessible to a class or interface D if + * and only if any of the following conditions is true:
    + *
  • R is public. + *
  • R is protected and is declared in a class C, and D is either + * a subclass of C or C itself. Furthermore, if R is not + * static, then the symbolic reference to R must contain a + * symbolic reference to a class T, such that T is either a + * subclass of D, a superclass of D or D itself. + *
  • R is either protected or has default access (that is, + * neither public nor protected nor private), and is declared + * by a class in the same runtime package as D. + *
  • R is private and is declared in D. + *
+ * This discussion of access control omits a related restriction + * on the target of a protected field access or method invocation + * (the target must be of class D or a subtype of D). That + * requirement is checked as part of the verification process + * (5.4.1); it is not part of link-time access control. + * @param refc the class used in the symbolic reference to the proposed member + * @param defc the class in which the proposed member is actually defined + * @param mods modifier flags for the proposed member + * @param lookupClass the class for which the access check is being made + * @return true iff the the accessing class can access such a member + */ + public static boolean isMemberAccessible(Class refc, // symbolic ref class + Class defc, // actual def class + int mods, // actual member mods + Class lookupClass, + int allowedModes) { + if (allowedModes == 0) return false; + assert((allowedModes & PUBLIC) != 0 && + (allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED)) == 0); + // The symbolic reference class (refc) must always be fully verified. + if (!isClassAccessible(refc, lookupClass, allowedModes)) { + return false; + } + // Usually refc and defc are the same, but verify defc also in case they differ. + if (defc == lookupClass && + (allowedModes & PRIVATE) != 0) + return true; // easy check; all self-access is OK + switch (mods & ALL_ACCESS_MODES) { + case PUBLIC: + return true; // already checked above + case PROTECTED: + if ((allowedModes & PROTECTED_OR_PACKAGE_ALLOWED) != 0 && + isSamePackage(defc, lookupClass)) + return true; + if ((allowedModes & PROTECTED) == 0) + return false; + if ((mods & STATIC) != 0 && + !isRelatedClass(refc, lookupClass)) + return false; + if ((allowedModes & PROTECTED) != 0 && + isSuperClass(defc, lookupClass)) + return true; + return false; + case PACKAGE_ONLY: // That is, zero. Unmarked member is package-only access. + return ((allowedModes & PACKAGE_ALLOWED) != 0 && + isSamePackage(defc, lookupClass)); + case PRIVATE: + // Loosened rules for privates follows access rules for inner classes. + return (ALLOW_NESTMATE_ACCESS && + (allowedModes & PRIVATE) != 0 && + isSamePackageMember(defc, lookupClass)); + default: + throw new IllegalArgumentException("bad modifiers: "+Modifier.toString(mods)); + } + } + + static boolean isRelatedClass(Class refc, Class lookupClass) { + return (refc == lookupClass || + refc.isAssignableFrom(lookupClass) || + lookupClass.isAssignableFrom(refc)); + } + + static boolean isSuperClass(Class defc, Class lookupClass) { + return defc.isAssignableFrom(lookupClass); + } + + static int getClassModifiers(Class c) { + // This would return the mask stored by javac for the source-level modifiers. + // return c.getModifiers(); + // But what we need for JVM access checks are the actual bits from the class header. + // ...But arrays and primitives are synthesized with their own odd flags: + if (c.isArray() || c.isPrimitive()) + return c.getModifiers(); + return Reflection.getClassAccessFlags(c); + } + + /** + * Evaluate the JVM linkage rules for access to the given class on behalf of caller. + *

JVM Specification, 5.4.4 "Access Control"

+ * A class or interface C is accessible to a class or interface D + * if and only if either of the following conditions are true:
    + *
  • C is public. + *
  • C and D are members of the same runtime package. + *
+ * @param refc the symbolic reference class to which access is being checked (C) + * @param lookupClass the class performing the lookup (D) + */ + public static boolean isClassAccessible(Class refc, Class lookupClass, + int allowedModes) { + if (allowedModes == 0) return false; + assert((allowedModes & PUBLIC) != 0 && + (allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED)) == 0); + int mods = getClassModifiers(refc); + if (isPublic(mods)) + return true; + if ((allowedModes & PACKAGE_ALLOWED) != 0 && + isSamePackage(lookupClass, refc)) + return true; + return false; + } + + /** + * Decide if the given method type, attributed to a member or symbolic + * reference of a given reference class, is really visible to that class. + * @param type the supposed type of a member or symbolic reference of refc + * @param refc the class attempting to make the reference + */ + public static boolean isTypeVisible(Class type, Class refc) { + if (type == refc) return true; // easy check + while (type.isArray()) type = type.getComponentType(); + if (type.isPrimitive() || type == Object.class) return true; + ClassLoader parent = type.getClassLoader(); + if (parent == null) return true; + ClassLoader child = refc.getClassLoader(); + if (child == null) return false; + if (parent == child || loadersAreRelated(parent, child, true)) + return true; + // Do it the hard way: Look up the type name from the refc loader. + try { + Class res = child.loadClass(type.getName()); + return (type == res); + } catch (ClassNotFoundException ex) { + return false; + } + } + + /** + * Decide if the given method type, attributed to a member or symbolic + * reference of a given reference class, is really visible to that class. + * @param type the supposed type of a member or symbolic reference of refc + * @param refc the class attempting to make the reference + */ + public static boolean isTypeVisible(java.lang.invoke.MethodType type, Class refc) { + for (int n = -1, max = type.parameterCount(); n < max; n++) { + Class ptype = (n < 0 ? type.returnType() : type.parameterType(n)); + if (!isTypeVisible(ptype, refc)) + return false; + } + return true; + } + + /** + * Test if two classes have the same class loader and package qualifier. + * @param class1 a class + * @param class2 another class + * @return whether they are in the same package + */ + public static boolean isSamePackage(Class class1, Class class2) { + assert(!class1.isArray() && !class2.isArray()); + if (class1 == class2) + return true; + if (class1.getClassLoader() != class2.getClassLoader()) + return false; + String name1 = class1.getName(), name2 = class2.getName(); + int dot = name1.lastIndexOf('.'); + if (dot != name2.lastIndexOf('.')) + return false; + for (int i = 0; i < dot; i++) { + if (name1.charAt(i) != name2.charAt(i)) + return false; + } + return true; + } + + /** Return the package name for this class. + */ + public static String getPackageName(Class cls) { + assert(!cls.isArray()); + String name = cls.getName(); + int dot = name.lastIndexOf('.'); + if (dot < 0) return ""; + return name.substring(0, dot); + } + + /** + * Test if two classes are defined as part of the same package member (top-level class). + * If this is true, they can share private access with each other. + * @param class1 a class + * @param class2 another class + * @return whether they are identical or nested together + */ + public static boolean isSamePackageMember(Class class1, Class class2) { + if (class1 == class2) + return true; + if (!isSamePackage(class1, class2)) + return false; + if (getOutermostEnclosingClass(class1) != getOutermostEnclosingClass(class2)) + return false; + return true; + } + + private static Class getOutermostEnclosingClass(Class c) { + Class pkgmem = c; + for (Class enc = c; (enc = enc.getEnclosingClass()) != null; ) + pkgmem = enc; + return pkgmem; + } + + private static boolean loadersAreRelated(ClassLoader loader1, ClassLoader loader2, + boolean loader1MustBeParent) { + if (loader1 == loader2 || loader1 == null + || (loader2 == null && !loader1MustBeParent)) { + return true; + } + for (ClassLoader scan2 = loader2; + scan2 != null; scan2 = scan2.getParent()) { + if (scan2 == loader1) return true; + } + if (loader1MustBeParent) return false; + // see if loader2 is a parent of loader1: + for (ClassLoader scan1 = loader1; + scan1 != null; scan1 = scan1.getParent()) { + if (scan1 == loader2) return true; + } + return false; + } + + /** + * Is the class loader of parentClass identical to, or an ancestor of, + * the class loader of childClass? + * @param parentClass a class + * @param childClass another class, which may be a descendent of the first class + * @return whether parentClass precedes or equals childClass in class loader order + */ + public static boolean classLoaderIsAncestor(Class parentClass, Class childClass) { + return loadersAreRelated(parentClass.getClassLoader(), childClass.getClassLoader(), true); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/VerifyType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/VerifyType.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,194 @@ +/* + * 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 sun.invoke.util; + +import java.lang.invoke.MethodType; +import sun.invoke.empty.Empty; + +/** + * This class centralizes information about the JVM verifier + * and its requirements about type correctness. + * @author jrose + */ +public class VerifyType { + + private VerifyType() { } // cannot instantiate + + /** + * True if a value can be stacked as the source type and unstacked as the + * destination type, without violating the JVM's type consistency. + * + * @param src the type of a stacked value + * @param dst the type by which we'd like to treat it + * @return whether the retyping can be done without motion or reformatting + */ + public static boolean isNullConversion(Class src, Class dst) { + if (src == dst) return true; + // Verifier allows any interface to be treated as Object: + if (dst.isInterface()) dst = Object.class; + if (src.isInterface()) src = Object.class; + if (src == dst) return true; // check again + if (dst == void.class) return true; // drop any return value + if (isNullType(src)) return !dst.isPrimitive(); + if (!src.isPrimitive()) return dst.isAssignableFrom(src); + if (!dst.isPrimitive()) return false; + // Verifier allows an int to carry byte, short, char, or even boolean: + Wrapper sw = Wrapper.forPrimitiveType(src); + if (dst == int.class) return sw.isSubwordOrInt(); + Wrapper dw = Wrapper.forPrimitiveType(dst); + if (!sw.isSubwordOrInt()) return false; + if (!dw.isSubwordOrInt()) return false; + if (!dw.isSigned() && sw.isSigned()) return false; + return dw.bitWidth() > sw.bitWidth(); + } + + /** + * Specialization of isNullConversion to reference types. + * @param src the type of a stacked value + * @param dst the reference type by which we'd like to treat it + * @return whether the retyping can be done without a cast + */ + public static boolean isNullReferenceConversion(Class src, Class dst) { + assert(!dst.isPrimitive()); + if (dst.isInterface()) return true; // verifier allows this + if (isNullType(src)) return true; + return dst.isAssignableFrom(src); + } + + /** + * Is the given type java.lang.Null or an equivalent null-only type? + */ + public static boolean isNullType(Class type) { + if (type == null) return false; + return type == NULL_CLASS + // This one may also be used as a null type. + // TO DO: Decide if we really want to legitimize it here. + // Probably we do, unless java.lang.Null really makes it into Java 7 + //|| type == Void.class + // Locally known null-only class: + || type == Empty.class + ; + } + private static final Class NULL_CLASS; + static { + Class nullClass = null; + try { + nullClass = Class.forName("java.lang.Null"); + } catch (ClassNotFoundException ex) { + // OK, we'll cope + } + NULL_CLASS = nullClass; + } + + /** + * True if a method handle can receive a call under a slightly different + * method type, without moving or reformatting any stack elements. + * + * @param call the type of call being made + * @param recv the type of the method handle receiving the call + * @return whether the retyping can be done without motion or reformatting + */ + public static boolean isNullConversion(MethodType call, MethodType recv) { + if (call == recv) return true; + int len = call.parameterCount(); + if (len != recv.parameterCount()) return false; + for (int i = 0; i < len; i++) + if (!isNullConversion(call.parameterType(i), recv.parameterType(i))) + return false; + return isNullConversion(recv.returnType(), call.returnType()); + } + + /** + * Determine if the JVM verifier allows a value of type call to be + * passed to a formal parameter (or return variable) of type recv. + * Returns 1 if the verifier allows the types to match without conversion. + * Returns -1 if the types can be made to match by a JVM-supported adapter. + * Cases supported are: + *
  • checkcast + *
  • conversion between any two integral types (but not floats) + *
  • unboxing from a wrapper to its corresponding primitive type + *
  • conversion in either direction between float and double + *
+ * (Autoboxing is not supported here; it must be done via Java code.) + * Returns 0 otherwise. + */ + public static int canPassUnchecked(Class src, Class dst) { + if (src == dst) + return 1; + + if (dst.isPrimitive()) { + if (dst == void.class) + // Return anything to a caller expecting void. + // This is a property of the implementation, which links + // return values via a register rather than via a stack push. + // This makes it possible to ignore cleanly. + return 1; + if (src == void.class) + return 0; // void-to-something? + if (!src.isPrimitive()) + // Cannot pass a reference to any primitive type (exc. void). + return 0; + Wrapper sw = Wrapper.forPrimitiveType(src); + Wrapper dw = Wrapper.forPrimitiveType(dst); + if (sw.isSubwordOrInt() && dw.isSubwordOrInt()) { + if (sw.bitWidth() >= dw.bitWidth()) + return -1; // truncation may be required + if (!dw.isSigned() && sw.isSigned()) + return -1; // sign elimination may be required + return 1; + } + if (src == float.class || dst == float.class) { + if (src == double.class || dst == double.class) + return -1; // floating conversion may be required + else + return 0; // other primitive conversions NYI + } else { + // all fixed-point conversions are supported + return 0; + } + } else if (src.isPrimitive()) { + // Cannot pass a primitive to any reference type. + // (Maybe allow null.class?) + return 0; + } + + // Handle reference types in the rest of the block: + + // The verifier treats interfaces exactly like Object. + if (isNullReferenceConversion(src, dst)) + // pass any reference to object or an arb. interface + return 1; + // else it's a definite "maybe" (cast is required) + return -1; + } + + public static boolean isSpreadArgType(Class spreadArg) { + return spreadArg.isArray(); + } + public static Class spreadArgElementType(Class spreadArg, int i) { + return spreadArg.getComponentType(); + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/Wrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/Wrapper.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2008, 2012, 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 sun.invoke.util; + +public enum Wrapper { + BOOLEAN(Boolean.class, boolean.class, 'Z', (Boolean)false, new boolean[0], Format.unsigned(1)), + // These must be in the order defined for widening primitive conversions in JLS 5.1.2 + BYTE(Byte.class, byte.class, 'B', (Byte)(byte)0, new byte[0], Format.signed(8)), + SHORT(Short.class, short.class, 'S', (Short)(short)0, new short[0], Format.signed(16)), + CHAR(Character.class, char.class, 'C', (Character)(char)0, new char[0], Format.unsigned(16)), + INT(Integer.class, int.class, 'I', (Integer)/*(int)*/0, new int[0], Format.signed(32)), + LONG(Long.class, long.class, 'J', (Long)(long)0, new long[0], Format.signed(64)), + FLOAT(Float.class, float.class, 'F', (Float)(float)0, new float[0], Format.floating(32)), + DOUBLE(Double.class, double.class, 'D', (Double)(double)0, new double[0], Format.floating(64)), + //NULL(Null.class, null.class, 'N', null, null, Format.other(1)), + OBJECT(Object.class, Object.class, 'L', null, new Object[0], Format.other(1)), + // VOID must be the last type, since it is "assignable" from any other type: + VOID(Void.class, void.class, 'V', null, null, Format.other(0)), + ; + + private final Class wrapperType; + private final Class primitiveType; + private final char basicTypeChar; + private final Object zero; + private final Object emptyArray; + private final int format; + private final String wrapperSimpleName; + private final String primitiveSimpleName; + + private Wrapper(Class wtype, Class ptype, char tchar, Object zero, Object emptyArray, int format) { + this.wrapperType = wtype; + this.primitiveType = ptype; + this.basicTypeChar = tchar; + this.zero = zero; + this.emptyArray = emptyArray; + this.format = format; + this.wrapperSimpleName = wtype.getSimpleName(); + this.primitiveSimpleName = ptype.getSimpleName(); + } + + /** For debugging, give the details of this wrapper. */ + public String detailString() { + return wrapperSimpleName+ + java.util.Arrays.asList(wrapperType, primitiveType, + basicTypeChar, zero, + "0x"+Integer.toHexString(format)); + } + + private static abstract class Format { + static final int SLOT_SHIFT = 0, SIZE_SHIFT = 2, KIND_SHIFT = 12; + static final int + SIGNED = (-1) << KIND_SHIFT, + UNSIGNED = 0 << KIND_SHIFT, + FLOATING = 1 << KIND_SHIFT; + static final int + SLOT_MASK = ((1<<(SIZE_SHIFT-SLOT_SHIFT))-1), + SIZE_MASK = ((1<<(KIND_SHIFT-SIZE_SHIFT))-1); + static int format(int kind, int size, int slots) { + assert(((kind >> KIND_SHIFT) << KIND_SHIFT) == kind); + assert((size & (size-1)) == 0); // power of two + assert((kind == SIGNED) ? (size > 0) : + (kind == UNSIGNED) ? (size > 0) : + (kind == FLOATING) ? (size == 32 || size == 64) : + false); + assert((slots == 2) ? (size == 64) : + (slots == 1) ? (size <= 32) : + false); + return kind | (size << SIZE_SHIFT) | (slots << SLOT_SHIFT); + } + static final int + INT = SIGNED | (32 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + SHORT = SIGNED | (16 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + BOOLEAN = UNSIGNED | (1 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + CHAR = UNSIGNED | (16 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + FLOAT = FLOATING | (32 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + VOID = UNSIGNED | (0 << SIZE_SHIFT) | (0 << SLOT_SHIFT), + NUM_MASK = (-1) << SIZE_SHIFT; + static int signed(int size) { return format(SIGNED, size, (size > 32 ? 2 : 1)); } + static int unsigned(int size) { return format(UNSIGNED, size, (size > 32 ? 2 : 1)); } + static int floating(int size) { return format(FLOATING, size, (size > 32 ? 2 : 1)); } + static int other(int slots) { return slots << SLOT_SHIFT; } + } + + /// format queries: + + /** How many bits are in the wrapped value? Returns 0 for OBJECT or VOID. */ + public int bitWidth() { return (format >> Format.SIZE_SHIFT) & Format.SIZE_MASK; } + /** How many JVM stack slots occupied by the wrapped value? Returns 0 for VOID. */ + public int stackSlots() { return (format >> Format.SLOT_SHIFT) & Format.SLOT_MASK; } + /** Does the wrapped value occupy a single JVM stack slot? */ + public boolean isSingleWord() { return (format & (1 << Format.SLOT_SHIFT)) != 0; } + /** Does the wrapped value occupy two JVM stack slots? */ + public boolean isDoubleWord() { return (format & (2 << Format.SLOT_SHIFT)) != 0; } + /** Is the wrapped type numeric (not void or object)? */ + public boolean isNumeric() { return (format & Format.NUM_MASK) != 0; } + /** Is the wrapped type a primitive other than float, double, or void? */ + public boolean isIntegral() { return isNumeric() && format < Format.FLOAT; } + /** Is the wrapped type one of int, boolean, byte, char, or short? */ + public boolean isSubwordOrInt() { return isIntegral() && isSingleWord(); } + /* Is the wrapped value a signed integral type (one of byte, short, int, or long)? */ + public boolean isSigned() { return format < Format.VOID; } + /* Is the wrapped value an unsigned integral type (one of boolean or char)? */ + public boolean isUnsigned() { return format >= Format.BOOLEAN && format < Format.FLOAT; } + /** Is the wrapped type either float or double? */ + public boolean isFloating() { return format >= Format.FLOAT; } + /** Is the wrapped type either void or a reference? */ + public boolean isOther() { return (format & ~Format.SLOT_MASK) == 0; } + + /** Does the JLS 5.1.2 allow a variable of this wrapper's + * primitive type to be assigned from a value of the given wrapper's primitive type? + * Cases: + *
    + *
  • unboxing followed by widening primitive conversion + *
  • any type converted to {@code void} (i.e., dropping a method call's value) + *
  • boxing conversion followed by widening reference conversion to {@code Object} + *
+ * These are the cases allowed by MethodHandle.asType. + */ + public boolean isConvertibleFrom(Wrapper source) { + if (this == source) return true; + if (this.compareTo(source) < 0) { + // At best, this is a narrowing conversion. + return false; + } + // All conversions are allowed in the enum order between floats and signed ints. + // First detect non-signed non-float types (boolean, char, Object, void). + boolean floatOrSigned = (((this.format & source.format) & Format.SIGNED) != 0); + if (!floatOrSigned) { + if (this.isOther()) return true; + // can convert char to int or wider, but nothing else + if (source.format == Format.CHAR) return true; + // no other conversions are classified as widening + return false; + } + // All signed and float conversions in the enum order are widening. + assert(this.isFloating() || this.isSigned()); + assert(source.isFloating() || source.isSigned()); + return true; + } + + static { assert(checkConvertibleFrom()); } + private static boolean checkConvertibleFrom() { + // Check the matrix for correct classification of widening conversions. + for (Wrapper w : values()) { + assert(w.isConvertibleFrom(w)); + assert(VOID.isConvertibleFrom(w)); + if (w != VOID) { + assert(OBJECT.isConvertibleFrom(w)); + assert(!w.isConvertibleFrom(VOID)); + } + // check relations with unsigned integral types: + if (w != CHAR) { + assert(!CHAR.isConvertibleFrom(w)); + if (!w.isConvertibleFrom(INT)) + assert(!w.isConvertibleFrom(CHAR)); + } + if (w != BOOLEAN) { + assert(!BOOLEAN.isConvertibleFrom(w)); + if (w != VOID && w != OBJECT) + assert(!w.isConvertibleFrom(BOOLEAN)); + } + // check relations with signed integral types: + if (w.isSigned()) { + for (Wrapper x : values()) { + if (w == x) continue; + if (x.isFloating()) + assert(!w.isConvertibleFrom(x)); + else if (x.isSigned()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + // check relations with floating types: + if (w.isFloating()) { + for (Wrapper x : values()) { + if (w == x) continue; + if (x.isSigned()) + assert(w.isConvertibleFrom(x)); + else if (x.isFloating()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + } + return true; // i.e., assert(true) + } + + /** Produce a zero value for the given wrapper type. + * This will be a numeric zero for a number or character, + * false for a boolean, and null for a reference or void. + * The common thread is that this is what is contained + * in a default-initialized variable of the given primitive + * type. (For void, it is what a reflective method returns + * instead of no value at all.) + */ + public Object zero() { return zero; } + + /** Produce a zero value for the given wrapper type T. + * The optional argument must a type compatible with this wrapper. + * Equivalent to {@code this.cast(this.zero(), type)}. + */ + public T zero(Class type) { return convert(zero, type); } + +// /** Produce a wrapper for the given wrapper or primitive type. */ +// public static Wrapper valueOf(Class type) { +// if (isPrimitiveType(type)) +// return forPrimitiveType(type); +// else +// return forWrapperType(type); +// } + + /** Return the wrapper that wraps values of the given type. + * The type may be {@code Object}, meaning the {@code OBJECT} wrapper. + * Otherwise, the type must be a primitive. + * @throws IllegalArgumentException for unexpected types + */ + public static Wrapper forPrimitiveType(Class type) { + Wrapper w = findPrimitiveType(type); + if (w != null) return w; + if (type.isPrimitive()) + throw new InternalError(); // redo hash function + throw newIllegalArgumentException("not primitive: "+type); + } + + static Wrapper findPrimitiveType(Class type) { + Wrapper w = FROM_PRIM[hashPrim(type)]; + if (w != null && w.primitiveType == type) { + return w; + } + return null; + } + + /** Return the wrapper that wraps values into the given wrapper type. + * If it is {@code Object}, return {@code OBJECT}. + * Otherwise, it must be a wrapper type. + * The type must not be a primitive type. + * @throws IllegalArgumentException for unexpected types + */ + public static Wrapper forWrapperType(Class type) { + Wrapper w = findWrapperType(type); + if (w != null) return w; + for (Wrapper x : values()) + if (x.wrapperType == type) + throw new InternalError(); // redo hash function + throw newIllegalArgumentException("not wrapper: "+type); + } + + static Wrapper findWrapperType(Class type) { + Wrapper w = FROM_WRAP[hashWrap(type)]; + if (w != null && w.wrapperType == type) { + return w; + } + return null; + } + + /** Return the wrapper that corresponds to the given bytecode + * signature character. Return {@code OBJECT} for the character 'L'. + * @throws IllegalArgumentException for any non-signature character or {@code '['}. + */ + public static Wrapper forBasicType(char type) { + Wrapper w = FROM_CHAR[hashChar(type)]; + if (w != null && w.basicTypeChar == type) { + return w; + } + for (Wrapper x : values()) + if (w.basicTypeChar == type) + throw new InternalError(); // redo hash function + throw newIllegalArgumentException("not basic type char: "+type); + } + + /** Return the wrapper for the given type, if it is + * a primitive type, else return {@code OBJECT}. + */ + public static Wrapper forBasicType(Class type) { + if (type.isPrimitive()) + return forPrimitiveType(type); + return OBJECT; // any reference, including wrappers or arrays + } + + // Note on perfect hashes: + // for signature chars c, do (c + (c >> 1)) % 16 + // for primitive type names n, do (n[0] + n[2]) % 16 + // The type name hash works for both primitive and wrapper names. + // You can add "java/lang/Object" to the primitive names. + // But you add the wrapper name Object, use (n[2] + (3*n[1])) % 16. + private static final Wrapper[] FROM_PRIM = new Wrapper[16]; + private static final Wrapper[] FROM_WRAP = new Wrapper[16]; + private static final Wrapper[] FROM_CHAR = new Wrapper[16]; + private static int hashPrim(Class x) { + String xn = x.getName(); + if (xn.length() < 3) return 0; + return (xn.charAt(0) + xn.charAt(2)) % 16; + } + private static int hashWrap(Class x) { + String xn = x.getName(); + final int offset = 10; assert(offset == "java.lang.".length()); + if (xn.length() < offset+3) return 0; + return (3*xn.charAt(offset+1) + xn.charAt(offset+2)) % 16; + } + private static int hashChar(char x) { + return (x + (x >> 1)) % 16; + } + static { + for (Wrapper w : values()) { + int pi = hashPrim(w.primitiveType); + int wi = hashWrap(w.wrapperType); + int ci = hashChar(w.basicTypeChar); + assert(FROM_PRIM[pi] == null); + assert(FROM_WRAP[wi] == null); + assert(FROM_CHAR[ci] == null); + FROM_PRIM[pi] = w; + FROM_WRAP[wi] = w; + FROM_CHAR[ci] = w; + } + //assert(jdk.sun.invoke.util.WrapperTest.test(false)); + } + + /** What is the primitive type wrapped by this wrapper? */ + public Class primitiveType() { return primitiveType; } + + /** What is the wrapper type for this wrapper? */ + public Class wrapperType() { return wrapperType; } + + /** What is the wrapper type for this wrapper? + * Otherwise, the example type must be the wrapper type, + * or the corresponding primitive type. + * (For {@code OBJECT}, the example type can be any non-primitive, + * and is normalized to {@code Object.class}.) + * The resulting class type has the same type parameter. + */ + public Class wrapperType(Class exampleType) { + if (exampleType == wrapperType) { + return exampleType; + } else if (exampleType == primitiveType || + wrapperType == Object.class || + exampleType.isInterface()) { + return forceType(wrapperType, exampleType); + } + throw newClassCastException(exampleType, primitiveType); + } + + private static ClassCastException newClassCastException(Class actual, Class expected) { + return new ClassCastException(actual + " is not compatible with " + expected); + } + + /** If {@code type} is a primitive type, return the corresponding + * wrapper type, else return {@code type} unchanged. + */ + public static Class asWrapperType(Class type) { + if (type.isPrimitive()) { + return forPrimitiveType(type).wrapperType(type); + } + return type; + } + + /** If {@code type} is a wrapper type, return the corresponding + * primitive type, else return {@code type} unchanged. + */ + public static Class asPrimitiveType(Class type) { + Wrapper w = findWrapperType(type); + if (w != null) { + return forceType(w.primitiveType(), type); + } + return type; + } + + /** Query: Is the given type a wrapper, such as {@code Integer} or {@code Void}? */ + public static boolean isWrapperType(Class type) { + return findWrapperType(type) != null; + } + + /** Query: Is the given type a primitive, such as {@code int} or {@code void}? */ + public static boolean isPrimitiveType(Class type) { + return type.isPrimitive(); + } + + /** What is the bytecode signature character for this type? + * All non-primitives, including array types, report as 'L', the signature character for references. + */ + public static char basicTypeChar(Class type) { + if (!type.isPrimitive()) + return 'L'; + else + return forPrimitiveType(type).basicTypeChar(); + } + + /** What is the bytecode signature character for this wrapper's + * primitive type? + */ + public char basicTypeChar() { return basicTypeChar; } + + /** What is the simple name of the wrapper type? + */ + public String wrapperSimpleName() { return wrapperSimpleName; } + + /** What is the simple name of the primitive type? + */ + public String primitiveSimpleName() { return primitiveSimpleName; } + +// /** Wrap a value in the given type, which may be either a primitive or wrapper type. +// * Performs standard primitive conversions, including truncation and float conversions. +// */ +// public static T wrap(Object x, Class type) { +// return Wrapper.valueOf(type).cast(x, type); +// } + + /** Cast a wrapped value to the given type, which may be either a primitive or wrapper type. + * The given target type must be this wrapper's primitive or wrapper type. + * If this wrapper is OBJECT, the target type may also be an interface, perform no runtime check. + * Performs standard primitive conversions, including truncation and float conversions. + * The given type must be compatible with this wrapper. That is, it must either + * be the wrapper type (or a subtype, in the case of {@code OBJECT}) or else + * it must be the wrapper's primitive type. + * Primitive conversions are only performed if the given type is itself a primitive. + * @throws ClassCastException if the given type is not compatible with this wrapper + */ + public T cast(Object x, Class type) { + return convert(x, type, true); + } + + /** Convert a wrapped value to the given type. + * The given target type must be this wrapper's primitive or wrapper type. + * This is equivalent to {@link #cast}, except that it refuses to perform + * narrowing primitive conversions. + */ + public T convert(Object x, Class type) { + return convert(x, type, false); + } + + private T convert(Object x, Class type, boolean isCast) { + if (this == OBJECT) { + // If the target wrapper is OBJECT, just do a reference cast. + // If the target type is an interface, perform no runtime check. + // (This loophole is safe, and is allowed by the JVM verifier.) + // If the target type is a primitive, change it to a wrapper. + assert(!type.isPrimitive()); + if (!type.isInterface()) + type.cast(x); + @SuppressWarnings("unchecked") + T result = (T) x; // unchecked warning is expected here + return result; + } + Class wtype = wrapperType(type); + if (wtype.isInstance(x)) { + return wtype.cast(x); + } + if (!isCast) { + Class sourceType = x.getClass(); // throw NPE if x is null + Wrapper source = findWrapperType(sourceType); + if (source == null || !this.isConvertibleFrom(source)) { + throw newClassCastException(wtype, sourceType); + } + } else if (x == null) { + @SuppressWarnings("unchecked") + T z = (T) zero; + return z; + } + @SuppressWarnings("unchecked") + T result = (T) wrap(x); // unchecked warning is expected here + assert (result == null ? Void.class : result.getClass()) == wtype; + return result; + } + + /** Cast a reference type to another reference type. + * If the target type is an interface, perform no runtime check. + * (This loophole is safe, and is allowed by the JVM verifier.) + * If the target type is a primitive, change it to a wrapper. + */ + static Class forceType(Class type, Class exampleType) { + boolean z = (type == exampleType || + type.isPrimitive() && forPrimitiveType(type) == findWrapperType(exampleType) || + exampleType.isPrimitive() && forPrimitiveType(exampleType) == findWrapperType(type) || + type == Object.class && !exampleType.isPrimitive()); + if (!z) + System.out.println(type+" <= "+exampleType); + assert(type == exampleType || + type.isPrimitive() && forPrimitiveType(type) == findWrapperType(exampleType) || + exampleType.isPrimitive() && forPrimitiveType(exampleType) == findWrapperType(type) || + type == Object.class && !exampleType.isPrimitive()); + @SuppressWarnings("unchecked") + Class result = (Class) type; // unchecked warning is expected here + return result; + } + + /** Wrap a value in this wrapper's type. + * Performs standard primitive conversions, including truncation and float conversions. + * Performs returns the unchanged reference for {@code OBJECT}. + * Returns null for {@code VOID}. + * Returns a zero value for a null input. + * @throws ClassCastException if this wrapper is numeric and the operand + * is not a number, character, boolean, or null + */ + public Object wrap(Object x) { + // do non-numeric wrappers first + switch (basicTypeChar) { + case 'L': return x; + case 'V': return null; + } + Number xn = numberValue(x); + switch (basicTypeChar) { + case 'I': return Integer.valueOf(xn.intValue()); + case 'J': return Long.valueOf(xn.longValue()); + case 'F': return Float.valueOf(xn.floatValue()); + case 'D': return Double.valueOf(xn.doubleValue()); + case 'S': return Short.valueOf((short) xn.intValue()); + case 'B': return Byte.valueOf((byte) xn.intValue()); + case 'C': return Character.valueOf((char) xn.intValue()); + case 'Z': return Boolean.valueOf(boolValue(xn.byteValue())); + } + throw new InternalError("bad wrapper"); + } + + /** Wrap a value (an int or smaller value) in this wrapper's type. + * Performs standard primitive conversions, including truncation and float conversions. + * Produces an {@code Integer} for {@code OBJECT}, although the exact type + * of the operand is not known. + * Returns null for {@code VOID}. + */ + public Object wrap(int x) { + if (basicTypeChar == 'L') return (Integer)x; + switch (basicTypeChar) { + case 'L': throw newIllegalArgumentException("cannot wrap to object type"); + case 'V': return null; + case 'I': return Integer.valueOf(x); + case 'J': return Long.valueOf(x); + case 'F': return Float.valueOf(x); + case 'D': return Double.valueOf(x); + case 'S': return Short.valueOf((short) x); + case 'B': return Byte.valueOf((byte) x); + case 'C': return Character.valueOf((char) x); + case 'Z': return Boolean.valueOf(boolValue((byte) x)); + } + throw new InternalError("bad wrapper"); + } + + private static Number numberValue(Object x) { + if (x instanceof Number) return (Number)x; + if (x instanceof Character) return (int)(Character)x; + if (x instanceof Boolean) return (Boolean)x ? 1 : 0; + // Remaining allowed case of void: Must be a null reference. + return (Number)x; + } + + // Parameter type of boolValue must be byte, because + // MethodHandles.explicitCastArguments defines boolean + // conversion as first converting to byte. + private static boolean boolValue(byte bits) { + bits &= 1; // simple 31-bit zero extension + return (bits != 0); + } + + private static RuntimeException newIllegalArgumentException(String message, Object x) { + return newIllegalArgumentException(message + x); + } + private static RuntimeException newIllegalArgumentException(String message) { + return new IllegalArgumentException(message); + } + + // primitive array support + public Object makeArray(int len) { + return java.lang.reflect.Array.newInstance(primitiveType, len); + } + public Class arrayType() { + return emptyArray.getClass(); + } + public void copyArrayUnboxing(Object[] values, int vpos, Object a, int apos, int length) { + if (a.getClass() != arrayType()) + arrayType().cast(a); // throw NPE or CCE if bad type + for (int i = 0; i < length; i++) { + Object value = values[i+vpos]; + value = convert(value, primitiveType); + java.lang.reflect.Array.set(a, i+apos, value); + } + } + public void copyArrayBoxing(Object a, int apos, Object[] values, int vpos, int length) { + if (a.getClass() != arrayType()) + arrayType().cast(a); // throw NPE or CCE if bad type + for (int i = 0; i < length; i++) { + Object value = java.lang.reflect.Array.get(a, i+apos); + //Already done: value = convert(value, primitiveType); + assert(value.getClass() == wrapperType); + values[i+vpos] = value; + } + } +} diff -r 0101d10bd2e0 -r c880a8a8803b rt/emul/compact/src/main/java/sun/invoke/util/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/sun/invoke/util/package-info.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2008, 2011, 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. + */ + +/** + * Extra support for using JSR 292 RI, package java.lang.invoke. + * @author jrose + */ + +package sun.invoke.util;