Skip to content
Permalink
Browse files
8048190: NoClassDefFoundError omits original ExceptionInInitializerError
Reviewed-by: dholmes, iklam
  • Loading branch information
coleenp committed Aug 12, 2021
1 parent 7e14c3c commit 464e874a5c6b46fcc729227764d07feb1801314d
Showing 9 changed files with 255 additions and 13 deletions.
@@ -2644,6 +2644,51 @@ void java_lang_Throwable::get_stack_trace_elements(Handle throwable,
}
}

Handle java_lang_Throwable::get_cause_with_stack_trace(Handle throwable, TRAPS) {
// Call to JVM to fill in the stack trace and clear declaringClassObject to
// not keep classes alive in the stack trace.
// call this: public StackTraceElement[] getStackTrace()
assert(throwable.not_null(), "shouldn't be");

JavaValue result(T_ARRAY);
JavaCalls::call_virtual(&result, throwable,
vmClasses::Throwable_klass(),
vmSymbols::getStackTrace_name(),
vmSymbols::getStackTrace_signature(),
CHECK_NH);
Handle stack_trace(THREAD, result.get_oop());
assert(stack_trace->is_objArray(), "Should be an array");

// Throw ExceptionInInitializerError as the cause with this exception in
// the message and stack trace.

// Now create the message with the original exception and thread name.
Symbol* message = java_lang_Throwable::detail_message(throwable());
ResourceMark rm(THREAD);
stringStream st;
st.print("Exception %s%s ", throwable()->klass()->name()->as_klass_external_name(),
message == nullptr ? "" : ":");
if (message == NULL) {
st.print("[in thread \"%s\"]", THREAD->name());
} else {
st.print("%s [in thread \"%s\"]", message->as_C_string(), THREAD->name());
}

Symbol* exception_name = vmSymbols::java_lang_ExceptionInInitializerError();
Handle h_cause = Exceptions::new_exception(THREAD, exception_name, st.as_string());

// If new_exception returns a different exception while creating the exception, return null.
if (h_cause->klass()->name() != exception_name) {
log_info(class, init)("Exception thrown while saving initialization exception %s",
h_cause->klass()->external_name());
return Handle();
}
java_lang_Throwable::set_stacktrace(h_cause(), stack_trace());
// Clear backtrace because the stacktrace should be used instead.
set_backtrace(h_cause(), NULL);
return h_cause;
}

bool java_lang_Throwable::get_top_method_and_bci(oop throwable, Method** method, int* bci) {
JavaThread* current = JavaThread::current();
objArrayHandle result(current, objArrayOop(backtrace(throwable)));
@@ -563,6 +563,10 @@ class java_lang_Throwable: AllStatic {
static void fill_in_stack_trace(Handle throwable, const methodHandle& method = methodHandle());
// Programmatic access to stack trace
static void get_stack_trace_elements(Handle throwable, objArrayHandle stack_trace, TRAPS);

// For recreating class initialization error exceptions.
static Handle get_cause_with_stack_trace(Handle throwable, TRAPS);

// Printing
static void print(oop throwable, outputStream* st);
static void print_stack_trace(Handle throwable, outputStream* st);
@@ -1623,6 +1623,8 @@ bool SystemDictionary::do_unloading(GCTimer* gc_timer) {
} else {
assert(_pd_cache_table->number_of_entries() == 0, "should be empty");
}

InstanceKlass::clean_initialization_error_table();
}

return unloading_occurred;
@@ -369,6 +369,7 @@
template(class_initializer_name, "<clinit>") \
template(println_name, "println") \
template(printStackTrace_name, "printStackTrace") \
template(getStackTrace_name, "getStackTrace") \
template(main_name, "main") \
template(name_name, "name") \
template(priority_name, "priority") \
@@ -595,7 +596,9 @@
template(int_String_signature, "(I)Ljava/lang/String;") \
template(boolean_boolean_int_signature, "(ZZ)I") \
template(big_integer_shift_worker_signature, "([I[IIII)V") \
template(reflect_method_signature, "Ljava/lang/reflect/Method;") \
template(reflect_method_signature, "Ljava/lang/reflect/Method;") \
template(getStackTrace_signature, "()[Ljava/lang/StackTraceElement;") \
\
/* signature symbols needed by intrinsics */ \
VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, VM_SYMBOL_IGNORE, template, VM_ALIAS_IGNORE) \
\
@@ -1015,6 +1015,54 @@ void InstanceKlass::initialize_super_interfaces(TRAPS) {
}
}

ResourceHashtable<const InstanceKlass*, OopHandle, 107, ResourceObj::C_HEAP, mtClass>
_initialization_error_table;

