Skip to content

Commit ca19ab0

Browse files
committed
fix(agent): Support running agent on bootstrap classpath
- Update `Agent.java` to use `Agent.class.getResource()` instead of `Agent.class.getClassLoader().getResource()` when locating the agent JAR. This prevents a `NullPointerException` when the agent is loaded by the bootstrap class loader (where `getClassLoader()` returns null). - Modify `Properties.java` to automatically default `appmap.debug.disableGit` to `true` if the agent is running on the bootstrap classpath. This avoids crashes in JGit initialization, which relies on `ResourceBundle` loading that is problematic in the bootstrap context. - Add a warning log in `Agent.premain` when running on the bootstrap classpath, advising that this configuration is for troubleshooting only.
1 parent 5efd73f commit ca19ab0

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

agent/src/main/java/com/appland/appmap/Agent.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public static void premain(String agentArgs, Instrumentation inst) {
7474
logger.info("config: {}", AppMapConfig.get());
7575
logger.debug("System properties: {}", System.getProperties());
7676

77+
if (Agent.class.getClassLoader() == null) {
78+
logger.warn("AppMap agent is running on the bootstrap classpath. This is not a recommended configuration and should only be used for troubleshooting. Git integration will be disabled.");
79+
}
80+
7781
addAgentJars(agentArgs, inst);
7882

7983

@@ -162,12 +166,18 @@ private static void addAgentJars(String agentArgs, Instrumentation inst) {
162166
Path agentJarPath = null;
163167
try {
164168
Class<Agent> agentClass = Agent.class;
165-
URL resourceURL = agentClass.getClassLoader()
166-
.getResource(agentClass.getName().replace('.', '/') + ".class");
169+
// When the agent is loaded by the bootstrap class loader (e.g., via -Xbootclasspath/a:),
170+
// agentClass.getClassLoader() returns null, leading to a NullPointerException. To handle
171+
// this, we use Class.getResource() which correctly resolves resources even when the
172+
// class is loaded by the bootstrap class loader. The leading '/' in the resource name
173+
// is crucial for absolute path resolution when using Class.getResource().
174+
URL resourceURL = agentClass.getResource("/" + agentClass.getName().replace('.', '/') + ".class");
175+
167176
// During testing of the agent itself, classes get loaded from a directory, and will have the
168177
// protocol "file". The rest of the time (i.e. when it's actually deployed), they'll always
169-
// come from a jar file.
170-
if (resourceURL.getProtocol().equals("jar")) {
178+
// come from a jar file. We must also check that resourceURL is not null before using it,
179+
// as getResource() can return null if the resource is not found.
180+
if (resourceURL != null && resourceURL.getProtocol().equals("jar")) {
171181
String resourcePath = resourceURL.getPath();
172182
URL jarURL = new URL(resourcePath.substring(0, resourcePath.indexOf('!')));
173183
logger.debug("jarURL: {}", jarURL);

agent/src/main/java/com/appland/appmap/config/Properties.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ public class Properties {
2121
public static final String DebugClassPrefix = resolveProperty("appmap.debug.classPrefix", (String) null);
2222
public static final Boolean SaveInstrumented =
2323
resolveProperty("appmap.debug.saveInstrumented", false);
24-
public static final Boolean DisableGit = resolveProperty("appmap.debug.disableGit", false);
24+
public static final Boolean DisableGit =
25+
// Git integration (JGit) uses resource bundles, which are not reliably available
26+
// when the agent is loaded by the bootstrap class loader (i.e., when
27+
// getClassLoader() returns null). In such cases, automatically disable Git
28+
// to prevent NullPointerExceptions during initialization.
29+
resolveProperty("appmap.debug.disableGit", Properties.class.getClassLoader() == null);
2530

2631
public static final Boolean RecordingAuto = resolveProperty("appmap.recording.auto", false);
2732
public static final String RecordingName = resolveProperty("appmap.recording.name", (String) null);

0 commit comments

Comments
 (0)