Implement invokedynamic advice bootstrapping (#9382)
This commit is contained in:
parent
3b77cc4b2d
commit
10480adc64
|
|
@ -105,8 +105,9 @@ tasks {
|
|||
// some moving.
|
||||
disable("DefaultPackage")
|
||||
|
||||
// we use modified OtelPrivateConstructorForUtilityClass which ignores *Advice classes
|
||||
// we use modified Otel* checks which ignore *Advice classes
|
||||
disable("PrivateConstructorForUtilityClass")
|
||||
disable("CanIgnoreReturnValueSuggester")
|
||||
|
||||
// TODO(anuraaga): Remove this, probably after instrumenter API migration instead of dealing
|
||||
// with older APIs.
|
||||
|
|
@ -125,9 +126,9 @@ tasks {
|
|||
// Allow underscore in test-type method names
|
||||
disable("MemberName")
|
||||
}
|
||||
if (project.path.endsWith(":testing") || name.contains("Test")) {
|
||||
if ((project.path.endsWith(":testing") || name.contains("Test")) && !project.name.equals("custom-checks")) {
|
||||
// This check causes too many failures, ignore the ones in tests
|
||||
disable("CanIgnoreReturnValueSuggester")
|
||||
disable("OtelCanIgnoreReturnValueSuggester")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.customchecks;
|
||||
|
||||
import static com.google.errorprone.matchers.Description.NO_MATCH;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.checkreturnvalue.CanIgnoreReturnValueSuggester;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"Methods with ignorable return values (including methods that always 'return this') should be annotated with @com.google.errorprone.annotations.CanIgnoreReturnValue",
|
||||
severity = BugPattern.SeverityLevel.WARNING)
|
||||
public class OtelCanIgnoreReturnValueSuggester extends BugChecker
|
||||
implements BugChecker.MethodTreeMatcher {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CanIgnoreReturnValueSuggester delegate = new CanIgnoreReturnValueSuggester();
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree methodTree, VisitorState visitorState) {
|
||||
ClassTree containerClass = findContainingClass(visitorState.getPath());
|
||||
if (containerClass.getSimpleName().toString().endsWith("Advice")) {
|
||||
return NO_MATCH;
|
||||
}
|
||||
Description description = delegate.matchMethod(methodTree, visitorState);
|
||||
if (description == NO_MATCH) {
|
||||
return description;
|
||||
}
|
||||
return describeMatch(methodTree);
|
||||
}
|
||||
|
||||
private static ClassTree findContainingClass(TreePath path) {
|
||||
TreePath parent = path.getParentPath();
|
||||
while (parent != null && !(parent.getLeaf() instanceof ClassTree)) {
|
||||
parent = parent.getParentPath();
|
||||
}
|
||||
if (parent == null) {
|
||||
throw new IllegalStateException(
|
||||
"Method is expected to be contained in a class, something must be wrong");
|
||||
}
|
||||
ClassTree containerClass = (ClassTree) parent.getLeaf();
|
||||
return containerClass;
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ public final class SupportabilityMetrics {
|
|||
}
|
||||
|
||||
// this private method is designed for assignment of the return value
|
||||
@SuppressWarnings("CanIgnoreReturnValueSuggester")
|
||||
@SuppressWarnings("OtelCanIgnoreReturnValueSuggester")
|
||||
private SupportabilityMetrics start() {
|
||||
if (agentDebugEnabled) {
|
||||
ScheduledExecutorService executor =
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public class WeakConcurrentMap<K, V>
|
|||
private K key;
|
||||
private int hashCode;
|
||||
|
||||
@SuppressWarnings("CanIgnoreReturnValueSuggester")
|
||||
@SuppressWarnings("OtelCanIgnoreReturnValueSuggester")
|
||||
LookupKey<K> withValue(K key) {
|
||||
this.key = key;
|
||||
hashCode = System.identityHashCode(key);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ package io.opentelemetry.instrumentation.micrometer.v1_5;
|
|||
|
||||
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
||||
|
||||
@SuppressWarnings("CanIgnoreReturnValueSuggester")
|
||||
@SuppressWarnings("OtelCanIgnoreReturnValueSuggester")
|
||||
enum DistributionStatisticConfigModifier {
|
||||
DISABLE_HISTOGRAM_GAUGES {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.bootstrap;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.ConstantCallSite;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
/**
|
||||
* Contains the bootstrap method for initializing invokedynamic callsites which are added via agent
|
||||
* instrumentation.
|
||||
*/
|
||||
public class IndyBootstrapDispatcher {
|
||||
|
||||
private static volatile MethodHandle bootstrap;
|
||||
|
||||
private IndyBootstrapDispatcher() {}
|
||||
|
||||
/**
|
||||
* Initialized the invokedynamic bootstrapping method to which this class will delegate.
|
||||
*
|
||||
* @param bootstrapMethod the method to delegate to. Must have the same type as {@link
|
||||
* #bootstrap}.
|
||||
*/
|
||||
public static void init(MethodHandle bootstrapMethod) {
|
||||
bootstrap = bootstrapMethod;
|
||||
}
|
||||
|
||||
public static CallSite bootstrap(
|
||||
MethodHandles.Lookup lookup,
|
||||
String adviceMethodName,
|
||||
MethodType adviceMethodType,
|
||||
Object... args) {
|
||||
CallSite callSite = null;
|
||||
if (bootstrap != null) {
|
||||
try {
|
||||
callSite = (CallSite) bootstrap.invoke(lookup, adviceMethodName, adviceMethodType, args);
|
||||
} catch (Throwable e) {
|
||||
ExceptionLogger.logSuppressedError("Error bootstrapping indy instruction", e);
|
||||
}
|
||||
}
|
||||
if (callSite == null) {
|
||||
// The MethodHandle pointing to the Advice could not be created for some reason,
|
||||
// fallback to a Noop MethodHandle to not crash the application
|
||||
MethodHandle noop = generateNoopMethodHandle(adviceMethodType);
|
||||
callSite = new ConstantCallSite(noop);
|
||||
}
|
||||
return callSite;
|
||||
}
|
||||
|
||||
// package visibility for testing
|
||||
static MethodHandle generateNoopMethodHandle(MethodType methodType) {
|
||||
Class<?> returnType = methodType.returnType();
|
||||
MethodHandle noopNoArg;
|
||||
if (returnType == void.class) {
|
||||
noopNoArg =
|
||||
MethodHandles.constant(Void.class, null).asType(MethodType.methodType(void.class));
|
||||
} else {
|
||||
noopNoArg = MethodHandles.constant(returnType, getDefaultValue(returnType));
|
||||
}
|
||||
return MethodHandles.dropArguments(noopNoArg, 0, methodType.parameterList());
|
||||
}
|
||||
|
||||
private static Object getDefaultValue(Class<?> classOrPrimitive) {
|
||||
if (classOrPrimitive.isPrimitive()) {
|
||||
// arrays of primitives are initialized with the correct primitive default value (e.g. 0 for
|
||||
// int.class)
|
||||
// we use this fact to generate the correct default value reflectively
|
||||
return Array.get(Array.newInstance(classOrPrimitive, 1), 0);
|
||||
} else {
|
||||
return null; // null is the default value for reference types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.bootstrap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class IndyBootstrapDispatcherTest {
|
||||
|
||||
@Test
|
||||
void testVoidNoopMethodHandle() throws Throwable {
|
||||
MethodHandle noArg = generateAndCheck(MethodType.methodType(void.class));
|
||||
noArg.invokeExact();
|
||||
|
||||
MethodHandle intArg = generateAndCheck(MethodType.methodType(void.class, int.class));
|
||||
intArg.invokeExact(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntNoopMethodHandle() throws Throwable {
|
||||
MethodHandle noArg = generateAndCheck(MethodType.methodType(int.class));
|
||||
assertThat((int) noArg.invokeExact()).isEqualTo(0);
|
||||
|
||||
MethodHandle intArg = generateAndCheck(MethodType.methodType(int.class, int.class));
|
||||
assertThat((int) intArg.invokeExact(42)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanNoopMethodHandle() throws Throwable {
|
||||
MethodHandle noArg = generateAndCheck(MethodType.methodType(boolean.class));
|
||||
assertThat((boolean) noArg.invokeExact()).isEqualTo(false);
|
||||
|
||||
MethodHandle intArg = generateAndCheck(MethodType.methodType(boolean.class, int.class));
|
||||
assertThat((boolean) intArg.invokeExact(42)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReferenceNoopMethodHandle() throws Throwable {
|
||||
MethodHandle noArg = generateAndCheck(MethodType.methodType(Runnable.class));
|
||||
assertThat((Runnable) noArg.invokeExact()).isEqualTo(null);
|
||||
|
||||
MethodHandle intArg = generateAndCheck(MethodType.methodType(Runnable.class, int.class));
|
||||
assertThat((Runnable) intArg.invokeExact(42)).isEqualTo(null);
|
||||
}
|
||||
|
||||
private static MethodHandle generateAndCheck(MethodType type) {
|
||||
MethodHandle mh = IndyBootstrapDispatcher.generateNoopMethodHandle(type);
|
||||
assertThat(mh.type()).isEqualTo(type);
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,9 @@ import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
|
|||
import io.opentelemetry.javaagent.tooling.config.AgentConfig;
|
||||
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstaller;
|
||||
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstallerFactory;
|
||||
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyModuleRegistry;
|
||||
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyTypeTransformerImpl;
|
||||
import io.opentelemetry.javaagent.tooling.instrumentation.indy.PatchByteCodeVersionTransformer;
|
||||
import io.opentelemetry.javaagent.tooling.muzzle.HelperResourceBuilderImpl;
|
||||
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle;
|
||||
import io.opentelemetry.javaagent.tooling.util.IgnoreFailedTypeMatcher;
|
||||
|
|
@ -62,6 +65,52 @@ public final class InstrumentationModuleInstaller {
|
|||
FINE, "Instrumentation {0} is disabled", instrumentationModule.instrumentationName());
|
||||
return parentAgentBuilder;
|
||||
}
|
||||
|
||||
if (instrumentationModule.isIndyModule()) {
|
||||
return installIndyModule(instrumentationModule, parentAgentBuilder);
|
||||
} else {
|
||||
return installInjectingModule(instrumentationModule, parentAgentBuilder, config);
|
||||
}
|
||||
}
|
||||
|
||||
private AgentBuilder installIndyModule(
|
||||
InstrumentationModule instrumentationModule, AgentBuilder parentAgentBuilder) {
|
||||
|
||||
IndyModuleRegistry.registerIndyModule(instrumentationModule);
|
||||
|
||||
// TODO (Jonas): Adapt MuzzleMatcher to use the same type lookup strategy as the
|
||||
// InstrumentationModuleClassLoader
|
||||
// MuzzleMatcher muzzleMatcher = new MuzzleMatcher(logger, instrumentationModule, config);
|
||||
VirtualFieldImplementationInstaller contextProvider =
|
||||
virtualFieldInstallerFactory.create(instrumentationModule);
|
||||
|
||||
AgentBuilder agentBuilder = parentAgentBuilder;
|
||||
for (TypeInstrumentation typeInstrumentation : instrumentationModule.typeInstrumentations()) {
|
||||
AgentBuilder.Identified.Extendable extendableAgentBuilder =
|
||||
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
|
||||
.transform(new PatchByteCodeVersionTransformer());
|
||||
|
||||
IndyTypeTransformerImpl typeTransformer =
|
||||
new IndyTypeTransformerImpl(extendableAgentBuilder, instrumentationModule);
|
||||
typeInstrumentation.transform(typeTransformer);
|
||||
extendableAgentBuilder = typeTransformer.getAgentBuilder();
|
||||
// TODO (Jonas): make instrumentation of bytecode older than 1.4 opt-in via a config option
|
||||
// TODO (Jonas): we are not calling
|
||||
// contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder) anymore
|
||||
// As a result the advices should store `VirtualFields` as static variables instead of having
|
||||
// the lookup inline
|
||||
// We need to update our documentation on that
|
||||
extendableAgentBuilder = contextProvider.injectFields(extendableAgentBuilder);
|
||||
|
||||
agentBuilder = extendableAgentBuilder;
|
||||
}
|
||||
return agentBuilder;
|
||||
}
|
||||
|
||||
private AgentBuilder installInjectingModule(
|
||||
InstrumentationModule instrumentationModule,
|
||||
AgentBuilder parentAgentBuilder,
|
||||
ConfigProperties config) {
|
||||
List<String> helperClassNames =
|
||||
InstrumentationModuleMuzzle.getHelperClassNames(instrumentationModule);
|
||||
HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
|
||||
|
|
@ -78,8 +127,6 @@ public final class InstrumentationModuleInstaller {
|
|||
return parentAgentBuilder;
|
||||
}
|
||||
|
||||
ElementMatcher.Junction<ClassLoader> moduleClassLoaderMatcher =
|
||||
instrumentationModule.classLoaderMatcher();
|
||||
MuzzleMatcher muzzleMatcher = new MuzzleMatcher(logger, instrumentationModule, config);
|
||||
AgentBuilder.Transformer helperInjector =
|
||||
new HelperInjector(
|
||||
|
|
@ -93,32 +140,9 @@ public final class InstrumentationModuleInstaller {
|
|||
|
||||
AgentBuilder agentBuilder = parentAgentBuilder;
|
||||
for (TypeInstrumentation typeInstrumentation : typeInstrumentations) {
|
||||
ElementMatcher<TypeDescription> typeMatcher =
|
||||
new NamedMatcher<>(
|
||||
instrumentationModule.getClass().getSimpleName()
|
||||
+ "#"
|
||||
+ typeInstrumentation.getClass().getSimpleName(),
|
||||
new IgnoreFailedTypeMatcher(typeInstrumentation.typeMatcher()));
|
||||
ElementMatcher<ClassLoader> classLoaderMatcher =
|
||||
new NamedMatcher<>(
|
||||
instrumentationModule.getClass().getSimpleName()
|
||||
+ "#"
|
||||
+ typeInstrumentation.getClass().getSimpleName(),
|
||||
moduleClassLoaderMatcher.and(typeInstrumentation.classLoaderOptimization()));
|
||||
|
||||
AgentBuilder.Identified.Extendable extendableAgentBuilder =
|
||||
agentBuilder
|
||||
.type(
|
||||
new LoggingFailSafeMatcher<>(
|
||||
typeMatcher,
|
||||
"Instrumentation type matcher unexpected exception: " + typeMatcher),
|
||||
new LoggingFailSafeMatcher<>(
|
||||
classLoaderMatcher,
|
||||
"Instrumentation class loader matcher unexpected exception: "
|
||||
+ classLoaderMatcher))
|
||||
.and(
|
||||
(typeDescription, classLoader, module, classBeingRedefined, protectionDomain) ->
|
||||
classLoader == null || NOT_DECORATOR_MATCHER.matches(typeDescription))
|
||||
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
|
||||
.and(muzzleMatcher)
|
||||
.transform(ConstantAdjuster.instance())
|
||||
.transform(helperInjector);
|
||||
|
|
@ -133,4 +157,37 @@ public final class InstrumentationModuleInstaller {
|
|||
|
||||
return agentBuilder;
|
||||
}
|
||||
|
||||
private static AgentBuilder.Identified.Narrowable setTypeMatcher(
|
||||
AgentBuilder agentBuilder,
|
||||
InstrumentationModule instrumentationModule,
|
||||
TypeInstrumentation typeInstrumentation) {
|
||||
|
||||
ElementMatcher.Junction<ClassLoader> moduleClassLoaderMatcher =
|
||||
instrumentationModule.classLoaderMatcher();
|
||||
|
||||
ElementMatcher<TypeDescription> typeMatcher =
|
||||
new NamedMatcher<>(
|
||||
instrumentationModule.getClass().getSimpleName()
|
||||
+ "#"
|
||||
+ typeInstrumentation.getClass().getSimpleName(),
|
||||
new IgnoreFailedTypeMatcher(typeInstrumentation.typeMatcher()));
|
||||
ElementMatcher<ClassLoader> classLoaderMatcher =
|
||||
new NamedMatcher<>(
|
||||
instrumentationModule.getClass().getSimpleName()
|
||||
+ "#"
|
||||
+ typeInstrumentation.getClass().getSimpleName(),
|
||||
moduleClassLoaderMatcher.and(typeInstrumentation.classLoaderOptimization()));
|
||||
|
||||
return agentBuilder
|
||||
.type(
|
||||
new LoggingFailSafeMatcher<>(
|
||||
typeMatcher, "Instrumentation type matcher unexpected exception: " + typeMatcher),
|
||||
new LoggingFailSafeMatcher<>(
|
||||
classLoaderMatcher,
|
||||
"Instrumentation class loader matcher unexpected exception: " + classLoaderMatcher))
|
||||
.and(
|
||||
(typeDescription, classLoader, module, classBeingRedefined, protectionDomain) ->
|
||||
classLoader == null || NOT_DECORATOR_MATCHER.matches(typeDescription));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
||||
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.IndyBootstrapDispatcher;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.ConstantCallSite;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.utility.JavaConstant;
|
||||
|
||||
/**
|
||||
* We instruct Byte Buddy (via {@link Advice.WithCustomMapping#bootstrap(java.lang.reflect.Method)})
|
||||
* to dispatch {@linkplain Advice.OnMethodEnter#inline() non-inlined advices} via an invokedynamic
|
||||
* (indy) instruction. The target method is linked to a dynamically created instrumentation module
|
||||
* class loader that is specific to an instrumentation module and the class loader of the
|
||||
* instrumented method.
|
||||
*
|
||||
* <p>The first invocation of an {@code INVOKEDYNAMIC} causes the JVM to dynamically link a {@link
|
||||
* CallSite}. In this case, it will use the {@link #bootstrap} method to do that. This will also
|
||||
* create the {@link InstrumentationModuleClassLoader}.
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* Bootstrap CL ←──────────────────────────── Agent CL
|
||||
* ↑ └───────── IndyBootstrapDispatcher ─ ↑ ──→ └────────────── {@link IndyBootstrap#bootstrap}
|
||||
* Ext/Platform CL ↑ │ ╷
|
||||
* ↑ ╷ │ ↓
|
||||
* System CL ╷ │ {@link IndyModuleRegistry#getInstrumentationClassloader(String, ClassLoader)}
|
||||
* ↑ ╷ │ ╷
|
||||
* Common linking of CallSite │ ╷
|
||||
* ↑ ↑ (on first invocation) │ ╷
|
||||
* WebApp1 WebApp2 ╷ │ creates
|
||||
* ↑ - InstrumentedClass ╷ │ ╷
|
||||
* │ ╷ ╷ │ ╷
|
||||
* │ INVOKEDYNAMIC │ ↓
|
||||
* └────────────────┼──────────────────{@link InstrumentationModuleClassLoader}
|
||||
* └╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶→├ AdviceClass
|
||||
* ├ AdviceHelper
|
||||
* └ {@link LookupExposer}
|
||||
*
|
||||
* Legend:
|
||||
* ╶╶→ method calls
|
||||
* ──→ class loader parent/child relationships
|
||||
* </pre>
|
||||
*/
|
||||
public class IndyBootstrap {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IndyBootstrap.class.getName());
|
||||
|
||||
private static final Method indyBootstrapMethod;
|
||||
|
||||
private static final CallDepth callDepth = CallDepth.forClass(IndyBootstrap.class);
|
||||
|
||||
static {
|
||||
try {
|
||||
indyBootstrapMethod =
|
||||
IndyBootstrapDispatcher.class.getMethod(
|
||||
"bootstrap",
|
||||
MethodHandles.Lookup.class,
|
||||
String.class,
|
||||
MethodType.class,
|
||||
Object[].class);
|
||||
|
||||
MethodType bootstrapMethodType =
|
||||
MethodType.methodType(
|
||||
ConstantCallSite.class,
|
||||
MethodHandles.Lookup.class,
|
||||
String.class,
|
||||
MethodType.class,
|
||||
Object[].class);
|
||||
|
||||
IndyBootstrapDispatcher.init(
|
||||
MethodHandles.lookup().findStatic(IndyBootstrap.class, "bootstrap", bootstrapMethodType));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IndyBootstrap() {}
|
||||
|
||||
public static Method getIndyBootstrapMethod() {
|
||||
return indyBootstrapMethod;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"unused", "removal"}) // SecurityManager and AccessController are deprecated
|
||||
private static ConstantCallSite bootstrap(
|
||||
MethodHandles.Lookup lookup,
|
||||
String adviceMethodName,
|
||||
MethodType adviceMethodType,
|
||||
Object[] args) {
|
||||
|
||||
if (System.getSecurityManager() == null) {
|
||||
return internalBootstrap(lookup, adviceMethodName, adviceMethodType, args);
|
||||
}
|
||||
|
||||
// callsite resolution needs privileged access to call Class#getClassLoader() and
|
||||
// MethodHandles$Lookup#findStatic
|
||||
return java.security.AccessController.doPrivileged(
|
||||
(PrivilegedAction<ConstantCallSite>)
|
||||
() -> internalBootstrap(lookup, adviceMethodName, adviceMethodType, args));
|
||||
}
|
||||
|
||||
private static ConstantCallSite internalBootstrap(
|
||||
MethodHandles.Lookup lookup,
|
||||
String adviceMethodName,
|
||||
MethodType adviceMethodType,
|
||||
Object[] args) {
|
||||
try {
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
// avoid re-entrancy and stack overflow errors, which may happen when bootstrapping an
|
||||
// instrumentation that also gets triggered during the bootstrap
|
||||
// for example, adding correlation ids to the thread context when executing logger.debug.
|
||||
logger.log(
|
||||
Level.WARNING,
|
||||
"Nested instrumented invokedynamic instruction linkage detected",
|
||||
new Throwable());
|
||||
return null;
|
||||
}
|
||||
// See the getAdviceBootstrapArguments method for where these arguments come from
|
||||
String moduleClassName = (String) args[0];
|
||||
String adviceClassName = (String) args[1];
|
||||
|
||||
InstrumentationModuleClassLoader instrumentationClassloader =
|
||||
IndyModuleRegistry.getInstrumentationClassloader(
|
||||
moduleClassName, lookup.lookupClass().getClassLoader());
|
||||
|
||||
// Advices are not inlined. They are loaded as normal classes by the
|
||||
// InstrumentationModuleClassloader and invoked via a method call from the instrumented method
|
||||
Class<?> adviceClass = instrumentationClassloader.loadClass(adviceClassName);
|
||||
MethodHandle methodHandle =
|
||||
instrumentationClassloader
|
||||
.getLookup()
|
||||
.findStatic(adviceClass, adviceMethodName, adviceMethodType);
|
||||
return new ConstantCallSite(methodHandle);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
return null;
|
||||
} finally {
|
||||
callDepth.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
static Advice.BootstrapArgumentResolver.Factory getAdviceBootstrapArguments(
|
||||
InstrumentationModule instrumentationModule) {
|
||||
String moduleName = instrumentationModule.getClass().getName();
|
||||
return (adviceMethod, exit) ->
|
||||
(instrumentedType, instrumentedMethod) ->
|
||||
Arrays.asList(
|
||||
JavaConstant.Simple.ofLoaded(moduleName),
|
||||
JavaConstant.Simple.ofLoaded(adviceMethod.getDeclaringType().getName()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
public class IndyModuleRegistry {
|
||||
|
||||
private IndyModuleRegistry() {}
|
||||
|
||||
private static final ConcurrentHashMap<String, InstrumentationModule> modulesByName =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Weakly references the {@link InstrumentationModuleClassLoader}s for a given application
|
||||
* classloader. We only store weak references to make sure we don't prevent application
|
||||
* classloaders from being GCed. The application classloaders will strongly reference the {@link
|
||||
* InstrumentationModuleClassLoader} through the invokedynamic callsites.
|
||||
*/
|
||||
private static final ConcurrentHashMap<
|
||||
InstrumentationModule,
|
||||
Cache<ClassLoader, WeakReference<InstrumentationModuleClassLoader>>>
|
||||
instrumentationClassloaders = new ConcurrentHashMap<>();
|
||||
|
||||
public static InstrumentationModuleClassLoader getInstrumentationClassloader(
|
||||
String moduleClassName, ClassLoader instrumentedClassloader) {
|
||||
InstrumentationModule instrumentationModule = modulesByName.get(moduleClassName);
|
||||
if (instrumentationModule == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"No module with the class name " + modulesByName + " has been registered!");
|
||||
}
|
||||
return getInstrumentationClassloader(instrumentationModule, instrumentedClassloader);
|
||||
}
|
||||
|
||||
private static synchronized InstrumentationModuleClassLoader getInstrumentationClassloader(
|
||||
InstrumentationModule module, ClassLoader instrumentedClassloader) {
|
||||
|
||||
Cache<ClassLoader, WeakReference<InstrumentationModuleClassLoader>> cacheForModule =
|
||||
instrumentationClassloaders.computeIfAbsent(module, (k) -> Cache.weak());
|
||||
|
||||
WeakReference<InstrumentationModuleClassLoader> cached =
|
||||
cacheForModule.get(instrumentedClassloader);
|
||||
if (cached != null) {
|
||||
InstrumentationModuleClassLoader cachedCl = cached.get();
|
||||
if (cachedCl != null) {
|
||||
return cachedCl;
|
||||
}
|
||||
}
|
||||
// We can't directly use "compute-if-absent" here because then for a short time only the
|
||||
// WeakReference will point to the InstrumentationModuleCL
|
||||
InstrumentationModuleClassLoader created =
|
||||
createInstrumentationModuleClassloader(module, instrumentedClassloader);
|
||||
cacheForModule.put(instrumentedClassloader, new WeakReference<>(created));
|
||||
return created;
|
||||
}
|
||||
|
||||
private static InstrumentationModuleClassLoader createInstrumentationModuleClassloader(
|
||||
InstrumentationModule module, ClassLoader instrumentedClassloader) {
|
||||
|
||||
Set<String> toInject = new HashSet<>(InstrumentationModuleMuzzle.getHelperClassNames(module));
|
||||
// TODO (Jonas): Make muzzle include advice classes as helper classes
|
||||
// so that we don't have to include them here
|
||||
toInject.addAll(getModuleAdviceNames(module));
|
||||
|
||||
ClassLoader agentOrExtensionCl = module.getClass().getClassLoader();
|
||||
Map<String, ClassCopySource> injectedClasses =
|
||||
toInject.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
name -> name, name -> ClassCopySource.create(name, agentOrExtensionCl)));
|
||||
|
||||
return new InstrumentationModuleClassLoader(
|
||||
instrumentedClassloader, agentOrExtensionCl, injectedClasses);
|
||||
}
|
||||
|
||||
public static void registerIndyModule(InstrumentationModule module) {
|
||||
if (!module.isIndyModule()) {
|
||||
throw new IllegalArgumentException("Provided module is not an indy module!");
|
||||
}
|
||||
String moduleName = module.getClass().getName();
|
||||
if (modulesByName.putIfAbsent(moduleName, module) != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"A module with the class name " + moduleName + " has already been registered!");
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> getModuleAdviceNames(InstrumentationModule module) {
|
||||
Set<String> adviceNames = new HashSet<>();
|
||||
TypeTransformer nameCollector =
|
||||
new TypeTransformer() {
|
||||
@Override
|
||||
public void applyAdviceToMethod(
|
||||
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName) {
|
||||
adviceNames.add(adviceClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTransformer(AgentBuilder.Transformer transformer) {}
|
||||
};
|
||||
for (TypeInstrumentation instr : module.typeInstrumentations()) {
|
||||
instr.transform(nameCollector);
|
||||
}
|
||||
return adviceNames;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
||||
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.tooling.bytebuddy.ExceptionHandlers;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
public final class IndyTypeTransformerImpl implements TypeTransformer {
|
||||
private final Advice.WithCustomMapping adviceMapping;
|
||||
private AgentBuilder.Identified.Extendable agentBuilder;
|
||||
private final InstrumentationModule instrumentationModule;
|
||||
|
||||
public IndyTypeTransformerImpl(
|
||||
AgentBuilder.Identified.Extendable agentBuilder, InstrumentationModule module) {
|
||||
this.agentBuilder = agentBuilder;
|
||||
this.instrumentationModule = module;
|
||||
this.adviceMapping =
|
||||
Advice.withCustomMapping()
|
||||
.with(new Advice.AssignReturned.Factory().withSuppressed(Throwable.class))
|
||||
.bootstrap(
|
||||
IndyBootstrap.getIndyBootstrapMethod(),
|
||||
IndyBootstrap.getAdviceBootstrapArguments(instrumentationModule));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyAdviceToMethod(
|
||||
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName) {
|
||||
agentBuilder =
|
||||
agentBuilder.transform(
|
||||
new AgentBuilder.Transformer.ForAdvice(adviceMapping)
|
||||
.advice(methodMatcher, adviceClassName)
|
||||
.include(instrumentationModule.getClass().getClassLoader())
|
||||
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTransformer(AgentBuilder.Transformer transformer) {
|
||||
agentBuilder = agentBuilder.transform(transformer);
|
||||
}
|
||||
|
||||
public AgentBuilder.Identified.Extendable getAgentBuilder() {
|
||||
return agentBuilder;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,10 +45,27 @@ tasks {
|
|||
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
|
||||
}
|
||||
|
||||
val testIndyModuleOldBytecodeInstrumentation by registering(Test::class) {
|
||||
filter {
|
||||
includeTestsMatching("InstrumentOldBytecode")
|
||||
}
|
||||
include("**/InstrumentOldBytecode.*")
|
||||
jvmArgs("-Dotel.instrumentation.inline-ibm-resource-level.enabled=false")
|
||||
}
|
||||
|
||||
val testInlineModuleOldBytecodeInstrumentation by registering(Test::class) {
|
||||
filter {
|
||||
includeTestsMatching("InstrumentOldBytecode")
|
||||
}
|
||||
include("**/InstrumentOldBytecode.*")
|
||||
jvmArgs("-Dotel.instrumentation.indy-ibm-resource-level.enabled=false")
|
||||
}
|
||||
|
||||
test {
|
||||
filter {
|
||||
excludeTestsMatching("context.FieldInjectionDisabledTest")
|
||||
excludeTestsMatching("context.FieldBackedImplementationTest")
|
||||
excludeTestsMatching("InstrumentOldBytecode")
|
||||
}
|
||||
// this is needed for AgentInstrumentationSpecificationTest
|
||||
jvmArgs("-Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass\$NestedClass")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import java.util.List;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class IndyIbmResourceLevelInstrumentationModule extends InstrumentationModule {
|
||||
public IndyIbmResourceLevelInstrumentationModule() {
|
||||
super("indy-ibm-resource-level");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndyModule() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return singletonList(new IndyResourceLevelInstrumentation());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
public class IndyResourceLevelInstrumentation implements TypeInstrumentation {
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("com.ibm.as400.resource.ResourceLevel");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
named("toString"), this.getClass().getName() + "$ToStringAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class ToStringAdvice {
|
||||
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
public static String toStringReplace() {
|
||||
return "instrumented";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,13 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
|||
import java.util.List;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class IbmResourceLevelInstrumentationModule extends InstrumentationModule {
|
||||
public IbmResourceLevelInstrumentationModule() {
|
||||
super(IbmResourceLevelInstrumentationModule.class.getName());
|
||||
public class InlineIbmResourceLevelInstrumentationModule extends InstrumentationModule {
|
||||
public InlineIbmResourceLevelInstrumentationModule() {
|
||||
super("inline-ibm-resource-level");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return singletonList(new ResourceLevelInstrumentation());
|
||||
return singletonList(new InlineResourceLevelInstrumentation());
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import net.bytebuddy.asm.Advice;
|
|||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
public class ResourceLevelInstrumentation implements TypeInstrumentation {
|
||||
public class InlineResourceLevelInstrumentation implements TypeInstrumentation {
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("com.ibm.as400.resource.ResourceLevel");
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package indy;
|
||||
|
||||
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
|
||||
import net.bytebuddy.asm.Advice.AssignReturned.ToFields.ToField;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class IndyInstrumentationTestModule extends InstrumentationModule {
|
||||
|
||||
public IndyInstrumentationTestModule() {
|
||||
super("indy-test");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndyModule() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return Collections.singletonList(new Instrumentation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHelperClass(String className) {
|
||||
return className.equals(LocalHelper.class.getName());
|
||||
}
|
||||
|
||||
public static class Instrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("indy.IndyInstrumentationTest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
String prefix = getClass().getName();
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToFieldViaReturn"), prefix + "$AssignFieldViaReturnAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToFieldViaArray"), prefix + "$AssignFieldViaArrayAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToArgumentViaReturn"), prefix + "$AssignArgumentViaReturnAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToArgumentViaArray"), prefix + "$AssignArgumentViaArrayAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToReturnViaReturn"), prefix + "$AssignReturnViaReturnAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("assignToReturnViaArray"), prefix + "$AssignReturnViaArrayAdvice");
|
||||
transformer.applyAdviceToMethod(named("getHelperClass"), prefix + "$GetHelperClassAdvice");
|
||||
transformer.applyAdviceToMethod(named("exceptionPlease"), prefix + "$ThrowExceptionAdvice");
|
||||
transformer.applyAdviceToMethod(
|
||||
named("noExceptionPlease"), prefix + "$SuppressExceptionAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignFieldViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField"))
|
||||
public static String onEnter(@Advice.Argument(0) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignFieldViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToFields(@ToField(value = "privateField", index = 1, typing = DYNAMIC))
|
||||
public static Object[] onEnter(@Advice.Argument(0) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignArgumentViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(0))
|
||||
public static String onEnter(@Advice.Argument(1) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignArgumentViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(inline = false)
|
||||
@Advice.AssignReturned.ToArguments(@ToArgument(value = 0, index = 1, typing = DYNAMIC))
|
||||
public static Object[] onEnter(@Advice.Argument(1) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignReturnViaReturnAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
public static String onExit(@Advice.Argument(0) String toAssign) {
|
||||
return toAssign;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class AssignReturnViaArrayAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned(index = 1, typing = DYNAMIC)
|
||||
public static Object[] onExit(@Advice.Argument(0) String toAssign) {
|
||||
return new Object[] {"ignoreme", toAssign};
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public static class GetHelperClassAdvice {
|
||||
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
@Advice.AssignReturned.ToReturned
|
||||
public static Class<?> onExit(@Advice.Argument(0) boolean localHelper) {
|
||||
if (localHelper) {
|
||||
return LocalHelper.class;
|
||||
} else {
|
||||
return GlobalHelper.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "ThrowSpecificExceptions"})
|
||||
public static class ThrowExceptionAdvice {
|
||||
@Advice.OnMethodExit(inline = false)
|
||||
public static void onMethodExit() {
|
||||
throw new RuntimeException("This exception should not be suppressed");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "ThrowSpecificExceptions"})
|
||||
public static class SuppressExceptionAdvice {
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
|
||||
public static void onMethodEnter() {
|
||||
throw new RuntimeException("This exception should be suppressed");
|
||||
}
|
||||
|
||||
@Advice.AssignReturned.ToReturned
|
||||
@Advice.OnMethodExit(
|
||||
suppress = Throwable.class,
|
||||
onThrowable = Throwable.class,
|
||||
inline = false)
|
||||
public static void onMethodExit(@Advice.Thrown Throwable throwable) {
|
||||
throw new RuntimeException("This exception should be suppressed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GlobalHelper {}
|
||||
|
||||
public static class LocalHelper {}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package indy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@SuppressWarnings({"unused", "MethodCanBeStatic"})
|
||||
public class IndyInstrumentationTest {
|
||||
|
||||
private String privateField;
|
||||
|
||||
// The following methods are instrumented by the IndyInstrumentationTestModule
|
||||
|
||||
private void assignToFieldViaReturn(String toAssign) {}
|
||||
|
||||
private void assignToFieldViaArray(String toAssign) {}
|
||||
|
||||
private String assignToArgumentViaReturn(String a, String toAssign) {
|
||||
return "Arg:" + a;
|
||||
}
|
||||
|
||||
private String assignToArgumentViaArray(String a, String toAssign) {
|
||||
return "Arg:" + a;
|
||||
}
|
||||
|
||||
private String assignToReturnViaReturn(String toAssign) {
|
||||
return "replace_me";
|
||||
}
|
||||
|
||||
private String assignToReturnViaArray(String toAssign) {
|
||||
return "replace_me";
|
||||
}
|
||||
|
||||
private String noExceptionPlease(String s) {
|
||||
return s + "_no_exception";
|
||||
}
|
||||
|
||||
private void exceptionPlease() {}
|
||||
|
||||
private Class<?> getHelperClass(boolean local) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void reset() {
|
||||
privateField = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToFieldViaReturn() {
|
||||
assignToFieldViaReturn("field_val");
|
||||
assertThat(privateField).isEqualTo("field_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToFieldViaArray() {
|
||||
assignToFieldViaArray("array_field_val");
|
||||
assertThat(privateField).isEqualTo("array_field_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToArgumentViaReturn() {
|
||||
String value = assignToArgumentViaReturn("", "arg_val");
|
||||
assertThat(value).isEqualTo("Arg:arg_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToArgumentViaArray() {
|
||||
String value = assignToArgumentViaArray("", "arg_array_val");
|
||||
assertThat(value).isEqualTo("Arg:arg_array_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToReturnViaReturn() {
|
||||
String value = assignToReturnViaReturn("ret_val");
|
||||
assertThat(value).isEqualTo("ret_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignToReturnViaArray() {
|
||||
String value = assignToReturnViaReturn("ret_array_val");
|
||||
assertThat(value).isEqualTo("ret_array_val");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuppressException() {
|
||||
assertThat(noExceptionPlease("foo")).isEqualTo("foo_no_exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testThrowExceptionIntoUserCode() {
|
||||
assertThatThrownBy(this::exceptionPlease).isInstanceOf(RuntimeException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHelperClassLoading() {
|
||||
Class<?> localHelper = getHelperClass(true);
|
||||
assertThat(localHelper.getName()).endsWith("LocalHelper");
|
||||
assertThat(localHelper.getClassLoader().getClass().getName())
|
||||
.endsWith("InstrumentationModuleClassLoader");
|
||||
|
||||
Class<?> globalHelper = getHelperClass(false);
|
||||
assertThat(globalHelper.getName()).endsWith("GlobalHelper");
|
||||
assertThat(globalHelper.getClassLoader().getClass().getName()).endsWith("AgentClassLoader");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue