1 /* 2 * Copyright (c) 1999, 2023, 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.lang.classfile.*; 29 import java.lang.classfile.constantpool.*; 30 import java.lang.classfile.attribute.ExceptionsAttribute; 31 import sun.security.action.GetBooleanAction; 32 33 import java.io.IOException; 34 import static java.lang.classfile.ClassFile.*; 35 import java.lang.classfile.attribute.StackMapFrameInfo; 36 import java.lang.classfile.attribute.StackMapTableAttribute; 37 import java.lang.constant.ClassDesc; 38 import static java.lang.constant.ConstantDescs.*; 39 import java.lang.constant.MethodTypeDesc; 40 import java.nio.file.Files; 41 import java.nio.file.Path; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.LinkedHashMap; 45 import java.util.List; 46 import java.util.ListIterator; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.function.IntFunction; 50 51 /** 52 * ProxyGenerator contains the code to generate a dynamic proxy class 53 * for the java.lang.reflect.Proxy API. 54 * <p> 55 * The external interface to ProxyGenerator is the static 56 * "generateProxyClass" method. 57 */ 58 final class ProxyGenerator { 59 60 private static final ClassDesc 61 CD_ClassLoader = ClassDesc.ofInternalName("java/lang/ClassLoader"), 62 CD_ClassNotFoundException = ClassDesc.ofInternalName("java/lang/ClassNotFoundException"), 63 CD_IllegalAccessException = ClassDesc.ofInternalName("java/lang/IllegalAccessException"), 64 CD_InvocationHandler = ClassDesc.ofInternalName("java/lang/reflect/InvocationHandler"), 65 CD_Method = ClassDesc.ofInternalName("java/lang/reflect/Method"), 66 CD_NoClassDefFoundError = ClassDesc.ofInternalName("java/lang/NoClassDefFoundError"), 67 CD_NoSuchMethodError = ClassDesc.ofInternalName("java/lang/NoSuchMethodError"), 68 CD_NoSuchMethodException = ClassDesc.ofInternalName("java/lang/NoSuchMethodException"), 69 CD_Proxy = ClassDesc.ofInternalName("java/lang/reflect/Proxy"), 70 CD_UndeclaredThrowableException = ClassDesc.ofInternalName("java/lang/reflect/UndeclaredThrowableException"); 71 72 private static final MethodTypeDesc 73 MTD_boolean = MethodTypeDesc.of(CD_boolean), 74 MTD_void_InvocationHandler = MethodTypeDesc.of(CD_void, CD_InvocationHandler), 75 MTD_void_String = MethodTypeDesc.of(CD_void, CD_String), 76 MTD_void_Throwable = MethodTypeDesc.of(CD_void, CD_Throwable), 77 MTD_Class = MethodTypeDesc.of(CD_Class), 78 MTD_Class_String_boolean_ClassLoader = MethodTypeDesc.of(CD_Class, CD_String, CD_boolean, CD_ClassLoader), 79 MTD_ClassLoader = MethodTypeDesc.of(CD_ClassLoader), 80 MTD_MethodHandles$Lookup = MethodTypeDesc.of(CD_MethodHandles_Lookup), 81 MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDesc.of(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup), 82 MTD_Method_String_ClassArray = MethodTypeDesc.of(CD_Method, CD_String, CD_Class.arrayType()), 83 MTD_Object_Object_Method_ObjectArray = MethodTypeDesc.of(CD_Object, CD_Object, CD_Method, CD_Object.arrayType()), 84 MTD_String = MethodTypeDesc.of(CD_String); 85 86 private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; 87 88 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; 89 90 /** 91 * name of field for storing a proxy instance's invocation handler 92 */ 93 private static final String handlerFieldName = "h"; 94 95 /** 96 * debugging flag for saving generated class files 97 */ 98 @SuppressWarnings("removal") 99 private static final boolean saveGeneratedFiles = 100 java.security.AccessController.doPrivileged( 101 new GetBooleanAction( 102 "jdk.proxy.ProxyGenerator.saveGeneratedFiles")); 103 104 /* Preloaded ProxyMethod objects for methods in java.lang.Object */ 105 private static final ProxyMethod hashCodeMethod; 106 private static final ProxyMethod equalsMethod; 107 private static final ProxyMethod toStringMethod; 108 109 private static final ClassModel TEMPLATE; 110 111 private static final ClassEntry CE_Class; 112 private static final ClassEntry CE_ClassNotFoundException; 113 private static final ClassEntry CE_NoClassDefFoundError; 114 private static final ClassEntry CE_NoSuchMethodError; 115 private static final ClassEntry CE_NoSuchMethodException; 116 private static final ClassEntry CE_Object; 117 private static final ClassEntry CE_Throwable; 118 private static final ClassEntry CE_UndeclaredThrowableException; 119 120 private static final FieldRefEntry FRE_Proxy_h; 121 122 private static final InterfaceMethodRefEntry IMRE_InvocationHandler_invoke; 123 124 private static final MethodRefEntry MRE_Class_forName; 125 private static final MethodRefEntry MRE_Class_getClassLoader; 126 private static final MethodRefEntry MRE_Class_getMethod; 127 private static final MethodRefEntry MRE_NoClassDefFoundError_init; 128 private static final MethodRefEntry MRE_NoSuchMethodError_init; 129 private static final MethodRefEntry MRE_Throwable_getMessage; 130 private static final MethodRefEntry MRE_UndeclaredThrowableException_init; 131 132 private static final Utf8Entry UE_Method; 133 134 private static final List<StackMapFrameInfo.VerificationTypeInfo> THROWABLE_STACK; 135 136 @SuppressWarnings("unchecked") 137 private static <T extends PoolEntry> T entryByIndex(int index) { 138 return (T) TEMPLATE.constantPool().entryByIndex(index); 139 } 140 141 static { 142 // static template ClassModel holds pre-defined constant pool entries 143 // proxy transformed from the template shares the template constant pool 144 // each direct use of the template pool entry is significantly faster 145 var cc = ClassFile.of(); 146 var ei = new int[21]; 147 TEMPLATE = cc.parse(cc.build(CD_Proxy, clb -> { 148 clb.withSuperclass(CD_Proxy); 149 generateConstructor(clb); 150 generateLookupAccessor(clb); 151 var cp = clb.constantPool(); 152 153 ei[0] = cp.classEntry(CD_Class).index(); 154 ei[1] = cp.classEntry(CD_ClassNotFoundException).index(); 155 ei[2] = cp.classEntry(CD_NoClassDefFoundError).index(); 156 ei[3] = cp.classEntry(CD_NoSuchMethodError).index(); 157 ei[4] = cp.classEntry(CD_NoSuchMethodException).index(); 158 ei[5] = cp.classEntry(CD_Object).index(); 159 ei[6] = cp.classEntry(CD_Throwable).index(); 160 ei[7] = cp.classEntry(CD_UndeclaredThrowableException).index(); 161 162 ei[8] = cp.fieldRefEntry(CD_Proxy, handlerFieldName, CD_InvocationHandler).index(); 163 164 ei[9] = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray).index(); 165 166 ei[10] = cp.methodRefEntry(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader).index(); 167 ei[11] = cp.methodRefEntry(CD_Class, "getClassLoader", MTD_ClassLoader).index(); 168 ei[12] = cp.methodRefEntry(CD_Class, "getMethod", MTD_Method_String_ClassArray).index(); 169 ei[13] = cp.methodRefEntry(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String).index(); 170 ei[14] = cp.methodRefEntry(CD_NoSuchMethodError, INIT_NAME, MTD_void_String).index(); 171 ei[15] = cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String).index(); 172 ei[16] = cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable).index(); 173 174 ei[17] = cp.utf8Entry(CD_Method).index(); 175 176 ei[18] = cp.utf8Entry("m0").index(); 177 ei[19] = cp.utf8Entry("m1").index(); 178 ei[20] = cp.utf8Entry("m2").index(); 179 })); 180 181 CE_Class = entryByIndex(ei[0]); 182 CE_ClassNotFoundException = entryByIndex(ei[1]); 183 CE_NoClassDefFoundError = entryByIndex(ei[2]); 184 CE_NoSuchMethodError = entryByIndex(ei[3]); 185 CE_NoSuchMethodException = entryByIndex(ei[4]); 186 CE_Object = entryByIndex(ei[5]); 187 CE_Throwable = entryByIndex(ei[6]); 188 CE_UndeclaredThrowableException = entryByIndex(ei[7]); 189 190 FRE_Proxy_h = entryByIndex(ei[8]); 191 192 IMRE_InvocationHandler_invoke = entryByIndex(ei[9]); 193 194 MRE_Class_forName = entryByIndex(ei[10]); 195 MRE_Class_getClassLoader = entryByIndex(ei[11]); 196 MRE_Class_getMethod = entryByIndex(ei[12]); 197 MRE_NoClassDefFoundError_init = entryByIndex(ei[13]); 198 MRE_NoSuchMethodError_init = entryByIndex(ei[14]); 199 MRE_Throwable_getMessage = entryByIndex(ei[15]); 200 MRE_UndeclaredThrowableException_init = entryByIndex(ei[16]); 201 202 UE_Method = entryByIndex(ei[17]); 203 204 try { 205 hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), entryByIndex(ei[18])); 206 equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), entryByIndex(ei[19])); 207 toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), entryByIndex(ei[20])); 208 } catch (NoSuchMethodException e) { 209 throw new NoSuchMethodError(e.getMessage()); 210 } 211 212 THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); 213 } 214 215 /** 216 * Classfile context 217 */ 218 private final ClassFile classfileContext; 219 private final ConstantPoolBuilder cp; 220 221 /** 222 * Name of proxy class 223 */ 224 private ClassEntry classEntry; 225 226 /** 227 * Proxy interfaces 228 */ 229 private final List<Class<?>> interfaces; 230 231 /** 232 * Proxy class access flags 233 */ 234 private final int accessFlags; 235 236 /** 237 * Maps method signature string to list of ProxyMethod objects for 238 * proxy methods with that signature. 239 * Kept in insertion order to make it easier to compare old and new. 240 */ 241 private final Map<String, List<ProxyMethod>> proxyMethods = new LinkedHashMap<>(); 242 243 /** 244 * Ordinal of next ProxyMethod object added to proxyMethods. 245 * Indexes are reserved for hashcode(0), equals(1), toString(2). 246 */ 247 private int proxyMethodCount = 3; 248 249 /** 250 * Construct a ProxyGenerator to generate a proxy class with the 251 * specified name and for the given interfaces. 252 * <p> 253 * A ProxyGenerator object contains the state for the ongoing 254 * generation of a particular proxy class. 255 */ 256 private ProxyGenerator(ClassLoader loader, String className, List<Class<?>> interfaces, 257 int accessFlags) { 258 this.classfileContext = ClassFile.of( 259 ClassFile.StackMapsOption.DROP_STACK_MAPS, 260 ClassFile.ClassHierarchyResolverOption.of( 261 ClassHierarchyResolver.ofClassLoading(loader).cached())); 262 this.cp = ConstantPoolBuilder.of(TEMPLATE); 263 this.classEntry = cp.classEntry(ClassDesc.of(className)); 264 this.interfaces = interfaces; 265 this.accessFlags = accessFlags; 266 } 267 268 /** 269 * Generate a proxy class given a name and a list of proxy interfaces. 270 * 271 * @param name the class name of the proxy class 272 * @param interfaces proxy interfaces 273 * @param accessFlags access flags of the proxy class 274 */ 275 @SuppressWarnings("removal") 276 static byte[] generateProxyClass(ClassLoader loader, 277 final String name, 278 List<Class<?>> interfaces, 279 int accessFlags) { 280 Objects.requireNonNull(interfaces); 281 ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); 282 final byte[] classFile = gen.generateClassFile(); 283 284 if (saveGeneratedFiles) { 285 java.security.AccessController.doPrivileged( 286 new java.security.PrivilegedAction<Void>() { 287 public Void run() { 288 try { 289 int i = name.lastIndexOf('.'); 290 Path path; 291 if (i > 0) { 292 Path dir = Path.of(name.substring(0, i).replace('.', '/')); 293 Files.createDirectories(dir); 294 path = dir.resolve(name.substring(i + 1) + ".class"); 295 } else { 296 path = Path.of(name + ".class"); 297 } 298 Files.write(path, classFile); 299 return null; 300 } catch (IOException e) { 301 throw new InternalError( 302 "I/O exception saving generated file: " + e); 303 } 304 } 305 }); 306 } 307 308 return classFile; 309 } 310 311 /** 312 * {@return the entries of the given type} 313 * @param types the {@code Class} objects, not primitive types nor array types 314 */ 315 private static ClassEntry[] toClassEntries(ConstantPoolBuilder cp, List<Class<?>> types) { 316 var ces = new ClassEntry[types.size()]; 317 for (int i = 0; i < ces.length; i++) 318 ces[i] = cp.classEntry(cp.utf8Entry(types.get(i).getName().replace('.', '/'))); 319 return ces; 320 } 321 322 /** 323 * {@return the {@code ClassDesc} of the given type} 324 * @param type the {@code Class} object 325 */ 326 private static ClassDesc toClassDesc(Class<?> type) { 327 return ClassDesc.ofDescriptor(type.descriptorString()); 328 } 329 330 /** 331 * For a given set of proxy methods with the same signature, check 332 * that their return types are compatible according to the Proxy 333 * specification. 334 * 335 * Specifically, if there is more than one such method, then all 336 * of the return types must be reference types, and there must be 337 * one return type that is assignable to each of the rest of them. 338 */ 339 private static void checkReturnTypes(List<ProxyMethod> methods) { 340 /* 341 * If there is only one method with a given signature, there 342 * cannot be a conflict. This is the only case in which a 343 * primitive (or void) return type is allowed. 344 */ 345 if (methods.size() < 2) { 346 return; 347 } 348 349 /* 350 * List of return types that are not yet known to be 351 * assignable from ("covered" by) any of the others. 352 */ 353 List<Class<?>> uncoveredReturnTypes = new ArrayList<>(1); 354 355 nextNewReturnType: 356 for (ProxyMethod pm : methods) { 357 Class<?> newReturnType = pm.returnType; 358 if (newReturnType.isPrimitive()) { 359 throw new IllegalArgumentException( 360 "methods with same signature " + 361 pm.shortSignature + 362 " but incompatible return types: " + 363 newReturnType.getName() + " and others"); 364 } 365 boolean added = false; 366 367 /* 368 * Compare the new return type to the existing uncovered 369 * return types. 370 */ 371 ListIterator<Class<?>> liter = uncoveredReturnTypes.listIterator(); 372 while (liter.hasNext()) { 373 Class<?> uncoveredReturnType = liter.next(); 374 375 /* 376 * If an existing uncovered return type is assignable 377 * to this new one, then we can forget the new one. 378 */ 379 if (newReturnType.isAssignableFrom(uncoveredReturnType)) { 380 assert !added; 381 continue nextNewReturnType; 382 } 383 384 /* 385 * If the new return type is assignable to an existing 386 * uncovered one, then should replace the existing one 387 * with the new one (or just forget the existing one, 388 * if the new one has already be put in the list). 389 */ 390 if (uncoveredReturnType.isAssignableFrom(newReturnType)) { 391 // (we can assume that each return type is unique) 392 if (!added) { 393 liter.set(newReturnType); 394 added = true; 395 } else { 396 liter.remove(); 397 } 398 } 399 } 400 401 /* 402 * If we got through the list of existing uncovered return 403 * types without an assignability relationship, then add 404 * the new return type to the list of uncovered ones. 405 */ 406 if (!added) { 407 uncoveredReturnTypes.add(newReturnType); 408 } 409 } 410 411 /* 412 * We shouldn't end up with more than one return type that is 413 * not assignable from any of the others. 414 */ 415 if (uncoveredReturnTypes.size() > 1) { 416 ProxyMethod pm = methods.get(0); 417 throw new IllegalArgumentException( 418 "methods with same signature " + 419 pm.shortSignature + 420 " but incompatible return types: " + uncoveredReturnTypes); 421 } 422 } 423 424 /** 425 * Given the exceptions declared in the throws clause of a proxy method, 426 * compute the exceptions that need to be caught from the invocation 427 * handler's invoke method and rethrown intact in the method's 428 * implementation before catching other Throwables and wrapping them 429 * in UndeclaredThrowableExceptions. 430 * 431 * The exceptions to be caught are returned in a List object. Each 432 * exception in the returned list is guaranteed to not be a subclass of 433 * any of the other exceptions in the list, so the catch blocks for 434 * these exceptions may be generated in any order relative to each other. 435 * 436 * Error and RuntimeException are each always contained by the returned 437 * list (if none of their superclasses are contained), since those 438 * unchecked exceptions should always be rethrown intact, and thus their 439 * subclasses will never appear in the returned list. 440 * 441 * The returned List will be empty if java.lang.Throwable is in the 442 * given list of declared exceptions, indicating that no exceptions 443 * need to be caught. 444 */ 445 private static List<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) { 446 List<Class<?>> uniqueList = new ArrayList<>(); 447 // unique exceptions to catch 448 449 uniqueList.add(Error.class); // always catch/rethrow these 450 uniqueList.add(RuntimeException.class); 451 452 nextException: 453 for (Class<?> ex : exceptions) { 454 if (ex.isAssignableFrom(Throwable.class)) { 455 /* 456 * If Throwable is declared to be thrown by the proxy method, 457 * then no catch blocks are necessary, because the invoke 458 * can, at most, throw Throwable anyway. 459 */ 460 uniqueList.clear(); 461 break; 462 } else if (!Throwable.class.isAssignableFrom(ex)) { 463 /* 464 * Ignore types that cannot be thrown by the invoke method. 465 */ 466 continue; 467 } 468 /* 469 * Compare this exception against the current list of 470 * exceptions that need to be caught: 471 */ 472 for (int j = 0; j < uniqueList.size(); ) { 473 Class<?> ex2 = uniqueList.get(j); 474 if (ex2.isAssignableFrom(ex)) { 475 /* 476 * if a superclass of this exception is already on 477 * the list to catch, then ignore this one and continue; 478 */ 479 continue nextException; 480 } else if (ex.isAssignableFrom(ex2)) { 481 /* 482 * if a subclass of this exception is on the list 483 * to catch, then remove it; 484 */ 485 uniqueList.remove(j); 486 } else { 487 j++; // else continue comparing. 488 } 489 } 490 // This exception is unique (so far): add it to the list to catch. 491 uniqueList.add(ex); 492 } 493 return uniqueList; 494 } 495 496 /** 497 * Add to the given list all of the types in the "from" array that 498 * are not already contained in the list and are assignable to at 499 * least one of the types in the "with" array. 500 * <p> 501 * This method is useful for computing the greatest common set of 502 * declared exceptions from duplicate methods inherited from 503 * different interfaces. 504 */ 505 private static void collectCompatibleTypes(Class<?>[] from, 506 Class<?>[] with, 507 List<Class<?>> list) { 508 for (Class<?> fc : from) { 509 if (!list.contains(fc)) { 510 for (Class<?> wc : with) { 511 if (wc.isAssignableFrom(fc)) { 512 list.add(fc); 513 break; 514 } 515 } 516 } 517 } 518 } 519 520 /** 521 * Generate a class file for the proxy class. This method drives the 522 * class file generation process. 523 * 524 * If a proxy interface references any value classes, the value classes 525 * are listed in the loadable descriptors attribute of the interface class. The 526 * classes that are referenced by the proxy interface have already 527 * been loaded before the proxy class. Hence the proxy class is 528 * generated with no loadable descriptors attributes as it essentially has no effect. 529 */ 530 private byte[] generateClassFile() { 531 /* 532 * Add proxy methods for the hashCode, equals, 533 * and toString methods of java.lang.Object. This is done before 534 * the methods from the proxy interfaces so that the methods from 535 * java.lang.Object take precedence over duplicate methods in the 536 * proxy interfaces. 537 */ 538 addProxyMethod(hashCodeMethod); 539 addProxyMethod(equalsMethod); 540 addProxyMethod(toStringMethod); 541 542 /* 543 * Accumulate all of the methods from the proxy interfaces. 544 */ 545 for (Class<?> intf : interfaces) { 546 for (Method m : intf.getMethods()) { 547 if (!Modifier.isStatic(m.getModifiers())) { 548 addProxyMethod(m, intf, cp); 549 } 550 } 551 } 552 553 /* 554 * For each set of proxy methods with the same signature, 555 * verify that the methods' return types are compatible. 556 */ 557 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 558 checkReturnTypes(sigmethods); 559 } 560 561 return classfileContext.build(classEntry, cp, clb -> { 562 TEMPLATE.forEach(clb); 563 clb.withFlags(accessFlags); 564 clb.withInterfaces(toClassEntries(cp, interfaces)); 565 566 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 567 for (ProxyMethod pm : sigmethods) { 568 // add static field for the Method object 569 clb.withField(pm.methodFieldName, UE_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); 570 571 // Generate code for proxy method 572 pm.generateMethod(clb, classEntry); 573 } 574 } 575 576 generateStaticInitializer(clb); 577 }); 578 } 579 580 /** 581 * Add another method to be proxied, either by creating a new 582 * ProxyMethod object or augmenting an old one for a duplicate 583 * method. 584 * 585 * "fromClass" indicates the proxy interface that the method was 586 * found through, which may be different from (a subinterface of) 587 * the method's "declaring class". Note that the first Method 588 * object passed for a given name and descriptor identifies the 589 * Method object (and thus the declaring class) that will be 590 * passed to the invocation handler's "invoke" method for a given 591 * set of duplicate methods. 592 */ 593 private void addProxyMethod(Method m, Class<?> fromClass, ConstantPoolBuilder cp) { 594 Class<?> returnType = m.getReturnType(); 595 Class<?>[] exceptionTypes = m.getSharedExceptionTypes(); 596 597 String sig = m.toShortSignature(); 598 List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, 599 (f) -> new ArrayList<>(3)); 600 for (ProxyMethod pm : sigmethods) { 601 if (returnType == pm.returnType) { 602 /* 603 * Found a match: reduce exception types to the 604 * greatest set of exceptions that can be thrown 605 * compatibly with the throws clauses of both 606 * overridden methods. 607 */ 608 List<Class<?>> legalExceptions = new ArrayList<>(); 609 collectCompatibleTypes( 610 exceptionTypes, pm.exceptionTypes, legalExceptions); 611 collectCompatibleTypes( 612 pm.exceptionTypes, exceptionTypes, legalExceptions); 613 pm.exceptionTypes = legalExceptions.toArray(EMPTY_CLASS_ARRAY); 614 return; 615 } 616 } 617 sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, 618 exceptionTypes, fromClass, 619 cp.utf8Entry("m" + proxyMethodCount++))); 620 } 621 622 /** 623 * Add an existing ProxyMethod (hashcode, equals, toString). 624 * 625 * @param pm an existing ProxyMethod 626 */ 627 private void addProxyMethod(ProxyMethod pm) { 628 String sig = pm.shortSignature; 629 List<ProxyMethod> sigmethods = proxyMethods.computeIfAbsent(sig, 630 (f) -> new ArrayList<>(3)); 631 sigmethods.add(pm); 632 } 633 634 /** 635 * Generate the constructor method for the proxy class. 636 */ 637 private static void generateConstructor(ClassBuilder clb) { 638 clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob 639 .aload(cob.receiverSlot()) 640 .aload(cob.parameterSlot(0)) 641 .invokespecial(CD_Proxy, INIT_NAME, MTD_void_InvocationHandler) 642 .return_()); 643 } 644 645 /** 646 * Generate the static initializer method for the proxy class. 647 */ 648 private void generateStaticInitializer(ClassBuilder clb) { 649 clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> { 650 // Put ClassLoader at local variable index 0, used by 651 // Class.forName(String, boolean, ClassLoader) calls 652 cob.ldc(classEntry) 653 .invokevirtual(MRE_Class_getClassLoader) 654 .astore(0); 655 var ts = cob.newBoundLabel(); 656 for (List<ProxyMethod> sigmethods : proxyMethods.values()) { 657 for (ProxyMethod pm : sigmethods) { 658 pm.codeFieldInitialization(cob, classEntry); 659 } 660 } 661 cob.return_(); 662 var c1 = cob.newBoundLabel(); 663 cob.exceptionCatch(ts, c1, c1, CE_NoSuchMethodException) 664 .new_(CE_NoSuchMethodError) 665 .dup_x1() 666 .swap() 667 .invokevirtual(MRE_Throwable_getMessage) 668 .invokespecial(MRE_NoSuchMethodError_init) 669 .athrow(); 670 var c2 = cob.newBoundLabel(); 671 cob.exceptionCatch(ts, c1, c2, CE_ClassNotFoundException) 672 .new_(CE_NoClassDefFoundError) 673 .dup_x1() 674 .swap() 675 .invokevirtual(MRE_Throwable_getMessage) 676 .invokespecial(MRE_NoClassDefFoundError_init) 677 .athrow() 678 .with(StackMapTableAttribute.of(List.of( 679 StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), 680 StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); 681 }); 682 } 683 684 /** 685 * Generate the static lookup accessor method that returns the Lookup 686 * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; 687 * otherwise, IllegalAccessException is thrown 688 */ 689 private static void generateLookupAccessor(ClassBuilder clb) { 690 clb.withMethod(NAME_LOOKUP_ACCESSOR, 691 MTD_MethodHandles$Lookup_MethodHandles$Lookup, 692 ACC_PRIVATE | ACC_STATIC, 693 mb -> mb.with(ExceptionsAttribute.of(List.of(mb.constantPool().classEntry(CD_IllegalAccessException)))) 694 .withCode(cob -> cob 695 .block(blockBuilder -> blockBuilder 696 .aload(cob.parameterSlot(0)) 697 .invokevirtual(CD_MethodHandles_Lookup, "lookupClass", MTD_Class) 698 .constantInstruction(Opcode.LDC, CD_Proxy) 699 .if_acmpne(blockBuilder.breakLabel()) 700 .aload(cob.parameterSlot(0)) 701 .invokevirtual(CD_MethodHandles_Lookup, "hasFullPrivilegeAccess", MTD_boolean) 702 .ifeq(blockBuilder.breakLabel()) 703 .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup) 704 .areturn()) 705 .new_(CD_IllegalAccessException) 706 .dup() 707 .aload(cob.parameterSlot(0)) 708 .invokevirtual(CD_MethodHandles_Lookup, "toString", MTD_String) 709 .invokespecial(CD_IllegalAccessException, INIT_NAME, MTD_void_String) 710 .athrow())); 711 } 712 713 /** 714 * A ProxyMethod object represents a proxy method in the proxy class 715 * being generated: a method whose implementation will encode and 716 * dispatch invocations to the proxy instance's invocation handler. 717 */ 718 private static class ProxyMethod { 719 720 private final Method method; 721 private final String shortSignature; 722 private final Class<?> fromClass; 723 private final Class<?>[] parameterTypes; 724 private final Class<?> returnType; 725 private final Utf8Entry methodFieldName; 726 private Class<?>[] exceptionTypes; 727 728 private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes, 729 Class<?> returnType, Class<?>[] exceptionTypes, 730 Class<?> fromClass, Utf8Entry methodFieldName) { 731 this.method = method; 732 this.shortSignature = sig; 733 this.parameterTypes = parameterTypes; 734 this.returnType = returnType; 735 this.exceptionTypes = exceptionTypes; 736 this.fromClass = fromClass; 737 this.methodFieldName = methodFieldName; 738 } 739 740 /** 741 * Create a new specific ProxyMethod with a specific field name 742 * 743 * @param method The method for which to create a proxy 744 * @param methodFieldName the fieldName to generate 745 */ 746 private ProxyMethod(Method method, Utf8Entry methodFieldName) { 747 this(method, method.toShortSignature(), 748 method.getSharedParameterTypes(), method.getReturnType(), 749 method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); 750 } 751 752 /** 753 * Generate this method, including the code and exception table entry. 754 */ 755 private void generateMethod(ClassBuilder clb, ClassEntry className) { 756 var cp = clb.constantPool(); 757 MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), 758 Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); 759 int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL 760 : ACC_PUBLIC | ACC_FINAL; 761 var catchList = computeUniqueCatchList(exceptionTypes); 762 clb.withMethod(method.getName(), desc, accessFlags, mb -> 763 mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes)))) 764 .withCode(cob -> { 765 cob.aload(cob.receiverSlot()) 766 .getfield(FRE_Proxy_h) 767 .aload(cob.receiverSlot()) 768 .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); 769 770 if (parameterTypes.length > 0) { 771 // Create an array and fill with the parameters converting primitives to wrappers 772 cob.constantInstruction(parameterTypes.length) 773 .anewarray(CE_Object); 774 for (int i = 0; i < parameterTypes.length; i++) { 775 cob.dup() 776 .constantInstruction(i); 777 codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); 778 cob.aastore(); 779 } 780 } else { 781 cob.aconst_null(); 782 } 783 784 cob.invokeinterface(IMRE_InvocationHandler_invoke); 785 786 if (returnType == void.class) { 787 cob.pop() 788 .return_(); 789 } else { 790 codeUnwrapReturnValue(cob, returnType); 791 } 792 if (!catchList.isEmpty()) { 793 var c1 = cob.newBoundLabel(); 794 for (var exc : catchList) { 795 cob.exceptionCatch(cob.startLabel(), c1, c1, toClassDesc(exc)); 796 } 797 cob.athrow(); // just rethrow the exception 798 var c2 = cob.newBoundLabel(); 799 cob.exceptionCatchAll(cob.startLabel(), c1, c2) 800 .new_(CE_UndeclaredThrowableException) 801 .dup_x1() 802 .swap() 803 .invokespecial(MRE_UndeclaredThrowableException_init) 804 .athrow() 805 .with(StackMapTableAttribute.of(List.of( 806 StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), 807 StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); 808 } 809 })); 810 } 811 812 /** 813 * Generate code for wrapping an argument of the given type 814 * whose value can be found at the specified local variable 815 * index, in order for it to be passed (as an Object) to the 816 * invocation handler's "invoke" method. 817 */ 818 private void codeWrapArgument(CodeBuilder cob, Class<?> type, int slot) { 819 if (type.isPrimitive()) { 820 cob.loadInstruction(TypeKind.from(type).asLoadable(), slot); 821 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); 822 cob.invokestatic(prim.wrapperMethodRef); 823 } else { 824 cob.aload(slot); 825 } 826 } 827 828 /** 829 * Generate code for unwrapping a return value of the given 830 * type from the invocation handler's "invoke" method (as type 831 * Object) to its correct type. 832 */ 833 private void codeUnwrapReturnValue(CodeBuilder cob, Class<?> type) { 834 if (type.isPrimitive()) { 835 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); 836 837 cob.checkcast(prim.wrapperClass) 838 .invokevirtual(prim.unwrapMethodRef) 839 .returnInstruction(TypeKind.from(type).asLoadable()); 840 } else { 841 cob.checkcast(toClassDesc(type)) 842 .areturn(); 843 } 844 } 845 846 /** 847 * Generate code for initializing the static field that stores 848 * the Method object for this proxy method. A class loader is 849 * anticipated at local variable index 0. 850 */ 851 private void codeFieldInitialization(CodeBuilder cob, ClassEntry className) { 852 var cp = cob.constantPool(); 853 codeClassForName(cob, fromClass); 854 855 cob.ldc(method.getName()) 856 .constantInstruction(parameterTypes.length) 857 .anewarray(CE_Class); 858 859 // Construct an array with the parameter types mapping primitives to Wrapper types 860 for (int i = 0; i < parameterTypes.length; i++) { 861 cob.dup() 862 .constantInstruction(i); 863 if (parameterTypes[i].isPrimitive()) { 864 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); 865 cob.getstatic(prim.typeFieldRef); 866 } else { 867 codeClassForName(cob, parameterTypes[i]); 868 } 869 cob.aastore(); 870 } 871 // lookup the method 872 cob.invokevirtual(MRE_Class_getMethod) 873 .putstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); 874 } 875 876 /* 877 * =============== Code Generation Utility Methods =============== 878 */ 879 880 /** 881 * Generate code to invoke the Class.forName with the name of the given 882 * class to get its Class object at runtime. The code is written to 883 * the supplied stream. Note that the code generated by this method 884 * may cause the checked ClassNotFoundException to be thrown. A class 885 * loader is anticipated at local variable index 0. 886 */ 887 private void codeClassForName(CodeBuilder cob, Class<?> cl) { 888 cob.ldc(cl.getName()) 889 .iconst_0() // false 890 .aload(0)// classLoader 891 .invokestatic(MRE_Class_forName); 892 } 893 894 @Override 895 public String toString() { 896 return method.toShortString(); 897 } 898 } 899 900 private static final ConstantPoolBuilder CP = ConstantPoolBuilder.of(); 901 /** 902 * A PrimitiveTypeInfo object contains bytecode-related information about 903 * a primitive type in its instance fields. The struct for a particular 904 * primitive type can be obtained using the static "get" method. 905 */ 906 private enum PrimitiveTypeInfo { 907 BYTE(byte.class, CD_byte, CD_Byte), 908 CHAR(char.class, CD_char, CD_Character), 909 DOUBLE(double.class, CD_double, CD_Double), 910 FLOAT(float.class, CD_float, CD_Float), 911 INT(int.class, CD_int, CD_Integer), 912 LONG(long.class, CD_long, CD_Long), 913 SHORT(short.class, CD_short, CD_Short), 914 BOOLEAN(boolean.class, CD_boolean, CD_Boolean); 915 916 /** 917 * CP entry of corresponding wrapper class 918 */ 919 private final ClassEntry wrapperClass; 920 /** 921 * CP entry for wrapper class "valueOf" factory method 922 */ 923 private final MethodRefEntry wrapperMethodRef; 924 /** 925 * CP entry of wrapper class method for retrieving primitive value 926 */ 927 private final MethodRefEntry unwrapMethodRef; 928 /** 929 * CP entry of wrapper class TYPE field 930 */ 931 private final FieldRefEntry typeFieldRef; 932 933 PrimitiveTypeInfo(Class<?> primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { 934 assert baseType.isPrimitive(); 935 this.wrapperClass = CP.classEntry(wrapperClass); 936 this.wrapperMethodRef = CP.methodRefEntry(wrapperClass, "valueOf", MethodTypeDesc.of(wrapperClass, baseType)); 937 this.unwrapMethodRef = CP.methodRefEntry(wrapperClass, primitiveClass.getName() + "Value", MethodTypeDesc.of(baseType)); 938 this.typeFieldRef = CP.fieldRefEntry(wrapperClass, "TYPE", CD_Class); 939 } 940 941 public static PrimitiveTypeInfo get(Class<?> cl) { 942 // Uses if chain for speed: 8284880 943 if (cl == int.class) return INT; 944 if (cl == long.class) return LONG; 945 if (cl == boolean.class) return BOOLEAN; 946 if (cl == short.class) return SHORT; 947 if (cl == byte.class) return BYTE; 948 if (cl == char.class) return CHAR; 949 if (cl == float.class) return FLOAT; 950 if (cl == double.class) return DOUBLE; 951 throw new AssertionError(cl); 952 } 953 } 954 }