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