diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index cdd3ba526f8..8068d784b00 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -2777,7 +2777,7 @@ Handle java_lang_Throwable::create_initialization_error(JavaThread* current, Han // If new_exception returns a different exception while creating the exception, // abandon the attempt to save the initialization error and return null. if (init_error->klass()->name() != exception_name) { - log_info(class, init)("Exception thrown while saving initialization exception %s", + log_info(class, init)("Exception %s thrown while saving initialization exception", init_error->klass()->external_name()); return Handle(); } diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 58c1792633e..8dcdb6a5f92 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -114,6 +114,7 @@ enum OutOfMemoryInstance { _oom_java_heap, _oom_count }; OopHandle Universe::_out_of_memory_errors; +OopHandle Universe:: _class_init_stack_overflow_error; OopHandle Universe::_delayed_stack_overflow_error_message; OopHandle Universe::_preallocated_out_of_memory_error_array; volatile jint Universe::_preallocated_out_of_memory_error_avail_count = 0; @@ -610,6 +611,9 @@ oop Universe::out_of_memory_error_realloc_objects() { // Throw default _out_of_memory_error_retry object as it will never propagate out of the VM oop Universe::out_of_memory_error_retry() { return out_of_memory_errors()->obj_at(_oom_retry); } + +oop Universe::class_init_out_of_memory_error() { return out_of_memory_errors()->obj_at(_oom_java_heap); } +oop Universe::class_init_stack_overflow_error() { return _class_init_stack_overflow_error.resolve(); } oop Universe::delayed_stack_overflow_error_message() { return _delayed_stack_overflow_error_message.resolve(); } @@ -1028,6 +1032,11 @@ bool universe_post_init() { Handle msg = java_lang_String::create_from_str("/ by zero", CHECK_false); java_lang_Throwable::set_message(Universe::arithmetic_exception_instance(), msg()); + // Setup preallocated StackOverflowError for use with class initialization failure + k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_StackOverflowError(), true, CHECK_false); + instance = InstanceKlass::cast(k)->allocate_instance(CHECK_false); + Universe::_class_init_stack_overflow_error = OopHandle(Universe::vm_global(), instance); + Universe::initialize_known_methods(CHECK_false); // This needs to be done before the first scavenge/gc, since diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index aee154a98bc..5bdc4111ff3 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -110,6 +110,7 @@ class Universe: AllStatic { // preallocated error objects (no backtrace) static OopHandle _out_of_memory_errors; + static OopHandle _class_init_stack_overflow_error; // preallocated cause message for delayed StackOverflowError static OopHandle _delayed_stack_overflow_error_message; @@ -313,6 +314,11 @@ class Universe: AllStatic { static oop out_of_memory_error_retry(); static oop delayed_stack_overflow_error_message(); + // Saved StackOverflowError and OutOfMemoryError for use when + // class initialization can't create ExceptionInInitializerError. + static oop class_init_stack_overflow_error(); + static oop class_init_out_of_memory_error(); + // If it's a certain type of OOME object static bool is_out_of_memory_error_metaspace(oop ex_obj); static bool is_out_of_memory_error_class_metaspace(oop ex_obj); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 51f406435c4..d8c67f88c12 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -984,17 +984,26 @@ ResourceHashtable 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 init_error = java_lang_Throwable::create_initialization_error(current, exception); - ResourceMark rm(THREAD); + ResourceMark rm(current); if (init_error.is_null()) { - log_trace(class, init)("Initialization error is null for class %s", external_name()); - return; + log_trace(class, init)("Unable to create the desired initialization error for class %s", external_name()); + + // We failed to create the new exception, most likely due to either out-of-memory or + // a stackoverflow error. If the original exception was either of those then we save + // the shared, pre-allocated, stackless, instance of that exception. + if (exception->klass() == vmClasses::StackOverflowError_klass()) { + log_debug(class, init)("Using shared StackOverflowError as initialization error for class %s", external_name()); + init_error = Handle(current, Universe::class_init_stack_overflow_error()); + } else if (exception->klass() == vmClasses::OutOfMemoryError_klass()) { + log_debug(class, init)("Using shared OutOfMemoryError as initialization error for class %s", external_name()); + init_error = Handle(current, Universe::class_init_out_of_memory_error()); + } else { + return; + } } - MutexLocker ml(THREAD, ClassInitError_lock); + MutexLocker ml(current, ClassInitError_lock); OopHandle elem = OopHandle(Universe::vm_global(), init_error()); bool created; _initialization_error_table.put_if_absent(this, elem, &created); diff --git a/test/hotspot/jtreg/runtime/ClassInitErrors/TestOutOfMemoryDuringInit.java b/test/hotspot/jtreg/runtime/ClassInitErrors/TestOutOfMemoryDuringInit.java new file mode 100644 index 00000000000..1001ede5447 --- /dev/null +++ b/test/hotspot/jtreg/runtime/ClassInitErrors/TestOutOfMemoryDuringInit.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, 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 8309034 + * @summary Test that when saving a class initialization failure caused by + * an OutOfMemoryError, that we record the OOME as the underlying + * cause, even if we can't create the ExceptionInInitializerError + * + * @comment Enable logging to ease failure diagnosis + * @run main/othervm -Xms64m -Xmx64m TestOutOfMemoryDuringInit + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedList; + +public class TestOutOfMemoryDuringInit { + + static LinkedList theList = new LinkedList<>(); + + static class Nested { + static void forceInit() { } + static { + while (theList != null) { + theList.add(new Object()); + } + } + } + + public static void main(String[] args) throws Exception { + String expected = "java.lang.NoClassDefFoundError: Could not initialize class TestOutOfMemoryDuringInit$Nested"; + // This cause will match either the shared OOME or the EIIE we get + // with some GC's. + String cause = "java.lang.OutOfMemoryError"; + + try { + Nested.forceInit(); + } catch (OutOfMemoryError oome) { + theList = null; // free memory for verification process + System.out.println("Trying to access class Nested ..."); + try { + Nested.forceInit(); + throw new RuntimeException("NoClassDefFoundError was not thrown"); + } catch (NoClassDefFoundError ncdfe) { + verify_stack(ncdfe, expected, cause); + } + } + } + + private static void verify_stack(Throwable e, String expected, String cause) throws Exception { + ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); + try (PrintStream printStream = new PrintStream(byteOS)) { + e.printStackTrace(printStream); + } + String stackTrace = byteOS.toString("ASCII"); + System.out.println(stackTrace); + if (!stackTrace.contains(expected) || + (cause != null && !stackTrace.contains(cause))) { + throw new RuntimeException(expected + " and/or " + cause + " missing from stacktrace"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/ClassInitErrors/TestStackOverflowDuringInit.java b/test/hotspot/jtreg/runtime/ClassInitErrors/TestStackOverflowDuringInit.java new file mode 100644 index 00000000000..674affeded1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/ClassInitErrors/TestStackOverflowDuringInit.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023, 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 8309034 + * @summary Test that when saving a class initialization failure caused by + * a StackOverflowError, that we record the SOE as the underlying + * cause, even if we can't create the ExceptionInInitializerError + * @requires os.simpleArch == "x64" + * @comment The reproducer only fails in the desired way on x64. + * @requires vm.flagless + * @comment This test could easily be perturbed so don't allow flag settings. + * + * @run main/othervm TestStackOverflowDuringInit + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class TestStackOverflowDuringInit { + + // Test case is fuzzed/obfuscated + + public static void main(String[] args) throws Exception { + String expected = "java.lang.NoClassDefFoundError: Could not initialize class java.lang.Long$LongCache"; + String cause = "Caused by: java.lang.StackOverflowError"; + + TestStackOverflowDuringInit i = new TestStackOverflowDuringInit(); + try { + i.j(); + } catch (Throwable ex) { + // ex.printStackTrace(); + verify_stack(ex, expected, cause); + } + } + + void j() { ((e) new a()).g = 0; } + + private static void verify_stack(Throwable e, String expected, String cause) throws Exception { + ByteArrayOutputStream byteOS = new ByteArrayOutputStream(); + try (PrintStream printStream = new PrintStream(byteOS)) { + e.printStackTrace(printStream); + } + String stackTrace = byteOS.toString("ASCII"); + System.out.println(stackTrace); + if (!stackTrace.contains(expected) || + (cause != null && !stackTrace.contains(cause))) { + throw new RuntimeException(expected + " and/or " + cause + " missing from stacktrace"); + } + } +} + +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); + } + } +} +class e extends a { + double g; + int f; +} +class d extends a {}