8310829: guarantee(!HAS_PENDING_EXCEPTION) failed in ExceptionTranslation::doit

Reviewed-by: never, kvn
This commit is contained in:
Doug Simon 2023-06-30 20:10:24 +00:00
parent 456bf115aa
commit f6bdccb45c
6 changed files with 155 additions and 53 deletions

View File

@ -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.
//

View File

@ -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") \

View File

@ -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);

View File

@ -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);

View File

@ -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}:
* <pre>
* 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
* </pre>
* @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;
}
/**

View File

@ -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);