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