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