1 /* 2 * Copyright (c) 1999, 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.reflect; 27 28 import java.io.IOException; 29 import java.lang.classfile.*; 30 import java.lang.classfile.attribute.ExceptionsAttribute; 31 import java.lang.classfile.constantpool.*; 32 import java.lang.constant.ClassDesc; 33 import java.lang.constant.MethodTypeDesc; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.util.ArrayList; 37 import java.util.LinkedHashMap; 38 import java.util.List; 39 import java.util.ListIterator; 40 import java.util.Map; 41 import java.util.Objects; 42 import jdk.internal.constant.ConstantUtils; 43 import jdk.internal.constant.MethodTypeDescImpl; 44 import jdk.internal.constant.ReferenceClassDescImpl; 45 import sun.security.action.GetBooleanAction; 46 47 import static java.lang.classfile.ClassFile.*; 48 import java.lang.classfile.attribute.StackMapFrameInfo; 49 import java.lang.classfile.attribute.StackMapTableAttribute; 50 import java.lang.constant.ConstantDescs; 51 import static java.lang.constant.ConstantDescs.*; 52 import static jdk.internal.constant.ConstantUtils.*; 53 54 import java.lang.constant.DirectMethodHandleDesc; 55 import java.lang.constant.DynamicConstantDesc; 56 57 /** 58 * ProxyGenerator contains the code to generate a dynamic proxy class 59 * for the java.lang.reflect.Proxy API. 60 * <p> 61 * The external interface to ProxyGenerator is the static 62 * "generateProxyClass" method. 63 */ 64 final class ProxyGenerator { 65 66 private static final ClassFile CF_CONTEXT = 67 ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS); 68 69 private static final ClassDesc 70 CD_Class_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Class;"), 71 CD_IllegalAccessException = ReferenceClassDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"), 72 CD_InvocationHandler = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"), 73 CD_Method = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Method;"), 74 CD_NoSuchMethodError = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodError;"), 75 CD_NoSuchMethodException = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodException;"), 76 CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"), 77 CD_Proxy = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Proxy;"), 78 CD_UndeclaredThrowableException = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/UndeclaredThrowableException;"); 79 80 private static final MethodTypeDesc 81 MTD_boolean = MethodTypeDescImpl.ofValidated(CD_boolean), 82 MTD_void_InvocationHandler = MethodTypeDescImpl.ofValidated(CD_void, CD_InvocationHandler), 83 MTD_void_String = MethodTypeDescImpl.ofValidated(CD_void, CD_String), 84 MTD_void_Throwable = MethodTypeDescImpl.ofValidated(CD_void, CD_Throwable), 85 MTD_Class = MethodTypeDescImpl.ofValidated(CD_Class), 86 MTD_Class_array = MethodTypeDescImpl.ofValidated(CD_Class_array), 87 MTD_Method_String_Class_array = MethodTypeDescImpl.ofValidated(CD_Method, ConstantDescs.CD_String, CD_Class_array), 88 MTD_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup), 89 MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup), 90 MTD_Object_Object_Method_ObjectArray = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Method, CD_Object_array), 91 MTD_String = MethodTypeDescImpl.ofValidated(CD_String); 92 93 private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; 94 95 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; 96 97 /** 98 * name of field for storing a proxy instance's invocation handler 99 */ 100 private static final String NAME_HANDLER_FIELD = "h"; 101 102 /** 103 * debugging flag for saving generated class files 104 */ 105 @SuppressWarnings("removal") 106 private static final boolean SAVE_GENERATED_FILES = 107 java.security.AccessController.doPrivileged( 108 new GetBooleanAction( 109 "jdk.proxy.ProxyGenerator.saveGeneratedFiles")); 110 111 /* Preloaded ProxyMethod objects for methods in java.lang.Object */ 112 private static final ProxyMethod HASH_CODE_METHOD; 113 private static final ProxyMethod EQUALS_METHOD; 114 private static final ProxyMethod TO_STRING_METHOD; 115 116 static { 117 try { 118 HASH_CODE_METHOD = new ProxyMethod(Object.class.getMethod("hashCode")); 119 EQUALS_METHOD = new ProxyMethod(Object.class.getMethod("equals", Object.class)); 120 TO_STRING_METHOD = new ProxyMethod(Object.class.getMethod("toString")); 121 } catch (NoSuchMethodException e) { 122 throw new NoSuchMethodError(e.getMessage()); 123 } 124 } 125 126 private final ConstantPoolBuilder cp; 127 private final List<StackMapFrameInfo.VerificationTypeInfo> throwableStack; 128 private final NameAndTypeEntry exInit; 129 private final ClassEntry object, proxy, ute; 130 private final FieldRefEntry handlerField; 131 private final InterfaceMethodRefEntry invoke; 132 private final MethodRefEntry uteInit; 133 private final DirectMethodHandleDesc bsm; 134 135 136 /** 137 * Name of proxy class 138 */ 139 private final ClassEntry classEntry; 140 141 /** 142 * Proxy interfaces 143 */ 144 private final List<Class<?>> interfaces; 145 146 /** 147 * Proxy class access flags 148 */ 149 private final int accessFlags; 150 151 /** 152 * Maps method signature string to list of ProxyMethod objects for 153 * proxy methods with that signature. 154 * Kept in insertion order to make it easier to compare old and new. 155 */ 156 private final Map<String, List<ProxyMethod>> proxyMethods = new LinkedHashMap<>(); 157 158 /** 159 * Construct a ProxyGenerator to generate a proxy class with the 160 * specified name and for the given interfaces. 161 * <p> 162 * A ProxyGenerator object contains the state for the ongoing 163 * generation of a particular proxy class. 164 */ 165 private ProxyGenerator(String className, List<Class<?>> interfaces, 166 int accessFlags) { 167 this.cp = ConstantPoolBuilder.of(); 168 this.classEntry = cp.classEntry(ConstantUtils.binaryNameToDesc(className)); 169 this.interfaces = interfaces; 170 this.accessFlags = accessFlags; 171 this.throwableStack = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(cp.classEntry(CD_Throwable))); 172 this.exInit = cp.nameAndTypeEntry(INIT_NAME, MTD_void_String); 173 this.object = cp.classEntry(CD_Object); 174 this.proxy = cp.classEntry(CD_Proxy); 175 this.handlerField = cp.fieldRefEntry(proxy, cp.nameAndTypeEntry(NAME_HANDLER_FIELD, CD_InvocationHandler)); 176 this.invoke = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); 177 this.ute = cp.classEntry(CD_UndeclaredThrowableException); 178 this.uteInit = cp.methodRefEntry(ute, cp.nameAndTypeEntry(INIT_NAME, MTD_void_Throwable)); 179 this.bsm = ConstantDescs.ofConstantBootstrap(classEntry.asSymbol(), "$getMethod", CD_Method, CD_Class, CD_String, CD_MethodType); 180 } 181 182 /** 183 * Generate a proxy class given a name and a list of proxy interfaces. 184 * 185 * @param name the class name of the proxy class 186 * @param interfaces proxy interfaces 187 * @param accessFlags access flags of the proxy class 188 */ 189 @SuppressWarnings("removal") 190 static byte[] generateProxyClass(ClassLoader loader, 191 final String name, 192 List<Class<?>> interfaces, 193 int accessFlags) { 194 Objects.requireNonNull(interfaces); 195 ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags); 196 final byte[] classFile = gen.generateClassFile(); 197 198 if (SAVE_GENERATED_FILES) { 199 java.security.AccessController.doPrivileged( 200 new java.security.PrivilegedAction<Void>() { 201 public Void run() { 202 try { 203 int i = name.lastIndexOf('.'); 204 Path path; 205 if (i > 0) { 206 Path dir = Path.of(name.substring(0, i).replace('.', '/')); 207 Files.createDirectories(dir); 208 path = dir.resolve(name.substring(i + 1) + ".class"); 209 } else { 210 path = Path.of(name + ".class"); 211 } 212 Files.write(path, classFile); 213 return null; 214 } catch (IOException e) { 215 throw new InternalError( 216 "I/O exception saving generated file: " + e); 217 } 218 } 219 }); 220 } 221 222 return classFile; 223 } 224 225 /** 226 * {@return the entries of the given type} 227 * @param types the {@code Class} objects, not primitive types nor array types 228 */ 229 private static List<ClassEntry> toClassEntries(ConstantPoolBuilder cp, List<Class<?>> types) { 230 var ces = new ArrayList<ClassEntry>(types.size()); 231 for (var t : types) 232 ces.add(cp.classEntry(ConstantUtils.binaryNameToDesc(t.getName()))); 233 return ces; 234 } 235 236 /** 237 * For a given set of proxy methods with the same signature, check 238 * that their return types are compatible according to the Proxy 239 * specification. 240 * 241 * Specifically, if there is more than one such method, then all 242 * of the return types must be reference types, and there must be 243 * one return type that is assignable to each of the rest of them. 244 */ 245 private static void checkReturnTypes(List<ProxyMethod> methods) { 246 /* 247 * If there is only one method with a given signature, there 248 * cannot be a conflict. This is the only case in which a 249 * primitive (or void) return type is allowed. 250 */ 251 if (methods.size() < 2) { 252 return; 253 } 254 255 /* 256 * List of return types that are not yet known to be 257 * assignable from ("covered" by) any of the others. 258 */ 259 List<Class<?>> uncoveredReturnTypes = new ArrayList<>(1); 260 261 nextNewReturnType: 262 for (ProxyMethod pm : methods) { 263 Class<?> newReturnType = pm.returnType; 264 if (newReturnType.isPrimitive()) { 265 throw new IllegalArgumentException( 266 "methods with same signature " + 267 pm.shortSignature + 268 " but incompatible return types: " + 269 newReturnType.getName() + " and others"); 270 } 271 boolean added = false; 272 273 /* 274 * Compare the new return type to the existing uncovered 275 * return types. 276 */ 277 ListIterator<Class<?>> liter = uncoveredReturnTypes.listIterator(); 278 while (liter.hasNext()) { 279 Class<?> uncoveredReturnType = liter.next(); 280 281 /* 282 * If an existing uncovered return type is assignable 283 * to this new one, then we can forget the new one. 284 */ 285 if (newReturnType.isAssignableFrom(uncoveredReturnType)) { 286 assert !added; 287 continue nextNewReturnType; 288 } 289 290 /* 291 * If the new return type is assignable to an existing 292 * uncovered one, then should replace the existing one 293 * with the new one (or just forget the existing one, 294 * if the new one has already be put in the list). 295 */ 296 if (uncoveredReturnType.isAssignableFrom(newReturnType)) { 297 // (we can assume that each return type is unique) 298 if (!added) { 299 liter.set(newReturnType); 300 added = true; 301 } else { 302 liter.remove(); 303 } 304 } 305 } 306 307 /* 308 * If we got through the list of existing uncovered return 309 * types without an assignability relationship, then add 310 * the new return type to the list of uncovered ones. 311 */ 312 if (!added) { 313 uncoveredReturnTypes.add(newReturnType); 314 } 315 } 316 317 /* 318 * We shouldn't end up with more than one return type that is 319 * not assignable from any of the others. 320 */ 321 if (uncoveredReturnTypes.size() > 1) { 322 ProxyMethod pm = methods.getFirst(); 323 throw new IllegalArgumentException( 324 "methods with same signature " + 325 pm.shortSignature + 326 " but incompatible return types: " + uncoveredReturnTypes); 327 } 328 } 329 330 /** 331 * Given the exceptions declared in the throws clause of a proxy method, 332 * compute the exceptions that need to be caught from the invocation 333 * handler's invoke method and rethrown intact in the method's 334 * implementation before catching other Throwables and wrapping them 335 * in UndeclaredThrowableExceptions. 336 * 337 * The exceptions to be caught are returned in a List object. Each 338 * exception in the returned list is guaranteed to not be a subclass of 339 * any of the other exceptions in the list, so the catch blocks for 340 * these exceptions may be generated in any order relative to each other. 341 * 342 * Error and RuntimeException are each always contained by the returned 343 * list (if none of their superclasses are contained), since those 344 * unchecked exceptions should always be rethrown intact, and thus their 345 * subclasses will never appear in the returned list. 346 * 347 * The returned List will be empty if java.lang.Throwable is in the 348 * given list of declared exceptions, indicating that no exceptions 349 * need to be caught. 350 */ 351 private static List<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) { 352 List<Class<?>> uniqueList = new ArrayList<>(); 353 // unique exceptions to catch 354 355 uniqueList.add(Error.class); // always catch/rethrow these 356 uniqueList.add(RuntimeException.class); 357 358 nextException: 359 for (Class<?> ex : exceptions) { 360 if (ex.isAssignableFrom(Throwable.class)) { 361 /* 362 * If Throwable is declared to be thrown by the proxy method, 363 * then no catch blocks are necessary, because the invoke 364 * can, at most, throw Throwable anyway. 365 */ 366 uniqueList.clear(); 367 break; 368 } else if (!Throwable.class.isAssignableFrom(ex)) { 369 /* 370 * Ignore types that cannot be thrown by the invoke method. 371 */ 372 continue; 373 } 374 /* 375 * Compare this exception against the current list of 376 * exceptions that need to be caught: 377 */ 378 for (int j = 0; j < uniqueList.size(); ) { 379 Class<?> ex2 = uniqueList.get(j); 380 if (ex2.isAssignableFrom(ex)) { 381 /* 382 * if a superclass of this exception is already on 383 * the list to catch, then ignore this one and continue; 384 */ 385 continue nextException; 386 } else if (ex.isAssignableFrom(ex2)) { 387 /* 388 * if a subclass of this exception is on the list 389 * to catch, then remove it; 390 */ 391 uniqueList.remove(j); 392 } else { 393 j++; // else continue comparing. 394 } 395 } 396 // This exception is unique (so far): add it to the list to catch. 397 uniqueList.add(ex); 398 } 399 return uniqueList; 400 } 401 402 /** 403 * Add to the given list all of the types in the "from" array that 404 * are not already contained in the list and are assignable to at 405 * least one of the types in the "with" array. 406 * <p> 407 * This method is useful for computing the greatest common set of 408 * declared exceptions from duplicate methods inherited from 409 * different interfaces. 410 */ 411 private static void collectCompatibleTypes(Class<?>[] from, 412 Class<?>[] with, 413 List<Class<?>> list) { 414 for (Class<?> fc : from) { 415 if (!list.contains(fc)) { 416 for (Class<?> wc : with) { 417 if (wc.isAssignableFrom(fc)) { 418 list.add(fc); 419 break; 420 } 421 } 422 } 423 } 424 } 425 426 /** 427 * Generate a class file for the proxy class. This method drives the 428 * class file generation process. 429 */ 430 private byte[] generateClassFile() { 431 /* 432 * Add proxy methods for the hashCode, equals, 433 * and toString methods of java.lang.Object. This is done before 434 * the methods from the proxy interfaces so that the methods from 435 * java.lang.Object take precedence over duplicate methods in the 436 * proxy interfaces. 437 */ 438 addProxyMethod(HASH_CODE_METHOD); 439 addProxyMethod(EQUALS_METHOD); 440 addProxyMethod(TO_STRING_METHOD); 441 442 /* 443 * Accumulate all of the methods from the proxy interfaces. 444 */ 445 for (Class<?> intf : interfaces) { 446 for (Method m : intf.getMethods()) { 447 if (!Modifier.isStatic(m.getModifiers())) { 448 addProxyMethod(m, intf); 449 } 450 } 451 } 452 453 /* 454 * For each set of proxy methods with the same signature, 455 * verify that the methods' return types are compatible. 456 */ 457 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 458 checkReturnTypes(sigmethods); 459 } 460 461 return CF_CONTEXT.build(classEntry, cp, clb -> { 462 clb.withSuperclass(proxy); 463 clb.withFlags(accessFlags); 464 clb.withInterfaces(toClassEntries(cp, interfaces)); 465 generateConstructor(clb); 466 467 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 468 for (ProxyMethod pm : sigmethods) { 469 // Generate code for proxy method 470 pm.generateMethod(this, clb); 471 } 472 } 473 474 generateBootstrapMethod(clb); 475 generateLookupAccessor(clb); 476 }); 477 } 478 479 /** 480 * Add another method to be proxied, either by creating a new 481 * ProxyMethod object or augmenting an old one for a duplicate 482 * method. 483 * 484 * "fromClass" indicates the proxy interface that the method was 485 * found through, which may be different from (a subinterface of) 486 * the method's "declaring class". Note that the first Method 487 * object passed for a given name and descriptor identifies the 488 * Method object (and thus the declaring class) that will be 489 * passed to the invocation handler's "invoke" method for a given 490 * set of duplicate methods. 491 */ 492 private void addProxyMethod(Method m, Class<?> fromClass) { 493 Class<?> returnType = m.getReturnType(); 494 Class<?>[] exceptionTypes = m.getSharedExceptionTypes(); 495 496 String sig = m.toShortSignature(); 497 List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, 498 _ -> new ArrayList<>(3)); 499 for (ProxyMethod pm : sigmethods) { 500 if (returnType == pm.returnType) { 501 /* 502 * Found a match: reduce exception types to the 503 * greatest set of exceptions that can be thrown 504 * compatibly with the throws clauses of both 505 * overridden methods. 506 */ 507 List<Class<?>> legalExceptions = new ArrayList<>(); 508 collectCompatibleTypes( 509 exceptionTypes, pm.exceptionTypes, legalExceptions); 510 collectCompatibleTypes( 511 pm.exceptionTypes, exceptionTypes, legalExceptions); 512 pm.exceptionTypes = legalExceptions.toArray(EMPTY_CLASS_ARRAY); 513 return; 514 } 515 } 516 sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, 517 exceptionTypes, fromClass)); 518 } 519 520 /** 521 * Add an existing ProxyMethod (hashcode, equals, toString). 522 * 523 * @param pm an existing ProxyMethod 524 */ 525 private void addProxyMethod(ProxyMethod pm) { 526 String sig = pm.shortSignature; 527 List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, 528 _ -> new ArrayList<>(3)); 529 sigmethods.add(pm); 530 } 531 532 /** 533 * Generate the constructor method for the proxy class. 534 */ 535 private void generateConstructor(ClassBuilder clb) { 536 clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob 537 .aload(0) 538 .aload(1) 539 .invokespecial(cp.methodRefEntry(proxy, cp.nameAndTypeEntry(INIT_NAME, MTD_void_InvocationHandler))) 540 .return_()); 541 } 542 543 /** 544 * Generate CONDY bootstrap method for the proxy class to retrieve {@link Method} instances. 545 */ 546 private void generateBootstrapMethod(ClassBuilder clb) { 547 clb.withMethodBody(bsm.methodName(), bsm.invocationType(), ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC, cob -> { 548 cob.aload(3) //interface Class 549 .aload(4) //interface method name String 550 .aload(5) //interface MethodType 551 .invokevirtual(CD_MethodType, "parameterArray", MTD_Class_array) 552 .invokevirtual(ConstantDescs.CD_Class, "getMethod", MTD_Method_String_Class_array) 553 .areturn(); 554 Label failLabel = cob.newBoundLabel(); 555 ClassEntry nsme = cp.classEntry(CD_NoSuchMethodError); 556 cob.exceptionCatch(cob.startLabel(), failLabel, failLabel, CD_NoSuchMethodException) 557 .new_(nsme) 558 .dup_x1() 559 .swap() 560 .invokevirtual(cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String)) 561 .invokespecial(cp.methodRefEntry(nsme, exInit)) 562 .athrow() 563 .with(StackMapTableAttribute.of(List.of( 564 StackMapFrameInfo.of(failLabel, List.of(), throwableStack)))); 565 }); 566 } 567 568 /** 569 * Generate the static lookup accessor method that returns the Lookup 570 * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; 571 * otherwise, IllegalAccessException is thrown 572 */ 573 private void generateLookupAccessor(ClassBuilder clb) { 574 clb.withMethod(NAME_LOOKUP_ACCESSOR, 575 MTD_MethodHandles$Lookup_MethodHandles$Lookup, 576 ACC_PRIVATE | ACC_STATIC, 577 mb -> mb.with(ExceptionsAttribute.of(List.of(mb.constantPool().classEntry(CD_IllegalAccessException)))) 578 .withCode(cob -> { 579 Label failLabel = cob.newLabel(); 580 ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup); 581 ClassEntry iae = cp.classEntry(CD_IllegalAccessException); 582 cob.aload(cob.parameterSlot(0)) 583 .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class))) 584 .ldc(proxy) 585 .if_acmpne(failLabel) 586 .aload(cob.parameterSlot(0)) 587 .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean))) 588 .ifeq(failLabel) 589 .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup) 590 .areturn() 591 .labelBinding(failLabel) 592 .new_(iae) 593 .dup() 594 .aload(cob.parameterSlot(0)) 595 .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String))) 596 .invokespecial(cp.methodRefEntry(iae, exInit)) 597 .athrow() 598 .with(StackMapTableAttribute.of(List.of( 599 StackMapFrameInfo.of(failLabel, 600 List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(mhl)), 601 List.of())))); 602 })); 603 } 604 605 /** 606 * A ProxyMethod object represents a proxy method in the proxy class 607 * being generated: a method whose implementation will encode and 608 * dispatch invocations to the proxy instance's invocation handler. 609 */ 610 private static class ProxyMethod { 611 612 private final Method method; 613 private final String shortSignature; 614 private final Class<?> fromClass; 615 private final Class<?>[] parameterTypes; 616 private final Class<?> returnType; 617 private Class<?>[] exceptionTypes; 618 619 private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes, 620 Class<?> returnType, Class<?>[] exceptionTypes, 621 Class<?> fromClass) { 622 this.method = method; 623 this.shortSignature = sig; 624 this.parameterTypes = parameterTypes; 625 this.returnType = returnType; 626 this.exceptionTypes = exceptionTypes; 627 this.fromClass = fromClass; 628 } 629 630 /** 631 * Create a new specific ProxyMethod with a specific field name 632 * 633 * @param method The method for which to create a proxy 634 */ 635 private ProxyMethod(Method method) { 636 this(method, method.toShortSignature(), 637 method.getSharedParameterTypes(), method.getReturnType(), 638 method.getSharedExceptionTypes(), method.getDeclaringClass()); 639 } 640 641 /** 642 * Generate this method, including the code and exception table entry. 643 */ 644 private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { 645 var cp = pg.cp; 646 var desc = methodTypeDesc(returnType, parameterTypes); 647 int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL 648 : ACC_PUBLIC | ACC_FINAL; 649 var catchList = computeUniqueCatchList(exceptionTypes); 650 clb.withMethod(method.getName(), desc, accessFlags, mb -> 651 mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes)))) 652 .withCode(cob -> { 653 cob.aload(0) 654 .getfield(pg.handlerField) 655 .aload(0) 656 .ldc(DynamicConstantDesc.of(pg.bsm, 657 referenceClassDesc(fromClass), 658 method.getName(), 659 desc)); 660 if (parameterTypes.length > 0) { 661 // Create an array and fill with the parameters converting primitives to wrappers 662 cob.loadConstant(parameterTypes.length) 663 .anewarray(pg.object); 664 for (int i = 0; i < parameterTypes.length; i++) { 665 cob.dup() 666 .loadConstant(i); 667 codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); 668 cob.aastore(); 669 } 670 } else { 671 cob.aconst_null(); 672 } 673 674 cob.invokeinterface(pg.invoke); 675 676 if (returnType == void.class) { 677 cob.pop() 678 .return_(); 679 } else { 680 codeUnwrapReturnValue(cob, returnType); 681 } 682 if (!catchList.isEmpty()) { 683 var c1 = cob.newBoundLabel(); 684 for (var exc : catchList) { 685 cob.exceptionCatch(cob.startLabel(), c1, c1, referenceClassDesc(exc)); 686 } 687 cob.athrow(); // just rethrow the exception 688 var c2 = cob.newBoundLabel(); 689 cob.exceptionCatchAll(cob.startLabel(), c1, c2) 690 .new_(pg.ute) 691 .dup_x1() 692 .swap() 693 .invokespecial(pg.uteInit) 694 .athrow() 695 .with(StackMapTableAttribute.of(List.of( 696 StackMapFrameInfo.of(c1, List.of(), pg.throwableStack), 697 StackMapFrameInfo.of(c2, List.of(), pg.throwableStack)))); 698 } 699 })); 700 } 701 702 /** 703 * Generate code for wrapping an argument of the given type 704 * whose value can be found at the specified local variable 705 * index, in order for it to be passed (as an Object) to the 706 * invocation handler's "invoke" method. 707 */ 708 private void codeWrapArgument(CodeBuilder cob, Class<?> type, int slot) { 709 if (type.isPrimitive()) { 710 cob.loadLocal(TypeKind.from(type).asLoadable(), slot); 711 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); 712 cob.invokestatic(prim.wrapperMethodRef(cob.constantPool())); 713 } else { 714 cob.aload(slot); 715 } 716 } 717 718 /** 719 * Generate code for unwrapping a return value of the given 720 * type from the invocation handler's "invoke" method (as type 721 * Object) to its correct type. 722 */ 723 private void codeUnwrapReturnValue(CodeBuilder cob, Class<?> type) { 724 if (type.isPrimitive()) { 725 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); 726 727 cob.checkcast(prim.wrapperClass) 728 .invokevirtual(prim.unwrapMethodRef(cob.constantPool())) 729 .return_(TypeKind.from(type).asLoadable()); 730 } else { 731 cob.checkcast(referenceClassDesc(type)) 732 .areturn(); 733 } 734 } 735 736 @Override 737 public String toString() { 738 return method.toShortString(); 739 } 740 } 741 742 /** 743 * A PrimitiveTypeInfo object contains bytecode-related information about 744 * a primitive type in its instance fields. The struct for a particular 745 * primitive type can be obtained using the static "get" method. 746 */ 747 private enum PrimitiveTypeInfo { 748 BYTE(byte.class, CD_byte, CD_Byte), 749 CHAR(char.class, CD_char, CD_Character), 750 DOUBLE(double.class, CD_double, CD_Double), 751 FLOAT(float.class, CD_float, CD_Float), 752 INT(int.class, CD_int, CD_Integer), 753 LONG(long.class, CD_long, CD_Long), 754 SHORT(short.class, CD_short, CD_Short), 755 BOOLEAN(boolean.class, CD_boolean, CD_Boolean); 756 757 /** 758 * wrapper class 759 */ 760 private final ClassDesc wrapperClass; 761 /** 762 * wrapper factory method type 763 */ 764 private final MethodTypeDesc wrapperMethodType; 765 /** 766 * wrapper class method name for retrieving primitive value 767 */ 768 private final String unwrapMethodName; 769 /** 770 * wrapper class method type for retrieving primitive value 771 */ 772 private final MethodTypeDesc unwrapMethodType; 773 774 PrimitiveTypeInfo(Class<?> primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { 775 assert baseType.isPrimitive(); 776 this.wrapperClass = wrapperClass; 777 this.wrapperMethodType = MethodTypeDescImpl.ofValidated(wrapperClass, baseType); 778 this.unwrapMethodName = primitiveClass.getName() + "Value"; 779 this.unwrapMethodType = MethodTypeDescImpl.ofValidated(baseType); 780 } 781 782 public static PrimitiveTypeInfo get(Class<?> cl) { 783 // Uses if chain for speed: 8284880 784 if (cl == int.class) return INT; 785 if (cl == long.class) return LONG; 786 if (cl == boolean.class) return BOOLEAN; 787 if (cl == short.class) return SHORT; 788 if (cl == byte.class) return BYTE; 789 if (cl == char.class) return CHAR; 790 if (cl == float.class) return FLOAT; 791 if (cl == double.class) return DOUBLE; 792 throw new AssertionError(cl); 793 } 794 795 public MethodRefEntry wrapperMethodRef(ConstantPoolBuilder cp) { 796 return cp.methodRefEntry(wrapperClass, "valueOf", wrapperMethodType); 797 } 798 799 public MethodRefEntry unwrapMethodRef(ConstantPoolBuilder cp) { 800 return cp.methodRefEntry(wrapperClass, unwrapMethodName, unwrapMethodType); 801 } 802 } 803 }