JDK-8302491 : NoClassDefFoundError omits the original cause of an error
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 11,13,17,19,20,21
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-02-14
  • Updated: 2023-06-05
  • Resolved: 2023-03-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17 JDK 20 JDK 21
17.0.8Fixed 20.0.2Fixed 21 b14Fixed
Related Reports
Relates :  
Relates :  
Description
If StackOverflowError causes NoClassDefFoundError, the original SOE is swallowed and isn't reported in the stack trace.

The issue happens in specific circumstances, when the class initialization procedure detects an error in class instantiation, tries to throw NCDFE with a correctly discovered cause (an original SOE), and catches another SOE when it tries to get the cause.

The reproducer is attached as jtreg test to the proposed fix.

Testing shows that the issue exists in versions 11, 13, 17, and 19.
Comments
Fix request 20u Risk: low. Tested with tier1 and tier2 tests.
14-04-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk20u/pull/55 Date: 2023-04-14 00:20:03 +0000
14-04-2023

[~inakonechnyy], could you please also backport this to jdk20u as well? I'd like to see it there as the fix is brand new and this could give it a little more coverage.
13-04-2023

Fix request 17u. The backporting of this patch enhances the workflow of JDK-8048190 by expanding the range of scenarios in which the root cause of a NoClassDefFoundError can be accurately reported. Risk: low, as the patch only corrects the process of creating ExceptionInInitializerError for a specific scenario. The patch does not apply cleanly, a small adjustment has been done. Tested with tier1 and tier2 tests.
31-03-2023

You miss a risk assessment. Please use the usual heading in your comment. Do some more testing. Tier1 is already done by the GitHub actions and only a basic sanitiy check.
29-03-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/1226 Date: 2023-03-28 20:13:41 +0000
28-03-2023

As the linked issue was ported to downstream, I'd like to backport the fix to the 17 and 11 versions. Fix to 17 isn't applied cleanly. Run tier1 tests, passed ok on Mac OS.
28-03-2023

Changeset: 56851075 Author: Ilarion Nakonechnyy <inakonechnyy@openjdk.org> Committer: Coleen Phillimore <coleenp@openjdk.org> Date: 2023-03-13 17:26:25 +0000 URL: https://git.openjdk.org/jdk/commit/5685107579f0f00b5eae881311315cec34c1ddcb
13-03-2023

The small and nice stacktrace with proper cause for SOE case, that was implemented in the first iteration of proposed fix: Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class java.lang.invoke.MethodHandleImpl$AsVarargsCollector at java.sql/java.sql.DriverManager.ensureDriversInitialized(DriverManager.java:628) at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:427) at jdbc.repro.DriverA.<init>(DriverA.java:22) at jdbc.repro.DriverRepro.main(DriverRepro.java:12) Caused by: java.lang.StackOverflowError ... 4 more can't be achieved in such a straightforward way: return the original Throwable (SOE in this particular case), if EIIE can't be created because of another SOE during the java call. Why - is described in JDK-8048190
06-03-2023

Just to clarify the exact scenario here, this is what happens with class initialization errors. If C.<clinit> throws an exception E of any kind, it is caught, a representation of it is stored away for future reference as the "initialization error" and the class C is marked as erroneous. If the E is a subclass of Error then it is rethrown directly; otherwise an ExceptionInInitializerError is created with E as its cause, and that is thrown. Once the class C has been marked erroneous subsequent attempts to use it, that require it to be initialized, will result in throwing NCDFE with the "initialization error" as the cause. If initialization of a C's superclasses, or super interfaces, throws an exception E of any kind, then E is caught, a representation stored away for future reference as the "initialization error", C is marked erroneous and E is rethrown. (Note: no EIIE wrapper in this case.) --- The way in which a representation of E is created for the "initialization error" is as follows (this code is in the very badly named `Throwable::get_cause_with_stack_trace` method). 1. An upcall to Java is performed to get the stacktrace for E 2. A message string is created consisting of E's type name, the thread name and E's message string 3. A new ExceptionInInitializerError instance is created using the message string from #2. 4. The stacktrace of the new exception is set with the stacktrace from step #1. Both steps 1 and 3 can trigger secondary exceptions. If step 1 throws an exception it remains pending and we return null. If step 3 throws an exception it is not pending, but is actually produced in place of the requested EIIE instance. But that exception is ignored and we again return null. If the process of creating the "initialization error" representation leaves an exception pending, or returns null, then any exception is cleared and no initializaton error is recorded for class C. In that case any future NCDFE pertaining to C will have a null cause and we have lost all information that E originally occurred. This PR wants to try to ensure we produce the initialization error as expected with its references to E's name, message and stacktrace.
22-02-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/12566 Date: 2023-02-14 21:58:01 +0000
14-02-2023