JDK-8310489 : New test runtime/ClassInitErrors/TestStackOverflowDuringInit.java failed
Type:Bug
Component:hotspot
Sub-Component:runtime
Affected Version:22
Priority:P2
Status:Resolved
Resolution:Fixed
Submitted:2023-06-20
Updated:2024-01-03
Resolved:2023-06-27
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.
[~dcubed] Fixed the label. I'm not adding to PL now anyway.
26-06-2023
[~dholmes] - We usually add the 'problemlist' label after the ProblemListing
changeset is integrated.
26-06-2023
I've extracted the essence of the test into a version that is easier to understand and documented the expect behaviour. I change to interpreter mode to remove any chance of JIT oddities.
26-06-2023
A pull request was submitted for review.
URL: https://git.openjdk.org/jdk/pull/14648
Date: 2023-06-26 06:38:52 +0000
26-06-2023
My previous analysis was inaccurate. Here is the code:
class a {
Boolean b;
{
try {
Long.valueOf(509505376256L);
Boolean c =
true ? new d().b
: 5 != ((e)java.util.HashSet.newHashSet(301758).clone()).f;
} finally {
Long.valueOf(0);
}
}
}
Bear in mind that class 'd` extends class `a`.
When the test passes the initial SOE happens here:
Long.valueOf(509505376256L)
-> Long.<init>(val)
-> Number.<init>() <- SOE here
we then hit the finally block:
Long.valueOf(0);
LongCache.<clinit> <- second SOE here
and so LongCache is marked as erroneous and we get the class initialization error we expect later on.
In the failing case the initial SOE hits when we invoke d.<init>() and we then hit the finally block, but this time Long.valueof throws the SOE immediately and we don't reach the <clinit> of LongCache.
However, this is strange because we throw SOE when invoking d.<init>(), yet immediately before that we successfully called Long.valueOf(509505376256L). Now you could explain that if Long.valueOf got inlined, but in that case why would d.<init> not also get inlined?
26-06-2023
So the way this test works is that it has the form:
void recurse() {
try {
recurse();
} finally {
x();
}
}
so the call to recurse() eventually throws SOE and we try to invoke x() which fails, in a predictable way, by also throwing SOE. When this test fails we see the original SOE being thrown, indicating that x() succeeded. So somehow the initial SOE is not thrown directly by the attempt to invoke recurse(), but by something incidental that requires a more significant amount of stack, such that when the SOE is thrown and we start the finally block, we've already freed up enough stack for the call to x() to complete successfully.
I can make the test more robust by allowing for x() to succeed in these rare instances, but it would hide if we actually stopped testing what the test is trying to test - the correct capture of the SOE as the cause of a class initialization failure.
If I could get the failure to reproduce I would be able to investigate what code actually causes the initial "unexpected" SOE.
22-06-2023
SOE is one of those exceptions that we probably can't use to reliably test anything.