void InstanceKlass::add_initialization_error(JavaThread* current, Handle exception) {
// Create the same exception with a message indicating the thread name,
// and the StackTraceElements.
// If the initialization error is OOM, this might not work, but if GC kicks in
// this would be still be helpful.
JavaThread* THREAD = current;
Handle cause = java_lang_Throwable::get_cause_with_stack_trace(exception, THREAD);
if (HAS_PENDING_EXCEPTION || cause.is_null()) {
CLEAR_PENDING_EXCEPTION;
return;
}

MutexLocker ml(THREAD, ClassInitError_lock);
OopHandle elem = OopHandle(Universe::vm_global(), cause());
bool created = false;
_initialization_error_table.put_if_absent(this, elem, &created);
assert(created, "Initialization is single threaded");
ResourceMark rm(THREAD);
log_trace(class, init)("Initialization error added for class %s", external_name());
}

oop InstanceKlass::get_initialization_error(JavaThread* current) {
MutexLocker ml(current, ClassInitError_lock);
OopHandle* h = _initialization_error_table.get(this);
return (h != nullptr) ? h->resolve() : nullptr;
}

// Need to remove entries for unloaded classes.
void InstanceKlass::clean_initialization_error_table() {
struct InitErrorTableCleaner {
bool do_entry(const InstanceKlass* ik, OopHandle h) {
if (!ik->is_loader_alive()) {
h.release(Universe::vm_global());
return true;
} else {
return false;
}
}
};

MutexLocker ml(ClassInitError_lock);
InitErrorTableCleaner cleaner;
_initialization_error_table.unlink(&cleaner);
}

