-
Notifications
You must be signed in to change notification settings - Fork 869
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Force dynamic typing on AssignReturned annotations (#11884)
- Loading branch information
Showing
5 changed files
with
265 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
...ry/javaagent/tooling/instrumentation/indy/ForceDynamicallyTypedAssignReturnedFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) {}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...avaagent/tooling/instrumentation/indy/ForceDynamicallyTypedAssignReturnedFactoryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters