From d7185a808eeb5a2cb2666cec5b927480b5fe0a77 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Sun, 18 Oct 2026 19:44:48 +0100 Subject: [PATCH] Fix invalid shaded helper package names Advice shading appended the signed relocation hash directly to helper package names. Negative hashes introduced a hyphen into the generated package segment, making it an invalid Java identifier and potentially breaking code generation that referenced it. Encode the hash as hexadecimal to keep every generated package segment valid while preserving all hash bits. Add regression coverage for Jakarta JMS helper references and zero, positive, and minimum integer hashes. --- .../trace/agent/tooling/AdviceShader.java | 4 +- .../trace/agent/tooling/AdviceShaderTest.java | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/tooling/AdviceShaderTest.java diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AdviceShader.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AdviceShader.java index 07b4426767d..e3ba70a5c0e 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AdviceShader.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AdviceShader.java @@ -1,5 +1,6 @@ package datadog.trace.agent.tooling; +import static java.lang.Integer.toHexString; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -84,9 +85,10 @@ public byte[] shadeClass(byte[] bytecode) { public String uniqueHelper(String dottedName) { int packageEnd = dottedName.lastIndexOf('.'); if (packageEnd > 0) { + // Hex encoding keeps negative hashes valid in Java package names. return dottedName.substring(0, packageEnd + 1) + "shaded" - + relocations.hashCode() + + toHexString(relocations.hashCode()) + dottedName.substring(packageEnd); } return dottedName; diff --git a/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/tooling/AdviceShaderTest.java b/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/tooling/AdviceShaderTest.java new file mode 100644 index 00000000000..b6c21f5b9cc --- /dev/null +++ b/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/tooling/AdviceShaderTest.java @@ -0,0 +1,62 @@ +package datadog.trace.agent.tooling; + +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.tabletest.junit.TableTest; + +class AdviceShaderTest { + private static final String HELPER = "datadog.trace.instrumentation.jms.DatadogMessageListener"; + + @TableTest({ + "scenario | relocation ", + "zero hash | '' ", + "positive hash | a ", + "minimum int hash | min-hash-7052265601" + }) + void helperPackageIsAJavaIdentifierForEveryHashSign(String relocation) { + AdviceShader shader = AdviceShader.with(singletonMap("", relocation)); + + assertValidClassName(shader.uniqueHelper(HELPER)); + } + + @Test + void jakartaJmsHelperNamesAndReferencesMatch() { + InstrumenterModule module = + new InstrumenterModule("test-jakarta-jms") { + @Override + public Map adviceShading() { + return singletonMap("javax", "jakarta"); + } + + @Override + public String[] helperClassNames() { + return new String[] {HELPER}; + } + }; + AdviceShader shader = AdviceShader.with(module); + String shadedHelper = shader.uniqueHelper(HELPER); + + assertTrue(module.adviceShading().hashCode() < 0); + assertValidClassName(shadedHelper); + assertNotEquals(HELPER, shadedHelper); + assertEquals(shadedHelper, shader.shadeClassName(HELPER)); + assertEquals( + "L" + shadedHelper.replace('.', '/') + ";", + shader.shadeTypeDescriptor("L" + HELPER.replace('.', '/') + ";")); + assertEquals("jakarta.jms.MessageListener", shader.shadeClassName("javax.jms.MessageListener")); + } + + private static void assertValidClassName(String className) { + for (String segment : className.split("\\.")) { + assertTrue(Character.isJavaIdentifierStart(segment.charAt(0)), className); + for (int i = 1; i < segment.length(); i++) { + assertTrue(Character.isJavaIdentifierPart(segment.charAt(i)), className); + } + } + } +}