1 /* 2 * Copyright (c) 2012, 2024, 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.misc.CDS; 29 import jdk.internal.util.ClassFileDumper; 30 import sun.invoke.util.VerifyAccess; 31 import sun.security.action.GetBooleanAction; 32 33 import java.io.Serializable; 34 import java.lang.classfile.ClassBuilder; 35 import java.lang.classfile.ClassFile; 36 import java.lang.classfile.CodeBuilder; 37 import java.lang.classfile.FieldBuilder; 38 import java.lang.classfile.MethodBuilder; 39 import java.lang.classfile.Opcode; 40 import java.lang.classfile.TypeKind; 41 import java.lang.classfile.constantpool.MethodHandleEntry; 42 import java.lang.classfile.constantpool.NameAndTypeEntry; 43 import java.lang.constant.ClassDesc; 44 import java.lang.constant.DynamicConstantDesc; 45 import java.lang.constant.MethodTypeDesc; 46 import java.lang.invoke.MethodHandles.Lookup; 47 import java.lang.module.Configuration; 48 import java.lang.module.ModuleFinder; 49 import java.lang.reflect.Modifier; 50 import java.util.LinkedHashSet; 51 import java.util.List; 52 import java.util.Optional; 53 import java.util.ServiceLoader; 54 import java.util.Set; 55 import java.util.function.Consumer; 56 57 import static java.lang.classfile.ClassFile.*; 58 import java.lang.classfile.attribute.ExceptionsAttribute; 59 import java.lang.classfile.constantpool.ClassEntry; 60 import java.lang.classfile.constantpool.ConstantPoolBuilder; 61 import java.lang.classfile.constantpool.MethodRefEntry; 62 import static java.lang.constant.ConstantDescs.*; 63 import static java.lang.invoke.MethodHandleNatives.Constants.NESTMATE_CLASS; 64 import static java.lang.invoke.MethodHandleNatives.Constants.STRONG_LOADER_LINK; 65 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; 66 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; 67 import static java.lang.invoke.MethodType.methodType; 68 import jdk.internal.constant.ConstantUtils; 69 import jdk.internal.constant.MethodTypeDescImpl; 70 import jdk.internal.constant.ReferenceClassDescImpl; 71 import sun.invoke.util.Wrapper; 72 73 /** 74 * Lambda metafactory implementation which dynamically creates an 75 * inner-class-like class per lambda callsite. 76 * 77 * @see LambdaMetafactory 78 */ 79 /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { 80 private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$"; 81 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 82 private static final ClassDesc[] EMPTY_CLASSDESC_ARRAY = ConstantUtils.EMPTY_CLASSDESC; 83 84 // Static builders to avoid lambdas 85 record MethodBody(Consumer<CodeBuilder> code) implements Consumer<MethodBuilder> { 86 @Override 87 public void accept(MethodBuilder mb) { 88 mb.withCode(code); 89 } 90 }; 91 92 // For dumping generated classes to disk, for debugging purposes 93 private static final ClassFileDumper lambdaProxyClassFileDumper; 94 95 private static final boolean disableEagerInitialization; 96 97 private static final String NAME_METHOD_QUOTED = "quoted"; 98 private static final String quotedInstanceFieldName = "quoted"; 99 100 static { 101 // To dump the lambda proxy classes, set this system property: 102 // -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles 103 // or -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true 104 final String dumpProxyClassesKey = "jdk.invoke.LambdaMetafactory.dumpProxyClassFiles"; 105 lambdaProxyClassFileDumper = ClassFileDumper.getInstance(dumpProxyClassesKey, "DUMP_LAMBDA_PROXY_CLASS_FILES"); 106 107 final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization"; 108 disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey); 109 } 110 111 // See context values in AbstractValidatingLambdaMetafactory 112 private final ClassDesc implMethodClassDesc; // Name of type containing implementation "CC" 113 private final String implMethodName; // Name of implementation method "impl" 114 private final MethodTypeDesc implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" 115 private final MethodType constructorType; // Generated class constructor type "(CC)void" 116 private final MethodTypeDesc constructorTypeDesc;// Type descriptor for the generated class constructor type "(CC)void" 117 private final String[] argNames; // Generated names for the constructor arguments 118 private final ClassDesc[] argDescs; // Type descriptors for the constructor arguments 119 private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" 120 private final ConstantPoolBuilder pool = ConstantPoolBuilder.of(); 121 private final ClassEntry lambdaClassEntry; // Class entry for the generated class "X$$Lambda$1" 122 private final boolean useImplMethodHandle; // use MethodHandle invocation instead of symbolic bytecode invocation 123 124 /** 125 * General meta-factory constructor, supporting both standard cases and 126 * allowing for uncommon options such as serialization or bridging. 127 * 128 * @param caller Stacked automatically by VM; represents a lookup context 129 * with the accessibility privileges of the caller. 130 * @param factoryType Stacked automatically by VM; the signature of the 131 * invoked method, which includes the expected static 132 * type of the returned lambda object, and the static 133 * types of the captured arguments for the lambda. In 134 * the event that the implementation method is an 135 * instance method, the first argument in the invocation 136 * signature will correspond to the receiver. 137 * @param interfaceMethodName Name of the method in the functional interface to 138 * which the lambda or method reference is being 139 * converted, represented as a String. 140 * @param interfaceMethodType Type of the method in the functional interface to 141 * which the lambda or method reference is being 142 * converted, represented as a MethodType. 143 * @param implementation The implementation method which should be called (with 144 * suitable adaptation of argument types, return types, 145 * and adjustment for captured arguments) when methods of 146 * the resulting functional interface instance are invoked. 147 * @param dynamicMethodType The signature of the primary functional 148 * interface method after type variables are 149 * substituted with their instantiation from 150 * the capture site 151 * @param isSerializable Should the lambda be made serializable? If set, 152 * either the target type or one of the additional SAM 153 * types must extend {@code Serializable}. 154 * @param altInterfaces Additional interfaces which the lambda object 155 * should implement. 156 * @param altMethods Method types for additional signatures to be 157 * implemented by invoking the implementation method 158 * @throws LambdaConversionException If any of the meta-factory protocol 159 * invariants are violated 160 * @throws SecurityException If a security manager is present, and it 161 * <a href="MethodHandles.Lookup.html#secmgr">denies access</a> 162 * from {@code caller} to the package of {@code implementation}. 163 */ 164 public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, 165 MethodType factoryType, 166 String interfaceMethodName, 167 MethodType interfaceMethodType, 168 MethodHandle implementation, 169 MethodType dynamicMethodType, 170 boolean isSerializable, 171 Class<?>[] altInterfaces, 172 MethodType[] altMethods, 173 MethodHandle reflectiveField) 174 throws LambdaConversionException { 175 super(caller, factoryType, interfaceMethodName, interfaceMethodType, 176 implementation, dynamicMethodType, 177 isSerializable, altInterfaces, altMethods, reflectiveField); 178 implMethodClassDesc = implClassDesc(implClass); 179 implMethodName = implInfo.getName(); 180 implMethodDesc = methodDesc(implInfo.getMethodType()); 181 constructorType = factoryType.changeReturnType(Void.TYPE); 182 lambdaClassName = lambdaClassName(targetClass); 183 lambdaClassEntry = pool.classEntry(ReferenceClassDescImpl.ofValidated(ConstantUtils.concat("L", lambdaClassName, ";"))); 184 // If the target class invokes a protected method inherited from a 185 // superclass in a different package, or does 'invokespecial', the 186 // lambda class has no access to the resolved method, or does 187 // 'invokestatic' on a hidden class which cannot be resolved by name. 188 // Instead, we need to pass the live implementation method handle to 189 // the proxy class to invoke directly. (javac prefers to avoid this 190 // situation by generating bridges in the target class) 191 useImplMethodHandle = (Modifier.isProtected(implInfo.getModifiers()) && 192 !VerifyAccess.isSamePackage(targetClass, implInfo.getDeclaringClass())) || 193 implKind == MethodHandleInfo.REF_invokeSpecial || 194 implKind == MethodHandleInfo.REF_invokeStatic && implClass.isHidden(); 195 int parameterCount = factoryType.parameterCount(); 196 if (parameterCount > 0) { 197 argNames = new String[parameterCount]; 198 argDescs = new ClassDesc[parameterCount]; 199 for (int i = 0; i < parameterCount; i++) { 200 argNames[i] = "arg$" + (i + 1); 201 argDescs[i] = classDesc(factoryType.parameterType(i)); 202 } 203 } else { 204 argNames = EMPTY_STRING_ARRAY; 205 argDescs = EMPTY_CLASSDESC_ARRAY; 206 } 207 constructorTypeDesc = MethodTypeDescImpl.ofValidated(CD_void, argDescs); 208 } 209 210 private static String lambdaClassName(Class<?> targetClass) { 211 String name = targetClass.getName(); 212 if (targetClass.isHidden()) { 213 // use the original class name 214 name = name.replace('/', '_'); 215 } 216 return name.replace('.', '/').concat("$$Lambda"); 217 } 218 219 /** 220 * Build the CallSite. Generate a class file which implements the functional 221 * interface, define the class, if there are no parameters create an instance 222 * of the class which the CallSite will return, otherwise, generate handles 223 * which will call the class' constructor. 224 * 225 * @return a CallSite, which, when invoked, will return an instance of the 226 * functional interface 227 * @throws LambdaConversionException If properly formed functional interface 228 * is not found 229 */ 230 @Override 231 CallSite buildCallSite() throws LambdaConversionException { 232 final Class<?> innerClass = spinInnerClass(); 233 if (factoryType.parameterCount() == 0 && disableEagerInitialization) { 234 try { 235 return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD, 236 factoryType.returnType())); 237 } catch (ReflectiveOperationException e) { 238 throw new LambdaConversionException( 239 "Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e); 240 } 241 } else { 242 try { 243 MethodHandle mh = caller.findConstructor(innerClass, constructorType); 244 if (factoryType.parameterCount() == 0) { 245 // In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance 246 Object inst = mh.invokeBasic(); 247 return new ConstantCallSite(MethodHandles.constant(interfaceClass, inst)); 248 } else { 249 return new ConstantCallSite(mh.asType(factoryType)); 250 } 251 } catch (ReflectiveOperationException e) { 252 throw new LambdaConversionException("Exception finding constructor", e); 253 } catch (Throwable e) { 254 throw new LambdaConversionException("Exception instantiating lambda object", e); 255 } 256 } 257 } 258 259 /** 260 * Spins the lambda proxy class. 261 * 262 * This first checks if a lambda proxy class can be loaded from CDS archive. 263 * Otherwise, generate the lambda proxy class. If CDS dumping is enabled, it 264 * registers the lambda proxy class for including into the CDS archive. 265 */ 266 private Class<?> spinInnerClass() throws LambdaConversionException { 267 // CDS does not handle disableEagerInitialization or useImplMethodHandle 268 if (!disableEagerInitialization && !useImplMethodHandle) { 269 if (CDS.isUsingArchive()) { 270 // load from CDS archive if present 271 Class<?> innerClass = LambdaProxyClassArchive.find(targetClass, 272 interfaceMethodName, 273 factoryType, 274 interfaceMethodType, 275 implementation, 276 dynamicMethodType, 277 isSerializable, 278 altInterfaces, 279 altMethods); 280 if (innerClass != null) return innerClass; 281 } 282 283 // include lambda proxy class in CDS archive at dump time 284 if (CDS.isDumpingArchive()) { 285 Class<?> innerClass = generateInnerClass(); 286 LambdaProxyClassArchive.register(targetClass, 287 interfaceMethodName, 288 factoryType, 289 interfaceMethodType, 290 implementation, 291 dynamicMethodType, 292 isSerializable, 293 altInterfaces, 294 altMethods, 295 innerClass); 296 return innerClass; 297 } 298 299 } 300 return generateInnerClass(); 301 } 302 303 /** 304 * Generate a class file which implements the functional 305 * interface, define and return the class. 306 * 307 * @return a Class which implements the functional interface 308 * @throws LambdaConversionException If properly formed functional interface 309 * is not found 310 */ 311 private Class<?> generateInnerClass() throws LambdaConversionException { 312 List<ClassDesc> interfaces; 313 ClassDesc interfaceDesc = classDesc(interfaceClass); 314 boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(interfaceClass); 315 if (altInterfaces.length == 0) { 316 interfaces = List.of(interfaceDesc); 317 } else { 318 // Assure no duplicate interfaces (ClassFormatError) 319 Set<ClassDesc> itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); 320 itfs.add(interfaceDesc); 321 for (Class<?> i : altInterfaces) { 322 itfs.add(classDesc(i)); 323 accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(i); 324 } 325 interfaces = List.copyOf(itfs); 326 } 327 final boolean finalAccidentallySerializable = accidentallySerializable; 328 final byte[] classBytes = ClassFile.of().build(lambdaClassEntry, pool, new Consumer<ClassBuilder>() { 329 @Override 330 public void accept(ClassBuilder clb) { 331 clb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) 332 .withInterfaceSymbols(interfaces); 333 // Generate final fields to be filled in by constructor 334 for (int i = 0; i < argDescs.length; i++) { 335 clb.withField(argNames[i], argDescs[i], ACC_PRIVATE | ACC_FINAL); 336 } 337 338 // if quotable, generate the field that will hold the value of quoted 339 if (quotableOpField != null) { 340 clb.withField(quotedInstanceFieldName, CodeReflectionSupport.CD_Quoted, ACC_PRIVATE + ACC_FINAL); 341 } 342 343 generateConstructor(clb); 344 345 if (factoryType.parameterCount() == 0 && disableEagerInitialization) { 346 generateClassInitializer(clb); 347 } 348 349 // Forward the SAM method 350 clb.withMethodBody(interfaceMethodName, 351 methodDesc(interfaceMethodType), 352 ACC_PUBLIC, 353 forwardingMethod(interfaceMethodType)); 354 355 // Forward the bridges 356 if (altMethods != null) { 357 for (MethodType mt : altMethods) { 358 clb.withMethodBody(interfaceMethodName, 359 methodDesc(mt), 360 ACC_PUBLIC | ACC_BRIDGE, 361 forwardingMethod(mt)); 362 } 363 } 364 365 if (isSerializable) 366 generateSerializationFriendlyMethods(clb); 367 else if (finalAccidentallySerializable) 368 generateSerializationHostileMethods(clb); 369 370 if (quotableOpField != null) { 371 generateQuotableMethod(clb); 372 } 373 } 374 }); 375 376 // Define the generated class in this VM. 377 378 try { 379 // this class is linked at the indy callsite; so define a hidden nestmate 380 List<?> classdata; 381 if (useImplMethodHandle || quotableOpField != null) { 382 classdata = quotableOpField == null ? 383 List.of(implementation) : 384 List.of(implementation, quotableOpField, CodeReflectionSupport.HANDLE_MAKE_QUOTED); 385 } else { 386 classdata = null; 387 } 388 return caller.makeHiddenClassDefiner(lambdaClassName, classBytes, lambdaProxyClassFileDumper, NESTMATE_CLASS | STRONG_LOADER_LINK) 389 .defineClass(!disableEagerInitialization, classdata); 390 391 } catch (Throwable t) { 392 throw new InternalError(t); 393 } 394 } 395 396 /** 397 * Generate a static field and a static initializer that sets this field to an instance of the lambda 398 */ 399 private void generateClassInitializer(ClassBuilder clb) { 400 ClassDesc lambdaTypeDescriptor = classDesc(factoryType.returnType()); 401 402 // Generate the static final field that holds the lambda singleton 403 clb.withField(LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); 404 405 // Instantiate the lambda and store it to the static final field 406 clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new Consumer<>() { 407 @Override 408 public void accept(CodeBuilder cob) { 409 assert factoryType.parameterCount() == 0; 410 cob.new_(lambdaClassEntry) 411 .dup() 412 .invokespecial(pool.methodRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(INIT_NAME, constructorTypeDesc))) 413 .putstatic(pool.fieldRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor))) 414 .return_(); 415 } 416 }); 417 } 418 419 /** 420 * Generate the constructor for the class 421 */ 422 private void generateConstructor(ClassBuilder clb) { 423 // Generate constructor 424 clb.withMethodBody(INIT_NAME, constructorTypeDesc, ACC_PRIVATE, 425 new Consumer<>() { 426 @Override 427 public void accept(CodeBuilder cob) { 428 cob.aload(0) 429 .invokespecial(CD_Object, INIT_NAME, MTD_void); 430 int parameterCount = factoryType.parameterCount(); 431 for (int i = 0; i < parameterCount; i++) { 432 cob.aload(0); 433 Class<?> argType = factoryType.parameterType(i); 434 cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i)); 435 cob.putfield(pool.fieldRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(argNames[i], argDescs[i]))); 436 } 437 if (quotableOpField != null) { 438 generateQuotedFieldInitializer(cob); 439 } 440 cob.return_(); 441 } 442 }); 443 } 444 445 private void generateQuotedFieldInitializer(CodeBuilder cob) { 446 ConstantPoolBuilder cp = cob.constantPool(); 447 MethodHandleEntry bsmDataAt = cp.methodHandleEntry(BSM_CLASS_DATA_AT); 448 NameAndTypeEntry natMH = cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle); 449 // push the receiver on the stack for operand of put field instruction 450 cob.aload(0) 451 .ldc(cp.constantDynamicEntry(cp.bsmEntry(bsmDataAt, List.of(cp.intEntry(2))), natMH)) 452 // load op string from field 453 .ldc(cp.constantDynamicEntry(cp.bsmEntry(bsmDataAt, List.of(cp.intEntry(1))), natMH)); 454 MethodType mtype = quotableOpFieldInfo.getMethodType(); 455 if (quotableOpFieldInfo.getReferenceKind() != MethodHandleInfo.REF_getStatic) { 456 mtype = mtype.insertParameterTypes(0, implClass); 457 } 458 cob.invokevirtual(CD_MethodHandle, "invokeExact", mtype.describeConstable().get()); 459 460 // load captured args in array 461 462 int capturedArity = factoryType.parameterCount(); 463 cob.loadConstant(capturedArity) 464 .anewarray(CD_Object); 465 // initialize quoted captures 466 for (int i = 0; i < capturedArity; i++) { 467 cob.dup() 468 .loadConstant(i) 469 .aload(0) 470 .getfield(lambdaClassEntry.asSymbol(), argNames[i], argDescs[i]); 471 TypeConvertingMethodAdapter.boxIfTypePrimitive(cob, TypeKind.from(argDescs[i])); 472 cob.aastore(); 473 } 474 475 // now create a Quoted from String and captured args Object[] 476 477 cob.invokevirtual(CD_MethodHandle, "invokeExact", methodDesc(CodeReflectionSupport.HANDLE_MAKE_QUOTED.type())) 478 .putfield(lambdaClassEntry.asSymbol(), quotedInstanceFieldName, CodeReflectionSupport.CD_Quoted); 479 } 480 481 static class CodeReflectionSupport { 482 static final Class<?> QUOTED_CLASS; 483 static final Class<?> QUOTABLE_CLASS; 484 static final MethodHandle HANDLE_MAKE_QUOTED; 485 486 static { 487 try { 488 // we know jdk.incubator code is there, no need to check 489 ModuleLayer parent = ModuleLayer.boot(); 490 Configuration cf = parent.configuration() 491 .resolve(ModuleFinder.of(), ModuleFinder.ofSystem(), Set.of("jdk.incubator.code")); 492 ClassLoader scl = ClassLoader.getSystemClassLoader(); 493 ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl); 494 QUOTED_CLASS = layer.findLoader("jdk.incubator.code") 495 .loadClass("jdk.incubator.code.Quoted"); 496 QUOTABLE_CLASS = layer.findLoader("jdk.incubator.code") 497 .loadClass("jdk.incubator.code.Quotable"); 498 Class<?> quotedHelper = layer.findLoader("jdk.incubator.code") 499 .loadClass("jdk.incubator.code.internal.QuotedHelper"); 500 MethodHandle makeQuoted = Lookup.IMPL_LOOKUP.findStatic(quotedHelper, "makeQuoted", 501 MethodType.methodType(QUOTED_CLASS, MethodHandles.Lookup.class, String.class, Object[].class)); 502 HANDLE_MAKE_QUOTED = makeQuoted.bindTo(Lookup.IMPL_LOOKUP); 503 } catch (Throwable ex) { 504 throw new ExceptionInInitializerError(ex); 505 } 506 } 507 508 static final ClassDesc CD_Quoted = QUOTED_CLASS.describeConstable().get(); 509 static final MethodTypeDesc MTD_Quoted = MethodTypeDescImpl.ofValidated(CD_Quoted); 510 } 511 512 private static class SerializationSupport { 513 // Serialization support 514 private static final ClassDesc CD_SerializedLambda = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/SerializedLambda;"); 515 private static final ClassDesc CD_ObjectOutputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectOutputStream;"); 516 private static final ClassDesc CD_ObjectInputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectInputStream;"); 517 private static final MethodTypeDesc MTD_Object = MethodTypeDescImpl.ofValidated(CD_Object); 518 private static final MethodTypeDesc MTD_void_ObjectOutputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectOutputStream); 519 private static final MethodTypeDesc MTD_void_ObjectInputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectInputStream); 520 521 private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; 522 private static final String NAME_METHOD_READ_OBJECT = "readObject"; 523 private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; 524 525 static final ClassDesc CD_NotSerializableException = ReferenceClassDescImpl.ofValidated("Ljava/io/NotSerializableException;"); 526 static final MethodTypeDesc MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION = MethodTypeDescImpl.ofValidated(CD_void, CD_String); 527 static final MethodTypeDesc MTD_CTOR_SERIALIZED_LAMBDA = MethodTypeDescImpl.ofValidated(CD_void, 528 CD_Class, CD_String, CD_String, CD_String, CD_int, CD_String, CD_String, CD_String, CD_String, ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;")); 529 530 } 531 532 /** 533 * Generate a writeReplace method that supports serialization 534 */ 535 private void generateSerializationFriendlyMethods(ClassBuilder clb) { 536 clb.withMethodBody(SerializationSupport.NAME_METHOD_WRITE_REPLACE, SerializationSupport.MTD_Object, ACC_PRIVATE | ACC_FINAL, 537 new Consumer<>() { 538 @Override 539 public void accept(CodeBuilder cob) { 540 cob.new_(SerializationSupport.CD_SerializedLambda) 541 .dup() 542 .ldc(classDesc(targetClass)) 543 .ldc(factoryType.returnType().getName().replace('.', '/')) 544 .ldc(interfaceMethodName) 545 .ldc(interfaceMethodType.toMethodDescriptorString()) 546 .ldc(implInfo.getReferenceKind()) 547 .ldc(implInfo.getDeclaringClass().getName().replace('.', '/')) 548 .ldc(implInfo.getName()) 549 .ldc(implInfo.getMethodType().toMethodDescriptorString()) 550 .ldc(dynamicMethodType.toMethodDescriptorString()) 551 .loadConstant(argDescs.length) 552 .anewarray(CD_Object); 553 for (int i = 0; i < argDescs.length; i++) { 554 cob.dup() 555 .loadConstant(i) 556 .aload(0) 557 .getfield(pool.fieldRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(argNames[i], argDescs[i]))); 558 TypeConvertingMethodAdapter.boxIfTypePrimitive(cob, TypeKind.from(argDescs[i])); 559 cob.aastore(); 560 } 561 cob.invokespecial(SerializationSupport.CD_SerializedLambda, INIT_NAME, 562 SerializationSupport.MTD_CTOR_SERIALIZED_LAMBDA) 563 .areturn(); 564 } 565 }); 566 } 567 568 /** 569 * Generate a writeReplace method that supports serialization 570 */ 571 private void generateQuotableMethod(ClassBuilder clb) { 572 clb.withMethod(NAME_METHOD_QUOTED, CodeReflectionSupport.MTD_Quoted, ACC_PUBLIC + ACC_FINAL, new MethodBody(new Consumer<CodeBuilder>() { 573 @Override 574 public void accept(CodeBuilder cob) { 575 cob.aload(0) 576 .getfield(lambdaClassEntry.asSymbol(), quotedInstanceFieldName, CodeReflectionSupport.CD_Quoted) 577 .areturn(); 578 } 579 })); 580 } 581 582 /** 583 * Generate a readObject/writeObject method that is hostile to serialization 584 */ 585 private void generateSerializationHostileMethods(ClassBuilder clb) { 586 var hostileMethod = new Consumer<MethodBuilder>() { 587 @Override 588 public void accept(MethodBuilder mb) { 589 ConstantPoolBuilder cp = mb.constantPool(); 590 ClassEntry nseCE = cp.classEntry(SerializationSupport.CD_NotSerializableException); 591 mb.with(ExceptionsAttribute.of(nseCE)) 592 .withCode(new Consumer<CodeBuilder>() { 593 @Override 594 public void accept(CodeBuilder cob) { 595 cob.new_(nseCE) 596 .dup() 597 .ldc("Non-serializable lambda") 598 .invokespecial(cp.methodRefEntry(nseCE, cp.nameAndTypeEntry(INIT_NAME, 599 SerializationSupport.MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION))) 600 .athrow(); 601 } 602 }); 603 } 604 }; 605 clb.withMethod(SerializationSupport.NAME_METHOD_WRITE_OBJECT, SerializationSupport.MTD_void_ObjectOutputStream, 606 ACC_PRIVATE + ACC_FINAL, hostileMethod); 607 clb.withMethod(SerializationSupport.NAME_METHOD_READ_OBJECT, SerializationSupport.MTD_void_ObjectInputStream, 608 ACC_PRIVATE + ACC_FINAL, hostileMethod); 609 } 610 611 /** 612 * This method generates a method body which calls the lambda implementation 613 * method, converting arguments, as needed. 614 */ 615 Consumer<CodeBuilder> forwardingMethod(MethodType methodType) { 616 return new Consumer<>() { 617 @Override 618 public void accept(CodeBuilder cob) { 619 if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { 620 cob.new_(implMethodClassDesc) 621 .dup(); 622 } 623 if (useImplMethodHandle) { 624 ConstantPoolBuilder cp = cob.constantPool(); 625 cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA_AT), List.of(cp.intEntry(0))), 626 cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle))); 627 } 628 for (int i = 0; i < argNames.length ; i++) { 629 cob.aload(0) 630 .getfield(pool.fieldRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(argNames[i], argDescs[i]))); 631 } 632 633 convertArgumentTypes(cob, methodType); 634 635 if (useImplMethodHandle) { 636 MethodType mtype = implInfo.getMethodType(); 637 if (implKind != MethodHandleInfo.REF_invokeStatic) { 638 mtype = mtype.insertParameterTypes(0, implClass); 639 } 640 cob.invokevirtual(CD_MethodHandle, "invokeExact", methodDesc(mtype)); 641 } else { 642 // Invoke the method we want to forward to 643 cob.invoke(invocationOpcode(), implMethodClassDesc, implMethodName, implMethodDesc, implClass.isInterface()); 644 } 645 // Convert the return value (if any) and return it 646 // Note: if adapting from non-void to void, the 'return' 647 // instruction will pop the unneeded result 648 Class<?> implReturnClass = implMethodType.returnType(); 649 Class<?> samReturnClass = methodType.returnType(); 650 TypeConvertingMethodAdapter.convertType(cob, implReturnClass, samReturnClass, samReturnClass); 651 cob.return_(TypeKind.from(samReturnClass)); 652 } 653 }; 654 } 655 656 private void convertArgumentTypes(CodeBuilder cob, MethodType samType) { 657 int samParametersLength = samType.parameterCount(); 658 int captureArity = factoryType.parameterCount(); 659 for (int i = 0; i < samParametersLength; i++) { 660 Class<?> argType = samType.parameterType(i); 661 cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i)); 662 TypeConvertingMethodAdapter.convertType(cob, argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i)); 663 } 664 } 665 666 private Opcode invocationOpcode() throws InternalError { 667 return switch (implKind) { 668 case MethodHandleInfo.REF_invokeStatic -> Opcode.INVOKESTATIC; 669 case MethodHandleInfo.REF_newInvokeSpecial -> Opcode.INVOKESPECIAL; 670 case MethodHandleInfo.REF_invokeVirtual -> Opcode.INVOKEVIRTUAL; 671 case MethodHandleInfo.REF_invokeInterface -> Opcode.INVOKEINTERFACE; 672 case MethodHandleInfo.REF_invokeSpecial -> Opcode.INVOKESPECIAL; 673 default -> throw new InternalError("Unexpected invocation kind: " + implKind); 674 }; 675 } 676 677 static ClassDesc implClassDesc(Class<?> cls) { 678 return cls.isHidden() ? null : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); 679 } 680 681 static ClassDesc classDesc(Class<?> cls) { 682 return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() 683 : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); 684 } 685 686 static MethodTypeDesc methodDesc(MethodType mt) { 687 var params = new ClassDesc[mt.parameterCount()]; 688 for (int i = 0; i < params.length; i++) { 689 params[i] = classDesc(mt.parameterType(i)); 690 } 691 return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params); 692 } 693 }