Skip to content

Commit bf51354

Browse files
committedMar 3, 2023
8048190: NoClassDefFoundError omits original ExceptionInInitializerError
Reviewed-by: phh Backport-of: 464e874a5c6b46fcc729227764d07feb1801314d
1 parent 7b967bf commit bf51354

File tree

9 files changed

+260
-13
lines changed

9 files changed

+260
-13
lines changed
 

‎src/hotspot/share/classfile/javaClasses.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,51 @@ void java_lang_Throwable::get_stack_trace_elements(Handle throwable,
27192719
}
27202720
}
27212721

2722+
Handle java_lang_Throwable::get_cause_with_stack_trace(Handle throwable, TRAPS) {
2723+
// Call to JVM to fill in the stack trace and clear declaringClassObject to
2724+
// not keep classes alive in the stack trace.
2725+
// call this: public StackTraceElement[] getStackTrace()
2726+
assert(throwable.not_null(), "shouldn't be");
2727+
2728+
JavaValue result(T_ARRAY);
2729+
JavaCalls::call_virtual(&result, throwable,
2730+
vmClasses::Throwable_klass(),
2731+
vmSymbols::getStackTrace_name(),
2732+
vmSymbols::getStackTrace_signature(),
2733+
CHECK_NH);
2734+
Handle stack_trace(THREAD, result.get_oop());
2735+
assert(stack_trace->is_objArray(), "Should be an array");
2736+
2737+
// Throw ExceptionInInitializerError as the cause with this exception in
2738+
// the message and stack trace.
2739+
2740+
// Now create the message with the original exception and thread name.
2741+
Symbol* message = java_lang_Throwable::detail_message(throwable());
2742+
ResourceMark rm(THREAD);
2743+
stringStream st;
2744+
st.print("Exception %s%s ", throwable()->klass()->name()->as_klass_external_name(),
2745+
message == nullptr ? "" : ":");
2746+
if (message == NULL) {
2747+
st.print("[in thread \"%s\"]", THREAD->name());
2748+
} else {
2749+
st.print("%s [in thread \"%s\"]", message->as_C_string(), THREAD->name());
2750+
}
2751+
2752+
Symbol* exception_name = vmSymbols::java_lang_ExceptionInInitializerError();
2753+
Handle h_cause = Exceptions::new_exception(THREAD, exception_name, st.as_string());
2754+
2755+
// If new_exception returns a different exception while creating the exception, return null.
2756+
if (h_cause->klass()->name() != exception_name) {
2757+
log_info(class, init)("Exception thrown while saving initialization exception %s",
2758+
h_cause->klass()->external_name());
2759+
return Handle();
2760+
}
2761+
java_lang_Throwable::set_stacktrace(h_cause(), stack_trace());
2762+
// Clear backtrace because the stacktrace should be used instead.
2763+
set_backtrace(h_cause(), NULL);
2764+
return h_cause;
2765+
}
2766+
27222767
bool java_lang_Throwable::get_top_method_and_bci(oop throwable, Method** method, int* bci) {
27232768
JavaThread* current = JavaThread::current();
27242769
objArrayHandle result(current, objArrayOop(backtrace(throwable)));

‎src/hotspot/share/classfile/javaClasses.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,10 @@ class java_lang_Throwable: AllStatic {
567567
static void fill_in_stack_trace(Handle throwable, const methodHandle& method = methodHandle());
568568
// Programmatic access to stack trace
569569
static void get_stack_trace_elements(Handle throwable, objArrayHandle stack_trace, TRAPS);
570+
571+
// For recreating class initialization error exceptions.
572+
static Handle get_cause_with_stack_trace(Handle throwable, TRAPS);
573+
570574
// Printing
571575
static void print(oop throwable, outputStream* st);
572576
static void print_stack_trace(Handle throwable, outputStream* st);

‎src/hotspot/share/classfile/systemDictionary.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,8 @@ bool SystemDictionary::do_unloading(GCTimer* gc_timer) {
16261626
} else {
16271627
assert(_pd_cache_table->number_of_entries() == 0, "should be empty");
16281628
}
1629+
1630+
InstanceKlass::clean_initialization_error_table();
16291631
}
16301632

16311633
return unloading_occurred;

‎src/hotspot/share/classfile/vmSymbols.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
template(class_initializer_name, "<clinit>") \
369369
template(println_name, "println") \
370370
template(printStackTrace_name, "printStackTrace") \
371+
template(getStackTrace_name, "getStackTrace") \
371372
template(main_name, "main") \
372373
template(name_name, "name") \
373374
template(priority_name, "priority") \
@@ -593,7 +594,9 @@
593594
template(int_String_signature, "(I)Ljava/lang/String;") \
594595
template(boolean_boolean_int_signature, "(ZZ)I") \
595596
template(big_integer_shift_worker_signature, "([I[IIII)V") \
596-
template(reflect_method_signature, "Ljava/lang/reflect/Method;") \
597+
template(reflect_method_signature, "Ljava/lang/reflect/Method;") \
598+
template(getStackTrace_signature, "()[Ljava/lang/StackTraceElement;") \
599+
\
597600
/* signature symbols needed by intrinsics */ \
598601
VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, VM_SYMBOL_IGNORE, template, VM_ALIAS_IGNORE) \
599602
\

