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 26 package java.lang.invoke; 27 28 import jdk.internal.org.objectweb.asm.*; 29 import sun.misc.Unsafe; 30 import sun.util.logging.PlatformLogger; 31 32 import java.io.File; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.lang.reflect.Constructor; 36 import java.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.security.ProtectionDomain; 39 import java.util.concurrent.atomic.AtomicBoolean; 40 import java.util.concurrent.atomic.AtomicInteger; 41 42 import static jdk.internal.org.objectweb.asm.Opcodes.*; 43 44 /** 45 * Lambda metafactory implementation which dynamically creates an 46 * inner-class-like class per lambda callsite. 47 * 48 * @see LambdaMetafactory 49 */ 50 /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { 51 private static final Unsafe UNSAFE = Unsafe.getUnsafe(); 52 53 private static final int CLASSFILE_VERSION = 51; 54 private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); 55 private static final String NAME_MAGIC_ACCESSOR_IMPL = "java/lang/invoke/MagicLambdaImpl"; 56 private static final String NAME_CTOR = "<init>"; 57 58 //Serialization support 59 private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; 60 private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; 61 private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; 62 private static final String NAME_OBJECT = "java/lang/Object"; 63 private static final String DESCR_CTOR_SERIALIZED_LAMBDA 64 = MethodType.methodType(void.class, 65 Class.class, 66 String.class, String.class, String.class, 67 int.class, String.class, String.class, String.class, 68 String.class, 69 Object[].class).toMethodDescriptorString(); 70 71 // Used to ensure that each spun class name is unique 72 private static final AtomicInteger counter = new AtomicInteger(0); 73 74 // For dumping generated classes to disk, for debugging purposes 75 private static final String DUMP_PATH_PROPERTY = "jdk.internal.lambda.dumpProxyClasses"; 76 private static final String dumpDir; 77 private static AtomicBoolean invalidDir = new AtomicBoolean(false); 78 static { 79 dumpDir = AccessController.doPrivileged(new PrivilegedAction<String>() { 80 @Override 81 public String run() { 82 String path = System.getProperty(DUMP_PATH_PROPERTY); 83 if (path != null) { 84 validateDumpDir(path); 85 } 86 return path; 87 } 88 }); 89 } 90 91 // See context values in AbstractValidatingLambdaMetafactory 92 private final String implMethodClassName; // Name of type containing implementation "CC" 93 private final String implMethodName; // Name of implementation method "impl" 94 private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" 95 private final Type[] implMethodArgumentTypes; // ASM types for implementaion method parameters 96 private final Type implMethodReturnType; // ASM type for implementaion method return type "Ljava/lang/String;" 97 private final MethodType constructorType; // Generated class constructor type "(CC)void" 98 private final String constructorDesc; // Type descriptor for constructor "(LCC;)V" 99 private final ClassWriter cw; // ASM class writer 100 private final Type[] argTypes; // ASM types for the constructor arguments 101 private final String[] argNames; // Generated names for the constructor arguments 102 private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" 103 private final Type[] instantiatedArgumentTypes; // ASM types for the functional interface arguments 104 105 /** 106 * General meta-factory constructor, supporting both standard cases and 107 * allowing for uncommon options such as serialization or bridging. 108 * 109 * @param caller Stacked automatically by VM; represents a lookup context 110 * with the accessibility privileges of the caller. 111 * @param invokedType Stacked automatically by VM; the signature of the 112 * invoked method, which includes the expected static 113 * type of the returned lambda object, and the static 114 * types of the captured arguments for the lambda. In 115 * the event that the implementation method is an 116 * instance method, the first argument in the invocation 117 * signature will correspond to the receiver. 118 * @param samMethodName Name of the method in the functional interface to 119 * which the lambda or method reference is being 120 * converted, represented as a String. 121 * @param samMethodType Type of the method in the functional interface to 122 * which the lambda or method reference is being 123 * converted, represented as a MethodType. 124 * @param implMethod The implementation method which should be called (with 125 * suitable adaptation of argument types, return types, 126 * and adjustment for captured arguments) when methods of 127 * the resulting functional interface instance are invoked. 128 * @param instantiatedMethodType The signature of the primary functional 129 * interface method after type variables are 130 * substituted with their instantiation from 131 * the capture site 132 * @param isSerializable Should the lambda be made serializable? If set, 133 * either the target type or one of the additional SAM 134 * types must extend {@code Serializable}. 135 * @param markerInterfaces Additional interfaces which the lambda object 136 * should implement. 137 * @param additionalBridges Method types for additional signatures to be 138 * bridged to the implementation method 139 * @throws ReflectiveOperationException 140 * @throws LambdaConversionException If any of the meta-factory protocol 141 * invariants are violated 142 */ 143 public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, 144 MethodType invokedType, 145 String samMethodName, 146 MethodType samMethodType, 147 MethodHandle implMethod, 148 MethodType instantiatedMethodType, 149 boolean isSerializable, 150 Class<?>[] markerInterfaces, 151 MethodType[] additionalBridges) 152 throws ReflectiveOperationException, LambdaConversionException { 153 super(caller, invokedType, samMethodName, samMethodType, 154 implMethod, instantiatedMethodType, 155 isSerializable, markerInterfaces, additionalBridges); 156 implMethodClassName = implDefiningClass.getName().replace('.', '/'); 157 implMethodName = implInfo.getName(); 158 implMethodDesc = implMethodType.toMethodDescriptorString(); 159 Type implMethodAsmType = Type.getMethodType(implMethodDesc); 160 implMethodArgumentTypes = implMethodAsmType.getArgumentTypes(); 161 implMethodReturnType = (implKind == MethodHandleInfo.REF_newInvokeSpecial) 162 ? Type.getObjectType(implMethodClassName) 163 : implMethodAsmType.getReturnType(); 164 constructorType = invokedType.changeReturnType(Void.TYPE); 165 constructorDesc = constructorType.toMethodDescriptorString(); 166 lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); 167 cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 168 argTypes = Type.getArgumentTypes(constructorDesc); 169 argNames = new String[argTypes.length]; 170 for (int i = 0; i < argTypes.length; i++) { 171 argNames[i] = "arg$" + (i + 1); 172 } 173 instantiatedArgumentTypes = Type.getArgumentTypes( 174 instantiatedMethodType.toMethodDescriptorString()); 175 } 176 177 /** 178 * Build the CallSite. Generate a class file which implements the functional 179 * interface, define the class, if there are no parameters create an instance 180 * of the class which the CallSite will return, otherwise, generate handles 181 * which will call the class' constructor. 182 * 183 * @return a CallSite, which, when invoked, will return an instance of the 184 * functional interface 185 * @throws ReflectiveOperationException 186 * @throws LambdaConversionException If properly formed functional interface 187 * is not found 188 */ 189 @Override 190 CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException { 191 final Class<?> innerClass = spinInnerClass(); 192 if (invokedType.parameterCount() == 0) { 193 final Constructor[] ctrs = AccessController.doPrivileged( 194 new PrivilegedAction<Constructor[]>() { 195 @Override 196 public Constructor[] run() { 197 return innerClass.getDeclaredConstructors(); 198 } 199 }); 200 if (ctrs.length != 1) { 201 throw new ReflectiveOperationException("Expected one lambda constructor for " 202 + innerClass.getCanonicalName() + ", got " + ctrs.length); 203 } 204 // The lambda implementing inner class constructor is private, set 205 // it accessible (by us) before creating the constant sole instance 206 AccessController.doPrivileged(new PrivilegedAction<Void>() { 207 @Override 208 public Void run() { 209 ctrs[0].setAccessible(true); 210 return null; 211 } 212 }); 213 Object inst = ctrs[0].newInstance(); 214 return new ConstantCallSite(MethodHandles.constant(samBase, inst)); 215 } else { 216 return new ConstantCallSite( 217 MethodHandles.Lookup.IMPL_LOOKUP 218 .findConstructor(innerClass, constructorType) 219 .asType(constructorType.changeReturnType(samBase))); 220 } 221 } 222 223 private static File validateDumpDir(String path) { 224 File dirPath = new File(path); 225 String errMsg = null; 226 if (!dirPath.exists()) { 227 errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses does not exist"; 228 } else if (!dirPath.isDirectory()) { 229 errMsg = "Path at jdk.internal.lambda.dumpProxyClasses is not a directory"; 230 } else if (!dirPath.canWrite()) { 231 errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses is not writable"; 232 } else { 233 // validate dump directory, ready to go 234 return dirPath; 235 } 236 237 // show error message about invalid directory once 238 if (! invalidDir.getAndSet(true)) { 239 PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName()) 240 .warning(errMsg); 241 } 242 return null; 243 } 244 245 /** 246 * Generate a class file which implements the functional 247 * interface, define and return the class. 248 * 249 * @implNote The class that is generated does not include signature 250 * information for exceptions that may be present on the SAM method. 251 * This is to reduce classfile size, and is harmless as checked exceptions 252 * are erased anyway, no one will ever compile against this classfile, 253 * and we make no guarantees about the reflective properties of lambda 254 * objects. 255 * 256 * @return a Class which implements the functional interface 257 * @throws LambdaConversionException If properly formed functional interface 258 * is not found 259 */ 260 private Class<?> spinInnerClass() throws LambdaConversionException { 261 String[] interfaces = new String[markerInterfaces.length + 1]; 262 interfaces[0] = samBase.getName().replace('.', '/'); 263 for (int i=0; i<markerInterfaces.length; i++) { 264 interfaces[i+1] = markerInterfaces[i].getName().replace('.', '/'); 265 } 266 cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, 267 lambdaClassName, null, 268 NAME_MAGIC_ACCESSOR_IMPL, interfaces); 269 270 // Generate final fields to be filled in by constructor 271 for (int i = 0; i < argTypes.length; i++) { 272 FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, 273 argNames[i], 274 argTypes[i].getDescriptor(), 275 null, null); 276 fv.visitEnd(); 277 } 278 279 generateConstructor(); 280 281 // Forward the SAM method 282 String methodDescriptor = samMethodType.toMethodDescriptorString(); 283 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, 284 methodDescriptor, null, null); 285 new ForwardingMethodGenerator(mv).generate(methodDescriptor); 286 287 // Forward the bridges 288 if (additionalBridges != null) { 289 for (MethodType mt : additionalBridges) { 290 methodDescriptor = mt.toMethodDescriptorString(); 291 mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName, 292 methodDescriptor, null, null); 293 new ForwardingMethodGenerator(mv).generate(methodDescriptor); 294 } 295 } 296 297 if (isSerializable) 298 generateWriteReplace(); 299 300 cw.visitEnd(); 301 302 // Define the generated class in this VM. 303 304 final byte[] classBytes = cw.toByteArray(); 305 306 // If requested, dump out to a file for debugging purposes 307 if (dumpDir != null) { 308 AccessController.doPrivileged(new PrivilegedAction<Void>() { 309 @Override 310 public Void run() { 311 File dirPath = validateDumpDir(dumpDir); 312 if (dirPath != null) { 313 File out = new File(dirPath, lambdaClassName.replace('/', '.') + ".class"); 314 try (FileOutputStream fos = new FileOutputStream(out)) { 315 fos.write(classBytes); 316 } catch (IOException ex) { 317 PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName()) 318 .warning("Exception writing to path at jdk.internal.lambda.dumpProxyClasses"); 319 } 320 } 321 return null; 322 } 323 }); 324 } 325 326 ClassLoader loader = targetClass.getClassLoader(); 327 ProtectionDomain pd = (loader == null) 328 ? null 329 : AccessController.doPrivileged( 330 new PrivilegedAction<ProtectionDomain>() { 331 @Override 332 public ProtectionDomain run() { 333 return targetClass.getProtectionDomain(); 334 } 335 } 336 ); 337 338 return UNSAFE.defineClass(lambdaClassName, 339 classBytes, 0, classBytes.length, 340 loader, pd); 341 } 342 343 /** 344 * Generate the constructor for the class 345 */ 346 private void generateConstructor() { 347 // Generate constructor 348 MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, 349 constructorDesc, null, null); 350 ctor.visitCode(); 351 ctor.visitVarInsn(ALOAD, 0); 352 ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR, 353 METHOD_DESCRIPTOR_VOID); 354 int lvIndex = 0; 355 for (int i = 0; i < argTypes.length; i++) { 356 ctor.visitVarInsn(ALOAD, 0); 357 ctor.visitVarInsn(argTypes[i].getOpcode(ILOAD), lvIndex + 1); 358 lvIndex += argTypes[i].getSize(); 359 ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], 360 argTypes[i].getDescriptor()); 361 } 362 ctor.visitInsn(RETURN); 363 // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored 364 ctor.visitMaxs(-1, -1); 365 ctor.visitEnd(); 366 } 367 368 /** 369 * Generate the writeReplace method (if needed for serialization) 370 */ 371 private void generateWriteReplace() { 372 TypeConvertingMethodAdapter mv 373 = new TypeConvertingMethodAdapter( 374 cw.visitMethod(ACC_PRIVATE + ACC_FINAL, 375 NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, 376 null, null)); 377 378 mv.visitCode(); 379 mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); 380 mv.visitInsn(DUP); 381 mv.visitLdcInsn(Type.getType(targetClass)); 382 mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/')); 383 mv.visitLdcInsn(samMethodName); 384 mv.visitLdcInsn(samMethodType.toMethodDescriptorString()); 385 mv.visitLdcInsn(implInfo.getReferenceKind()); 386 mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); 387 mv.visitLdcInsn(implInfo.getName()); 388 mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); 389 mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString()); 390 391 mv.iconst(argTypes.length); 392 mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT); 393 for (int i = 0; i < argTypes.length; i++) { 394 mv.visitInsn(DUP); 395 mv.iconst(i); 396 mv.visitVarInsn(ALOAD, 0); 397 mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], 398 argTypes[i].getDescriptor()); 399 mv.boxIfTypePrimitive(argTypes[i]); 400 mv.visitInsn(AASTORE); 401 } 402 mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, 403 DESCR_CTOR_SERIALIZED_LAMBDA); 404 mv.visitInsn(ARETURN); 405 // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored 406 mv.visitMaxs(-1, -1); 407 mv.visitEnd(); 408 } 409 410 /** 411 * This class generates a method body which calls the lambda implementation 412 * method, converting arguments, as needed. 413 */ 414 private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter { 415 416 ForwardingMethodGenerator(MethodVisitor mv) { 417 super(mv); 418 } 419 420 void generate(String methodDescriptor) { 421 visitCode(); 422 423 if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { 424 visitTypeInsn(NEW, implMethodClassName); 425 visitInsn(DUP); 426 } 427 for (int i = 0; i < argTypes.length; i++) { 428 visitVarInsn(ALOAD, 0); 429 visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], 430 argTypes[i].getDescriptor()); 431 } 432 433 convertArgumentTypes(Type.getArgumentTypes(methodDescriptor)); 434 435 // Invoke the method we want to forward to 436 visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc); 437 438 // Convert the return value (if any) and return it 439 // Note: if adapting from non-void to void, the 'return' 440 // instruction will pop the unneeded result 441 Type samReturnType = Type.getReturnType(methodDescriptor); 442 convertType(implMethodReturnType, samReturnType, samReturnType); 443 visitInsn(samReturnType.getOpcode(Opcodes.IRETURN)); 444 // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored 445 visitMaxs(-1, -1); 446 visitEnd(); 447 } 448 449 private void convertArgumentTypes(Type[] samArgumentTypes) { 450 int lvIndex = 0; 451 boolean samIncludesReceiver = implIsInstanceMethod && 452 argTypes.length == 0; 453 int samReceiverLength = samIncludesReceiver ? 1 : 0; 454 if (samIncludesReceiver) { 455 // push receiver 456 Type rcvrType = samArgumentTypes[0]; 457 Type instantiatedRcvrType = instantiatedArgumentTypes[0]; 458 459 visitVarInsn(rcvrType.getOpcode(ILOAD), lvIndex + 1); 460 lvIndex += rcvrType.getSize(); 461 convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType); 462 } 463 int argOffset = implMethodArgumentTypes.length - samArgumentTypes.length; 464 for (int i = samReceiverLength; i < samArgumentTypes.length; i++) { 465 Type argType = samArgumentTypes[i]; 466 Type targetType = implMethodArgumentTypes[argOffset + i]; 467 Type instantiatedArgType = instantiatedArgumentTypes[i]; 468 469 visitVarInsn(argType.getOpcode(ILOAD), lvIndex + 1); 470 lvIndex += argType.getSize(); 471 convertType(argType, targetType, instantiatedArgType); 472 } 473 } 474 475 private void convertType(Type argType, Type targetType, Type functionalType) { 476 convertType(argType.getDescriptor(), 477 targetType.getDescriptor(), 478 functionalType.getDescriptor()); 479 } 480 481 private int invocationOpcode() throws InternalError { 482 switch (implKind) { 483 case MethodHandleInfo.REF_invokeStatic: 484 return INVOKESTATIC; 485 case MethodHandleInfo.REF_newInvokeSpecial: 486 return INVOKESPECIAL; 487 case MethodHandleInfo.REF_invokeVirtual: 488 return INVOKEVIRTUAL; 489 case MethodHandleInfo.REF_invokeInterface: 490 return INVOKEINTERFACE; 491 case MethodHandleInfo.REF_invokeSpecial: 492 return INVOKESPECIAL; 493 default: 494 throw new InternalError("Unexpected invocation kind: " + implKind); 495 } 496 } 497 } 498 }