wendigo commented on issue #3460:
URL: 
https://github.com/apache/maven-surefire/issues/3460#issuecomment-5570352137

   Root cause, confirmed via bytecode analysis + full stack trace (`Caused by:` 
chain):
   
   `StackWalkerStrategy.<clinit>` calls 
`Thread.currentThread().getContextClassLoader()` and passes it to 
`ReflectionUtils.tryLoadClass(ClassLoader, String)`:
   ```java
   public static Class<?> tryLoadClass(ClassLoader loader, String name) {
       try {
           return loader.loadClass(name);  // NPE if loader == null
       } catch (NoClassDefFoundError | ClassNotFoundException e) {
           return null;
       }
   }
   ```
   This only catches `NoClassDefFoundError`/`ClassNotFoundException` — **not 
`NullPointerException`**. When the current thread's context classloader happens 
to be `null` at the moment `StackWalkerStrategy` is first touched, 
`tryLoadClass` throws an uncaught NPE, which propagates out of `<clinit>` as 
`ExceptionInInitializerError`, permanently poisoning the class for the rest of 
the JVM fork's lifetime — every subsequent reference (from any thread, any 
test) then fails with `NoClassDefFoundError: Could not initialize class 
...StackWalkerStrategy`.
   
   Full first-occurrence trace we captured:
   ```
   Caused by: java.lang.ExceptionInInitializerError: Exception 
java.lang.NullPointerException [in thread "main"]
        at 
org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass(ReflectionUtils.java:152)
        at 
org.apache.maven.surefire.api.report.StackWalkerStrategy.<clinit>(StackWalkerStrategy.java:60)
        at 
org.apache.maven.surefire.api.report.StackTraceProvider.getStack(StackTraceProvider.java:107)
        at 
org.apache.maven.surefire.api.report.TestOutputReportEntry.<init>(TestOutputReportEntry.java:52)
        ...
        at 
jakarta.validation.Validation$ProviderSpecificBootstrapImpl.configure(Validation.java:214)
   ```
   In our case the trigger is Jakarta Bean Validation's provider bootstrap 
(`Validation$ProviderSpecificBootstrapImpl.configure()`), which is known to 
temporarily swap/clear the thread's context classloader while doing its 
`ServiceLoader`-based provider discovery — a fairly common pattern in JVM 
libraries (also seen with `ForkJoinPool.commonPool()` worker threads, which 
have a `null` TCCL by design since JDK 9). Any first captured console write 
racing that window crashes the whole fork.
   
   Confirmed this is a 3.6.0-only regression: the exact same reproduction 
passes cleanly on 3.5.6 (`-Ddep.plugin.surefire.version=3.5.6`), with no code 
changes — same log line fires first, no crash.
   
   Minimal fix: catch `RuntimeException` (or at least `NullPointerException`) 
alongside `NoClassDefFoundError`/`ClassNotFoundException` in 
`ReflectionUtils.tryLoadClass`, so a null/broken classloader just disables the 
fast `StackWalker`-based path (`AVAILABLE = false`) instead of poisoning the 
class for the whole fork.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to