Skip to content

feat: provided-style extensions and manifest-libs#791

Open
jbachorik wants to merge 27 commits intodevelopfrom
jb/configurations
Open

feat: provided-style extensions and manifest-libs#791
jbachorik wants to merge 27 commits intodevelopfrom
jb/configurations

Conversation

@jbachorik
Copy link
Copy Markdown
Collaborator

@jbachorik jbachorik commented Jan 13, 2026

Rationale

BTrace has historically relied on libs=<profile> and $BTRACE_HOME/btrace-libs/<profile>/{boot,system}/*.jar to make application APIs (Spark, Hadoop, Kafka, …) visible to probes. That model has three structural problems:

  • Classpath pollution. Profile JARs are appended to the bootstrap and system class loaders, which is highly privileged, conflicts with application shading, and cannot be isolated per probe.
  • Fragile packaging. Resolution is heuristic (hardcoded agent-JAR name, URL string slicing, per-OS quirks). Renaming, JPMS, or non-filesystem deployments break it.
  • Doesn't ship. Spark drivers/executors, Hadoop containers, and Kubernetes pods don't have a shared $BTRACE_HOME with a matching profile layout. Users end up with custom init containers, sidecars, or wrapper scripts just to deliver one extra JAR.

This PR introduces two complementary mechanisms to fix this: provided-style extensions (APIs on bootstrap, implementation reflective and isolated) and fat agents (a single JAR containing the agent plus embedded extensions, built via a Gradle or Maven plugin).

Usability Improvements

  • Single-JAR deployment. java -javaagent:btrace-agent-fat.jar is now enough for Spark/Hadoop/K8s — no $BTRACE_HOME, no profile directory, no sidecars.
  • No more classpath injection. Provided-style extensions keep only minimal API types on bootstrap; the impl lives in an isolated extension class loader and resolves app types via TCCL. Application shading and version conflicts stop being BTrace's problem.
  • Build-time tooling. New Gradle plugins (org.openjdk.btrace.extension, org.openjdk.btrace.fat-agent) and a btrace-maven-plugin handle API/impl split, manifest metadata, permission scanning, relocations, and multi-project auto-discovery.
  • Declarative, manifest-driven libs. The legacy libs= heuristic is replaced by explicit manifest attributes (BTrace-Boot-Libs, BTrace-System-Libs, BTrace-Embedded-Extensions) with deduplication, validation, and diagnostics.
  • Helpers for writing adapters. ClassLoadingUtil (TCCL, service loading, scoped context) and MethodHandleCache (cached reflective handles) remove most of the boilerplate from reflective probes.
  • Working examples. btrace-extensions/examples/btrace-spark and btrace-extensions/examples/btrace-hadoop demonstrate the pattern end-to-end.

Quick Start

1. Build a fat agent with Gradle

plugins {
    id 'org.openjdk.btrace.fat-agent' version "${btraceVersion}"
}

btraceFatAgent {
    baseName = 'my-btrace-agent'
    embedExtensions {
        project(':my-spark-extension')              // multi-project
        maven('io.btrace:btrace-metrics:2.3.0')     // external
    }
}
./gradlew fatAgentJar
# -> build/libs/my-btrace-agent.jar

2. Attach it

java -javaagent:/path/to/my-btrace-agent.jar=debug=true,port=2020 -jar your-app.jar

Embedded extensions are discovered automatically from the JAR manifest — no $BTRACE_HOME required.

3. (Optional) Write a provided-style extension

API module (ships to bootstrap, pure interfaces / value types):

public interface SparkApi {
    void onJobStart(Object jobStartEvent);
}

Impl module (isolated class loader, reflective against app classes):

public final class SparkApiImpl implements SparkApi {
    private final MethodHandleCache mh = new MethodHandleCache();

    public void onJobStart(Object evt) {
        ClassLoadingUtil.withDefiningLoader(evt, () -> {
            Class<?> cls = ClassLoadingUtil.loadFromContext(
                "org.apache.spark.scheduler.SparkListenerJobStart", evt);
            int jobId = (int) mh.findVirtual(cls, "jobId", int.class).invoke(evt);
            // emit metrics / logs...
            return null;
        });
    }
}

See docs/architecture/provided-style-extensions.md and docs/architecture/migrating-from-libs-profiles.md for the full walk-through.

Maven equivalent

<plugin>
  <groupId>org.openjdk.btrace</groupId>
  <artifactId>btrace-maven-plugin</artifactId>
  <configuration>
    <extensions>
      <extension>io.btrace:btrace-metrics:${btrace.version}</extension>
    </extensions>
  </configuration>
</plugin>

Drawbacks and Remaining Issues

  • No hot-reload for embedded extensions. Updating an extension in a fat agent requires rebuilding the JAR and restarting the target JVM.
  • Larger artifact. A fat agent is strictly bigger than the minimal agent; users who ship BTrace through a managed $BTRACE_HOME don't benefit from embedding.
  • More boilerplate than profiles. Provided-style requires an explicit API/impl split and reflective adapters. It's a deliberate trade-off (isolation + portability) but the migration cost from existing profile-based setups is real — the migration guide documents it but cannot eliminate it.
  • Reflective cost at the hot path. Even with MethodHandleCache, an adapter call is more expensive than a direct invocation. Most tracing workloads won't notice, but very high-frequency probes should be profiled.
  • libs= / profiles= still accepted. They are deprecated and log a warning; planned removal is N+2 minor releases. Until then, both code paths coexist and users can mix them (not recommended).
  • Escape hatch kept for now. -Dbtrace.system.appendJar=/abs/path/lib.jar (requires -Dbtrace.trusted=true) exists for cases where immediate migration is impossible. It's restricted to BTRACE_HOME by default and is explicitly discouraged — expect it to be removed together with libs=.
  • Fat-agent plugin API surface. The Gradle/Maven plugins are new; DSL and task names may still shift before the first stable release cut.

Documentation

Document Description
Provided-Style Extensions API on bootstrap, impl via TCCL
Fat Agent Plugin Architecture Single-JAR deployment design
Migrating from libs/profiles Step-by-step migration guide
Extension Development Guide Building extensions from scratch
Gradle Plugin README Extension + fat-agent plugins

Breaking Changes

  • libs= and profiles= are deprecated. Planned removal: N+2 minor releases. A runtime warning is emitted on use.

🤖 Generated with Claude Code


This change is Reviewable

@jbachorik jbachorik added the AI AI-generated code label Jan 13, 2026
Copy link
Copy Markdown

@reviewabot reviewabot Bot left a comment

Choose a reason for hiding this comment

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

It looks like the pull request diff is too large to be displayed directly on GitHub. Here are some general recommendations for handling large pull requests:

  1. Break Down the PR: Try to break down the pull request into smaller, more manageable chunks. This makes it easier to review and ensures that each part can be thoroughly checked.

  2. Review Locally: Clone the repository and check out the branch locally. This allows you to review the changes using your preferred tools and environment.

  3. Focus on Key Areas: Identify and focus on the key areas of the code that are most critical or have the highest impact. This can help prioritize the review process.

  4. Automated Tools: Use automated code review tools and linters to catch common issues and enforce coding standards.

  5. Collaborate with the Team: If possible, involve multiple reviewers to share the workload and get different perspectives on the changes.

If you can provide a smaller subset of the changes or specific files that need review, I'd be happy to help with a more detailed review.

@jbachorik jbachorik changed the title fix(agent,client,tests): probe listing after disconnect and test isolation feat: manifest-libs, provided-style extensions, permission removal, and fixes Jan 13, 2026
@jbachorik jbachorik changed the title feat: manifest-libs, provided-style extensions, permission removal, and fixes feat: provided-style extensions and manifest-libs Jan 13, 2026
@jbachorik jbachorik requested a review from Copilot January 13, 2026 16:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces provided-style extensions and manifest-driven library loading to enable tracing application-specific classes without polluting the application classpath. The core innovation is using reflection and object hand-off patterns to access application classes at runtime, keeping BTrace isolated.

Changes:

  • New extension utilities (ClassLoadingUtil, MethodHandleCache) for runtime class resolution and reflective method access
  • Manifest-driven library path resolution (AgentManifestLibs) with security restrictions to BTRACE_HOME
  • Refactored agent classpath processing with deduplication and better error handling
  • Example extensions for Spark and Hadoop demonstrating the provided-style pattern
  • Comprehensive architecture documentation and migration guides
  • Deprecation of libs/profiles mechanism with escape hatch for edge cases

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
settings.gradle Added example extension modules (btrace-spark, btrace-hadoop)
integration-tests/src/test/java/tests/RuntimeTest.java Fixed debug flag insertion and added timing delays for test stability
integration-tests/src/test/java/tests/ManifestLibsTests.java New test suite for manifest-libs feature
integration-tests/src/test/java/tests/BTraceFunctionalTests.java Improved unattended test with better synchronization and cleanup
integration-tests/build.gradle Added test property for skipping preconfigured libs
docs/examples/README.md Guide for building and configuring provided-style extension examples
docs/architecture/provided-style-extensions.md Comprehensive guide on provided-style extension pattern
docs/architecture/migrating-from-libs-profiles.md Migration guide from deprecated libs/profiles to extensions
docs/architecture/agent-manifest-libs.md Design document for manifest-driven library loading
compile.log Build artifact that should not be committed
btrace-extensions/examples/btrace-spark/* Example Spark extension with API and provided-style implementation
btrace-extensions/examples/btrace-hadoop/* Example Hadoop extension with API and provided-style implementation
btrace-extension/src/main/java/org/openjdk/btrace/extension/util/MethodHandleCache.java Cache for reflective method handles to reduce lookup overhead
btrace-extension/src/main/java/org/openjdk/btrace/extension/util/ClassLoadingUtil.java Helper utilities for TCCL-based class loading and service discovery
btrace-client/src/main/java/org/openjdk/btrace/client/Main.java Removed exit hooks for list-only operations to prevent unintended probe removal
btrace-client/src/main/java/org/openjdk/btrace/client/Client.java Added agent-already-running detection and system property pass-through
btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java Improved disconnect handling with disconnecting flag
btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java Refactored classpath processing with manifest support, deduplication, and escape hatch for single-jar injection
btrace-agent/src/main/java/org/openjdk/btrace/agent/AgentManifestLibs.java New utility for manifest-driven library resolution with security checks
README.md Added deprecation notice for libs/profiles with migration guidance

Comment thread btrace-agent/src/main/java/org/openjdk/btrace/agent/AgentManifestLibs.java Outdated
Comment thread btrace-agent/src/main/java/org/openjdk/btrace/agent/AgentManifestLibs.java Outdated
Comment thread btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java Outdated
Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 13, 2026

@jbachorik I've opened a new pull request, #792, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 13, 2026

@jbachorik I've opened a new pull request, #793, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 13, 2026

@jbachorik I've opened a new pull request, #794, to work on those changes. Once the pull request is ready, I'll request review from you.

@jbachorik jbachorik closed this Mar 15, 2026
@jbachorik jbachorik reopened this Apr 6, 2026
@jbachorik
Copy link
Copy Markdown
Collaborator Author

ReDoS in EmbeddedExtensionRepository.isValidClassName — fixed in dbd3964

Addresses the CodeQL ReDoS alert (js/redos, alert #15) on the class-name validation pattern.

The regex had three ambiguities the engine could explore:

  1. Inner char class [a-zA-Z0-9_$]* inside the .-group and the $-group both allowed $
  2. The outer (\$[a-zA-Z_][a-zA-Z0-9_$]*)* group also started with $
  3. All three repetitions were standard (backtracking) *

On an input like A$A$A$A…! (trailing mismatch) this produced exponential partition searching before failing.

Fix: switch the three repetitions to possessive quantifiers (*+). Each commit is final, so the engine cannot re-partition $-separated tail segments. Match semantics are preserved for every accepted and rejected input in SecurityValidationTest > Class name validation (valid / invalid / special-chars / code-injection / null-and-empty — all green).

Stress-check: A$A × 30 + ! now rejects in ~0 ms instead of hanging.

Items 1–5 of the earlier review (unused Enumeration import, redundant homeStr null check, three zip-slip sites in FatAgentMojo) were already addressed in prior commits on this branch.

@jbachorik jbachorik marked this pull request as ready for review April 12, 2026 18:08
@jbachorik jbachorik requested a review from Copilot April 12, 2026 18:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 55 changed files in this pull request and generated 12 comments.

Comment thread integration-tests/src/test/java/tests/RuntimeTest.java
Comment thread integration-tests/src/test/java/tests/RuntimeTest.java
Comment thread docs/Troubleshooting.md Outdated
Comment thread docs/BTraceTutorial.md Outdated
Comment thread docs/README.md
Comment thread btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java Outdated
Comment thread btrace-agent/src/test/java/org/openjdk/btrace/agent/AgentManifestLibsTest.java Outdated
Comment thread btrace-maven-plugin/build.gradle
Comment thread integration-tests/src/test/java/tests/RuntimeTest.java
jbachorik and others added 5 commits April 17, 2026 23:49
…ibs deprecation; provided-style helpers; examples; tests\n\n- agent: manifest-driven libs (flagged), centralized classpath append with dedup + diagnostics; deprecate libs (debug log); add single-jar system CL escape hatch; test knob scoped to manifest-libs\n- client: pass-through of btrace.* props to agent using $ system props\n- extension: add ClassLoadingUtil and MethodHandleCache for provided-style extensions\n- examples: add btrace-extensions/examples/{btrace-spark,btrace-hadoop} with README\n- docs: research + provided-style guide + migration + examples index; README deprecation note\n- tests: add ManifestLibsTests; gradle knob for btrace.test.skipLibs\n\nBREAKING CHANGE: libs/profiles deprecated with planned removal after N+2; prefer extensions
- Proactively retransform java.lang.Thread after startup scripts load
- Move initExtensions() after transformer is installed
- Add delay in RuntimeTest to allow traced app output after ready marker

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation

- Add disconnecting flag to RemoteClient for immediate probe visibility
- Skip loadAgent when agent already running (check btrace.port)
- Add try-finally cleanup in tests to prevent port conflicts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…il/ClassLoadingUtil.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
jbachorik and others added 16 commits April 17, 2026 23:49
- Add ExtensionConfigurator interface for environment-aware probe selection
- Add ProbeConfiguration and RuntimeEnvironment classes
- Add ClassDataLoader for loading .classdata resources (dd-trace pattern)
- Add EmbeddedExtensionRepository for discovering embedded extensions
- Extend ExtensionDescriptorDTO with embedded, resourceBasePath, configuratorClass, bundledProbes fields
- Register EmbeddedExtensionRepository in ExtensionLoader (lowest priority)
- Handle embedded extensions in ExtensionLoaderImpl via ClassDataLoader

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- gradle: BTraceFatAgentPlugin with auto-discovery, project/maven/file sources
- maven: btrace-maven-plugin with fat-agent goal (Gradle-built module)
- fix: bootstrap classloader NPE in Main and EmbeddedExtensionRepository
- docs: fat-agent-plugin architecture, getting started, migration guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Zip Slip protection in FatAgentMojo (path traversal prevention)
- Add extension ID validation to prevent path traversal attacks
- Add bytecode validation in ClassDataLoader (magic number check)
- Add class name validation for services and configurators
- Validate ProbeConfiguration.setOutput() throws on invalid values
- Improve ClassFilter documentation for sensitive method filtering
- Add AgentManifestLibsTest (27 tests)
- Add BTraceFatAgentPluginTest using Gradle TestKit (10 tests)
- Add FatAgentMojoTest (26 tests)
- Add SecurityValidationTest (17 tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The class-name validation pattern in EmbeddedExtensionRepository used
ambiguous quantifiers where the inner character class allowed '$' and
the outer group matched '$'-prefixed segments. On input like 'A$A$A$A'
with a trailing mismatch, the engine explored exponentially many
partitions (CodeQL js/redos, alert #15).

Switch the three repetitions to possessive quantifiers (`*+`) so each
commit step is final. Semantics are preserved for all accepted and
rejected class names (verified against SecurityValidationTest).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and plugins

Applies 16 fixes surfaced by a multi-scholic review of the jb/configurations
branch. Each item below was verified against current HEAD before editing.

agent:
- Main.loadEmbeddedProbe: close the probe InputStream via try-with-resources
  and stop returning true from a stub that installs nothing; log an operator
  warn so the probes= argument no longer silently succeeds.
- Main.appendBootJar / appendSystemJar: synchronize the
  contains/appendTo/add block on the respective Set so two threads cannot
  both pass the dedup check and double-append the same jar.
- Main libs deprecation: emit at log.warn (was hidden behind isDebugEnabled).
- AgentManifestLibs.scanLibTree: close the Files.walk stream via
  try-with-resources (Files.walk is backed by a DirectoryStream).
- AgentManifestLibs.locateAgentPath: broaden the catch to include
  IllegalArgumentException and FileSystemNotFoundException (both thrown by
  Paths.get(URI) for non-file code sources), and drop the dead
  Files.isDirectory/else branch that returned the same value in both arms.
- AgentManifestLibs.filterAndNormalize: when toRealPath() fails, compare np
  against home.toAbsolutePath().normalize() instead of the raw home path so
  the fallback is at the same normalization level as np, and emit a warn so
  operators know symlink resolution was bypassed.
- RemoteClient.reconnect: publish this.disconnecting=false last so the
  volatile write acts as a release fence for the prior sock/ois/oos writes.

extension:
- EmbeddedExtensionRepository.readManifestIndex: trim each id after splitting
  the BTrace-Embedded-Extensions manifest value so a human-written
  "ext1, ext2" no longer produces a leading-space id that isValidExtensionId
  rejects.
- EmbeddedExtensionRepository.discoverBundledProbes: read the bundled probe
  list from the extension's properties file (probes=Probe1,Probe2) instead
  of always returning an empty list, so ExtensionDescriptorDTO actually
  carries the probes the extension declares.
- MethodHandleCache: add a negative cache so a repeated lookup for a missing
  method fails fast via the cached LookupRuntimeException instead of
  re-entering the reflective machinery on every call.

core:
- ProbeConfiguration.setOutput: use toLowerCase(Locale.ROOT) instead of
  default-locale toLowerCase() so Turkish dotted/dotless-I does not make
  "JFR" / "FILE" fall through to IllegalArgumentException.

maven plugin:
- FatAgentMojo.execute: wrap the staging-directory lifetime in try-finally
  so deleteDirectory runs on every exit path, not just the happy one.

gradle plugin:
- BTraceFatAgentExtension Maven/File extractFromZip: switch from
  project.buildDir (deprecated in Gradle 7, removed in Gradle 9) to
  project.layout.buildDirectory.dir(...).get().asFile.

housekeeping:
- Remove the accidentally-committed top-level compile.log and add it to
  .gitignore so it cannot be reintroduced.

Tests: full builds of btrace-core, btrace-extension, btrace-agent,
btrace-maven-plugin, and btrace-gradle-plugin are green, including
FatAgentMojoTest, AgentManifestLibsTest, SecurityValidationTest,
MainTest, and ExtensionBridgeImplPolicyTest.

The ClassDataLoader double-define race also surfaced in the review but the
chosen fix is still under discussion (see inline PR comment on
ClassDataLoader.findClass re: registerAsParallelCapable vs. synchronized
block on getClassLoadingLock) and is deliberately not included in this
commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two threads racing findClass for the same name could both reach
defineClass, which the JVM rejects with LinkageError. Guard the
cache-check + defineClass block with synchronized(getClassLoadingLock(name))
so only one thread per name defines, while a lock-free fast path still
handles the warm-cache case for already-loaded classes.

The synchronized block is chosen over registerAsParallelCapable because
it protects direct findClass callers in addition to the standard
loadClass delegation path, and keeps the invariant locally visible in
findClass itself.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ClassDataLoader: fix double-define race in findClass() by adding
  registerAsParallelCapable() and synchronized(getClassLoadingLock(name))
  with double-checked locking to prevent LinkageError from concurrent
  class definitions
- EmbeddedExtensionRepository: fix regex backtracking in isValidClassName()
  by removing $ from package-segment character class, and extract static
  Pattern field to avoid recompilation on each call

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…build.gradle

A rebase conflict resolution left a commit message fragment appended to
a systemProperty line, causing Groovy to fail parsing at line 77.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…itecture)

The masked JAR architecture merged agent + boot into a single `btraceJar`
task. The fat agent config still referenced the old `agentJar` and
`bootJar` tasks which no longer exist, causing build failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ient.java

- Main.java:898: commit message fragment appended to closing brace
- RemoteClient.java:351: reference to non-existent `disconnecting` field
  from pre-rebase branch (develop uses `disconnected`)

Both were leftovers from the rebase conflict resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebase conflict resolution left a spurious `try {` block wrapping
testOnMethodUnattended() without a matching catch/finally, causing
a compile error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ManifestLibsTests was failing on CI with "Port 2020 unavailable" because
a previous test class (BTraceFunctionalTests) left the default BTrace
port briefly occupied. Use port 2022 to avoid contention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The btrace CLI process launched by RuntimeTest.attach() was not receiving
the configured port — it defaulted to 2020 regardless of btrace.port.
Pass -Dbtrace.port and -p <port> to the spawned java process so
ManifestLibsTests (port 2022) avoids contention with other tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace btrace-agent.jar/btrace-boot.jar references with btrace.jar
  across all docs (README, GettingStarted, FAQ, Troubleshooting,
  QuickReference, BTraceTutorial, fat-agent-plugin arch doc)
- Fix broken doc links (casing: btrace-extension-development-guide.md
  → BTraceExtensionDevelopmentGuide.md; remove nonexistent
  extension-configuration.md reference)
- Update fat agent plugin docs: agentJar/bootJar → btraceJar task,
  document ClassDataLoader thread-safety (registerAsParallelCapable,
  per-class locking)
- Update Gradle plugin README: remove obsolete bootJarTask config
- Add JDK 25 Thread method exclusions to Troubleshooting.md
- Add test port isolation note to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applied via /muse:implement (direct mode):
- DC-08/DC-19: Remove duplicate -v flag and empty-string arg from RuntimeTest.java runBTrace helpers
- DC-10/DC-11: Fix confusing legacy JAR note in docs (substitute → replace with legacy jar names)
- DC-12: Fix broken link in docs/README.md (ExtensionInvokeDynamicBridge.md) + standardize link casing
- DC-13/DC-14: Fix BOOT_ADDED/SYSTEM_ADDED ordering — add to set AFTER successful append to prevent stale state on failure
- DC-15: Null-safe classloader in loadEmbeddedProbe (bootstrap classloader guard)
- DC-16: Normalize null resourceLoader in ClassDataLoader constructor to system classloader
- DC-17: Replace List.of() with Collections.singletonList() for Java 8 compatibility
- DC-18: Use rootProject.version in btrace-maven-plugin/build.gradle

DC-01/02/03/04/05/06/07 were already resolved in the branch.
DC-09 deferred (removing sleep risks test flakiness without a concrete condition mechanism).

<!-- muse-session:impl-20260417-225428 -->

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jbachorik added a commit that referenced this pull request Apr 17, 2026
After rebasing onto develop (which added PR #805's btracePort field and
conditional -p block), the unconditional -p inserted by PR #791 collides
with the conditional block, producing two -p flags when a test sets
btracePort. The btrace CLI rejects the duplicate and prints usage,
causing ExtensionLifecycleIntegrationTest to fail on every JDK.

Drop the unconditional pair from runBTraceDynamic so only the existing
conditional block adds -p when btracePort > 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rebasing onto develop brought in PR #805's conditional
  if (btracePort > 0) { add -p btracePort }
block, which collides with PR #791's unconditional
  "-p", String.valueOf(getBTracePort())
in runBTraceDynamic — producing two -p flags when a test overrides
btracePort.

Keep the unconditional form, since getBTracePort() already honours
both the develop-style btracePort field (ExtensionLifecycleIntegrationTest)
and the PR-style btrace.port system property (ManifestLibsTests), and
drop the now-redundant conditional block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jbachorik and others added 2 commits April 21, 2026 20:40
Operational logging & error handling:
- Main.java: log bootstrap/system jar append failures at warn (was debug),
  Thread.class retransform failure at warn (was debug), and warn when both
  deprecated libs= and manifest-based libs are in use
- AgentManifestLibs: upgrade skip/validation log levels from DEBUG to INFO
  (non-existent / non-jar entries) and WARN (path resolution exceptions)
- Main.java: add FIXME/TODO marker on loadBundledProbes() placeholder

Exception handling & resource lifecycle:
- ClassDataLoader.findClass: preserve IOException cause when throwing
  ClassNotFoundException
- MethodHandleCache: drop the negative cache; a failed lookup no longer
  permanently poisons the cache, so classes that become available later
  are found on retry
- FatAgentMojo.createJar: wrap Files.walk() in try-with-resources to
  release file handles on all exit paths

Classloader & instrumentation:
- ClassDataLoader: add canonical-pattern comment above
  registerAsParallelCapable() with JDK javadoc reference
- ClassFilter: expand inline comment on Thread-method exclusions to
  explain why getUncaughtExceptionHandler stays in the exclusion set
  (dispatchUncaughtException is the recursion source but probes hooking
  the getter could still re-enter the dispatcher path)
- Main.java: add comment on Thread.class retransform ordering clarifying
  that the current post-startScripts / pre-initExtensions placement is
  intentional

Extension repository:
- EmbeddedExtensionRepository.readManifestIndex: iterate all
  BTrace-Embedded-Extensions manifests instead of returning on first
  match; dedupe extension IDs and warn on duplicates with the offending
  manifest URL
- EmbeddedExtensionRepository: introduce a BOOTSTRAP_SENTINEL classloader
  + isBootstrap flag so null-classloader semantics are explicit rather
  than coalesced

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AgentManifestLibs.filterAndNormalize: javadoc describes that the
  returned list preserves LinkedHashSet insertion order (manifest
  declaration order), that callers must supply the set in desired
  precedence order, and that the first entry wins for duplicate class
  names.
- ExtensionLoaderImpl.loadEmbedded: comment at the ClassDataLoader
  construction site explains that a null parentClassLoader is
  intentional and well-defined — ClassLoader(ClassLoader) treats null
  as "use the bootstrap classloader as parent", which is the correct
  delegation chain when BTrace runs on the bootstrap classpath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jbachorik
Copy link
Copy Markdown
Collaborator Author

Review-fix commits pushed

Addressed 17 of 23 accepted findings from the muse chorus review (session review-20260421-095158), in two commits:

Highlights

  • Main.java: bootstrap/system jar append failures now log at warn; Thread.class retransform failure warns; explicit warn when both deprecated libs= and manifest-libs are in use; FIXME marker on loadBundledProbes() placeholder; comment clarifying Thread-class retransform ordering is intentional.
  • ClassDataLoader.findClass: IOException cause is preserved when converting to ClassNotFoundException.
  • MethodHandleCache: negative cache removed — failed lookups no longer permanently poison the cache.
  • FatAgentMojo.createJar: Files.walk() now in try-with-resources.
  • AgentManifestLibs: skip/validation log levels upgraded (DEBUG → INFO / WARN) so operators see non-existent / invalid entries.
  • EmbeddedExtensionRepository.readManifestIndex: iterates all BTrace-Embedded-Extensions manifests instead of returning on first match; dedupes extension IDs and warns on duplicates. Null-classloader handling now explicit via BOOTSTRAP_SENTINEL + isBootstrap flag.
  • ClassFilter: expanded rationale comment for JDK-25 Thread-method exclusions — kept getUncaughtExceptionHandler in the exclusion set because probes on the getter could re-enter the dispatcher path.

Verification

  • ./gradlew :btrace-agent:compileJava :btrace-extension:compileJava :btrace-instr:compileJava :btrace-maven-plugin:compileJava — green
  • ./gradlew spotlessCheck — green
  • Unit tests: 930/930 pass (920 module + 10 gradle-plugin), 0 failures, 0 errors

Deferred for follow-up

6 findings were not addressed in these commits because they need architectural or design decisions best handled separately:

  1. ClassDataLoader.java:98 — add ProtectionDomain/CodeSource on defineClass. Planner concertato recommended deferring: post-JDK 17 removal of SecurityManager makes a correct ProtectionDomain non-trivial.
  2. FatAgentMojo.java:~6055 — extract duplicated isValidExtensionId logic. Needs a module-boundary decision (new btrace-plugin-common vs. intentional duplication).
  3. FatAgentMojoTest.java:189 — replace "no-behavior-assertion" tests with concrete assertions.
  4. SecurityValidationTest.java:3392 — replace mock-based bytecode magic test with a real ClassDataLoader integration test.
  5. BTraceFatAgentPluginTest.java:~5117 — test duplication (phantom line numbers — requires locating the actual duplicated test code).
  6. BTraceFatAgentPlugin.groovy:~4722 — unify Gradle and Maven plugin extension-staging logic. Planner concertato flagged as out of scope for this PR.

The user also dismissed two earlier findings with explicit rationale: ClassDataLoader double-check-lock (intentional lockfree fast path) and ClassDataLoader:2468 concurrency (assumed harmless race, to be validated).

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 55 changed files in this pull request and generated 4 comments.

Comment on lines +1010 to +1012
if (debugBTrace) {
argVals.add(4, "-v"); // insert after Main class name
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

debugBTrace inserts -v at index 4, which places it between -cp and the eventsClassPath value. That changes the command to ... Loader -cp -v <eventsClassPath> ..., so -v is interpreted as the classpath value and breaks argument parsing. Insert -v immediately after the main class (org.openjdk.btrace.boot.Loader) instead (same position as the previous conditional element).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in commit 6295796. Counting the list literal: index 0=javaHome+"/bin/java", 1=-cp, 2=cp, 3=org.openjdk.btrace.boot.Loader, 4=-cp. argVals.add(4, "-v") inserts at index 4, shifting the existing -cp to index 5 — so -v lands immediately after the Loader main class and before the -cp/eventsClassPath pair, exactly as the reviewer requested. Argument parsing is no longer broken.

Comment on lines +728 to +737
if (manifestLibs != null && !manifestLibs.isEmpty()) {
agentArgs += "," + "$btrace.feature.manifestLibs" + "=" + manifestLibs;
}
if (sysAppendJar != null && !sysAppendJar.isEmpty()) {
agentArgs += "," + "$btrace.system.appendJar" + "=" + sysAppendJar;
}
if (allowExternalLibs != null && !allowExternalLibs.isEmpty()) {
agentArgs += "," + "$btrace.allowExternalLibs" + "=" + allowExternalLibs;
}
if (testSkipLibs != null && !testSkipLibs.isEmpty()) {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

These system property values are appended into a comma-separated agentArgs string without any escaping. If any value contains a comma (notably file paths on some systems), it will be split into multiple agent arguments and silently misconfigure the agent. Consider rejecting/escaping commas in values (or switching to a mechanism that doesn't use comma as a delimiter for these pass-through properties).

Suggested change
if (manifestLibs != null && !manifestLibs.isEmpty()) {
agentArgs += "," + "$btrace.feature.manifestLibs" + "=" + manifestLibs;
}
if (sysAppendJar != null && !sysAppendJar.isEmpty()) {
agentArgs += "," + "$btrace.system.appendJar" + "=" + sysAppendJar;
}
if (allowExternalLibs != null && !allowExternalLibs.isEmpty()) {
agentArgs += "," + "$btrace.allowExternalLibs" + "=" + allowExternalLibs;
}
if (testSkipLibs != null && !testSkipLibs.isEmpty()) {
if (manifestLibs != null && !manifestLibs.isEmpty()) {
if (manifestLibs.indexOf(',') >= 0) {
throw new IllegalArgumentException(
"System property btrace.feature.manifestLibs must not contain ','");
}
agentArgs += "," + "$btrace.feature.manifestLibs" + "=" + manifestLibs;
}
if (sysAppendJar != null && !sysAppendJar.isEmpty()) {
if (sysAppendJar.indexOf(',') >= 0) {
throw new IllegalArgumentException(
"System property btrace.system.appendJar must not contain ','");
}
agentArgs += "," + "$btrace.system.appendJar" + "=" + sysAppendJar;
}
if (allowExternalLibs != null && !allowExternalLibs.isEmpty()) {
if (allowExternalLibs.indexOf(',') >= 0) {
throw new IllegalArgumentException(
"System property btrace.allowExternalLibs must not contain ','");
}
agentArgs += "," + "$btrace.allowExternalLibs" + "=" + allowExternalLibs;
}
if (testSkipLibs != null && !testSkipLibs.isEmpty()) {
if (testSkipLibs.indexOf(',') >= 0) {
throw new IllegalArgumentException(
"System property btrace.test.skipLibs must not contain ','");
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed — Agent arg string uses ',' as delimiter; a stray comma in any pass-through property silently splits into extra args. Fail-fast with IllegalArgumentException is the minimally invasive fix that surfaces the misconfiguration rather than letting the agent read a mangled property.

Comment thread docs/README.md
Comment on lines +58 to +60
- **Method Tracing** → [Tutorial Lesson 1](BTraceTutorial.md), [Quick Reference: @OnMethod](QuickReference.md#onmethod)
- **Timing & Duration** → [Quick Reference: @Duration](QuickReference.md#parameter-annotations), [Pattern: Method Timing](QuickReference.md#pattern-1-method-entrye xit-timing)
- **Exception Tracking** → [Quick Reference: Kind.ERROR](QuickReference.md#location-kinds), [Pattern: Exception Tracking](QuickReference.md#pattern-3-exception-tracking)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The anchor in this link contains an embedded space (entrye xit), which will break the Markdown link target. Fix the anchor text to match the actual section id in QuickReference (and keep it space-free).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed — QuickReference.md has a section ### Pattern 1: Method Entry/Exit Timing which GitHub slugifies to pattern-1-method-entryexit-timing; the current README anchor contains a stray space (entrye xit) that will never match.

Comment on lines +9 to +12
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

This subproject sets sourceCompatibility/targetCompatibility to Java 11, while the repo-wide build defaults (via common.gradle) compile modules for Java 8. If this plugin is meant to be consumed by Java-8 users, compiling it to 11 will be a compatibility break; if it intentionally requires 11, it should be clearly isolated from the Java-8 toolchain assumptions and documented. Please align the target level with the repo convention or explicitly justify/enforce the higher requirement.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Intentional and isolated. The Maven Plugin API 3.9.x (declared at build.gradle:18-20) itself requires Java 11+ at runtime — downstream consumers who can run Maven 3.9 already have a Java-11 toolchain. The rest of the BTrace agent/client/runtime retains sourceCompatibility = 8 in common.gradle; only this subproject bumps to 11 because it is a Maven plugin, not a component shipped into target JVMs. No Java-8 user of the BTrace agent is affected.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 55 changed files in this pull request and generated no new comments.

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

Labels

AI AI-generated code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants