Skip to content

[SUREFIRE-3460] Don't NPE when StackWalkerStrategy inits with a null TCCL - #3464

Merged
olamy merged 2 commits into
apache:masterfrom
kalayciburak:fix-3460-null-tccl-stackwalker
Sep 10, 2026
Merged

olamy merged 2 commits into
apache:masterfrom
kalayciburak:fix-3460-null-tccl-stackwalker

Conversation

@kalayciburak

@kalayciburak kalayciburak commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

StackWalkerStrategy.<clinit> loaded java.lang.StackWalker through the current thread's TCCL. When that loader is null (user threads that cleared it, or the JVM Attach Listener while a dynamic agent is installed), ReflectionUtils.tryLoadClass threw an uncaught NullPointerException. That failed class initialization and every later reference became NoClassDefFoundError: Could not initialize class ...StackWalkerStrategy.

Because console capture walks the stack on every println, a write from a null-TCCL thread could poison the forked JVM. On 3.6.0 this also surfaces as a confusing Byte Buddy / BlockHound self-attach failure when the JDK prints the dynamic-agent warning from Attach Listener.

This change:

  • treats a null ClassLoader as "not found" in tryLoadClass (ClassNotFoundException / NoClassDefFoundError only)
  • resolves StackWalker / StackFrame from StackWalkerStrategy's defining loader (system loader if that is bootstrap null), since those types are JDK classes and do not need the TCCL

Fixes #3460

Following this checklist to help us incorporate your contribution quickly and easily:

  • Each commit in the pull request should have a meaningful subject line and body.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Run mvn clean install to make sure basic checks pass. A more thorough check will be performed on your pull request automatically.
  • You have run the integration tests successfully (mvn -Prun-its clean install).

Validation (JDK 21, Maven 3.9.11):

  • mvn -pl surefire-api -Dtest=ReflectionUtilsTest,StackTraceProviderTest test β€” 23 tests, 0 failures
  • mvn -pl surefire-api test β€” 217 tests, 0 failures, 6 skipped

If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.

To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.

@slawekjaranowski slawekjaranowski added the bug Something isn't working label Sep 8, 2026

@MatthewRyanRead MatthewRyanRead left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice, hopefully they will accept this for 3.6.1.

static {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// java.lang.StackWalker is a JDK class; do not depend on the current thread's TCCL, which can be null
// (Attach Listener, user threads that cleared it). A null TCCL used to NPE in tryLoadClass and poison

@elharo elharo Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

only describe current code, not what used to happen

try {
return classLoader.loadClass(className);
} catch (NoClassDefFoundError | ClassNotFoundException ignore) {
} catch (NoClassDefFoundError | ClassNotFoundException | RuntimeException ignore) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

return null should probably move into this catch block

Don't catch RuntimeException. Only catch more specific exceptions you actually expect. Otherwise we'll hide bugs we should know about

…TCCL

tryLoadClass treated a null ClassLoader as loadClass on null and threw,
which poisoned StackWalkerStrategy.<clinit> for the rest of the forked
JVM. Load JDK types from the defining class loader instead, and treat a
null loader as not found.

Fixes apache#3460

Signed-off-by: Burak KALAYCI <kalayciburak1996@gmail.com>
@kalayciburak
kalayciburak force-pushed the fix-3460-null-tccl-stackwalker branch from d3cd380 to 0349086 Compare September 9, 2026 12:11

@Test
void getStackDoesNotThrowWhenContextClassLoaderIsNull() {
ClassLoader original = Thread.currentThread().getContextClassLoader();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for your pull request.
Could you please provide an IT test as well. A real as simple as possible project reproducing the issue

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added a tiny IT, same repro as on the issue (null TCCL thread + one println). pushed

Minimal project that prints from a thread whose context class loader
is null, the same repro as on the issue.
@olamy
olamy merged commit b87cb03 into apache:master Sep 10, 2026
14 checks passed
@github-actions github-actions Bot added this to the 3.6.1 milestone Sep 10, 2026
marevol added a commit to codelibs/fess-parent that referenced this pull request Oct 1, 2026
maven-surefire-plugin and maven-failsafe-plugin 3.6.0 (bumped in #84) add
StackWalkerStrategy, whose static initializer calls
Thread.currentThread().getContextClassLoader().loadClass(...). The JVM's
"Attach Listener" thread has no context class loader, so the initializer
throws an NPE whenever that thread prints to the captured stdout/stderr.
Mockito's self-attach prints the dynamic agent loading warning from that
thread, so the agent fails to start ("Could not initialize plugin:
interface org.mockito.plugins.MockMaker"), and every later console line in
the same JVM fails with NoClassDefFoundError for StackWalkerStrategy.

The fess-crawler CI build went from passing to failing on the same commit once
the snapshot picked up 3.6.0. Running the same tests with the plugin version
forced shows 13 errors with 3.6.0 and none with 3.5.6, and the whole
fess-crawler module passes with 3.5.6.

The upstream fix is SUREFIRE-3460 (apache/maven-surefire#3464), scheduled
for 3.6.1, which is not released yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3.6.0 regression: java.lang.NoClassDefFoundError: Could not initialize class org.apache.maven.surefire.api.report.StackWalkerStrategy

5 participants