Skip to content

Commit f74af54

Browse files
authored
Force dynamic typing on AssignReturned annotations (#11884)
1 parent 2100a16 commit f74af54

File tree

5 files changed

+265
-12
lines changed

5 files changed

+265
-12
lines changed

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/TypeTransformerImpl.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
99
import io.opentelemetry.javaagent.tooling.Utils;
1010
import io.opentelemetry.javaagent.tooling.bytebuddy.ExceptionHandlers;
11+
import io.opentelemetry.javaagent.tooling.instrumentation.indy.ForceDynamicallyTypedAssignReturnedFactory;
1112
import net.bytebuddy.agent.builder.AgentBuilder;
1213
import net.bytebuddy.asm.Advice;
1314
import net.bytebuddy.description.method.MethodDescription;
@@ -21,7 +22,9 @@ final class TypeTransformerImpl implements TypeTransformer {
2122
this.agentBuilder = agentBuilder;
2223
adviceMapping =
2324
Advice.withCustomMapping()
24-
.with(new Advice.AssignReturned.Factory().withSuppressed(Throwable.class));
25+
.with(
26+
new ForceDynamicallyTypedAssignReturnedFactory(
27+
new Advice.AssignReturned.Factory().withSuppressed(Throwable.class)));
2528
}
2629

2730
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.util.Arrays;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.function.Function;
13+
import java.util.stream.Collectors;
14+
import net.bytebuddy.asm.Advice;
15+
import net.bytebuddy.description.annotation.AnnotationDescription;
16+
import net.bytebuddy.description.annotation.AnnotationValue;
17+
import net.bytebuddy.description.enumeration.EnumerationDescription;
18+
import net.bytebuddy.description.method.MethodDescription;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
21+
import net.bytebuddy.matcher.ElementMatchers;
22+
23+
/**
24+
* This factory is designed to wrap around {@link Advice.PostProcessor.Factory} and ensures that
25+
* {@link net.bytebuddy.implementation.bytecode.assign.Assigner.Typing#DYNAMIC} is used everywhere.
26+
*
27+
* <p>This helps by avoiding errors where the instrumented bytecode is suddenly unloadable due to
28+
* incompatible assignments and preventing cluttering advice code annotations with the explicit
29+
* typing.
30+
*/
31+
public class ForceDynamicallyTypedAssignReturnedFactory implements Advice.PostProcessor.Factory {
32+
33+
private static final String TO_ARGUMENTS_TYPENAME =
34+
Advice.AssignReturned.ToArguments.class.getName();
35+
private static final String TO_ARGUMENT_TYPENAME =
36+
Advice.AssignReturned.ToArguments.ToArgument.class.getName();
37+
private static final String TO_ALL_ARGUMENTS_TYPENAME =
38+
Advice.AssignReturned.ToAllArguments.class.getName();
39+
private static final String TO_THIS_TYPENAME = Advice.AssignReturned.ToThis.class.getName();
40+
private static final String TO_FIELDS_TYPENAME = Advice.AssignReturned.ToFields.class.getName();
41+
private static final String TO_FIELD_TYPENAME =
42+
Advice.AssignReturned.ToFields.ToField.class.getName();
43+
private static final String TO_RETURNED_TYPENAME =
44+
Advice.AssignReturned.ToReturned.class.getName();
45+
private static final String TO_THROWN_TYPENAME = Advice.AssignReturned.ToThrown.class.getName();
46+
private static final EnumerationDescription DYNAMIC_TYPING =
47+
new EnumerationDescription.ForLoadedEnumeration(Assigner.Typing.DYNAMIC);
48+
49+
private final Advice.PostProcessor.Factory delegate;
50+
51+
public ForceDynamicallyTypedAssignReturnedFactory(Advice.PostProcessor.Factory delegate) {
52+
this.delegate = delegate;
53+
}
54+
55+
@Override
56+
public Advice.PostProcessor make(MethodDescription.InDefinedShape adviceMethod, boolean exit) {
57+
return delegate.make(forceDynamicTyping(adviceMethod), exit);
58+
}
59+
60+
// Visible for testing
61+
static MethodDescription.InDefinedShape forceDynamicTyping(
62+
MethodDescription.InDefinedShape adviceMethod) {
63+
return new MethodDescription.Latent(
64+
adviceMethod.getDeclaringType(),
65+
adviceMethod.getInternalName(),
66+
adviceMethod.getModifiers(),
67+
adviceMethod.getTypeVariables().asTokenList(ElementMatchers.none()),
68+
adviceMethod.getReturnType(),
69+
adviceMethod.getParameters().asTokenList(ElementMatchers.none()),
70+
adviceMethod.getExceptionTypes(),
71+
forceDynamicTyping(adviceMethod.getDeclaredAnnotations()),
72+
adviceMethod.getDefaultValue(),
73+
adviceMethod.getReceiverType());
74+
}
75+
76+
private static List<? extends AnnotationDescription> forceDynamicTyping(
77+
List<? extends AnnotationDescription> declaredAnnotations) {
78+
return declaredAnnotations.stream()
79+
.map(ForceDynamicallyTypedAssignReturnedFactory::forceDynamicTyping)
80+
.collect(Collectors.toList());
81+
}
82+
83+
private static AnnotationDescription forceDynamicTyping(AnnotationDescription anno) {
84+
85+
String name = anno.getAnnotationType().getName();
86+
if (name.equals(TO_FIELD_TYPENAME)
87+
|| name.equals(TO_ARGUMENT_TYPENAME)
88+
|| name.equals(TO_THIS_TYPENAME)
89+
|| name.equals(TO_ALL_ARGUMENTS_TYPENAME)
90+
|| name.equals(TO_RETURNED_TYPENAME)
91+
|| name.equals(TO_THROWN_TYPENAME)) {
92+
return replaceAnnotationValue(
93+
anno, "typing", oldVal -> AnnotationValue.ForEnumerationDescription.of(DYNAMIC_TYPING));
94+
} else if (name.equals(TO_FIELDS_TYPENAME) || name.equals(TO_ARGUMENTS_TYPENAME)) {
95+
return replaceAnnotationValue(
96+
anno,
97+
"value",
98+
oldVal -> {
99+
if (!oldVal.getState().isDefined()) {
100+
return null;
101+
}
102+
AnnotationDescription[] resolve = (AnnotationDescription[]) oldVal.resolve();
103+
if (resolve.length == 0) {
104+
return oldVal;
105+
}
106+
AnnotationDescription[] newValueList =
107+
Arrays.stream(resolve)
108+
.map(ForceDynamicallyTypedAssignReturnedFactory::forceDynamicTyping)
109+
.toArray(AnnotationDescription[]::new);
110+
TypeDescription subType = newValueList[0].getAnnotationType();
111+
return AnnotationValue.ForDescriptionArray.of(subType, newValueList);
112+
});
113+
}
114+
return anno;
115+
}
116+
117+
private static AnnotationDescription replaceAnnotationValue(
118+
AnnotationDescription anno,
119+
String propertyName,
120+
Function<AnnotationValue<?, ?>, AnnotationValue<?, ?>> valueMapper) {
121+
AnnotationValue<?, ?> oldValue = anno.getValue(propertyName);
122+
AnnotationValue<?, ?> newValue = valueMapper.apply(oldValue);
123+
Map<String, AnnotationValue<?, ?>> updatedValues = new HashMap<>();
124+
for (MethodDescription.InDefinedShape property :
125+
anno.getAnnotationType().getDeclaredMethods()) {
126+
AnnotationValue<?, ?> value = anno.getValue(property);
127+
if (!propertyName.equals(property.getName()) && value.getState().isDefined()) {
128+
updatedValues.put(property.getName(), value);
129+
}
130+
}
131+
if (newValue != null) {
132+
updatedValues.put(propertyName, newValue);
133+
}
134+
return new AnnotationDescription.Latent(anno.getAnnotationType(), updatedValues) {};
135+
}
136+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyTypeTransformerImpl.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ public IndyTypeTransformerImpl(
3030
this.instrumentationModule = module;
3131
this.adviceMapping =
3232
Advice.withCustomMapping()
33-
.with(new Advice.AssignReturned.Factory().withSuppressed(Throwable.class))
33+
.with(
34+
new ForceDynamicallyTypedAssignReturnedFactory(
35+
new Advice.AssignReturned.Factory().withSuppressed(Throwable.class)))
3436
.bootstrap(
3537
IndyBootstrap.getIndyBootstrapMethod(),
3638
IndyBootstrap.getAdviceBootstrapArguments(instrumentationModule));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 static org.assertj.core.api.Assertions.assertThat;
9+
10+
import net.bytebuddy.asm.Advice;
11+
import net.bytebuddy.asm.Advice.AssignReturned;
12+
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
13+
import net.bytebuddy.asm.Advice.AssignReturned.ToFields.ToField;
14+
import net.bytebuddy.description.annotation.AnnotationDescription;
15+
import net.bytebuddy.description.method.MethodDescription;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
18+
import org.junit.jupiter.api.Test;
19+
20+
public class ForceDynamicallyTypedAssignReturnedFactoryTest {
21+
22+
@AssignReturned.ToFields(@ToField(value = "foo", index = 42))
23+
@AssignReturned.ToArguments(@ToArgument(value = 3, index = 7))
24+
@AssignReturned.ToReturned(index = 4)
25+
@AssignReturned.ToThrown(index = 5)
26+
@AssignReturned.ToThis(index = 6)
27+
@AssignReturned.ToAllArguments(index = 7)
28+
@Advice.OnMethodEnter
29+
static void testMethod() {}
30+
31+
@Test
32+
public void checkTypingMadeDynamic() {
33+
MethodDescription.InDefinedShape original =
34+
TypeDescription.ForLoadedType.of(ForceDynamicallyTypedAssignReturnedFactoryTest.class)
35+
.getDeclaredMethods()
36+
.stream()
37+
.filter(method -> method.getName().equals("testMethod"))
38+
.findFirst()
39+
.get();
40+
41+
ClassLoader cl = ForceDynamicallyTypedAssignReturnedFactoryTest.class.getClassLoader();
42+
43+
MethodDescription modified =
44+
ForceDynamicallyTypedAssignReturnedFactory.forceDynamicTyping(original);
45+
assertThat(modified.getDeclaredAnnotations())
46+
.hasSize(7)
47+
.anySatisfy(
48+
toFields -> {
49+
assertThat(toFields.getAnnotationType().getName())
50+
.isEqualTo(AssignReturned.ToFields.class.getName());
51+
assertThat((AnnotationDescription[]) toFields.getValue("value").resolve())
52+
.hasSize(1)
53+
.anySatisfy(
54+
toField -> {
55+
assertThat(toField.getValue("value").resolve()).isEqualTo("foo");
56+
assertThat(toField.getValue("index").resolve()).isEqualTo(42);
57+
assertThat(toField.getValue("typing").load(cl).resolve())
58+
.isEqualTo(Assigner.Typing.DYNAMIC);
59+
});
60+
})
61+
.anySatisfy(
62+
toArguments -> {
63+
assertThat(toArguments.getAnnotationType().getName())
64+
.isEqualTo(AssignReturned.ToArguments.class.getName());
65+
assertThat((AnnotationDescription[]) toArguments.getValue("value").resolve())
66+
.hasSize(1)
67+
.anySatisfy(
68+
toArgument -> {
69+
assertThat(toArgument.getValue("value").resolve()).isEqualTo(3);
70+
assertThat(toArgument.getValue("index").resolve()).isEqualTo(7);
71+
assertThat(toArgument.getValue("typing").load(cl).resolve())
72+
.isEqualTo(Assigner.Typing.DYNAMIC);
73+
});
74+
})
75+
.anySatisfy(
76+
toReturned -> {
77+
assertThat(toReturned.getAnnotationType().getName())
78+
.isEqualTo(AssignReturned.ToReturned.class.getName());
79+
assertThat(toReturned.getValue("index").resolve()).isEqualTo(4);
80+
assertThat(toReturned.getValue("typing").load(cl).resolve())
81+
.isEqualTo(Assigner.Typing.DYNAMIC);
82+
})
83+
.anySatisfy(
84+
toThrown -> {
85+
assertThat(toThrown.getAnnotationType().getName())
86+
.isEqualTo(AssignReturned.ToThrown.class.getName());
87+
assertThat(toThrown.getValue("index").resolve()).isEqualTo(5);
88+
assertThat(toThrown.getValue("typing").load(cl).resolve())
89+
.isEqualTo(Assigner.Typing.DYNAMIC);
90+
})
91+
.anySatisfy(
92+
toThis -> {
93+
assertThat(toThis.getAnnotationType().getName())
94+
.isEqualTo(AssignReturned.ToThis.class.getName());
95+
assertThat(toThis.getValue("index").resolve()).isEqualTo(6);
96+
assertThat(toThis.getValue("typing").load(cl).resolve())
97+
.isEqualTo(Assigner.Typing.DYNAMIC);
98+
})
99+
.anySatisfy(
100+
toAllArguments -> {
101+
assertThat(toAllArguments.getAnnotationType().getName())
102+
.isEqualTo(AssignReturned.ToAllArguments.class.getName());
103+
assertThat(toAllArguments.getValue("index").resolve()).isEqualTo(7);
104+
assertThat(toAllArguments.getValue("typing").load(cl).resolve())
105+
.isEqualTo(Assigner.Typing.DYNAMIC);
106+
})
107+
.anySatisfy(
108+
onMethodEnter -> {
109+
assertThat(onMethodEnter.getAnnotationType().getName())
110+
.isEqualTo(Advice.OnMethodEnter.class.getName());
111+
});
112+
}
113+
}

testing-common/integration-tests/src/main/java/io/opentelemetry/javaagent/IndyInstrumentationTestModule.java

+9-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package io.opentelemetry.javaagent;
77

8-
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
98
import static net.bytebuddy.matcher.ElementMatchers.named;
109

1110
import com.google.auto.service.AutoService;
@@ -92,7 +91,7 @@ public void transform(TypeTransformer transformer) {
9291
public static class AssignFieldViaReturnAdvice {
9392

9493
@Advice.OnMethodEnter(inline = false)
95-
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", typing = DYNAMIC))
94+
@Advice.AssignReturned.ToFields(@ToField(value = "privateField"))
9695
public static String onEnter(@Advice.Argument(0) String toAssign) {
9796
return toAssign;
9897
}
@@ -102,7 +101,7 @@ public static String onEnter(@Advice.Argument(0) String toAssign) {
102101
public static class AssignFieldViaArrayAdvice {
103102

104103
@Advice.OnMethodEnter(inline = false)
105-
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", index = 1, typing = DYNAMIC))
104+
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", index = 1))
106105
public static Object[] onEnter(@Advice.Argument(0) String toAssign) {
107106
return new Object[] {"ignoreme", toAssign};
108107
}
@@ -112,7 +111,7 @@ public static Object[] onEnter(@Advice.Argument(0) String toAssign) {
112111
public static class AssignArgumentViaReturnAdvice {
113112

114113
@Advice.OnMethodEnter(inline = false)
115-
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, typing = DYNAMIC))
114+
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0))
116115
public static String onEnter(@Advice.Argument(1) String toAssign) {
117116
return toAssign;
118117
}
@@ -122,7 +121,7 @@ public static String onEnter(@Advice.Argument(1) String toAssign) {
122121
public static class AssignArgumentViaArrayAdvice {
123122

124123
@Advice.OnMethodEnter(inline = false)
125-
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, index = 1, typing = DYNAMIC))
124+
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, index = 1))
126125
public static Object[] onEnter(@Advice.Argument(1) String toAssign) {
127126
return new Object[] {"ignoreme", toAssign};
128127
}
@@ -132,7 +131,7 @@ public static Object[] onEnter(@Advice.Argument(1) String toAssign) {
132131
public static class AssignReturnViaReturnAdvice {
133132

134133
@Advice.OnMethodExit(inline = false)
135-
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
134+
@Advice.AssignReturned.ToReturned
136135
public static String onExit(@Advice.Argument(0) String toAssign) {
137136
return toAssign;
138137
}
@@ -142,7 +141,7 @@ public static String onExit(@Advice.Argument(0) String toAssign) {
142141
public static class AssignReturnViaArrayAdvice {
143142

144143
@Advice.OnMethodExit(inline = false)
145-
@Advice.AssignReturned.ToReturned(index = 1, typing = DYNAMIC)
144+
@Advice.AssignReturned.ToReturned(index = 1)
146145
public static Object[] onExit(@Advice.Argument(0) String toAssign) {
147146
return new Object[] {"ignoreme", toAssign};
148147
}
@@ -152,7 +151,7 @@ public static Object[] onExit(@Advice.Argument(0) String toAssign) {
152151
public static class GetHelperClassAdvice {
153152

154153
@Advice.OnMethodExit(inline = false)
155-
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
154+
@Advice.AssignReturned.ToReturned
156155
public static Class<?> onExit(@Advice.Argument(0) boolean localHelper) {
157156
if (localHelper) {
158157
return LocalHelper.class;
@@ -177,7 +176,7 @@ public static void onMethodEnter() {
177176
throw new RuntimeException("This exception should be suppressed");
178177
}
179178

180-
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
179+
@Advice.AssignReturned.ToReturned
181180
@Advice.OnMethodExit(
182181
suppress = Throwable.class,
183182
onThrowable = Throwable.class,
@@ -194,7 +193,7 @@ public static LocalHelper onMethodEnter() {
194193
return new LocalHelper();
195194
}
196195

197-
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
196+
@Advice.AssignReturned.ToReturned
198197
@Advice.OnMethodExit(
199198
suppress = Throwable.class,
200199
onThrowable = Throwable.class,

0 commit comments

Comments
 (0)