jdk/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java
changeset 14323 5acca3d1f124
child 16001 fd4c8d3becf8
equal deleted inserted replaced
14322:f17cf2e58bb3 14323:5acca3d1f124
       
     1 /*
       
     2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package java.lang.invoke;
       
    26 
       
    27 import java.io.Serializable;
       
    28 import java.lang.reflect.Method;
       
    29 import java.lang.reflect.Modifier;
       
    30 import java.util.ArrayList;
       
    31 import java.util.Arrays;
       
    32 import java.util.List;
       
    33 import sun.invoke.util.Wrapper;
       
    34 import static sun.invoke.util.Wrapper.*;
       
    35 
       
    36 /**
       
    37  * Abstract implementation of a meta-factory which provides parameter unrolling and input validation.
       
    38  *
       
    39  * @author Robert Field
       
    40  */
       
    41 /*non-public*/ abstract class AbstractValidatingLambdaMetafactory {
       
    42 
       
    43     /*
       
    44      * For context, the comments for the following fields are marked in quotes with their values, given this program:
       
    45      * interface II<T> {  Object foo(T x); }
       
    46      * interface JJ<R extends Number> extends II<R> { }
       
    47      * class CC {  String impl(int i) { return "impl:"+i; }}
       
    48      * class X {
       
    49      *     public static void main(String[] args) {
       
    50      *         JJ<Integer> iii = (new CC())::impl;
       
    51      *         System.out.printf(">>> %s\n", iii.foo(44));
       
    52      * }}
       
    53      */
       
    54     final Class<?> targetClass;               // The class calling the meta-factory via invokedynamic "class X"
       
    55     final MethodType invokedType;             // The type of the invoked method "(CC)II"
       
    56     final Class<?> samBase;                   // The type of the returned instance "interface JJ"
       
    57     final boolean isSerializable;             // Should the returned instance be serializable
       
    58     final MethodHandleInfo samInfo;           // Info about the SAM method handle "MethodHandleInfo[9 II.foo(Object)Object]"
       
    59     final Class<?> samClass;                  // Interface containing the SAM method "interface II"
       
    60     final MethodType samMethodType;           // Type of the SAM method "(Object)Object"
       
    61     final MethodHandleInfo implInfo;          // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]"
       
    62     final int implKind;                       // Invocation kind for implementation "5"=invokevirtual
       
    63     final boolean implIsInstanceMethod;       // Is the implementation an instance method "true"
       
    64     final Class<?> implDefiningClass;         // Type defining the implementation "class CC"
       
    65     final MethodType implMethodType;          // Type of the implementation method "(int)String"
       
    66     final MethodType instantiatedMethodType;  // Instantiated erased functional interface method type "(Integer)Object"
       
    67 
       
    68 
       
    69     /**
       
    70      * Meta-factory constructor.
       
    71      *
       
    72      * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges
       
    73      *               of the caller.
       
    74      * @param invokedType Stacked automatically by VM; the signature of the invoked method, which includes the
       
    75      *                    expected static type of the returned lambda object, and the static types of the captured
       
    76      *                    arguments for the lambda.  In the event that the implementation method is an instance method,
       
    77      *                    the first argument in the invocation signature will correspond to the receiver.
       
    78      * @param samMethod The primary method in the functional interface to which the lambda or method reference is
       
    79      *                  being converted, represented as a method handle.
       
    80      * @param implMethod The implementation method which should be called (with suitable adaptation of argument
       
    81      *                   types, return types, and adjustment for captured arguments) when methods of the resulting
       
    82      *                   functional interface instance are invoked.
       
    83      * @param instantiatedMethodType The signature of the SAM method from the functional interface's perspective
       
    84      * @throws ReflectiveOperationException
       
    85      */
       
    86     AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller,
       
    87                                        MethodType invokedType,
       
    88                                        MethodHandle samMethod,
       
    89                                        MethodHandle implMethod,
       
    90                                        MethodType instantiatedMethodType)
       
    91             throws ReflectiveOperationException {
       
    92         this.targetClass = caller.lookupClass();
       
    93         this.invokedType = invokedType;
       
    94 
       
    95         this.samBase = invokedType.returnType();
       
    96         this.isSerializable = Serializable.class.isAssignableFrom(samBase);
       
    97 
       
    98         this.samInfo = new MethodHandleInfo(samMethod);
       
    99         this.samClass = samInfo.getDeclaringClass();
       
   100         this.samMethodType  = samInfo.getMethodType();
       
   101 
       
   102         this.implInfo = new MethodHandleInfo(implMethod);
       
   103         this.implKind = implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial? MethodHandleInfo.REF_invokeVirtual : implInfo.getReferenceKind(); // @@@ Temp work-around to hotspot incorrectly converting to invokespecial
       
   104         this.implIsInstanceMethod =
       
   105                 implKind == MethodHandleInfo.REF_invokeVirtual ||
       
   106                 implKind == MethodHandleInfo.REF_invokeSpecial ||
       
   107                 implKind == MethodHandleInfo.REF_invokeInterface;
       
   108         this.implDefiningClass = implInfo.getDeclaringClass();
       
   109         this.implMethodType = implInfo.getMethodType();
       
   110 
       
   111         this.instantiatedMethodType = instantiatedMethodType;
       
   112     }
       
   113 
       
   114     /**
       
   115      * Build the CallSite.
       
   116      *
       
   117      * @return a CallSite, which, when invoked, will return an instance of the
       
   118      * functional interface
       
   119      * @throws ReflectiveOperationException
       
   120      */
       
   121     abstract CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException;
       
   122 
       
   123     /**
       
   124      * Check the meta-factory arguments for errors
       
   125      * @throws LambdaConversionException if there are improper conversions
       
   126      */
       
   127     void validateMetafactoryArgs() throws LambdaConversionException {
       
   128         // Check target type is a subtype of class where SAM method is defined
       
   129         if (!samClass.isAssignableFrom(samBase)) {
       
   130             throw new LambdaConversionException(String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s",
       
   131                     samBase.getName(), samClass.getName()));
       
   132         }
       
   133 
       
   134         switch (implKind) {
       
   135             case MethodHandleInfo.REF_invokeInterface:
       
   136             case MethodHandleInfo.REF_invokeVirtual:
       
   137             case MethodHandleInfo.REF_invokeStatic:
       
   138             case MethodHandleInfo.REF_newInvokeSpecial:
       
   139             case MethodHandleInfo.REF_invokeSpecial:
       
   140                 break;
       
   141             default:
       
   142                 throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo));
       
   143         }
       
   144 
       
   145         // Check arity: optional-receiver + captured + SAM == impl
       
   146         final int implArity = implMethodType.parameterCount();
       
   147         final int receiverArity = implIsInstanceMethod ? 1 : 0;
       
   148         final int capturedArity = invokedType.parameterCount();
       
   149         final int samArity = samMethodType.parameterCount();
       
   150         final int instantiatedArity = instantiatedMethodType.parameterCount();
       
   151         if (implArity + receiverArity != capturedArity + samArity) {
       
   152             throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface parameters, %d implementation parameters",
       
   153                     implIsInstanceMethod ? "instance" : "static", implInfo,
       
   154                     capturedArity, samArity, implArity));
       
   155         }
       
   156         if (instantiatedArity != samArity) {
       
   157             throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d functional interface parameters, %d SAM method parameters",
       
   158                     implIsInstanceMethod ? "instance" : "static", implInfo,
       
   159                     instantiatedArity, samArity));
       
   160         }
       
   161 
       
   162         // If instance: first captured arg (receiver) must be subtype of class where impl method is defined
       
   163         final int capturedStart;
       
   164         final int samStart;
       
   165         if (implIsInstanceMethod) {
       
   166             final Class<?> receiverClass;
       
   167 
       
   168             // implementation is an instance method, adjust for receiver in captured variables / SAM arguments
       
   169             if (capturedArity == 0) {
       
   170                 // receiver is function parameter
       
   171                 capturedStart = 0;
       
   172                 samStart = 1;
       
   173                 receiverClass = instantiatedMethodType.parameterType(0);
       
   174             } else {
       
   175                 // receiver is a captured variable
       
   176                 capturedStart = 1;
       
   177                 samStart = 0;
       
   178                 receiverClass = invokedType.parameterType(0);
       
   179             }
       
   180 
       
   181             // check receiver type
       
   182             if (!implDefiningClass.isAssignableFrom(receiverClass)) {
       
   183                 throw new LambdaConversionException(String.format("Invalid receiver type %s; not a subtype of implementation type %s",
       
   184                                                                   receiverClass, implDefiningClass));
       
   185             }
       
   186         } else {
       
   187             // no receiver
       
   188             capturedStart = 0;
       
   189             samStart = 0;
       
   190         }
       
   191 
       
   192         // Check for exact match on non-receiver captured arguments
       
   193         final int implFromCaptured = capturedArity - capturedStart;
       
   194         for (int i=0; i<implFromCaptured; i++) {
       
   195             Class<?> implParamType = implMethodType.parameterType(i);
       
   196             Class<?> capturedParamType = invokedType.parameterType(i + capturedStart);
       
   197             if (!capturedParamType.equals(implParamType)) {
       
   198                 throw new LambdaConversionException(
       
   199                         String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", i, capturedParamType, implParamType));
       
   200             }
       
   201         }
       
   202         // Check for adaptation match on SAM arguments
       
   203         final int samOffset = samStart - implFromCaptured;
       
   204         for (int i=implFromCaptured; i<implArity; i++) {
       
   205             Class<?> implParamType = implMethodType.parameterType(i);
       
   206             Class<?> instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset);
       
   207             if (!isAdaptableTo(instantiatedParamType, implParamType, true)) {
       
   208                 throw new LambdaConversionException(
       
   209                         String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, instantiatedParamType, implParamType));
       
   210             }
       
   211         }
       
   212 
       
   213         // Adaptation match: return type
       
   214         Class<?> expectedType = instantiatedMethodType.returnType();
       
   215         Class<?> actualReturnType =
       
   216                 (implKind == MethodHandleInfo.REF_newInvokeSpecial)
       
   217                   ? implDefiningClass
       
   218                   : implMethodType.returnType();
       
   219         if (!isAdaptableToAsReturn(actualReturnType, expectedType)) {
       
   220             throw new LambdaConversionException(
       
   221                     String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType));
       
   222         }
       
   223      }
       
   224 
       
   225     /**
       
   226      * Check type adaptability
       
   227      * @param fromType
       
   228      * @param toType
       
   229      * @param strict If true, do strict checks, else allow that fromType may be parameterized
       
   230      * @return True if 'fromType' can be passed to an argument of 'toType'
       
   231      */
       
   232     private boolean isAdaptableTo(Class<?> fromType, Class<?> toType, boolean strict) {
       
   233         if (fromType.equals(toType)) {
       
   234             return true;
       
   235         }
       
   236         if (fromType.isPrimitive()) {
       
   237             Wrapper wfrom = forPrimitiveType(fromType);
       
   238             if (toType.isPrimitive()) {
       
   239                 // both are primitive: widening
       
   240                 Wrapper wto = forPrimitiveType(toType);
       
   241                 return wto.isConvertibleFrom(wfrom);
       
   242             } else {
       
   243                 // from primitive to reference: boxing
       
   244                 return toType.isAssignableFrom(wfrom.wrapperType());
       
   245             }
       
   246         } else {
       
   247             if (toType.isPrimitive()) {
       
   248                 // from reference to primitive: unboxing
       
   249                 Wrapper wfrom;
       
   250                 if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) {
       
   251                     // fromType is a primitive wrapper; unbox+widen
       
   252                     Wrapper wto = forPrimitiveType(toType);
       
   253                     return wto.isConvertibleFrom(wfrom);
       
   254                 } else {
       
   255                     // must be convertible to primitive
       
   256                     return !strict;
       
   257                 }
       
   258             } else {
       
   259                 // both are reference types: fromType should be a superclass of toType.
       
   260                 return strict? toType.isAssignableFrom(fromType) : true;
       
   261             }
       
   262         }
       
   263     }
       
   264 
       
   265     /**
       
   266      * Check type adaptability for return types -- special handling of void type) and parameterized fromType
       
   267      * @param fromType
       
   268      * @param toType
       
   269      * @return True if 'fromType' can be converted to 'toType'
       
   270      */
       
   271     private boolean isAdaptableToAsReturn(Class<?> fromType, Class<?> toType) {
       
   272         return toType.equals(void.class)
       
   273                || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false);
       
   274     }
       
   275 
       
   276 
       
   277     /*********** Logging support -- for debugging only
       
   278     static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only
       
   279     protected static void log(final String s) {
       
   280         MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
       
   281             @Override
       
   282             public void run() {
       
   283                 System.out.println(s);
       
   284             }
       
   285         });
       
   286     }
       
   287 
       
   288     protected static void log(final String s, final Throwable e) {
       
   289         MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
       
   290             @Override
       
   291             public void run() {
       
   292                 System.out.println(s);
       
   293                 e.printStackTrace(System.out);
       
   294             }
       
   295         });
       
   296     }
       
   297     ***********************/
       
   298 
       
   299     /**
       
   300      * Find the SAM method and corresponding methods which should be bridged. SAM method and those to be bridged
       
   301      * will have the same name and number of parameters. Check for matching default methods (non-abstract), they
       
   302      * should not be bridged-over and indicate a complex bridging situation.
       
   303      */
       
   304     class MethodAnalyzer {
       
   305         private final Method[] methods = samBase.getMethods();
       
   306         private final List<Method> methodsFound = new ArrayList<>(methods.length);
       
   307 
       
   308         private Method samMethod = null;
       
   309         private final List<Method> methodsToBridge = new ArrayList<>(methods.length);
       
   310         private boolean defaultMethodFound = false;
       
   311 
       
   312         MethodAnalyzer() {
       
   313             String samMethodName = samInfo.getName();
       
   314             Class<?>[] samParamTypes = samMethodType.parameterArray();
       
   315             int samParamLength = samParamTypes.length;
       
   316             Class<?> samReturnType = samMethodType.returnType();
       
   317             Class<?> objectClass = Object.class;
       
   318 
       
   319             for (Method m : methods) {
       
   320                 if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) {
       
   321                     Class<?>[] mParamTypes = m.getParameterTypes();
       
   322                     if (mParamTypes.length == samParamLength) {
       
   323                         if (Modifier.isAbstract(m.getModifiers())) {
       
   324                             // Exclude methods with duplicate signatures
       
   325                             if (methodUnique(m)) {
       
   326                                 if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) {
       
   327                                     // Exact match, this is the SAM method signature
       
   328                                     samMethod = m;
       
   329                                 } else {
       
   330                                     methodsToBridge.add(m);
       
   331                                 }
       
   332                             }
       
   333                         } else {
       
   334                             // This is a default method, flag for special processing
       
   335                             defaultMethodFound = true;
       
   336                             // Ignore future matching abstracts.
       
   337                             // Note, due to reabstraction, this is really a punt, hence pass-off to VM
       
   338                             methodUnique(m);
       
   339                         }
       
   340                     }
       
   341                 }
       
   342             }
       
   343         }
       
   344 
       
   345         Method getSamMethod() {
       
   346             return samMethod;
       
   347         }
       
   348 
       
   349         List<Method> getMethodsToBridge() {
       
   350             return methodsToBridge;
       
   351         }
       
   352 
       
   353         boolean wasDefaultMethodFound() {
       
   354             return defaultMethodFound;
       
   355         }
       
   356 
       
   357         /**
       
   358          * Search the list of previously found methods to determine if there is a method with the same signature
       
   359          * (return and parameter types) as the specified method. If it wasn't found before, add to the found list.
       
   360          *
       
   361          * @param m The method to match
       
   362          * @return False if the method was found, True otherwise
       
   363          */
       
   364         private boolean methodUnique(Method m) {
       
   365             Class<?>[] ptypes = m.getParameterTypes();
       
   366             Class<?> rtype = m.getReturnType();
       
   367             for (Method md : methodsFound) {
       
   368                 if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) {
       
   369                     return false;
       
   370                 }
       
   371             }
       
   372             methodsFound.add(m);
       
   373             return true;
       
   374         }
       
   375     }
       
   376 }