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