Force dynamic typing on AssignReturned annotations (#11884)
This commit is contained in:
parent
2100a1646c
commit
f74af5476e
|
@ -8,6 +8,7 @@ package io.opentelemetry.javaagent.tooling.instrumentation;
|
|||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.tooling.Utils;
|
||||
import io.opentelemetry.javaagent.tooling.bytebuddy.ExceptionHandlers;
|
||||
import io.opentelemetry.javaagent.tooling.instrumentation.indy.ForceDynamicallyTypedAssignReturnedFactory;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
|
@ -21,7 +22,9 @@ final class TypeTransformerImpl implements TypeTransformer {
|
|||
this.agentBuilder = agentBuilder;
|
||||
adviceMapping =
|
||||
Advice.withCustomMapping()
|
||||
.with(new Advice.AssignReturned.Factory().withSuppressed(Throwable.class));
|
||||
.with(
|
||||
new ForceDynamicallyTypedAssignReturnedFactory(
|
||||
new Advice.AssignReturned.Factory().withSuppressed(Throwable.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.annotation.AnnotationValue;
|
||||
import net.bytebuddy.description.enumeration.EnumerationDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
/**
|
||||
* This factory is designed to wrap around {@link Advice.PostProcessor.Factory} and ensures that
|
||||
* {@link net.bytebuddy.implementation.bytecode.assign.Assigner.Typing#DYNAMIC} is used everywhere.
|
||||
*
|
||||
* <p>This helps by avoiding errors where the instrumented bytecode is suddenly unloadable due to
|
||||
* incompatible assignments and preventing cluttering advice code annotations with the explicit
|
||||
* typing.
|
||||
*/
|
||||
public class ForceDynamicallyTypedAssignReturnedFactory implements Advice.PostProcessor.Factory {
|
||||
|
||||
private static final String TO_ARGUMENTS_TYPENAME =
|
||||
Advice.AssignReturned.ToArguments.class.getName();
|
||||
private static final String TO_ARGUMENT_TYPENAME =
|
||||
Advice.AssignReturned.ToArguments.ToArgument.class.getName();
|
||||
private static final String TO_ALL_ARGUMENTS_TYPENAME =
|
||||
Advice.AssignReturned.ToAllArguments.class.getName();
|
||||
private static final String TO_THIS_TYPENAME = Advice.AssignReturned.ToThis.class.getName();
|
||||
private static final String TO_FIELDS_TYPENAME = Advice.AssignReturned.ToFields.class.getName();
|
||||
private static final String TO_FIELD_TYPENAME =
|
||||
Advice.AssignReturned.ToFields.ToField.class.getName();
|
||||
private static final String TO_RETURNED_TYPENAME =
|
||||
Advice.AssignReturned.ToReturned.class.getName();
|
||||
private static final String TO_THROWN_TYPENAME = Advice.AssignReturned.ToThrown.class.getName();
|
||||
private static final EnumerationDescription DYNAMIC_TYPING =
|
||||
new EnumerationDescription.ForLoadedEnumeration(Assigner.Typing.DYNAMIC);
|
||||
|
||||
private final Advice.PostProcessor.Factory delegate;
|
||||
|
||||
public ForceDynamicallyTypedAssignReturnedFactory(Advice.PostProcessor.Factory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice.PostProcessor make(MethodDescription.InDefinedShape adviceMethod, boolean exit) {
|
||||
return delegate.make(forceDynamicTyping(adviceMethod), exit);
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static MethodDescription.InDefinedShape forceDynamicTyping(
|
||||
MethodDescription.InDefinedShape adviceMethod) {
|
||||
return new MethodDescription.Latent(
|
||||
adviceMethod.getDeclaringType(),
|
||||
adviceMethod.getInternalName(),
|
||||
adviceMethod.getModifiers(),
|
||||
adviceMethod.getTypeVariables().asTokenList(ElementMatchers.none()),
|
||||
adviceMethod.getReturnType(),
|
||||
adviceMethod.getParameters().asTokenList(ElementMatchers.none()),
|
||||
adviceMethod.getExceptionTypes(),
|
||||
forceDynamicTyping(adviceMethod.getDeclaredAnnotations()),
|
||||
adviceMethod.getDefaultValue(),
|
||||
adviceMethod.getReceiverType());
|
||||
}
|
||||
|
||||
private static List<? extends AnnotationDescription> forceDynamicTyping(
|
||||
List<? extends AnnotationDescription> declaredAnnotations) {
|
||||
return declaredAnnotations.stream()
|
||||
.map(ForceDynamicallyTypedAssignReturnedFactory::forceDynamicTyping)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static AnnotationDescription forceDynamicTyping(AnnotationDescription anno) {
|
||||
|
||||
String name = anno.getAnnotationType().getName();
|
||||
if (name.equals(TO_FIELD_TYPENAME)
|
||||
|| name.equals(TO_ARGUMENT_TYPENAME)
|
||||
|| name.equals(TO_THIS_TYPENAME)
|
||||
|| name.equals(TO_ALL_ARGUMENTS_TYPENAME)
|
||||
|| name.equals(TO_RETURNED_TYPENAME)
|
||||
|| name.equals(TO_THROWN_TYPENAME)) {
|
||||
return replaceAnnotationValue(
|
||||
anno, "typing", oldVal -> AnnotationValue.ForEnumerationDescription.of(DYNAMIC_TYPING));
|
||||
} else if (name.equals(TO_FIELDS_TYPENAME) || name.equals(TO_ARGUMENTS_TYPENAME)) {
|
||||
return replaceAnnotationValue(
|
||||
anno,
|
||||
"value",
|
||||
oldVal -> {
|
||||
if (!oldVal.getState().isDefined()) {
|
||||
return null;
|
||||
}
|
||||
AnnotationDescription[] resolve = (AnnotationDescription[]) oldVal.resolve();
|
||||
if (resolve.length == 0) {
|
||||
return oldVal;
|
||||
}
|
||||
AnnotationDescription[] newValueList =
|
||||
Arrays.stream(resolve)
|
||||
.map(ForceDynamicallyTypedAssignReturnedFactory::forceDynamicTyping)
|
||||
.toArray(AnnotationDescription[]::new);
|
||||
TypeDescription subType = newValueList[0].getAnnotationType();
|
||||
return AnnotationValue.ForDescriptionArray.of(subType, newValueList);
|
||||
});
|
||||
}
|
||||
return anno;
|
||||
}
|
||||
|
||||
private static AnnotationDescription replaceAnnotationValue(
|
||||
AnnotationDescription anno,
|
||||
String propertyName,
|
||||
Function<AnnotationValue<?, ?>, AnnotationValue<?, ?>> valueMapper) {
|
||||
AnnotationValue<?, ?> oldValue = anno.getValue(propertyName);
|
||||
AnnotationValue<?, ?> newValue = valueMapper.apply(oldValue);
|
||||
Map<String, AnnotationValue<?, ?>> updatedValues = new HashMap<>();
|
||||
for (MethodDescription.InDefinedShape property :
|
||||
anno.getAnnotationType().getDeclaredMethods()) {
|
||||
AnnotationValue<?, ?> value = anno.getValue(property);
|
||||
if (!propertyName.equals(property.getName()) && value.getState().isDefined()) {
|
||||
updatedValues.put(property.getName(), value);
|
||||
}
|
||||
}
|
||||
if (newValue != null) {
|
||||
updatedValues.put(propertyName, newValue);
|
||||
}
|
||||
return new AnnotationDescription.Latent(anno.getAnnotationType(), updatedValues) {};
|
||||
}
|
||||
}
|
|
@ -30,7 +30,9 @@ public final class IndyTypeTransformerImpl implements TypeTransformer {
|
|||
this.instrumentationModule = module;
|
||||
this.adviceMapping =
|
||||
Advice.withCustomMapping()
|
||||
.with(new Advice.AssignReturned.Factory().withSuppressed(Throwable.class))
|
||||
.with(
|
||||
new ForceDynamicallyTypedAssignReturnedFactory(
|
||||
new Advice.AssignReturned.Factory().withSuppressed(Throwable.class)))
|
||||
.bootstrap(
|
||||
IndyBootstrap.getIndyBootstrapMethod(),
|
||||
IndyBootstrap.getAdviceBootstrapArguments(instrumentationModule));
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToFields.ToField;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ForceDynamicallyTypedAssignReturnedFactoryTest {
|
||||
|
||||
@AssignReturned.ToFields(@ToField(value = "foo", index = 42))
|
||||
@AssignReturned.ToArguments(@ToArgument(value = 3, index = 7))
|
||||
@AssignReturned.ToReturned(index = 4)
|
||||
@AssignReturned.ToThrown(index = 5)
|
||||
@AssignReturned.ToThis(index = 6)
|
||||
@AssignReturned.ToAllArguments(index = 7)
|
||||
@Advice.OnMethodEnter
|
||||
static void testMethod() {}
|
||||
|
||||
@Test
|
||||
public void checkTypingMadeDynamic() {
|
||||
MethodDescription.InDefinedShape original =
|
||||
TypeDescription.ForLoadedType.of(ForceDynamicallyTypedAssignReturnedFactoryTest.class)
|
||||
.getDeclaredMethods()
|
||||
.stream()
|
||||
.filter(method -> method.getName().equals("testMethod"))
|
||||
.findFirst()
|
||||
.get();
|
||||
|
||||
ClassLoader cl = ForceDynamicallyTypedAssignReturnedFactoryTest.class.getClassLoader();
|
||||
|
||||
MethodDescription modified =
|
||||
ForceDynamicallyTypedAssignReturnedFactory.forceDynamicTyping(original);
|
||||
assertThat(modified.getDeclaredAnnotations())
|
||||
.hasSize(7)
|
||||
.anySatisfy(
|
||||
toFields -> {
|
||||
assertThat(toFields.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToFields.class.getName());
|
||||
assertThat((AnnotationDescription[]) toFields.getValue("value").resolve())
|
||||
.hasSize(1)
|
||||
.anySatisfy(
|
||||
toField -> {
|
||||
assertThat(toField.getValue("value").resolve()).isEqualTo("foo");
|
||||
assertThat(toField.getValue("index").resolve()).isEqualTo(42);
|
||||
assertThat(toField.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
});
|
||||
})
|
||||
.anySatisfy(
|
||||
toArguments -> {
|
||||
assertThat(toArguments.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToArguments.class.getName());
|
||||
assertThat((AnnotationDescription[]) toArguments.getValue("value").resolve())
|
||||
.hasSize(1)
|
||||
.anySatisfy(
|
||||
toArgument -> {
|
||||
assertThat(toArgument.getValue("value").resolve()).isEqualTo(3);
|
||||
assertThat(toArgument.getValue("index").resolve()).isEqualTo(7);
|
||||
assertThat(toArgument.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
});
|
||||
})
|
||||
.anySatisfy(
|
||||
toReturned -> {
|
||||
assertThat(toReturned.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToReturned.class.getName());
|
||||
assertThat(toReturned.getValue("index").resolve()).isEqualTo(4);
|
||||
assertThat(toReturned.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
})
|
||||
.anySatisfy(
|
||||
toThrown -> {
|
||||
assertThat(toThrown.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToThrown.class.getName());
|
||||
assertThat(toThrown.getValue("index").resolve()).isEqualTo(5);
|
||||
assertThat(toThrown.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
})
|
||||
.anySatisfy(
|
||||
toThis -> {
|
||||
assertThat(toThis.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToThis.class.getName());
|
||||
assertThat(toThis.getValue("index").resolve()).isEqualTo(6);
|
||||
assertThat(toThis.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
})
|
||||
.anySatisfy(
|
||||
toAllArguments -> {
|
||||
assertThat(toAllArguments.getAnnotationType().getName())
|
||||
.isEqualTo(AssignReturned.ToAllArguments.class.getName());
|
||||
assertThat(toAllArguments.getValue("index").resolve()).isEqualTo(7);
|
||||
assertThat(toAllArguments.getValue("typing").load(cl).resolve())
|
||||
.isEqualTo(Assigner.Typing.DYNAMIC);
|
||||
})
|
||||
.anySatisfy(
|
||||
onMethodEnter -> {
|
||||
assertThat(onMethodEnter.getAnnotationType().getName())
|
||||
.isEqualTo(Advice.OnMethodEnter.class.getName());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package io.opentelemetry.javaagent;
|
||||
|
||||
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
|
@ -92,7 +91,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignFieldViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", typing = DYNAMIC))
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField"))
|
||||
public static String onEnter(@Advice.Argument(0) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
|
@ -102,7 +101,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignFieldViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", index = 1, typing = DYNAMIC))
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", index = 1))
|
||||
public static Object[] onEnter(@Advice.Argument(0) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
|
@ -112,7 +111,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignArgumentViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, typing = DYNAMIC))
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0))
|
||||
public static String onEnter(@Advice.Argument(1) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
|
@ -122,7 +121,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignArgumentViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, index = 1, typing = DYNAMIC))
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, index = 1))
|
||||
public static Object[] onEnter(@Advice.Argument(1) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
|
@ -132,7 +131,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignReturnViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
public static String onExit(@Advice.Argument(0) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
|
@ -142,7 +141,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class AssignReturnViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned(index = 1, typing = DYNAMIC)
|
||||
@Advice.AssignReturned.ToReturned(index = 1)
|
||||
public static Object[] onExit(@Advice.Argument(0) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
|
@ -152,7 +151,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
public static class GetHelperClassAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
public static Class<?> onExit(@Advice.Argument(0) boolean localHelper) {
|
||||
if (localHelper) {
|
||||
return LocalHelper.class;
|
||||
|
@ -177,7 +176,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
throw new RuntimeException("This exception should be suppressed");
|
||||
}
|
||||
|
||||
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
@Advice.OnMethodExit(
|
||||
suppress = Throwable.class,
|
||||
onThrowable = Throwable.class,
|
||||
|
@ -194,7 +193,7 @@ public class IndyInstrumentationTestModule extends InstrumentationModule
|
|||
return new LocalHelper();
|
||||
}
|
||||
|
||||
@Advice.AssignReturned.ToReturned(typing = DYNAMIC)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
@Advice.OnMethodExit(
|
||||
suppress = Throwable.class,
|
||||
onThrowable = Throwable.class,
|
||||
|
|
Loading…
Reference in New Issue