diff -r f17cf2e58bb3 -r 5acca3d1f124 jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Thu Oct 25 17:34:24 2012 -0700 @@ -0,0 +1,376 @@ +/* + * 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.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import sun.invoke.util.Wrapper; +import static sun.invoke.util.Wrapper.*; + +/** + * Abstract implementation of a meta-factory which provides parameter unrolling and input validation. + * + * @author Robert Field + */ +/*non-public*/ 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 boolean isSerializable; // Should the returned instance be serializable + final MethodHandleInfo samInfo; // Info about the SAM method handle "MethodHandleInfo[9 II.foo(Object)Object]" + final Class samClass; // Interface containing the SAM method "interface II" + final MethodType samMethodType; // Type of the SAM method "(Object)Object" + 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" + + + /** + * 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 samMethod The primary method in the functional interface to which the lambda or method reference is + * being converted, represented as a method handle. + * @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 SAM method from the functional interface's perspective + * @throws ReflectiveOperationException + */ + AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + MethodHandle samMethod, + MethodHandle implMethod, + MethodType instantiatedMethodType) + throws ReflectiveOperationException { + this.targetClass = caller.lookupClass(); + this.invokedType = invokedType; + + this.samBase = invokedType.returnType(); + this.isSerializable = Serializable.class.isAssignableFrom(samBase); + + this.samInfo = new MethodHandleInfo(samMethod); + this.samClass = samInfo.getDeclaringClass(); + this.samMethodType = samInfo.getMethodType(); + + this.implInfo = new MethodHandleInfo(implMethod); + this.implKind = implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial? MethodHandleInfo.REF_invokeVirtual : implInfo.getReferenceKind(); // @@@ Temp work-around to hotspot incorrectly converting to invokespecial + 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; + } + + /** + * Build the CallSite. + * + * @return a CallSite, which, when invoked, will return an instance of the + * functional interface + * @throws ReflectiveOperationException + */ + abstract CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException; + + /** + * Check the meta-factory arguments for errors + * @throws LambdaConversionException if there are improper conversions + */ + void validateMetafactoryArgs() throws LambdaConversionException { + // Check target type is a subtype of class where SAM method is defined + if (!samClass.isAssignableFrom(samBase)) { + throw new LambdaConversionException(String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s", + samBase.getName(), samClass.getName())); + } + + 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 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 functional interface parameters, %d SAM method parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + instantiatedArity, samArity)); + } + + // 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)); + } + } 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(); + if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); + } + } + + /** + * Check type adaptability + * @param fromType + * @param toType + * @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) : true; + } + } + } + + /** + * Check type adaptability for return types -- special handling of void type) and parameterized fromType + * @param fromType + * @param toType + * @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); + } + + + /*********** Logging support -- for debugging only + static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only + 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); + } + }); + } + ***********************/ + + /** + * Find the SAM method and corresponding methods which should be bridged. SAM method and those to be bridged + * will have the same name and number of parameters. Check for matching default methods (non-abstract), they + * should not be bridged-over and indicate a complex bridging situation. + */ + class MethodAnalyzer { + private final Method[] methods = samBase.getMethods(); + private final List methodsFound = new ArrayList<>(methods.length); + + private Method samMethod = null; + private final List methodsToBridge = new ArrayList<>(methods.length); + private boolean defaultMethodFound = false; + + MethodAnalyzer() { + String samMethodName = samInfo.getName(); + Class[] samParamTypes = samMethodType.parameterArray(); + int samParamLength = samParamTypes.length; + Class samReturnType = samMethodType.returnType(); + Class objectClass = Object.class; + + for (Method m : methods) { + if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { + Class[] mParamTypes = m.getParameterTypes(); + if (mParamTypes.length == samParamLength) { + if (Modifier.isAbstract(m.getModifiers())) { + // Exclude methods with duplicate signatures + if (methodUnique(m)) { + if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) { + // Exact match, this is the SAM method signature + samMethod = m; + } else { + methodsToBridge.add(m); + } + } + } else { + // This is a default method, flag for special processing + defaultMethodFound = true; + // Ignore future matching abstracts. + // Note, due to reabstraction, this is really a punt, hence pass-off to VM + methodUnique(m); + } + } + } + } + } + + Method getSamMethod() { + return samMethod; + } + + List getMethodsToBridge() { + return methodsToBridge; + } + + boolean wasDefaultMethodFound() { + return defaultMethodFound; + } + + /** + * Search the list of previously found methods to determine if there is a method with the same signature + * (return and parameter types) as the specified method. If it wasn't found before, add to the found list. + * + * @param m The method to match + * @return False if the method was found, True otherwise + */ + private boolean methodUnique(Method m) { + Class[] ptypes = m.getParameterTypes(); + Class rtype = m.getReturnType(); + for (Method md : methodsFound) { + if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { + return false; + } + } + methodsFound.add(m); + return true; + } + } +}