jaroslav@1646: /* jaroslav@1646: * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. jaroslav@1646: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@1646: * jaroslav@1646: * This code is free software; you can redistribute it and/or modify it jaroslav@1646: * under the terms of the GNU General Public License version 2 only, as jaroslav@1646: * published by the Free Software Foundation. Oracle designates this jaroslav@1646: * particular file as subject to the "Classpath" exception as provided jaroslav@1646: * by Oracle in the LICENSE file that accompanied this code. jaroslav@1646: * jaroslav@1646: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@1646: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@1646: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@1646: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@1646: * accompanied this code). jaroslav@1646: * jaroslav@1646: * You should have received a copy of the GNU General Public License version jaroslav@1646: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@1646: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@1646: * jaroslav@1646: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@1646: * or visit www.oracle.com if you need additional information or have any jaroslav@1646: * questions. jaroslav@1646: */ jaroslav@1646: package java.lang.invoke; jaroslav@1646: jaroslav@1646: import sun.invoke.util.Wrapper; jaroslav@1646: jaroslav@1646: import static sun.invoke.util.Wrapper.forPrimitiveType; jaroslav@1646: import static sun.invoke.util.Wrapper.forWrapperType; jaroslav@1646: import static sun.invoke.util.Wrapper.isWrapperType; jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Abstract implementation of a lambda metafactory which provides parameter jaroslav@1646: * unrolling and input validation. jaroslav@1646: * jaroslav@1646: * @see LambdaMetafactory jaroslav@1646: */ jaroslav@1646: /* package */ abstract class AbstractValidatingLambdaMetafactory { jaroslav@1646: jaroslav@1646: /* jaroslav@1646: * For context, the comments for the following fields are marked in quotes jaroslav@1646: * with their values, given this program: jaroslav@1646: * interface II { Object foo(T x); } jaroslav@1646: * interface JJ extends II { } jaroslav@1646: * class CC { String impl(int i) { return "impl:"+i; }} jaroslav@1646: * class X { jaroslav@1646: * public static void main(String[] args) { jaroslav@1646: * JJ iii = (new CC())::impl; jaroslav@1646: * System.out.printf(">>> %s\n", iii.foo(44)); jaroslav@1646: * }} jaroslav@1646: */ jaroslav@1646: final Class targetClass; // The class calling the meta-factory via invokedynamic "class X" jaroslav@1646: final MethodType invokedType; // The type of the invoked method "(CC)II" jaroslav@1646: final Class samBase; // The type of the returned instance "interface JJ" jaroslav@1646: final String samMethodName; // Name of the SAM method "foo" jaroslav@1646: final MethodType samMethodType; // Type of the SAM method "(Object)Object" jaroslav@1646: final MethodHandle implMethod; // Raw method handle for the implementation method jaroslav@1646: final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]" jaroslav@1646: final int implKind; // Invocation kind for implementation "5"=invokevirtual jaroslav@1646: final boolean implIsInstanceMethod; // Is the implementation an instance method "true" jaroslav@1646: final Class implDefiningClass; // Type defining the implementation "class CC" jaroslav@1646: final MethodType implMethodType; // Type of the implementation method "(int)String" jaroslav@1646: final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" jaroslav@1646: final boolean isSerializable; // Should the returned instance be serializable jaroslav@1646: final Class[] markerInterfaces; // Additional marker interfaces to be implemented jaroslav@1646: final MethodType[] additionalBridges; // Signatures of additional methods to bridge jaroslav@1646: jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Meta-factory constructor. jaroslav@1646: * jaroslav@1646: * @param caller Stacked automatically by VM; represents a lookup context jaroslav@1646: * with the accessibility privileges of the caller. jaroslav@1646: * @param invokedType Stacked automatically by VM; the signature of the jaroslav@1646: * invoked method, which includes the expected static jaroslav@1646: * type of the returned lambda object, and the static jaroslav@1646: * types of the captured arguments for the lambda. In jaroslav@1646: * the event that the implementation method is an jaroslav@1646: * instance method, the first argument in the invocation jaroslav@1646: * signature will correspond to the receiver. jaroslav@1646: * @param samMethodName Name of the method in the functional interface to jaroslav@1646: * which the lambda or method reference is being jaroslav@1646: * converted, represented as a String. jaroslav@1646: * @param samMethodType Type of the method in the functional interface to jaroslav@1646: * which the lambda or method reference is being jaroslav@1646: * converted, represented as a MethodType. jaroslav@1646: * @param implMethod The implementation method which should be called jaroslav@1646: * (with suitable adaptation of argument types, return jaroslav@1646: * types, and adjustment for captured arguments) when jaroslav@1646: * methods of the resulting functional interface instance jaroslav@1646: * are invoked. jaroslav@1646: * @param instantiatedMethodType The signature of the primary functional jaroslav@1646: * interface method after type variables are jaroslav@1646: * substituted with their instantiation from jaroslav@1646: * the capture site jaroslav@1646: * @param isSerializable Should the lambda be made serializable? If set, jaroslav@1646: * either the target type or one of the additional SAM jaroslav@1646: * types must extend {@code Serializable}. jaroslav@1646: * @param markerInterfaces Additional interfaces which the lambda object jaroslav@1646: * should implement. jaroslav@1646: * @param additionalBridges Method types for additional signatures to be jaroslav@1646: * bridged to the implementation method jaroslav@1646: * @throws LambdaConversionException If any of the meta-factory protocol jaroslav@1646: * invariants are violated jaroslav@1646: */ jaroslav@1646: AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, jaroslav@1646: MethodType invokedType, jaroslav@1646: String samMethodName, jaroslav@1646: MethodType samMethodType, jaroslav@1646: MethodHandle implMethod, jaroslav@1646: MethodType instantiatedMethodType, jaroslav@1646: boolean isSerializable, jaroslav@1646: Class[] markerInterfaces, jaroslav@1646: MethodType[] additionalBridges) jaroslav@1646: throws LambdaConversionException { jaroslav@1646: if ((caller.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { jaroslav@1646: throw new LambdaConversionException(String.format( jaroslav@1646: "Invalid caller: %s", jaroslav@1646: caller.lookupClass().getName())); jaroslav@1646: } jaroslav@1646: this.targetClass = caller.lookupClass(); jaroslav@1646: this.invokedType = invokedType; jaroslav@1646: jaroslav@1646: this.samBase = invokedType.returnType(); jaroslav@1646: jaroslav@1646: this.samMethodName = samMethodName; jaroslav@1646: this.samMethodType = samMethodType; jaroslav@1646: jaroslav@1646: this.implMethod = implMethod; jaroslav@1646: this.implInfo = caller.revealDirect(implMethod); jaroslav@1646: this.implKind = implInfo.getReferenceKind(); jaroslav@1646: this.implIsInstanceMethod = jaroslav@1646: implKind == MethodHandleInfo.REF_invokeVirtual || jaroslav@1646: implKind == MethodHandleInfo.REF_invokeSpecial || jaroslav@1646: implKind == MethodHandleInfo.REF_invokeInterface; jaroslav@1646: this.implDefiningClass = implInfo.getDeclaringClass(); jaroslav@1646: this.implMethodType = implInfo.getMethodType(); jaroslav@1646: this.instantiatedMethodType = instantiatedMethodType; jaroslav@1646: this.isSerializable = isSerializable; jaroslav@1646: this.markerInterfaces = markerInterfaces; jaroslav@1646: this.additionalBridges = additionalBridges; jaroslav@1646: jaroslav@1646: if (!samBase.isInterface()) { jaroslav@1646: throw new LambdaConversionException(String.format( jaroslav@1646: "Functional interface %s is not an interface", jaroslav@1646: samBase.getName())); jaroslav@1646: } jaroslav@1646: jaroslav@1646: for (Class c : markerInterfaces) { jaroslav@1646: if (!c.isInterface()) { jaroslav@1646: throw new LambdaConversionException(String.format( jaroslav@1646: "Marker interface %s is not an interface", jaroslav@1646: c.getName())); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Build the CallSite. jaroslav@1646: * jaroslav@1646: * @return a CallSite, which, when invoked, will return an instance of the jaroslav@1646: * functional interface jaroslav@1646: * @throws ReflectiveOperationException jaroslav@1646: */ jaroslav@1646: abstract CallSite buildCallSite() jaroslav@1646: throws LambdaConversionException; jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Check the meta-factory arguments for errors jaroslav@1646: * @throws LambdaConversionException if there are improper conversions jaroslav@1646: */ jaroslav@1646: void validateMetafactoryArgs() throws LambdaConversionException { jaroslav@1646: switch (implKind) { jaroslav@1646: case MethodHandleInfo.REF_invokeInterface: jaroslav@1646: case MethodHandleInfo.REF_invokeVirtual: jaroslav@1646: case MethodHandleInfo.REF_invokeStatic: jaroslav@1646: case MethodHandleInfo.REF_newInvokeSpecial: jaroslav@1646: case MethodHandleInfo.REF_invokeSpecial: jaroslav@1646: break; jaroslav@1646: default: jaroslav@1646: throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: // Check arity: optional-receiver + captured + SAM == impl jaroslav@1646: final int implArity = implMethodType.parameterCount(); jaroslav@1646: final int receiverArity = implIsInstanceMethod ? 1 : 0; jaroslav@1646: final int capturedArity = invokedType.parameterCount(); jaroslav@1646: final int samArity = samMethodType.parameterCount(); jaroslav@1646: final int instantiatedArity = instantiatedMethodType.parameterCount(); jaroslav@1646: if (implArity + receiverArity != capturedArity + samArity) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters", jaroslav@1646: implIsInstanceMethod ? "instance" : "static", implInfo, jaroslav@1646: capturedArity, samArity, implArity)); jaroslav@1646: } jaroslav@1646: if (instantiatedArity != samArity) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Incorrect number of parameters for %s method %s; %d instantiated parameters, %d functional interface method parameters", jaroslav@1646: implIsInstanceMethod ? "instance" : "static", implInfo, jaroslav@1646: instantiatedArity, samArity)); jaroslav@1646: } jaroslav@1646: for (MethodType bridgeMT : additionalBridges) { jaroslav@1646: if (bridgeMT.parameterCount() != samArity) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Incorrect number of parameters for bridge signature %s; incompatible with %s", jaroslav@1646: bridgeMT, samMethodType)); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: // If instance: first captured arg (receiver) must be subtype of class where impl method is defined jaroslav@1646: final int capturedStart; jaroslav@1646: final int samStart; jaroslav@1646: if (implIsInstanceMethod) { jaroslav@1646: final Class receiverClass; jaroslav@1646: jaroslav@1646: // implementation is an instance method, adjust for receiver in captured variables / SAM arguments jaroslav@1646: if (capturedArity == 0) { jaroslav@1646: // receiver is function parameter jaroslav@1646: capturedStart = 0; jaroslav@1646: samStart = 1; jaroslav@1646: receiverClass = instantiatedMethodType.parameterType(0); jaroslav@1646: } else { jaroslav@1646: // receiver is a captured variable jaroslav@1646: capturedStart = 1; jaroslav@1646: samStart = 0; jaroslav@1646: receiverClass = invokedType.parameterType(0); jaroslav@1646: } jaroslav@1646: jaroslav@1646: // check receiver type jaroslav@1646: if (!implDefiningClass.isAssignableFrom(receiverClass)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Invalid receiver type %s; not a subtype of implementation type %s", jaroslav@1646: receiverClass, implDefiningClass)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: Class implReceiverClass = implMethod.type().parameterType(0); jaroslav@1646: if (implReceiverClass != implDefiningClass && !implReceiverClass.isAssignableFrom(receiverClass)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Invalid receiver type %s; not a subtype of implementation receiver type %s", jaroslav@1646: receiverClass, implReceiverClass)); jaroslav@1646: } jaroslav@1646: } else { jaroslav@1646: // no receiver jaroslav@1646: capturedStart = 0; jaroslav@1646: samStart = 0; jaroslav@1646: } jaroslav@1646: jaroslav@1646: // Check for exact match on non-receiver captured arguments jaroslav@1646: final int implFromCaptured = capturedArity - capturedStart; jaroslav@1646: for (int i=0; i implParamType = implMethodType.parameterType(i); jaroslav@1646: Class capturedParamType = invokedType.parameterType(i + capturedStart); jaroslav@1646: if (!capturedParamType.equals(implParamType)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", jaroslav@1646: i, capturedParamType, implParamType)); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: // Check for adaptation match on SAM arguments jaroslav@1646: final int samOffset = samStart - implFromCaptured; jaroslav@1646: for (int i=implFromCaptured; i implParamType = implMethodType.parameterType(i); jaroslav@1646: Class instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset); jaroslav@1646: if (!isAdaptableTo(instantiatedParamType, implParamType, true)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", jaroslav@1646: i, instantiatedParamType, implParamType)); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: // Adaptation match: return type jaroslav@1646: Class expectedType = instantiatedMethodType.returnType(); jaroslav@1646: Class actualReturnType = jaroslav@1646: (implKind == MethodHandleInfo.REF_newInvokeSpecial) jaroslav@1646: ? implDefiningClass jaroslav@1646: : implMethodType.returnType(); jaroslav@1646: Class samReturnType = samMethodType.returnType(); jaroslav@1646: if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Type mismatch for lambda return: %s is not convertible to %s", jaroslav@1646: actualReturnType, expectedType)); jaroslav@1646: } jaroslav@1646: if (!isAdaptableToAsReturnStrict(expectedType, samReturnType)) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Type mismatch for lambda expected return: %s is not convertible to %s", jaroslav@1646: expectedType, samReturnType)); jaroslav@1646: } jaroslav@1646: for (MethodType bridgeMT : additionalBridges) { jaroslav@1646: if (!isAdaptableToAsReturnStrict(expectedType, bridgeMT.returnType())) { jaroslav@1646: throw new LambdaConversionException( jaroslav@1646: String.format("Type mismatch for lambda expected return: %s is not convertible to %s", jaroslav@1646: expectedType, bridgeMT.returnType())); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Check type adaptability for parameter types. jaroslav@1646: * @param fromType Type to convert from jaroslav@1646: * @param toType Type to convert to jaroslav@1646: * @param strict If true, do strict checks, else allow that fromType may be parameterized jaroslav@1646: * @return True if 'fromType' can be passed to an argument of 'toType' jaroslav@1646: */ jaroslav@1646: private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { jaroslav@1646: if (fromType.equals(toType)) { jaroslav@1646: return true; jaroslav@1646: } jaroslav@1646: if (fromType.isPrimitive()) { jaroslav@1646: Wrapper wfrom = forPrimitiveType(fromType); jaroslav@1646: if (toType.isPrimitive()) { jaroslav@1646: // both are primitive: widening jaroslav@1646: Wrapper wto = forPrimitiveType(toType); jaroslav@1646: return wto.isConvertibleFrom(wfrom); jaroslav@1646: } else { jaroslav@1646: // from primitive to reference: boxing jaroslav@1646: return toType.isAssignableFrom(wfrom.wrapperType()); jaroslav@1646: } jaroslav@1646: } else { jaroslav@1646: if (toType.isPrimitive()) { jaroslav@1646: // from reference to primitive: unboxing jaroslav@1646: Wrapper wfrom; jaroslav@1646: if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) { jaroslav@1646: // fromType is a primitive wrapper; unbox+widen jaroslav@1646: Wrapper wto = forPrimitiveType(toType); jaroslav@1646: return wto.isConvertibleFrom(wfrom); jaroslav@1646: } else { jaroslav@1646: // must be convertible to primitive jaroslav@1646: return !strict; jaroslav@1646: } jaroslav@1646: } else { jaroslav@1646: // both are reference types: fromType should be a superclass of toType. jaroslav@1646: return !strict || toType.isAssignableFrom(fromType); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** jaroslav@1646: * Check type adaptability for return types -- jaroslav@1646: * special handling of void type) and parameterized fromType jaroslav@1646: * @return True if 'fromType' can be converted to 'toType' jaroslav@1646: */ jaroslav@1646: private boolean isAdaptableToAsReturn(Class fromType, Class toType) { jaroslav@1646: return toType.equals(void.class) jaroslav@1646: || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false); jaroslav@1646: } jaroslav@1646: private boolean isAdaptableToAsReturnStrict(Class fromType, Class toType) { jaroslav@1646: if (fromType.equals(void.class)) return toType.equals(void.class); jaroslav@1646: return isAdaptableTo(fromType, toType, true); jaroslav@1646: } jaroslav@1646: jaroslav@1646: jaroslav@1646: /*********** Logging support -- for debugging only, uncomment as needed jaroslav@1646: static final Executor logPool = Executors.newSingleThreadExecutor(); jaroslav@1646: protected static void log(final String s) { jaroslav@1646: MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { jaroslav@1646: @Override jaroslav@1646: public void run() { jaroslav@1646: System.out.println(s); jaroslav@1646: } jaroslav@1646: }); jaroslav@1646: } jaroslav@1646: jaroslav@1646: protected static void log(final String s, final Throwable e) { jaroslav@1646: MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { jaroslav@1646: @Override jaroslav@1646: public void run() { jaroslav@1646: System.out.println(s); jaroslav@1646: e.printStackTrace(System.out); jaroslav@1646: } jaroslav@1646: }); jaroslav@1646: } jaroslav@1646: ***********************/ jaroslav@1646: jaroslav@1646: }