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 lambda metafactory which provides parameter unrolling and input validation. 38 * 39 * @see LambdaMetafactory 40 */ 41 /* package */ 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 MethodHandle samMethod; // Raw method handle for the functional interface method 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 MethodHandle implMethod; // Raw method handle for the implementation method 62 final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]" 63 final int implKind; // Invocation kind for implementation "5"=invokevirtual 64 final boolean implIsInstanceMethod; // Is the implementation an instance method "true" 65 final Class<?> implDefiningClass; // Type defining the implementation "class CC" 66 final MethodType implMethodType; // Type of the implementation method "(int)String" 67 final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" 68 final boolean isSerializable; // Should the returned instance be serializable 69 final Class<?>[] markerInterfaces; // Additional marker interfaces to be implemented 70 71 72 /** 73 * Meta-factory constructor. 74 * 75 * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges 76 * of the caller. 77 * @param invokedType Stacked automatically by VM; the signature of the invoked method, which includes the 78 * expected static type of the returned lambda object, and the static types of the captured 79 * arguments for the lambda. In the event that the implementation method is an instance method, 80 * the first argument in the invocation signature will correspond to the receiver. 81 * @param samMethod The primary method in the functional interface to which the lambda or method reference is 82 * being converted, represented as a method handle. 83 * @param implMethod The implementation method which should be called (with suitable adaptation of argument 84 * types, return types, and adjustment for captured arguments) when methods of the resulting 85 * functional interface instance are invoked. 86 * @param instantiatedMethodType The signature of the primary functional interface method after type variables 87 * are substituted with their instantiation from the capture site 88 * @throws ReflectiveOperationException 89 * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated 90 */ 91 AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, 92 MethodType invokedType, 93 MethodHandle samMethod, 94 MethodHandle implMethod, 95 MethodType instantiatedMethodType, 96 int flags, 97 Class<?>[] markerInterfaces) 98 throws ReflectiveOperationException, LambdaConversionException { 99 this.targetClass = caller.lookupClass(); 100 this.invokedType = invokedType; 101 102 this.samBase = invokedType.returnType(); 103 104 this.samMethod = samMethod; 105 this.samInfo = new MethodHandleInfo(samMethod); 106 this.samClass = samInfo.getDeclaringClass(); 107 this.samMethodType = samInfo.getMethodType(); 108 109 this.implMethod = implMethod; 110 this.implInfo = new MethodHandleInfo(implMethod); 111 // @@@ Temporary work-around pending resolution of 8005119 112 this.implKind = (implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial) 113 ? MethodHandleInfo.REF_invokeVirtual 114 : implInfo.getReferenceKind(); 115 this.implIsInstanceMethod = 116 implKind == MethodHandleInfo.REF_invokeVirtual || 117 implKind == MethodHandleInfo.REF_invokeSpecial || 118 implKind == MethodHandleInfo.REF_invokeInterface; 119 this.implDefiningClass = implInfo.getDeclaringClass(); 120 this.implMethodType = implInfo.getMethodType(); 121 122 this.instantiatedMethodType = instantiatedMethodType; 123 124 if (!samClass.isInterface()) { 125 throw new LambdaConversionException(String.format( 126 "Functional interface %s is not an interface", 127 samClass.getName())); 128 } 129 130 boolean foundSerializableSupertype = Serializable.class.isAssignableFrom(samBase); 131 for (Class<?> c : markerInterfaces) { 132 if (!c.isInterface()) { 133 throw new LambdaConversionException(String.format( 134 "Marker interface %s is not an interface", 135 c.getName())); 136 } 137 foundSerializableSupertype |= Serializable.class.isAssignableFrom(c); 138 } 139 this.isSerializable = ((flags & LambdaMetafactory.FLAG_SERIALIZABLE) != 0) 140 || foundSerializableSupertype; 141 142 if (isSerializable && !foundSerializableSupertype) { 143 markerInterfaces = Arrays.copyOf(markerInterfaces, markerInterfaces.length + 1); 144 markerInterfaces[markerInterfaces.length-1] = Serializable.class; 145 } 146 this.markerInterfaces = markerInterfaces; 147 } 148 149 /** 150 * Build the CallSite. 151 * 152 * @return a CallSite, which, when invoked, will return an instance of the 153 * functional interface 154 * @throws ReflectiveOperationException 155 */ 156 abstract CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException; 157 158 /** 159 * Check the meta-factory arguments for errors 160 * @throws LambdaConversionException if there are improper conversions 161 */ 162 void validateMetafactoryArgs() throws LambdaConversionException { 163 // Check target type is a subtype of class where SAM method is defined 164 if (!samClass.isAssignableFrom(samBase)) { 165 throw new LambdaConversionException( 166 String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s", 167 samBase.getName(), samClass.getName())); 168 } 169 170 switch (implKind) { 171 case MethodHandleInfo.REF_invokeInterface: 172 case MethodHandleInfo.REF_invokeVirtual: 173 case MethodHandleInfo.REF_invokeStatic: 174 case MethodHandleInfo.REF_newInvokeSpecial: 175 case MethodHandleInfo.REF_invokeSpecial: 176 break; 177 default: 178 throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo)); 179 } 180 181 // Check arity: optional-receiver + captured + SAM == impl 182 final int implArity = implMethodType.parameterCount(); 183 final int receiverArity = implIsInstanceMethod ? 1 : 0; 184 final int capturedArity = invokedType.parameterCount(); 185 final int samArity = samMethodType.parameterCount(); 186 final int instantiatedArity = instantiatedMethodType.parameterCount(); 187 if (implArity + receiverArity != capturedArity + samArity) { 188 throw new LambdaConversionException( 189 String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters", 190 implIsInstanceMethod ? "instance" : "static", implInfo, 191 capturedArity, samArity, implArity)); 192 } 193 if (instantiatedArity != samArity) { 194 throw new LambdaConversionException( 195 String.format("Incorrect number of parameters for %s method %s; %d instantiated parameters, %d functional interface method parameters", 196 implIsInstanceMethod ? "instance" : "static", implInfo, 197 instantiatedArity, samArity)); 198 } 199 200 // If instance: first captured arg (receiver) must be subtype of class where impl method is defined 201 final int capturedStart; 202 final int samStart; 203 if (implIsInstanceMethod) { 204 final Class<?> receiverClass; 205 206 // implementation is an instance method, adjust for receiver in captured variables / SAM arguments 207 if (capturedArity == 0) { 208 // receiver is function parameter 209 capturedStart = 0; 210 samStart = 1; 211 receiverClass = instantiatedMethodType.parameterType(0); 212 } else { 213 // receiver is a captured variable 214 capturedStart = 1; 215 samStart = 0; 216 receiverClass = invokedType.parameterType(0); 217 } 218 219 // check receiver type 220 if (!implDefiningClass.isAssignableFrom(receiverClass)) { 221 throw new LambdaConversionException( 222 String.format("Invalid receiver type %s; not a subtype of implementation type %s", 223 receiverClass, implDefiningClass)); 224 } 225 } else { 226 // no receiver 227 capturedStart = 0; 228 samStart = 0; 229 } 230 231 // Check for exact match on non-receiver captured arguments 232 final int implFromCaptured = capturedArity - capturedStart; 233 for (int i=0; i<implFromCaptured; i++) { 234 Class<?> implParamType = implMethodType.parameterType(i); 235 Class<?> capturedParamType = invokedType.parameterType(i + capturedStart); 236 if (!capturedParamType.equals(implParamType)) { 237 throw new LambdaConversionException( 238 String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", 239 i, capturedParamType, implParamType)); 240 } 241 } 242 // Check for adaptation match on SAM arguments 243 final int samOffset = samStart - implFromCaptured; 244 for (int i=implFromCaptured; i<implArity; i++) { 245 Class<?> implParamType = implMethodType.parameterType(i); 246 Class<?> instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset); 247 if (!isAdaptableTo(instantiatedParamType, implParamType, true)) { 248 throw new LambdaConversionException( 249 String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", 250 i, instantiatedParamType, implParamType)); 251 } 252 } 253 254 // Adaptation match: return type 255 Class<?> expectedType = instantiatedMethodType.returnType(); 256 Class<?> actualReturnType = 257 (implKind == MethodHandleInfo.REF_newInvokeSpecial) 258 ? implDefiningClass 259 : implMethodType.returnType(); 260 if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { 261 throw new LambdaConversionException( 262 String.format("Type mismatch for lambda return: %s is not convertible to %s", 263 actualReturnType, expectedType)); 264 } 265 } 266 267 /** 268 * Check type adaptability 269 * @param fromType 270 * @param toType 271 * @param strict If true, do strict checks, else allow that fromType may be parameterized 272 * @return True if 'fromType' can be passed to an argument of 'toType' 273 */ 274 private boolean isAdaptableTo(Class<?> fromType, Class<?> toType, boolean strict) { 275 if (fromType.equals(toType)) { 276 return true; 277 } 278 if (fromType.isPrimitive()) { 279 Wrapper wfrom = forPrimitiveType(fromType); 280 if (toType.isPrimitive()) { 281 // both are primitive: widening 282 Wrapper wto = forPrimitiveType(toType); 283 return wto.isConvertibleFrom(wfrom); 284 } else { 285 // from primitive to reference: boxing 286 return toType.isAssignableFrom(wfrom.wrapperType()); 287 } 288 } else { 289 if (toType.isPrimitive()) { 290 // from reference to primitive: unboxing 291 Wrapper wfrom; 292 if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) { 293 // fromType is a primitive wrapper; unbox+widen 294 Wrapper wto = forPrimitiveType(toType); 295 return wto.isConvertibleFrom(wfrom); 296 } else { 297 // must be convertible to primitive 298 return !strict; 299 } 300 } else { 301 // both are reference types: fromType should be a superclass of toType. 302 return strict? toType.isAssignableFrom(fromType) : true; 303 } 304 } 305 } 306 307 /** 308 * Check type adaptability for return types -- special handling of void type) and parameterized fromType 309 * @param fromType 310 * @param toType 311 * @return True if 'fromType' can be converted to 'toType' 312 */ 313 private boolean isAdaptableToAsReturn(Class<?> fromType, Class<?> toType) { 314 return toType.equals(void.class) 315 || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false); 316 } 317 318 319 /*********** Logging support -- for debugging only, uncomment as needed 320 static final Executor logPool = Executors.newSingleThreadExecutor(); 321 protected static void log(final String s) { 322 MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { 323 @Override 324 public void run() { 325 System.out.println(s); 326 } 327 }); 328 } 329 330 protected static void log(final String s, final Throwable e) { 331 MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { 332 @Override 333 public void run() { 334 System.out.println(s); 335 e.printStackTrace(System.out); 336 } 337 }); 338 } 339 ***********************/ 340 341 /** 342 * Find the functional interface method and corresponding abstract methods 343 * which should be bridged. The functional interface method and those to be 344 * bridged will have the same name and number of parameters. Check for 345 * matching default methods (non-abstract), the VM will create bridges for 346 * default methods; We don't have enough readily available type information 347 * to distinguish between where the functional interface method should be 348 * bridged and where the default method should be bridged; This situation is 349 * flagged. 350 */ 351 class MethodAnalyzer { 352 private final Method[] methods = samBase.getMethods(); 353 354 private Method samMethod = null; 355 private final List<Method> methodsToBridge = new ArrayList<>(methods.length); 356 private boolean conflictFoundBetweenDefaultAndBridge = false; 357 358 MethodAnalyzer() { 359 String samMethodName = samInfo.getName(); 360 Class<?>[] samParamTypes = samMethodType.parameterArray(); 361 int samParamLength = samParamTypes.length; 362 Class<?> samReturnType = samMethodType.returnType(); 363 Class<?> objectClass = Object.class; 364 List<Method> defaultMethods = new ArrayList<>(methods.length); 365 366 for (Method m : methods) { 367 if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { 368 Class<?>[] mParamTypes = m.getParameterTypes(); 369 if (mParamTypes.length == samParamLength) { 370 // Method matches name and parameter length -- and is not Object 371 if (Modifier.isAbstract(m.getModifiers())) { 372 // Method is abstract 373 if (m.getReturnType().equals(samReturnType) 374 && Arrays.equals(mParamTypes, samParamTypes)) { 375 // Exact match, this is the SAM method signature 376 samMethod = m; 377 } else if (!hasMatchingBridgeSignature(m)) { 378 // Record bridges, exclude methods with duplicate signatures 379 methodsToBridge.add(m); 380 } 381 } else { 382 // Record default methods for conflict testing 383 defaultMethods.add(m); 384 } 385 } 386 } 387 } 388 for (Method dm : defaultMethods) { 389 if (hasMatchingBridgeSignature(dm)) { 390 conflictFoundBetweenDefaultAndBridge = true; 391 break; 392 } 393 } 394 } 395 396 Method getSamMethod() { 397 return samMethod; 398 } 399 400 List<Method> getMethodsToBridge() { 401 return methodsToBridge; 402 } 403 404 boolean conflictFoundBetweenDefaultAndBridge() { 405 return conflictFoundBetweenDefaultAndBridge; 406 } 407 408 /** 409 * Search the list of previously found bridge methods to determine if there is a method with the same signature 410 * (return and parameter types) as the specified method. 411 * 412 * @param m The method to match 413 * @return True if the method was found, False otherwise 414 */ 415 private boolean hasMatchingBridgeSignature(Method m) { 416 Class<?>[] ptypes = m.getParameterTypes(); 417 Class<?> rtype = m.getReturnType(); 418 for (Method md : methodsToBridge) { 419 if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { 420 return true; 421 } 422 } 423 return false; 424 } 425 } 426 }