diff --git a/dd-java-agent/agent-debugger/build.gradle b/dd-java-agent/agent-debugger/build.gradle index 32aef3e51ec..a4de7c2a0cf 100644 --- a/dd-java-agent/agent-debugger/build.gradle +++ b/dd-java-agent/agent-debugger/build.gradle @@ -77,6 +77,8 @@ dependencies { testImplementation project(':dd-java-agent:agent-debugger:debugger-test-scala') testImplementation project(':dd-trace-core') testImplementation project(':dd-java-agent:agent-installer') + testImplementation project(':dd-java-agent:instrumentation:rs:jax-rs:jax-rs-annotations:jax-rs-annotations-2.0') + testImplementation group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.1.1' testImplementation project(':remote-config:remote-config-core') testImplementation project(':utils:test-utils') diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index a800e1cb1e7..321758461d1 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -27,6 +27,7 @@ import static utils.InstrumentationTestHelper.compile; import static utils.InstrumentationTestHelper.compileAndLoadClass; import static utils.InstrumentationTestHelper.getLineForLineProbe; +import static utils.InstrumentationTestHelper.installTracerInstrumentation; import static utils.InstrumentationTestHelper.loadClass; import static utils.TestClassFileHelper.getClassFileBytes; import static utils.TestHelper.getFixtureContent; @@ -72,6 +73,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.net.URISyntaxException; import java.net.URL; @@ -1838,26 +1840,33 @@ public void multiLambdas() throws IOException, URISyntaxException { public void tracerInstrumentedClass() throws Exception { DebuggerContext.initClassFilter(new DenyListHelper(null)); final String CLASS_NAME = "com.datadog.debugger.jaxrs.MyResource"; - TestSnapshotListener listener = installMethodProbe(CLASS_NAME, "createResource", null); - // load a class file that was previously instrumented by the DD tracer as JAX-RS resource - Class testClass = - loadClass(CLASS_NAME, getClass().getResource("/MyResource.class").getFile()); - Object result = - Reflect.onClass(testClass) - .create() - .call("createResource", (Object) null, (Object) null, 1) - .get(); - Snapshot snapshot = assertOneSnapshot(listener); - Map arguments = - snapshot.getCaptures().getEntry().getArguments(); - // it's important there is no null key in this map, as Jackson is not happy about it - // it's means here that argument names are not resolved correctly - Assertions.assertFalse(arguments.containsKey(null)); - assertEquals(4, arguments.size()); - assertTrue(arguments.containsKey("this")); - assertTrue(arguments.containsKey("apiKey")); - assertTrue(arguments.containsKey("uriInfo")); - assertTrue(arguments.containsKey("value")); + // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS + // instrumentation, so this test exercises argument-name resolution against the same + // bytecode shape the tracer actually produces + ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); + Class testClass; + try { + TestSnapshotListener listener = installMethodProbe(CLASS_NAME, "createResource", null); + testClass = compileAndLoadClass(CLASS_NAME); + Object result = + Reflect.onClass(testClass) + .create() + .call("createResource", (Object) null, (Object) null, 1) + .get(); + Snapshot snapshot = assertOneSnapshot(listener); + Map arguments = + snapshot.getCaptures().getEntry().getArguments(); + // it's important there is no null key in this map, as Jackson is not happy about it + // it's means here that argument names are not resolved correctly + Assertions.assertFalse(arguments.containsKey(null)); + assertEquals(4, arguments.size()); + assertTrue(arguments.containsKey("this")); + assertTrue(arguments.containsKey("apiKey")); + assertTrue(arguments.containsKey("uriInfo")); + assertTrue(arguments.containsKey("value")); + } finally { + instr.removeTransformer(jaxRsTransformer); + } } @Test diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java index 413fa216cb9..d75ac862a29 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.when; import static utils.InstrumentationTestHelper.compileAndLoadClass; import static utils.InstrumentationTestHelper.getLineForLineProbe; -import static utils.InstrumentationTestHelper.loadClass; +import static utils.InstrumentationTestHelper.installTracerInstrumentation; import com.datadog.debugger.el.DSL; import com.datadog.debugger.el.ValueScript; @@ -1291,21 +1291,28 @@ public void primitivesFunction() throws IOException, URISyntaxException { public void localVarNotInScope() throws IOException, URISyntaxException { final String METRIC_NAME = "lenstr"; final String CLASS_NAME = "com.datadog.debugger.jaxrs.MyResource"; - MetricProbe metricProbe = - createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) - .where(CLASS_NAME, "createResource", null) - .valueScript(new ValueScript(DSL.len(DSL.ref("varStr")), "len(varStr)")) - .build(); - MetricForwarderListener listener = installMetricProbes(metricProbe); - Class testClass = - loadClass(CLASS_NAME, getClass().getResource("/MyResource.class").getFile()); - Object result = - Reflect.onClass(testClass) - .create() - .call("createResource", (Object) null, (Object) null, 1) - .get(); - assertNotNull(result); - assertFalse(listener.gauges.containsKey(METRIC_NAME)); + // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS + // instrumentation, so this test exercises the same bytecode shape the tracer produces + ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); + Class testClass; + try { + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "createResource", null) + .valueScript(new ValueScript(DSL.len(DSL.ref("varStr")), "len(varStr)")) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + testClass = compileAndLoadClass(CLASS_NAME); + Object result = + Reflect.onClass(testClass) + .create() + .call("createResource", (Object) null, (Object) null, 1) + .get(); + assertNotNull(result); + assertFalse(listener.gauges.containsKey(METRIC_NAME)); + } finally { + instr.removeTransformer(jaxRsTransformer); + } } private MetricForwarderListener installMethodMetric( diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java index 3c45c046014..b856fcd8a5d 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java @@ -179,6 +179,12 @@ public void roundTripCaughtException() throws IOException { new CapturedStackFrame("f2", 23), new CapturedStackFrame("f3", 34)), null)); + captures.addCaughtException( + new CapturedContext.CapturedThrowable( + NullPointerException.class.getTypeName(), + "illegal argument", + Collections.emptyList(), + null)); String buffer = adapter.toJson(snapshot); System.out.println("roundTripCaughtException: " + buffer); Snapshot deserializedSnapshot = adapter.fromJson(buffer); diff --git a/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java b/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java index 303d976d69b..4919e59cdc5 100644 --- a/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java +++ b/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java @@ -4,8 +4,13 @@ import static utils.TestHelper.getFixtureLines; import com.datadog.debugger.agent.CapturedSnapshotTest; +import datadog.instrument.classinject.ClassInjector; +import datadog.trace.agent.tooling.AgentInstaller; +import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.bootstrap.debugger.ProbeId; import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; @@ -13,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,6 +90,19 @@ public static int getLineForLineProbe(String className, ProbeId lineProbeId) { return getLineForLineProbe(className, ".java", lineProbeId); } + /** + * Installs the real tracer instrumentation modules (e.g. jax-rs-annotations) on top of the given + * {@link Instrumentation}, so that classes defined afterwards (typically via {@link + * #compileAndLoadClass}) are woven with actual tracer advice, the same way they would be by the + * production agent. Callers must remove the returned transformer once done, e.g. via {@link + * Instrumentation#removeTransformer(ClassFileTransformer)}. + */ + public static ClassFileTransformer installTracerInstrumentation(Instrumentation instr) { + ClassInjector.enableClassInjection(instr); + return AgentInstaller.installBytebuddyAgent( + instr, false, EnumSet.of(InstrumenterModule.TargetSystem.TRACING)); + } + public static int getLineForLineProbe(String className, String ext, ProbeId lineProbeId) { List lines = getFixtureLines("/" + className.replace('.', '/') + ext); for (int i = 0; i < lines.size(); i++) { diff --git a/dd-java-agent/agent-debugger/src/test/resources/MyResource.class b/dd-java-agent/agent-debugger/src/test/resources/MyResource.class deleted file mode 100644 index eba334f1d9c..00000000000 Binary files a/dd-java-agent/agent-debugger/src/test/resources/MyResource.class and /dev/null differ diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java new file mode 100644 index 00000000000..67a20e145e7 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java @@ -0,0 +1,18 @@ +package com.datadog.debugger.jaxrs; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; + +@Path("myresource") +public class MyResource { + + @GET + public Object createResource( + String apiKey, @Context String uriInfo, @QueryParam("value") int value) { + String varStr = "foo"; + Object response = new Object(); + return response; + } +}