‎src/hotspot/share/oops/instanceKlass.cpp

+64-12
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,59 @@ void InstanceKlass::initialize_super_interfaces(TRAPS) {
10211021
}
10221022
}
10231023

1024+
ResourceHashtable<const InstanceKlass*, OopHandle,
1025+
primitive_hash<const InstanceKlass*>,
1026+
primitive_equals<const InstanceKlass*>,
1027+
107,
1028+
ResourceObj::C_HEAP,
1029+
mtClass>
1030+
_initialization_error_table;
1031+
1032+
void InstanceKlass::add_initialization_error(JavaThread* current, Handle exception) {
1033+
// Create the same exception with a message indicating the thread name,
1034+
// and the StackTraceElements.
1035+
// If the initialization error is OOM, this might not work, but if GC kicks in
1036+
// this would be still be helpful.
1037+
JavaThread* THREAD = current;
1038+
Handle cause = java_lang_Throwable::get_cause_with_stack_trace(exception, THREAD);
1039+
if (HAS_PENDING_EXCEPTION || cause.is_null()) {
1040+
CLEAR_PENDING_EXCEPTION;
1041+
return;
1042+
}
1043+
1044+
MutexLocker ml(THREAD, ClassInitError_lock);
1045+
OopHandle elem = OopHandle(Universe::vm_global(), cause());
1046+
bool created = false;
1047+
_initialization_error_table.put_if_absent(this, elem, &created);
1048+
assert(created, "Initialization is single threaded");
1049+
ResourceMark rm(THREAD);
1050+
log_trace(class, init)("Initialization error added for class %s", external_name());
1051+
}
1052+
1053+
oop InstanceKlass::get_initialization_error(JavaThread* current) {
1054+
MutexLocker ml(current, ClassInitError_lock);
1055+
OopHandle* h = _initialization_error_table.get(this);
1056+
return (h != nullptr) ? h->resolve() : nullptr;
1057+
}
1058+
1059+
// Need to remove entries for unloaded classes.
1060+
void InstanceKlass::clean_initialization_error_table() {
1061+
struct InitErrorTableCleaner {
1062+
bool do_entry(const InstanceKlass* ik, OopHandle h) {
1063+
if (!ik->is_loader_alive()) {
1064+
h.release(Universe::vm_global());
1065+
return true;
1066+
} else {
1067+
return false;
1068+
}
1069+
}
1070+
};
1071+
1072+
MutexLocker ml(ClassInitError_lock);
1073+
InitErrorTableCleaner cleaner;
1074+
_initialization_error_table.unlink(&cleaner);
1075+
}
1076+
10241077
void InstanceKlass::initialize_impl(TRAPS) {
10251078
HandleMark hm(THREAD);
10261079

@@ -1067,16 +1120,15 @@ void InstanceKlass::initialize_impl(TRAPS) {
10671120
if (is_in_error_state()) {
10681121
DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
10691122
ResourceMark rm(THREAD);
1070-
const char* desc = "Could not initialize class ";
1071-
const char* className = external_name();
1072-
size_t msglen = strlen(desc) + strlen(className) + 1;
1073-
char* message = NEW_RESOURCE_ARRAY(char, msglen);
1074-
if (NULL == message) {
1075-
// Out of memory: can't create detailed error message
1076-
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
1123+
Handle cause(THREAD, get_initialization_error(THREAD));
1124+
1125+
stringStream ss;
1126+
ss.print("Could not initialize class %s", external_name());
1127+
if (cause.is_null()) {
1128+
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), ss.as_string());
10771129
} else {
1078-
jio_snprintf(message, msglen, "%s%s", desc, className);
1079-
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
1130+
THROW_MSG_CAUSE(vmSymbols::java_lang_NoClassDefFoundError(),
1131+
ss.as_string(), cause);
10801132
}
10811133
}
10821134

@@ -1107,6 +1159,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
11071159
CLEAR_PENDING_EXCEPTION;
11081160
{
11091161
EXCEPTION_MARK;
1162+
add_initialization_error(THREAD, e);
11101163
// Locks object, set state, and notify all waiting threads
11111164
set_initialization_state_and_notify(initialization_error, THREAD);
11121165
CLEAR_PENDING_EXCEPTION;
@@ -1142,9 +1195,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
11421195
// Step 9
11431196
if (!HAS_PENDING_EXCEPTION) {
11441197
set_initialization_state_and_notify(fully_initialized, CHECK);
1145-
{
1146-
debug_only(vtable().verify(tty, true);)
1147-
}
1198+
debug_only(vtable().verify(tty, true);)
11481199
}
11491200
else {
11501201
// Step 10 and 11
@@ -1155,6 +1206,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
11551206
JvmtiExport::clear_detected_exception(jt);
11561207
{
11571208
EXCEPTION_MARK;
1209+
add_initialization_error(THREAD, e);
11581210
set_initialization_state_and_notify(initialization_error, THREAD);
11591211
CLEAR_PENDING_EXCEPTION; // ignore any exception thrown, class initialization error is thrown below
11601212
// JVMTI has already reported the pending exception

‎src/hotspot/share/oops/instanceKlass.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@ class InstanceKlass: public Klass {
11961196
virtual Klass* array_klass(TRAPS);
11971197
virtual Klass* array_klass_or_null();
11981198

1199+
static void clean_initialization_error_table();
11991200
private:
12001201
void fence_and_clear_init_lock();
12011202

@@ -1205,6 +1206,9 @@ class InstanceKlass: public Klass {
12051206
void initialize_super_interfaces (TRAPS);
12061207
void eager_initialize_impl ();
12071208

1209+
void add_initialization_error(JavaThread* current, Handle exception);
1210+
oop get_initialization_error(JavaThread* current);
1211+
12081212
// find a local method (returns NULL if not found)
12091213
Method* find_method_impl(const Symbol* name,
12101214
const Symbol* signature,

‎src/hotspot/share/runtime/mutexLocker.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Mutex* Patching_lock = NULL;
4343
Mutex* CompiledMethod_lock = NULL;
4444
Monitor* SystemDictionary_lock = NULL;
4545
Mutex* SharedDictionary_lock = NULL;
46+
Monitor* ClassInitError_lock = NULL;
4647
Mutex* Module_lock = NULL;
4748
Mutex* CompiledIC_lock = NULL;
4849
Mutex* InlineCacheBuffer_lock = NULL;
@@ -255,6 +256,7 @@ void mutex_init() {
255256

256257
def(SystemDictionary_lock , PaddedMonitor, leaf, true, _safepoint_check_always);
257258
def(SharedDictionary_lock , PaddedMutex , leaf, true, _safepoint_check_always);
259+
def(ClassInitError_lock , PaddedMonitor, leaf+1, true, _safepoint_check_always);
258260
def(Module_lock , PaddedMutex , leaf+2, false, _safepoint_check_always);
259261
def(InlineCacheBuffer_lock , PaddedMutex , leaf, true, _safepoint_check_never);
260262
def(VMStatistic_lock , PaddedMutex , leaf, false, _safepoint_check_always);

‎src/hotspot/share/runtime/mutexLocker.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extern Mutex* Patching_lock; // a lock used to guard code pa
3535
extern Mutex* CompiledMethod_lock; // a lock used to guard a compiled method and OSR queues
3636
extern Monitor* SystemDictionary_lock; // a lock on the system dictionary
3737
extern Mutex* SharedDictionary_lock; // a lock on the CDS shared dictionary
38+
extern Monitor* ClassInitError_lock; // a lock on the class initialization error table
3839
extern Mutex* Module_lock; // a lock on module and package related data structures
3940
extern Mutex* CompiledIC_lock; // a lock used to guard compiled IC patching and access
4041
extern Mutex* InlineCacheBuffer_lock; // a lock used to guard the InlineCacheBuffer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8048190
27+
* @summary Test that the NCDFE saves the stack trace for the original exception
28+
* during class initialization with ExceptionInInitializationError,
29+
* and doesn't prevent the classes in the stacktrace to be unloaded.
30+
* @requires vm.opt.final.ClassUnloading
31+
* @modules java.base/jdk.internal.misc
32+
* @library /test/lib
33+
* @build sun.hotspot.WhiteBox
34+
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
35+
* @run main/othervm -Xbootclasspath/a:. -Xmn8m -XX:+UnlockDiagnosticVMOptions -Xlog:class+unload -XX:+WhiteBoxAPI InitExceptionUnloadTest
36+
*/
37+
38+
import java.io.ByteArrayOutputStream;
39+
import java.io.PrintStream;
40+
41+
import sun.hotspot.WhiteBox;
42+
import jdk.test.lib.classloader.ClassUnloadCommon;
43+
44+
public class InitExceptionUnloadTest {
45+
static public class ThrowsRuntimeException { static int x = 1/0; }
46+
static public class ThrowsError { static { if (true) throw new Error(); } }
47+
static public class SpecialException extends RuntimeException {
48+
SpecialException(int count, String message) {
49+
super(message + count);
50+
}
51+
}
52+
static public class ThrowsSpecialException {
53+
static {
54+
if (true) throw new SpecialException(3, "Very Special ");
55+
}
56+
}
57+
58+
static public class ThrowsOOM {
59+
static {
60+
if (true) {
61+
// Actually getting an OOM might be fragile but it was tested.
62+
throw new OutOfMemoryError("Java heap space");
63+
}
64+
}
65+
}
66+
67+
private static void verify_stack(Throwable e, String expected, String cause) throws Exception {
68+
ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
69+
PrintStream printStream = new PrintStream(byteOS);
70+
e.printStackTrace(printStream);
71+
printStream.close();
72+
String stackTrace = byteOS.toString("ASCII");
73+
if (!stackTrace.contains(expected) || (cause != null && !stackTrace.contains(cause))) {
74+
throw new RuntimeException(expected + " and " + cause + " missing from stacktrace");
75+
}
76+
}
77+
78+
static String[] expected = new String[] {
79+
"java.lang.ExceptionInInitializerError",
80+
"Caused by: java.lang.ArithmeticException: / by zero",
81+
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsRuntimeException",
82+
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.ArithmeticException: / by zero [in thread",
83+
"java.lang.Error",
84+
null,
85+
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsError",
86+
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.Error [in thread",
87+
"java.lang.ExceptionInInitializerError",
88+
"Caused by: InitExceptionUnloadTest$SpecialException: Very Special 3",
89+
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsSpecialException",
90+
"Caused by: java.lang.ExceptionInInitializerError: Exception InitExceptionUnloadTest$SpecialException: Very Special 3",
91+
"java.lang.OutOfMemoryError",
92+
"Java heap space",
93+
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsOOM",
94+
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.OutOfMemoryError: Java heap space [in thread"
95+
};
96+
97+
static String[] classNames = new String[] {
98+
"InitExceptionUnloadTest$ThrowsRuntimeException",
99+
"InitExceptionUnloadTest$ThrowsError",
100+
"InitExceptionUnloadTest$ThrowsSpecialException",
101+
"InitExceptionUnloadTest$ThrowsOOM" };
102+
103+
public static WhiteBox wb = WhiteBox.getWhiteBox();
104+
105+
static void test() throws Throwable {
106+
ClassLoader cl = ClassUnloadCommon.newClassLoader();
107+
int i = 0;
108+
for (String className : classNames) {
109+
for (int tries = 2; tries-- > 0; ) {
110+
System.err.println("--- try to load " + className);
111+
try {
112+
Class<?> c = cl.loadClass(className);
113+
Object inst = c.newInstance();
114+
} catch (Throwable t) {
115+
t.printStackTrace();
116+
System.err.println();
117+
System.err.println("Check results");
118+
verify_stack(t, expected[i], expected[i+1]);
119+
i += 2;
120+
System.err.println();
121+
}
122+
}
123+
}
124+
cl = null;
125+
ClassUnloadCommon.triggerUnloading(); // should unload these classes
126+
for (String className : classNames) {
127+
ClassUnloadCommon.failIf(wb.isClassAlive(className), "should be unloaded");
128+
}
129+
}
130+
public static void main(java.lang.String[] unused) throws Throwable {
131+
test();
132+
test();
133+
}
134+
}

0 commit comments

Comments
 (0)
Please sign in to comment.