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