Skip to content

Build DatadogClassLoader resource URL from agent jar URL, not OS path#11480

Open
dougqh wants to merge 1 commit into
masterfrom
fix/datadog-classloader-windows-url-prefix
Open

Build DatadogClassLoader resource URL from agent jar URL, not OS path#11480
dougqh wants to merge 1 commit into
masterfrom
fix/datadog-classloader-windows-url-prefix

Conversation

@dougqh
Copy link
Copy Markdown
Contributor

@dougqh dougqh commented May 27, 2026

What Does This Do

Fix Windows-only helper-class injection failure that breaks tracing on Spring Boot 1.3.5 fat jars / WARs (and any app using org.springframework.boot.loader.LaunchedURLClassLoader).

Motivation

Fixes a regression introduced in v0.107.0

Additional Notes

DatadogClassLoader builds agentResourcePrefix from JarFile.getName() (an OS-native path). On Windows that string is C:\Datadog\dd-java-agent.jar, producing the malformed URL jar:file:C:\Datadog\dd-java-agent.jar!/... — backslashes are not valid in a URL path and the embedded file:C:\... is missing the leading slash before the drive letter. new URL(...) accepts the string, but URLConnection.openStream() then fails, so findResource() returns URLs that cannot be read.

This breaks helper-class injection: byte-buddy's ClassFileLocator.ForClassLoader (used by HelperInjector) surfaces the failure as IllegalStateException: Could not locate class file for <helper>. Without the Servlet3 / Tomcat helpers no entry spans are created, so no traces reach the Agent.

The fix builds the prefix from the well-formed agentJarURL (already produced via File.toURI().toURL() upstream) instead of round-tripping through JarFile.getName().

Regression boundary: introduced by #3684 (commit 556da4ea35, between v0.106.0 and v0.107.0) — matches the working/broken matrix reported in the linked issue.

Test plan

  • New cross-platform regression test (findResourceUsesAgentJarUrlAsPrefix) places the test jar in a directory whose name contains a space. URL.toString() percent-encodes it; JarFile.getName() does not. The pre-fix code produces a URL with the literal space; the post-fix code matches the agent-jar URL prefix exactly. Verified the test fails on pre-fix code and passes on post-fix code.
  • :dd-java-agent:agent-bootstrap:test passes (all 4 tests, including the 3 migrated from DatadogClassLoaderTest.groovy).
  • Spotless clean.
  • Manual smoke test on Windows with a Spring Boot 1.3.5 fat jar (the original repro from Ddtrace version >0.106.0 in the window environment does not support springboot 1.3.5 #6398) — would benefit from a reviewer with Windows access; macOS/Linux cannot reproduce the underlying bug.

Notes

  • Also migrates DatadogClassLoaderTest.groovy → JUnit 5 Java per project convention. The pre-existing tests are preserved (getClassLoadingLock is reached via reflection since it's protected on java.lang.ClassLoader).

Fixes #6398

🤖 Generated with Claude Code

`agentResourcePrefix` was constructed via `"jar:file:" + JarFile.getName() +
"!/"`. `JarFile.getName()` returns the OS-native path. On Windows that is
`C:\Datadog\dd-java-agent.jar`, producing the malformed URL
`jar:file:C:\Datadog\dd-java-agent.jar!/...` — the backslashes are not
valid in a URL path and the embedded `file:C:\...` is missing the leading
slash before the drive letter. `new URL(...)` is permissive enough to
accept the string, but `URLConnection.openStream()` then fails, so
`findResource()` returns URLs that cannot be read.

This breaks helper-class injection on Spring Boot 1.3.5 fat jars (and
WARs) on Windows: byte-buddy's `ClassFileLocator.ForClassLoader` surfaces
the failure as `IllegalStateException: Could not locate class file for
<helper>` and `HelperInjector.getHelperMap` propagates it. Without the
Servlet3 and Tomcat helpers no entry spans are created, so no traces
reach the Agent.

Use the well-formed `agentJarURL` (already built via
`File.toURI().toURL()` upstream) instead. The migrated test exercises the
regression cross-platform by placing the test jar in a directory whose
name contains a space — `URL.toString()` percent-encodes it,
`JarFile.getName()` does not — which is the same class of malformation
Windows triggers.

Migrate `DatadogClassLoaderTest.groovy` to Java JUnit 5 per project
convention (uses reflection for the protected `getClassLoadingLock`).

Fixes #6398

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dougqh dougqh added inst: spring Spring instrumentation tag: ai generated Largely based on code generated by an AI or LLM type: bug Bug report and fix labels May 27, 2026
@dougqh dougqh marked this pull request as ready for review May 27, 2026 19:20
@dougqh dougqh requested a review from a team as a code owner May 27, 2026 19:20
@dougqh dougqh added the comp: core Tracer core label May 27, 2026
@dougqh dougqh requested a review from mcculls May 27, 2026 19:22
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb8f7171ec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// original Groovy test did implicitly.
private static Object classLoadingLock(ClassLoader cl, String name) throws Exception {
Method m = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class);
m.setAccessible(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid reflective access to getClassLoadingLock

When this test runs on Java 16+ (the module's test config only opens java.base/java.net for those launchers in dd-java-agent/agent-bootstrap/build.gradle), this setAccessible(true) throws InaccessibleObjectException because java.base/java.lang is not opened. That makes DatadogClassLoaderTest fail before it exercises the lock behavior on current JDKs; use a small test-only subclass/wrapper to call the protected method or add the matching --add-opens.

Useful? React with 👍 / 👎.

@dd-octo-sts
Copy link
Copy Markdown
Contributor

dd-octo-sts Bot commented May 27, 2026

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results

Startup Time

Scenario This PR master Change
insecure-bank / iast 14,009 ms 14,072 ms -0.4%
insecure-bank / tracing 12,984 ms 13,028 ms -0.3%
petclinic / appsec 16,487 ms 16,322 ms +1.0%
petclinic / iast 16,559 ms 16,609 ms -0.3%
petclinic / profiling 15,461 ms 16,475 ms -6.2%
petclinic / tracing 15,714 ms 15,742 ms -0.2%

Commit: eb8f7171 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core inst: spring Spring instrumentation tag: ai generated Largely based on code generated by an AI or LLM type: bug Bug report and fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ddtrace version >0.106.0 in the window environment does not support springboot 1.3.5

1 participant