diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 7567fff6fbd..928add990c4 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -31,7 +31,7 @@ #define VM_CLASS_ID(kname) vmClassID::_VM_CLASS_ENUM(kname) // VM_CLASSES_DO iterates the classes that are directly referenced -// by the VM, suhch as java.lang.Object and java.lang.String. These +// by the VM, such as java.lang.Object and java.lang.String. These // classes are resolved at VM bootstrap, before any Java code is executed, // so no class loader is able to provide a different definition. // diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 1b406550ef7..aad5de52075 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -757,7 +757,7 @@ template(decodeAndThrowThrowable_name, "decodeAndThrowThrowable") \ template(encodeAnnotations_name, "encodeAnnotations") \ template(encodeAnnotations_signature, "([BLjava/lang/Class;Ljdk/internal/reflect/ConstantPool;Z[Ljava/lang/Class;)[B")\ - template(decodeAndThrowThrowable_signature, "(JZ)V") \ + template(decodeAndThrowThrowable_signature, "(IJZ)V") \ template(classRedefinedCount_name, "classRedefinedCount") \ template(classLoader_name, "classLoader") \ template(componentType_name, "componentType") \ diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index 6a8d63991be..ae13fc86143 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -582,7 +582,7 @@ C2V_VMENTRY_NULL(jobject, lookupType, (JNIEnv* env, jobject, jstring jname, ARGU TempNewSymbol class_name = SymbolTable::new_symbol(str); if (class_name->utf8_length() <= 1) { - JVMCI_THROW_MSG_0(InternalError, err_msg("Primitive type %s should be handled in Java code", class_name->as_C_string())); + JVMCI_THROW_MSG_0(InternalError, err_msg("Primitive type %s should be handled in Java code", str)); } JVMCIKlassHandle resolved_klass(THREAD); diff --git a/src/hotspot/share/jvmci/jvmciEnv.cpp b/src/hotspot/share/jvmci/jvmciEnv.cpp index 348604fa937..40aadfe1670 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.cpp +++ b/src/hotspot/share/jvmci/jvmciEnv.cpp @@ -374,6 +374,15 @@ bool JVMCIEnv::pending_exception_as_string(const char** to_string, const char** // Shared code for translating an exception from HotSpot to libjvmci or vice versa. class ExceptionTranslation: public StackObj { protected: + enum DecodeFormat { + _encoded_ok = 0, // exception was successfully encoded into buffer + _buffer_alloc_fail = 1, // native memory for buffer could not be allocated + _encode_oome_fail = 2, // OutOfMemoryError thrown during encoding + _encode_fail = 3 // some other problem occured during encoding. If buffer != 0, + // buffer contains a `struct { u4 len; char[len] desc}` + // describing the problem + }; + JVMCIEnv* _from_env; // Source of translation. Can be null. JVMCIEnv* _to_env; // Destination of translation. Never null. @@ -382,49 +391,34 @@ class ExceptionTranslation: public StackObj { // Encodes the exception in `_from_env` into `buffer`. // Where N is the number of bytes needed for the encoding, returns N if N <= `buffer_size` // and the encoding was written to `buffer` otherwise returns -N. - virtual int encode(JavaThread* THREAD, Klass* vmSupport, jlong buffer, int buffer_size) = 0; + virtual int encode(JavaThread* THREAD, jlong buffer, int buffer_size) = 0; // Decodes the exception in `buffer` in `_to_env` and throws it. - virtual void decode(JavaThread* THREAD, Klass* vmSupport, jlong buffer) = 0; + virtual void decode(JavaThread* THREAD, DecodeFormat format, jlong buffer) = 0; public: void doit(JavaThread* THREAD) { - // Resolve VMSupport class explicitly as HotSpotJVMCI::compute_offsets - // may not have been called. - Klass* vmSupport = SystemDictionary::resolve_or_fail(vmSymbols::jdk_internal_vm_VMSupport(), true, THREAD); - guarantee(!HAS_PENDING_EXCEPTION, ""); - int buffer_size = 2048; while (true) { ResourceMark rm; jlong buffer = (jlong) NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, jbyte, buffer_size); if (buffer == 0L) { - decode(THREAD, vmSupport, 0L); + JVMCI_event_1("error translating exception: translation buffer allocation failed"); + decode(THREAD, _buffer_alloc_fail, 0L); return; } - int res = encode(THREAD, vmSupport, buffer, buffer_size); - if (_from_env != nullptr && !_from_env->is_hotspot() && _from_env->has_pending_exception()) { - // Cannot get name of exception thrown by `encode` as that involves - // calling into libjvmci which in turn can raise another exception. - _from_env->clear_pending_exception(); - decode(THREAD, vmSupport, -2L); + int res = encode(THREAD, buffer, buffer_size); + if (_to_env->has_pending_exception()) { + // Propagate pending exception return; - } else if (HAS_PENDING_EXCEPTION) { - Symbol *ex_name = PENDING_EXCEPTION->klass()->name(); - CLEAR_PENDING_EXCEPTION; - if (ex_name == vmSymbols::java_lang_OutOfMemoryError()) { - decode(THREAD, vmSupport, -1L); - } else { - decode(THREAD, vmSupport, -2L); - } - return; - } else if (res < 0) { + } + if (res < 0) { int required_buffer_size = -res; if (required_buffer_size > buffer_size) { buffer_size = required_buffer_size; } } else { - decode(THREAD, vmSupport, buffer); + decode(THREAD, _encoded_ok, buffer); if (!_to_env->has_pending_exception()) { _to_env->throw_InternalError("decodeAndThrowThrowable should have thrown an exception"); } @@ -439,7 +433,26 @@ class HotSpotToSharedLibraryExceptionTranslation : public ExceptionTranslation { private: const Handle& _throwable; - int encode(JavaThread* THREAD, Klass* vmSupport, jlong buffer, int buffer_size) { + int encode(JavaThread* THREAD, jlong buffer, int buffer_size) { + Klass* vmSupport = SystemDictionary::resolve_or_fail(vmSymbols::jdk_internal_vm_VMSupport(), true, THREAD); + if (HAS_PENDING_EXCEPTION) { + Handle throwable = Handle(THREAD, PENDING_EXCEPTION); + Symbol *ex_name = throwable->klass()->name(); + CLEAR_PENDING_EXCEPTION; + if (ex_name == vmSymbols::java_lang_OutOfMemoryError()) { + JVMCI_event_1("error translating exception: OutOfMemoryError"); + decode(THREAD, _encode_oome_fail, 0L); + } else { + char* char_buffer = (char*) buffer + 4; + stringStream st(char_buffer, (size_t) buffer_size - 4); + java_lang_Throwable::print_stack_trace(throwable, &st); + u4 len = (u4) st.size(); + *((u4*) buffer) = len; + JVMCI_event_1("error translating exception: %s", char_buffer); + decode(THREAD, _encode_fail, buffer); + } + return 0; + } JavaCallArguments jargs; jargs.push_oop(_throwable); jargs.push_long(buffer); @@ -452,11 +465,11 @@ class HotSpotToSharedLibraryExceptionTranslation : public ExceptionTranslation { return result.get_jint(); } - void decode(JavaThread* THREAD, Klass* vmSupport, jlong buffer) { + void decode(JavaThread* THREAD, DecodeFormat format, jlong buffer) { JNIAccessMark jni(_to_env, THREAD); jni()->CallStaticVoidMethod(JNIJVMCI::VMSupport::clazz(), JNIJVMCI::VMSupport::decodeAndThrowThrowable_method(), - buffer, false); + format, buffer, false); } public: HotSpotToSharedLibraryExceptionTranslation(JVMCIEnv* hotspot_env, JVMCIEnv* jni_env, const Handle& throwable) : @@ -468,15 +481,25 @@ class SharedLibraryToHotSpotExceptionTranslation : public ExceptionTranslation { private: jthrowable _throwable; - int encode(JavaThread* THREAD, Klass* vmSupport, jlong buffer, int buffer_size) { + int encode(JavaThread* THREAD, jlong buffer, int buffer_size) { JNIAccessMark jni(_from_env, THREAD); - return jni()->CallStaticIntMethod(JNIJVMCI::VMSupport::clazz(), + int res = jni()->CallStaticIntMethod(JNIJVMCI::VMSupport::clazz(), JNIJVMCI::VMSupport::encodeThrowable_method(), _throwable, buffer, buffer_size); + if (jni()->ExceptionCheck()) { + // Cannot get name of exception thrown as that can raise another exception. + jni()->ExceptionClear(); + JVMCI_event_1("error translating exception: unknown error"); + decode(THREAD, _encode_fail, 0L); + return 0; + } + return res; } - void decode(JavaThread* THREAD, Klass* vmSupport, jlong buffer) { + void decode(JavaThread* THREAD, DecodeFormat format, jlong buffer) { + Klass* vmSupport = SystemDictionary::resolve_or_fail(vmSymbols::jdk_internal_vm_VMSupport(), true, CHECK); JavaCallArguments jargs; + jargs.push_int(format); jargs.push_long(buffer); jargs.push_int(true); JavaValue result(T_VOID); 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 b84d55454e7..5c890b18804 100644 --- a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java +++ b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java @@ -40,6 +40,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.annotation.IncompleteAnnotationException; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -112,36 +113,46 @@ public class VMSupport { public static native String getVMTemporaryDirectory(); /** - * Decodes the exception encoded in {@code errorOrBuffer} and throws it. - * - * @param errorOrBuffer an error code or a native byte errorOrBuffer containing an exception encoded by - * {@link #encodeThrowable}. Error code values and their meanings are: + * Decodes the exception described by {@code format} and {@code buffer} and throws it. * + * @param format specifies how to interpret {@code buffer}: *
- * 0: native memory for the errorOrBuffer could not be allocated - * -1: an OutOfMemoryError was thrown while encoding the exception - * -2: some other throwable was thrown while encoding the exception + * 0: {@code buffer} was created by {@link #encodeThrowable} + * 1: native memory for {@code buffer} could not be allocated + * 2: an OutOfMemoryError was thrown while encoding the exception + * 3: some other problem occured while encoding the exception. If {@code buffer != 0}, + * it contains a {@code struct { u4 len; char[len] desc}} where {@code desc} describes the problem *- * @param errorOrBuffer a native byte errorOrBuffer containing an exception encoded by - * {@link #encodeThrowable} + * @param buffer encoded info about the exception to throw (depends on {@code format}) * @param inJVMHeap [@code true} if executing in the JVM heap, {@code false} otherwise */ - public static void decodeAndThrowThrowable(long errorOrBuffer, boolean inJVMHeap) throws Throwable { - if (errorOrBuffer >= -2L && errorOrBuffer <= 0) { + public static void decodeAndThrowThrowable(int format, long buffer, boolean inJVMHeap) throws Throwable { + if (format != 0) { String context = String.format("while encoding an exception to translate it %s the JVM heap", inJVMHeap ? "to" : "from"); - if (errorOrBuffer == 0) { - throw new InternalError("native errorOrBuffer could not be allocated " + context); + if (format == 1) { + throw new InternalError("native buffer could not be allocated " + context); } - if (errorOrBuffer == -1L) { + if (format == 2) { throw new OutOfMemoryError("OutOfMemoryError occurred " + context); } + if (format == 3 && buffer != 0L) { + byte[] bytes = bufferToBytes(buffer); + throw new InternalError("unexpected problem occurred " + context + ": " + new String(bytes, StandardCharsets.UTF_8)); + } throw new InternalError("unexpected problem occurred " + context); } - int encodingLength = U.getInt(errorOrBuffer); - byte[] encoding = new byte[encodingLength]; - U.copyMemory(null, errorOrBuffer + 4, encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, encodingLength); - throw TranslatedException.decodeThrowable(encoding); + throw TranslatedException.decodeThrowable(bufferToBytes(buffer)); + } + + private static byte[] bufferToBytes(long buffer) { + if (buffer == 0) { + return null; + } + int len = U.getInt(buffer); + byte[] bytes = new byte[len]; + U.copyMemory(null, buffer + 4, bytes, Unsafe.ARRAY_BYTE_BASE_OFFSET, len); + return bytes; } /** diff --git a/test/jdk/jdk/internal/vm/TestTranslatedException.java b/test/jdk/jdk/internal/vm/TestTranslatedException.java index 335f71ccf3a..6ad6d526bd3 100644 --- a/test/jdk/jdk/internal/vm/TestTranslatedException.java +++ b/test/jdk/jdk/internal/vm/TestTranslatedException.java @@ -34,6 +34,7 @@ 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; import org.testng.annotations.Test; @@ -57,11 +58,78 @@ public class TestTranslatedException { throwable = new ExceptionInInitializerError(new InvocationTargetException(new RuntimeException(String.valueOf(i), throwable), "invoke")); } encodeDecode(throwable); + + try { + VMSupport.decodeAndThrowThrowable(0, 0L, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (NullPointerException decoded) { + // Expected + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } + + try { + VMSupport.decodeAndThrowThrowable(1, 0L, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (InternalError decoded) { + if (!decoded.getMessage().startsWith("native buffer could not be allocated")) { + throw new AssertionError("unexpected exception: " + decoded); + } + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } + + try { + VMSupport.decodeAndThrowThrowable(2, 0L, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (OutOfMemoryError decoded) { + // Expected + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } + + try { + VMSupport.decodeAndThrowThrowable(3, 0L, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (InternalError decoded) { + // Expected + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } + + try { + VMSupport.decodeAndThrowThrowable(4, 0L, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (InternalError decoded) { + // Expected + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } + + Unsafe unsafe = Unsafe.getUnsafe(); + byte[] problem = "very unlikely problem".getBytes(StandardCharsets.UTF_8); + long buffer = unsafe.allocateMemory(problem.length + 4); + try { + unsafe.putInt(buffer, problem.length); + unsafe.copyMemory(problem, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, buffer + 4, problem.length); + VMSupport.decodeAndThrowThrowable(3, buffer, true); + throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); + } catch (InternalError decoded) { + String msg = decoded.getMessage(); + if (!msg.endsWith("very unlikely problem")) { + throw new AssertionError("unexpected exception: " + decoded); + } + } catch (Throwable decoded) { + throw new AssertionError("unexpected exception: " + decoded); + } finally { + unsafe.freeMemory(buffer); + } } private void encodeDecode(Throwable throwable) throws Exception { Unsafe unsafe = Unsafe.getUnsafe(); int bufferSize = 512; + int format = 0; long buffer = 0L; while (true) { buffer = unsafe.allocateMemory(bufferSize); @@ -71,7 +139,7 @@ public class TestTranslatedException { bufferSize = -res; } else { try { - VMSupport.decodeAndThrowThrowable(buffer, true); + VMSupport.decodeAndThrowThrowable(format, buffer, true); throw new AssertionError("expected decodeAndThrowThrowable to throw an exception"); } catch (Throwable decoded) { assertThrowableEquals(throwable, decoded);