diff --git a/src/hotspot/share/jvmci/jvmci.cpp b/src/hotspot/share/jvmci/jvmci.cpp index b59c7fa3444..ce2eea5e0d5 100644 --- a/src/hotspot/share/jvmci/jvmci.cpp +++ b/src/hotspot/share/jvmci/jvmci.cpp @@ -69,12 +69,17 @@ CompilerThread* CompilerThreadCanCallJava::update(JavaThread* current, bool new_ return nullptr; } -CompilerThreadCanCallJava::CompilerThreadCanCallJava(JavaThread* current, bool new_state) { +CompilerThreadCanCallJava::CompilerThreadCanCallJava(JavaThread* current, bool new_state, JVMCIEnv* env) { _current = CompilerThreadCanCallJava::update(current, new_state); + _env = env; } CompilerThreadCanCallJava::~CompilerThreadCanCallJava() { if (_current != nullptr) { + if (_current->_can_call_java && _env != nullptr && !_env->is_hotspot() && _current->has_pending_exception()) { + // Convert pending HotSpot exception while still inside scope that can call Java + JVMCIEnv::transfer_pending_exception_to_jni(_current, nullptr, _env); + } _current->_can_call_java = !_current->_can_call_java; } } @@ -209,7 +214,7 @@ void JVMCI::ensure_box_caches_initialized(TRAPS) { // Class resolution and initialization below // requires calling into Java - CompilerThreadCanCallJava ccj(THREAD, true); + CompilerThreadCanCallJava ccj(THREAD, true, nullptr); for (unsigned i = 0; i < sizeof(box_classes) / sizeof(Symbol*); i++) { Klass* k = SystemDictionary::resolve_or_fail(box_classes[i], true, CHECK); diff --git a/src/hotspot/share/jvmci/jvmci.hpp b/src/hotspot/share/jvmci/jvmci.hpp index 196007fc7dd..a574bfa52b5 100644 --- a/src/hotspot/share/jvmci/jvmci.hpp +++ b/src/hotspot/share/jvmci/jvmci.hpp @@ -60,6 +60,7 @@ typedef struct _jmetadata *jmetadata; class CompilerThreadCanCallJava : StackObj { private: CompilerThread* _current; // Only non-null if state of thread changed + JVMCIEnv* _env; // For translating an exception from HotSpot to JNI public: // If the current thread is a CompilerThread associated with // a JVMCI compiler where CompilerThread::_can_call_java != new_state, @@ -67,7 +68,7 @@ class CompilerThreadCanCallJava : StackObj { // Returns nullptr if no change was made, otherwise the current CompilerThread static CompilerThread* update(JavaThread* current, bool new_state); - CompilerThreadCanCallJava(JavaThread* current, bool new_state); + CompilerThreadCanCallJava(JavaThread* current, bool new_state, JVMCIEnv* env); // Resets CompilerThread::_can_call_java of the current thread if the // constructor changed it. diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index f993b3bed1e..91c508daa60 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -186,7 +186,7 @@ Handle JavaArgumentUnboxer::next_arg(BasicType expectedType) { ResourceMark rm; \ bool __is_hotspot = env == thread->jni_environment(); \ bool __block_can_call_java = __is_hotspot || !thread->is_Compiler_thread() || CompilerThread::cast(thread)->can_call_java(); \ - CompilerThreadCanCallJava ccj(thread, __block_can_call_java); \ + CompilerThreadCanCallJava ccj(thread, __block_can_call_java, nullptr); \ JVMCIENV_FROM_JNI(JVMCI::compilation_tick(thread), env); \ // Entry to native method implementation that transitions @@ -594,7 +594,7 @@ C2V_VMENTRY_0(jboolean, shouldInlineMethod,(JNIEnv* env, jobject, ARGUMENT_PAIR( C2V_END C2V_VMENTRY_NULL(jobject, lookupType, (JNIEnv* env, jobject, jstring jname, ARGUMENT_PAIR(accessing_klass), jint accessing_klass_loader, jboolean resolve)) - CompilerThreadCanCallJava canCallJava(thread, resolve); // Resolution requires Java calls + CompilerThreadCanCallJava canCallJava(thread, resolve, JVMCIENV); // Resolution requires Java calls JVMCIObject name = JVMCIENV->wrap(jname); const char* str = JVMCIENV->as_utf8_string(name); TempNewSymbol class_name = SymbolTable::new_symbol(str); @@ -1390,7 +1390,7 @@ C2V_VMENTRY(void, reprofile, (JNIEnv* env, jobject, ARGUMENT_PAIR(method))) if (method_data == nullptr) { method_data = get_profiling_method_data(method, CHECK); } else { - CompilerThreadCanCallJava canCallJava(THREAD, true); + CompilerThreadCanCallJava canCallJava(THREAD, true, JVMCIENV); method_data->reinitialize(); } C2V_END @@ -2097,7 +2097,7 @@ C2V_VMENTRY(void, ensureInitialized, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass) C2V_END C2V_VMENTRY(void, ensureLinked, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass))) - CompilerThreadCanCallJava canCallJava(thread, true); // Linking requires Java calls + CompilerThreadCanCallJava canCallJava(thread, true, JVMCIENV); // Linking requires Java calls Klass* klass = UNPACK_PAIR(Klass, klass); if (klass == nullptr) { JVMCI_THROW(NullPointerException); @@ -2908,7 +2908,7 @@ C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle, jbool return 0L; } PEER_JVMCIENV_FROM_THREAD(THREAD, !JVMCIENV->is_hotspot()); - CompilerThreadCanCallJava canCallJava(thread, PEER_JVMCIENV->is_hotspot()); + CompilerThreadCanCallJava canCallJava(thread, PEER_JVMCIENV->is_hotspot(), nullptr); PEER_JVMCIENV->check_init(JVMCI_CHECK_0); JVMCIEnv* thisEnv = JVMCIENV; @@ -3221,7 +3221,7 @@ C2V_VMENTRY(void, callSystemExit, (JNIEnv* env, jobject, jint status)) vm_exit_during_initialization(); } } - CompilerThreadCanCallJava canCallJava(thread, true); + CompilerThreadCanCallJava canCallJava(thread, true, JVMCIENV); JavaValue result(T_VOID); JavaCallArguments jargs(1); jargs.push_int(status); diff --git a/src/hotspot/share/jvmci/jvmciEnv.cpp b/src/hotspot/share/jvmci/jvmciEnv.cpp index d9e1ce3d731..559f7e01707 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.cpp +++ b/src/hotspot/share/jvmci/jvmciEnv.cpp @@ -926,7 +926,6 @@ DO_THROW(InvalidInstalledCodeException) DO_THROW(UnsatisfiedLinkError) DO_THROW(UnsupportedOperationException) DO_THROW(OutOfMemoryError) -DO_THROW(NoClassDefFoundError) #undef DO_THROW @@ -1370,7 +1369,7 @@ JVMCIObject JVMCIEnv::get_jvmci_type(const JVMCIKlassHandle& klass, JVMCI_TRAPS) JavaThread* THREAD = JVMCI::compilation_tick(JavaThread::current()); // For exception macros. jboolean exception = false; if (is_hotspot()) { - CompilerThreadCanCallJava ccj(THREAD, true); + CompilerThreadCanCallJava ccj(THREAD, true, JVMCIENV); JavaValue result(T_OBJECT); JavaCallArguments args; args.push_long(pointer); diff --git a/src/hotspot/share/jvmci/jvmciEnv.hpp b/src/hotspot/share/jvmci/jvmciEnv.hpp index 61eb989c492..91601cfd810 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.hpp +++ b/src/hotspot/share/jvmci/jvmciEnv.hpp @@ -381,7 +381,6 @@ class JVMCIEnv : public ResourceObj { DO_THROW(UnsatisfiedLinkError) DO_THROW(UnsupportedOperationException) DO_THROW(OutOfMemoryError) - DO_THROW(NoClassDefFoundError) #undef DO_THROW diff --git a/src/hotspot/share/jvmci/jvmciJavaClasses.cpp b/src/hotspot/share/jvmci/jvmciJavaClasses.cpp index e5c0017129c..3a8c1eb7f89 100644 --- a/src/hotspot/share/jvmci/jvmciJavaClasses.cpp +++ b/src/hotspot/share/jvmci/jvmciJavaClasses.cpp @@ -408,43 +408,6 @@ extern "C" { jlong JNICALL JVM_ReadSystemPropertiesInfo(JNIEnv *env, jclass c, jintArray offsets_handle); } -// Dumps symbols for public () and (String) methods of -// non-abstract Throwable subtypes known by the VM. This is to -// support the use of reflection in jdk.vm.ci.hotspot.TranslatedException.create(). -class ThrowableInitDumper : public SymbolClosure { - private: - fileStream* _st; - public: - ThrowableInitDumper(fileStream* st) { _st = st; } - void do_symbol(Symbol** p) { - JavaThread* THREAD = JavaThread::current(); // For exception macros. - Symbol* name = *p; - if (name == nullptr) { - return; - } - Klass* k = SystemDictionary::resolve_or_null(name, CHECK_EXIT); - if (k != nullptr && k->is_instance_klass()) { - InstanceKlass* iklass = InstanceKlass::cast(k); - if (iklass->is_subclass_of(vmClasses::Throwable_klass()) && iklass->is_public() && !iklass->is_abstract()) { - const char* class_name = nullptr; - Array* methods = iklass->methods(); - for (int i = 0; i < methods->length(); i++) { - Method* m = methods->at(i); - if (m->name() == vmSymbols::object_initializer_name() && - m->is_public() && - (m->signature() == vmSymbols::void_method_signature() || m->signature() == vmSymbols::string_void_signature())) { - if (class_name == nullptr) { - class_name = name->as_C_string(); - _st->print_cr("class %s", class_name); - } - _st->print_cr("method %s %s %s", class_name, m->name()->as_C_string(), m->signature()->as_C_string()); - } - } - } - } - } -}; - #define IN_CLASS(fullClassName) current_class_name = vmSymbols::fullClassName()->as_C_string() /** * Initializes the JNI method and field ids used in JNIJVMCI. @@ -530,8 +493,6 @@ void JNIJVMCI::initialize_ids(JNIEnv* env) { fileStream* st = JVMCIGlobals::get_jni_config_file(); DUMP_ALL_NATIVE_METHODS(vmSymbols::jdk_vm_ci_hotspot_CompilerToVM()); - ThrowableInitDumper dumper(st); - vmSymbols::symbols_do(&dumper); st->flush(); tty->print_cr("Dumped JVMCI shared library JNI configuration to %s", JVMCILibDumpJNIConfig); diff --git a/src/hotspot/share/jvmci/jvmciJavaClasses.hpp b/src/hotspot/share/jvmci/jvmciJavaClasses.hpp index 90624e61e63..73657aedf25 100644 --- a/src/hotspot/share/jvmci/jvmciJavaClasses.hpp +++ b/src/hotspot/share/jvmci/jvmciJavaClasses.hpp @@ -262,9 +262,6 @@ start_class(OutOfMemoryError, java_lang_OutOfMemoryError) \ jvmci_constructor(OutOfMemoryError, "(Ljava/lang/String;)V") \ end_class \ - start_class(NoClassDefFoundError, java_lang_NoClassDefFoundError) \ - jvmci_constructor(NoClassDefFoundError, "(Ljava/lang/String;)V") \ - end_class \ start_class(InvalidInstalledCodeException, jdk_vm_ci_code_InvalidInstalledCodeException) \ jvmci_constructor(InvalidInstalledCodeException, "(Ljava/lang/String;)V") \ end_class \ diff --git a/src/java.base/share/classes/jdk/internal/vm/TranslatedException.java b/src/java.base/share/classes/jdk/internal/vm/TranslatedException.java index 366766711f8..6f86e5deb19 100644 --- a/src/java.base/share/classes/jdk/internal/vm/TranslatedException.java +++ b/src/java.base/share/classes/jdk/internal/vm/TranslatedException.java @@ -31,7 +31,6 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; @@ -42,14 +41,20 @@ /** * Support for translating exceptions between the HotSpot heap and libjvmci heap. - * - * Successfully translated exceptions are wrapped in a TranslatedException instance. - * This allows callers to distiguish between a translated exception and an error + *

+ * Apart from {@link OutOfMemoryError}s, successfully translated exceptions have + * {@link #TRANSLATED_MARKER} as the first element in their stack trace. This + * allows a caller to distinguish between a translated exception and an error * that arose during translation. */ @SuppressWarnings("serial") public final class TranslatedException extends Exception { + /** + * Marker frame prepended to outermost stack of a translated exception. + */ + public static final StackTraceElement TRANSLATED_MARKER = new StackTraceElement(TranslatedException.class.getName(), "translated", null, -2); + /** * The value returned by {@link #encodeThrowable(Throwable)} when encoding * fails due to an {@link OutOfMemoryError}. @@ -114,7 +119,7 @@ public Throwable fillInStackTrace() { private static void debugPrintStackTrace(Throwable throwable, boolean debug) { if (debug) { System.err.print("DEBUG: "); - throwable.printStackTrace(); + throwable.printStackTrace(System.err); } } @@ -130,26 +135,71 @@ private static Throwable initCause(Throwable throwable, Throwable cause, boolean return throwable; } + /** + * Creates an exception if {@code className} is one of the supported + * core exceptions for translation. + * + * @param className class name of exception to create + * @param message the detailed message for the exception + * @return {@code null} if {@code className} is unsupported + */ + private static Throwable newThrowable(String className, String message) { + return switch (className) { + // Exceptions + case "java.lang.ArithmeticException" -> new ArithmeticException(message); + case "java.lang.ArrayIndexOutOfBoundsException" -> new ArrayIndexOutOfBoundsException(message); + case "java.lang.ArrayStoreException" -> new ArrayStoreException(message); + case "java.lang.ClassCastException" -> new ClassCastException(message); + case "java.lang.ClassNotFoundException" -> new ClassNotFoundException(message); + case "java.lang.CloneNotSupportedException" -> new CloneNotSupportedException(message); + case "java.lang.IllegalAccessException" -> new IllegalAccessException(message); + case "java.lang.IllegalArgumentException" -> new IllegalArgumentException(message); + case "java.lang.IndexOutOfBoundsException" -> new IndexOutOfBoundsException(message); + case "java.lang.InstantiationException" -> new InstantiationException(message); + case "java.lang.NegativeArraySizeException" -> new NegativeArraySizeException(message); + case "java.lang.NoSuchFieldException" -> new NoSuchFieldException(message); + case "java.lang.NoSuchMethodException" -> new NoSuchMethodException(message); + case "java.lang.NullPointerException" -> new NullPointerException(message); + case "java.lang.RuntimeException" -> new RuntimeException(message); + case "java.lang.StringIndexOutOfBoundsException" -> new StringIndexOutOfBoundsException(message); + case "java.lang.UnsupportedOperationException" -> new UnsupportedOperationException(message); + + // Errors + case "java.lang.AbstractMethodError" -> new AbstractMethodError(message); + case "java.lang.BootstrapMethodError" -> new BootstrapMethodError(message); + case "java.lang.ClassCircularityError" -> new ClassCircularityError(message); + case "java.lang.ClassFormatError" -> new ClassFormatError(message); + case "java.lang.IllegalAccessError" -> new IllegalAccessError(message); + case "java.lang.IncompatibleClassChangeError" -> new IncompatibleClassChangeError(message); + case "java.lang.InstantiationError" -> new InstantiationError(message); + case "java.lang.InternalError" -> new InternalError(message); + case "java.lang.LinkageError" -> new LinkageError(message); + case "java.lang.NoClassDefFoundError" -> new NoClassDefFoundError(message); + case "java.lang.NoSuchFieldError" -> new NoSuchFieldError(message); + case "java.lang.NoSuchMethodError" -> new NoSuchMethodError(message); + case "java.lang.OutOfMemoryError" -> new OutOfMemoryError(message); + case "java.lang.StackOverflowError" -> new StackOverflowError(message); + case "java.lang.UnsatisfiedLinkError" -> new UnsatisfiedLinkError(message); + default -> null; + }; + } + private static Throwable create(String className, String message, Throwable cause, boolean debug) { - // Try create with reflection first. try { - Class cls = Class.forName(className); - if (cause != null) { - // Handle known exception types whose cause must - // be set in the constructor - if (cls == InvocationTargetException.class) { - return new InvocationTargetException(cause, message); - } - if (cls == ExceptionInInitializerError.class) { - return new ExceptionInInitializerError(cause); - } + if (className.equals(InvocationTargetException.class.getName())) { + return new InvocationTargetException(cause, message); } - if (message == null) { - Constructor cons = cls.getConstructor(); - return initCause((Throwable) cons.newInstance(), cause, debug); + if (className.equals(ExceptionInInitializerError.class.getName())) { + return new ExceptionInInitializerError(cause); } - Constructor cons = cls.getDeclaredConstructor(String.class); - return initCause((Throwable) cons.newInstance(message), cause, debug); + if (className.equals(AssertionError.class.getName())) { + return initCause(new AssertionError(cause), cause,debug); + } + Throwable throwable = newThrowable(className, message); + if (throwable != null) { + return initCause(throwable, cause,debug); + } + return initCause(translationFailure("%s [%s]", message, className), cause, debug); } catch (Throwable translationFailure) { debugPrintStackTrace(translationFailure, debug); return initCause(translationFailure("%s [%s]", message, className), cause, debug); @@ -190,7 +240,7 @@ private static byte[] encodeThrowable(Throwable throwable, } } - // Encode from inner most cause outwards + // Encode from innermost cause outwards Collections.reverse(throwables); for (Throwable current : throwables) { @@ -201,8 +251,7 @@ private static byte[] encodeThrowable(Throwable throwable, stackTrace = new StackTraceElement[0]; } dos.writeInt(stackTrace.length); - for (int i = 0; i < stackTrace.length; i++) { - StackTraceElement frame = stackTrace[i]; + for (StackTraceElement frame : stackTrace) { if (frame != null) { dos.writeUTF(emptyIfNull(frame.getClassLoaderName())); dos.writeUTF(emptyIfNull(frame.getModuleName())); @@ -294,13 +343,20 @@ static Throwable decodeThrowable(byte[] encodedThrowable, boolean debug) { // Remove null entries at end of stackTrace stackTrace = Arrays.copyOf(stackTrace, stackTraceIndex); } + if (dis.available() == 0) { + // Prepend the marker frame to the outermost stack trace + StackTraceElement[] newStackTrace = new StackTraceElement[stackTrace.length + 1]; + System.arraycopy(stackTrace, 0, newStackTrace, 1, stackTrace.length); + newStackTrace[0] = TRANSLATED_MARKER; + stackTrace = newStackTrace; + } throwable.setStackTrace(stackTrace); cause = throwable; } - return new TranslatedException(throwable); + return throwable; } catch (Throwable translationFailure) { debugPrintStackTrace(translationFailure, debug); - return translationFailure("error decoding exception: %s", encodedThrowable); + return translationFailure("error decoding exception: %s", Arrays.toString(encodedThrowable)); } } } diff --git a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java index afb34d9ade8..519f56c2880 100644 --- a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java +++ b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java @@ -116,7 +116,7 @@ public static byte[] serializeAgentPropertiesToByteArray() throws IOException { public static void decodeAndThrowThrowable(int format, long buffer, boolean inJVMHeap, boolean debug) throws Throwable { if (format != 0) { if (format == 4) { - throw new TranslatedException(new OutOfMemoryError("in VM code and current thread cannot call Java")); + throw new OutOfMemoryError("in VM code and current thread cannot call Java"); } String context = String.format("while encoding an exception to translate it %s the JVM heap", inJVMHeap ? "to" : "from"); diff --git a/test/jdk/jdk/internal/vm/TestTranslatedException.java b/test/jdk/jdk/internal/vm/TestTranslatedException.java index 0a5bfb18c23..75ba13d3ea3 100644 --- a/test/jdk/jdk/internal/vm/TestTranslatedException.java +++ b/test/jdk/jdk/internal/vm/TestTranslatedException.java @@ -30,10 +30,7 @@ */ package jdk.internal.vm.test; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import org.testng.Assert; @@ -44,16 +41,14 @@ import jdk.internal.vm.VMSupport; public class TestTranslatedException { - @SuppressWarnings("serial") public static class Untranslatable extends RuntimeException { public Untranslatable(String message, Throwable cause) { super(message, cause); } } - @SuppressWarnings("unchecked") @Test - public void encodeDecodeTest() throws Exception { + public void encodeDecodeTest() { Throwable throwable = new ExceptionInInitializerError(new InvocationTargetException(new Untranslatable("test exception", new NullPointerException()), "invoke")); for (int i = 0; i < 10; i++) { throwable = new ExceptionInInitializerError(new InvocationTargetException(new RuntimeException(String.valueOf(i), throwable), "invoke")); @@ -101,8 +96,8 @@ public void encodeDecodeTest() throws Exception { try { VMSupport.decodeAndThrowThrowable(4, 0L, true, false); throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); - } catch (TranslatedException decoded) { - Assert.assertEquals(decoded.getCause().getClass(), OutOfMemoryError.class); + } catch (OutOfMemoryError decoded) { + // Expected } catch (Throwable decoded) { throw new AssertionError("unexpected exception: " + decoded); } @@ -136,13 +131,12 @@ public void encodeDecodeTest() throws Exception { } } - private void encodeDecode(Throwable throwable) throws Exception { + private void encodeDecode(Throwable throwable) { Unsafe unsafe = Unsafe.getUnsafe(); int bufferSize = 512; int format = 0; - long buffer = 0L; while (true) { - buffer = unsafe.allocateMemory(bufferSize); + long buffer = unsafe.allocateMemory(bufferSize); try { int res = VMSupport.encodeThrowable(throwable, buffer, bufferSize); if (res < 0) { @@ -152,7 +146,7 @@ private void encodeDecode(Throwable throwable) throws Exception { VMSupport.decodeAndThrowThrowable(format, buffer, true, false); throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); } catch (Throwable decoded) { - assertThrowableEquals(throwable, decoded.getCause()); + assertThrowableEquals(throwable, decoded); } return; } @@ -178,10 +172,18 @@ private static void assertThrowableEquals(Throwable originalIn, Throwable decode } StackTraceElement[] originalStack = original.getStackTrace(); StackTraceElement[] decodedStack = decoded.getStackTrace(); - Assert.assertEquals(originalStack.length, decodedStack.length); - for (int i = 0, n = originalStack.length; i < n; ++i) { + int decodedStartIndex; + if (decoded == decodedIn) { + Assert.assertEquals(decodedStack[0], TranslatedException.TRANSLATED_MARKER); + Assert.assertEquals(decodedStack.length, originalStack.length + 1); + decodedStartIndex = 1; + } else { + Assert.assertEquals(decodedStack.length, originalStack.length); + decodedStartIndex = 0; + } + for (int i = 0; i < originalStack.length; ++i) { StackTraceElement originalStackElement = originalStack[i]; - StackTraceElement decodedStackElement = decodedStack[i]; + StackTraceElement decodedStackElement = decodedStack[i + decodedStartIndex]; Assert.assertEquals(decodedStackElement.getClassLoaderName(), originalStackElement.getClassLoaderName()); Assert.assertEquals(decodedStackElement.getModuleName(), originalStackElement.getModuleName()); Assert.assertEquals(decodedStackElement.getClassName(), originalStackElement.getClassName());