void InstanceKlass::initialize_impl(TRAPS) {
HandleMark hm(THREAD);

@@ -1061,16 +1109,15 @@ void InstanceKlass::initialize_impl(TRAPS) {
if (is_in_error_state()) {
DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
ResourceMark rm(THREAD);
const char* desc = "Could not initialize class ";
const char* className = external_name();
size_t msglen = strlen(desc) + strlen(className) + 1;
char* message = NEW_RESOURCE_ARRAY(char, msglen);
if (NULL == message) {
// Out of memory: can't create detailed error message
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
Handle cause(THREAD, get_initialization_error(THREAD));

stringStream ss;
ss.print("Could not initialize class %s", external_name());
if (cause.is_null()) {
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), ss.as_string());
} else {
jio_snprintf(message, msglen, "%s%s", desc, className);
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
THROW_MSG_CAUSE(vmSymbols::java_lang_NoClassDefFoundError(),
ss.as_string(), cause);
}
}

@@ -1101,6 +1148,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
CLEAR_PENDING_EXCEPTION;
{
EXCEPTION_MARK;
add_initialization_error(THREAD, e);
// Locks object, set state, and notify all waiting threads
set_initialization_state_and_notify(initialization_error, THREAD);
CLEAR_PENDING_EXCEPTION;
@@ -1136,9 +1184,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
// Step 9
if (!HAS_PENDING_EXCEPTION) {
set_initialization_state_and_notify(fully_initialized, CHECK);
{
debug_only(vtable().verify(tty, true);)
}
debug_only(vtable().verify(tty, true);)
}
else {
// Step 10 and 11
@@ -1149,6 +1195,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
JvmtiExport::clear_detected_exception(jt);
{
EXCEPTION_MARK;
add_initialization_error(THREAD, e);
set_initialization_state_and_notify(initialization_error, THREAD);
CLEAR_PENDING_EXCEPTION; // ignore any exception thrown, class initialization error is thrown below
// JVMTI has already reported the pending exception
@@ -1196,6 +1196,7 @@ class InstanceKlass: public Klass {
virtual Klass* array_klass(TRAPS);
virtual Klass* array_klass_or_null();

static void clean_initialization_error_table();
private:
void fence_and_clear_init_lock();

@@ -1207,6 +1208,9 @@ class InstanceKlass: public Klass {
/* jni_id_for_impl for jfieldID only */
JNIid* jni_id_for_impl (int offset);

void add_initialization_error(JavaThread* current, Handle exception);
oop get_initialization_error(JavaThread* current);

// find a local method (returns NULL if not found)
Method* find_method_impl(const Symbol* name,
const Symbol* signature,
@@ -43,6 +43,7 @@ Mutex* Patching_lock = NULL;
Mutex* CompiledMethod_lock = NULL;
Monitor* SystemDictionary_lock = NULL;
Mutex* SharedDictionary_lock = NULL;
Monitor* ClassInitError_lock = NULL;
Mutex* Module_lock = NULL;
Mutex* CompiledIC_lock = NULL;
Mutex* InlineCacheBuffer_lock = NULL;
@@ -256,6 +257,7 @@ void mutex_init() {

def(SystemDictionary_lock , PaddedMonitor, leaf, true, _safepoint_check_always);
def(SharedDictionary_lock , PaddedMutex , leaf, true, _safepoint_check_always);
def(ClassInitError_lock , PaddedMonitor, leaf+1, true, _safepoint_check_always);
def(Module_lock , PaddedMutex , leaf+2, false, _safepoint_check_always);
def(InlineCacheBuffer_lock , PaddedMutex , leaf, true, _safepoint_check_never);
def(VMStatistic_lock , PaddedMutex , leaf, false, _safepoint_check_always);
@@ -35,6 +35,7 @@ extern Mutex* Patching_lock; // a lock used to guard code pa
extern Mutex* CompiledMethod_lock; // a lock used to guard a compiled method and OSR queues
extern Monitor* SystemDictionary_lock; // a lock on the system dictionary
extern Mutex* SharedDictionary_lock; // a lock on the CDS shared dictionary
extern Monitor* ClassInitError_lock; // a lock on the class initialization error table
extern Mutex* Module_lock; // a lock on module and package related data structures
extern Mutex* CompiledIC_lock; // a lock used to guard compiled IC patching and access
extern Mutex* InlineCacheBuffer_lock; // a lock used to guard the InlineCacheBuffer
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/**
* @test
* @bug 8048190
* @summary Test that the NCDFE saves the stack trace for the original exception
* during class initialization with ExceptionInInitializationError,
* and doesn't prevent the classes in the stacktrace to be unloaded.
* @requires vm.opt.final.ClassUnloading
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build sun.hotspot.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -Xmn8m -XX:+UnlockDiagnosticVMOptions -Xlog:class+unload -XX:+WhiteBoxAPI InitExceptionUnloadTest
*/

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import sun.hotspot.WhiteBox;
import jdk.test.lib.classloader.ClassUnloadCommon;

public class InitExceptionUnloadTest {
static public class ThrowsRuntimeException { static int x = 1/0; }
static public class ThrowsError { static { if (true) throw new Error(); } }
static public class SpecialException extends RuntimeException {
SpecialException(int count, String message) {
super(message + count);
}
}
static public class ThrowsSpecialException {
static {
if (true) throw new SpecialException(3, "Very Special ");
}
}

static public class ThrowsOOM {
static {
if (true) {
// Actually getting an OOM might be fragile but it was tested.
throw new OutOfMemoryError("Java heap space");
}
}
}

private static void verify_stack(Throwable e, String expected, String cause) throws Exception {
ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteOS);
e.printStackTrace(printStream);
printStream.close();
String stackTrace = byteOS.toString("ASCII");
if (!stackTrace.contains(expected) || (cause != null && !stackTrace.contains(cause))) {
throw new RuntimeException(expected + " and " + cause + " missing from stacktrace");
}
}

static String[] expected = new String[] {
"java.lang.ExceptionInInitializerError",
"Caused by: java.lang.ArithmeticException: / by zero",
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsRuntimeException",
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.ArithmeticException: / by zero [in thread",
"java.lang.Error",
null,
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsError",
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.Error [in thread",
"java.lang.ExceptionInInitializerError",
"Caused by: InitExceptionUnloadTest$SpecialException: Very Special 3",
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsSpecialException",
"Caused by: java.lang.ExceptionInInitializerError: Exception InitExceptionUnloadTest$SpecialException: Very Special 3",
"java.lang.OutOfMemoryError",
"Java heap space",
"java.lang.NoClassDefFoundError: Could not initialize class InitExceptionUnloadTest$ThrowsOOM",
"Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.OutOfMemoryError: Java heap space [in thread"
};

static String[] classNames = new String[] {
"InitExceptionUnloadTest$ThrowsRuntimeException",
"InitExceptionUnloadTest$ThrowsError",
"InitExceptionUnloadTest$ThrowsSpecialException",
"InitExceptionUnloadTest$ThrowsOOM" };

public static WhiteBox wb = WhiteBox.getWhiteBox();

static void test() throws Throwable {
ClassLoader cl = ClassUnloadCommon.newClassLoader();
int i = 0;
for (String className : classNames) {
for (int tries = 2; tries-- > 0; ) {
System.err.println("--- try to load " + className);
try {
Class<?> c = cl.loadClass(className);
Object inst = c.newInstance();
} catch (Throwable t) {
t.printStackTrace();
System.err.println();
System.err.println("Check results");
verify_stack(t, expected[i], expected[i+1]);
i += 2;
System.err.println();
}
}
}
cl = null;
ClassUnloadCommon.triggerUnloading(); // should unload these classes
for (String className : classNames) {
ClassUnloadCommon.failIf(wb.isClassAlive(className), "should be unloaded");
}
}
public static void main(java.lang.String[] unused) throws Throwable {
test();
test();
}
}

1 comment on commit 464e874

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 464e874 Aug 12, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.