Skip to content

Commit 446d9a2

Browse files
authored
Implemented factory for generating invokedynamic proxy classes (#9502)
1 parent 324de7f commit 446d9a2

File tree

3 files changed

+525
-0
lines changed

3 files changed

+525
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
7+
8+
import java.lang.reflect.Method;
9+
import java.lang.reflect.Modifier;
10+
import java.util.List;
11+
import net.bytebuddy.ByteBuddy;
12+
import net.bytebuddy.description.method.MethodDescription;
13+
import net.bytebuddy.description.method.ParameterDescription;
14+
import net.bytebuddy.description.type.TypeDescription;
15+
import net.bytebuddy.dynamic.DynamicType;
16+
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
17+
import net.bytebuddy.implementation.FieldAccessor;
18+
import net.bytebuddy.implementation.Implementation;
19+
import net.bytebuddy.implementation.InvokeDynamic;
20+
import net.bytebuddy.implementation.MethodCall;
21+
import net.bytebuddy.implementation.bytecode.StackManipulation;
22+
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
23+
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
24+
import net.bytebuddy.utility.JavaConstant;
25+
26+
/**
27+
* Factory for generating proxies which invoke their target via {@code INVOKEDYNAMIC}. Generated
28+
* proxy classes have the following properties: The generated proxies have the following basic
29+
* structure:
30+
*
31+
* <ul>
32+
* <li>it has same superclass as the proxied class
33+
* <li>it implements all interfaces implemented by the proxied class
34+
* <li>for every public constructor of the proxied class, it defined a matching public constructor
35+
* which:
36+
* <ul>
37+
* <li>invokes the default constructor of the superclass
38+
* <li>invoked the corresponding constructor of the proxied class to generate the object to
39+
* which the proxy delegates
40+
* </ul>
41+
* <li>it "copies" every declared static and non-static public method, the implementation will
42+
* delegate to the corresponding method in the proxied class
43+
* <li>all annotations on the proxied class and on its methods are copied to the proxy
44+
* </ul>
45+
*
46+
* <p>Note that only the public methods declared by the proxied class are actually proxied.
47+
* Inherited methods are not automatically proxied. If you want those to be proxied, you'll need to
48+
* explicitly override them in the proxied class.
49+
*/
50+
public class IndyProxyFactory {
51+
52+
@FunctionalInterface
53+
public interface BootstrapArgsProvider {
54+
55+
/**
56+
* Defines the additional arguments to pass to the invokedynamic bootstrap method for a given
57+
* proxied method. The arguments have to be storable in the constant pool.
58+
*
59+
* @param classBeingProxied the type for which {@link
60+
* IndyProxyFactory#generateProxy(TypeDescription, String)} was invoked
61+
* @param proxiedMethodOrCtor the method or constructor from the proxied class for which the
62+
* arguments are requested
63+
* @return the arguments to pass to the bootstrap method
64+
*/
65+
List<? extends JavaConstant> getBootstrapArgsForMethod(
66+
TypeDescription classBeingProxied, MethodDescription.InDefinedShape proxiedMethodOrCtor);
67+
}
68+
69+
private static final String DELEGATE_FIELD_NAME = "delegate";
70+
71+
private final MethodDescription.InDefinedShape indyBootstrapMethod;
72+
73+
private final BootstrapArgsProvider bootstrapArgsProvider;
74+
75+
public IndyProxyFactory(Method bootstrapMethod, BootstrapArgsProvider bootstrapArgsProvider) {
76+
this.indyBootstrapMethod = new MethodDescription.ForLoadedMethod(bootstrapMethod);
77+
this.bootstrapArgsProvider = bootstrapArgsProvider;
78+
}
79+
80+
/**
81+
* Generates a proxy.
82+
*
83+
* @param classToProxy the class for which a proxy will be generated
84+
* @param proxyClassName the desired fully qualified name for the proxy class
85+
* @return the generated proxy class
86+
*/
87+
public DynamicType.Unloaded<?> generateProxy(
88+
TypeDescription classToProxy, String proxyClassName) {
89+
TypeDescription.Generic superClass = classToProxy.getSuperClass();
90+
DynamicType.Builder<?> builder =
91+
new ByteBuddy()
92+
.subclass(superClass, ConstructorStrategy.Default.NO_CONSTRUCTORS)
93+
.implement(classToProxy.getInterfaces())
94+
.name(proxyClassName)
95+
.annotateType(classToProxy.getDeclaredAnnotations())
96+
.defineField(DELEGATE_FIELD_NAME, Object.class, Modifier.PRIVATE | Modifier.FINAL);
97+
98+
for (MethodDescription.InDefinedShape method : classToProxy.getDeclaredMethods()) {
99+
if (method.isPublic()) {
100+
if (method.isConstructor()) {
101+
List<? extends JavaConstant> bootstrapArgs =
102+
bootstrapArgsProvider.getBootstrapArgsForMethod(classToProxy, method);
103+
builder = createProxyConstructor(superClass, method, bootstrapArgs, builder);
104+
} else if (method.isMethod()) {
105+
List<? extends JavaConstant> bootstrapArgs =
106+
bootstrapArgsProvider.getBootstrapArgsForMethod(classToProxy, method);
107+
builder = createProxyMethod(method, bootstrapArgs, builder);
108+
}
109+
}
110+
}
111+
return builder.make();
112+
}
113+
114+
private DynamicType.Builder<?> createProxyMethod(
115+
MethodDescription.InDefinedShape proxiedMethod,
116+
List<? extends JavaConstant> bootstrapArgs,
117+
DynamicType.Builder<?> builder) {
118+
InvokeDynamic body = InvokeDynamic.bootstrap(indyBootstrapMethod, bootstrapArgs);
119+
if (!proxiedMethod.isStatic()) {
120+
body = body.withField(DELEGATE_FIELD_NAME);
121+
}
122+
body = body.withMethodArguments();
123+
int modifiers = Modifier.PUBLIC | (proxiedMethod.isStatic() ? Modifier.STATIC : 0);
124+
return createProxyMethodOrConstructor(
125+
proxiedMethod,
126+
builder.defineMethod(proxiedMethod.getName(), proxiedMethod.getReturnType(), modifiers),
127+
body);
128+
}
129+
130+
private DynamicType.Builder<?> createProxyConstructor(
131+
TypeDescription.Generic superClass,
132+
MethodDescription.InDefinedShape proxiedConstructor,
133+
List<? extends JavaConstant> bootstrapArgs,
134+
DynamicType.Builder<?> builder) {
135+
MethodDescription defaultSuperCtor = findDefaultConstructor(superClass);
136+
137+
Implementation.Composable fieldAssignment =
138+
FieldAccessor.ofField(DELEGATE_FIELD_NAME)
139+
.setsValue(
140+
new StackManipulation.Compound(
141+
MethodVariableAccess.allArgumentsOf(proxiedConstructor),
142+
MethodInvocation.invoke(indyBootstrapMethod)
143+
.dynamic(
144+
"ctor", // the actual <init> method name is not allowed by the verifier
145+
TypeDescription.ForLoadedType.of(Object.class),
146+
proxiedConstructor.getParameters().asTypeList().asErasures(),
147+
bootstrapArgs)),
148+
Object.class);
149+
Implementation.Composable ctorBody =
150+
MethodCall.invoke(defaultSuperCtor).andThen(fieldAssignment);
151+
return createProxyMethodOrConstructor(
152+
proxiedConstructor, builder.defineConstructor(Modifier.PUBLIC), ctorBody);
153+
}
154+
155+
private static MethodDescription findDefaultConstructor(TypeDescription.Generic superClass) {
156+
return superClass.getDeclaredMethods().stream()
157+
.filter(MethodDescription::isConstructor)
158+
.filter(constructor -> constructor.getParameters().isEmpty())
159+
.findFirst()
160+
.orElseThrow(
161+
() ->
162+
new IllegalArgumentException(
163+
"Superclass of provided type does not define a default constructor"));
164+
}
165+
166+
private static DynamicType.Builder<?> createProxyMethodOrConstructor(
167+
MethodDescription.InDefinedShape method,
168+
DynamicType.Builder.MethodDefinition.ParameterDefinition<?> methodDef,
169+
Implementation methodBody) {
170+
for (ParameterDescription param : method.getParameters()) {
171+
methodDef =
172+
methodDef
173+
.withParameter(param.getType(), param.getName(), param.getModifiers())
174+
.annotateParameter(param.getDeclaredAnnotations());
175+
}
176+
return methodDef
177+
.throwing(method.getExceptionTypes())
178+
.intercept(methodBody)
179+
.annotateMethod(method.getDeclaredAnnotations());
180+
}
181+
}

0 commit comments

Comments
 (0)