8279437: [JVMCI] exception in HotSpotJVMCIRuntime.translate can exit the VM

Reviewed-by: kvn
This commit is contained in:
Doug Simon 2022-01-08 21:47:54 +00:00
parent 77757ba974
commit e14fb4f4aa
10 changed files with 460 additions and 133 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -2376,7 +2376,7 @@ C2V_VMENTRY_PREFIX(void, detachCurrentThread, (JNIEnv* env, jobject c2vm))
}
C2V_END
C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle))
C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle, jboolean callPostTranslation))
requireJVMCINativeLibrary(JVMCI_CHECK_0);
if (obj_handle == NULL) {
return 0L;
@ -2427,7 +2427,9 @@ C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle))
const char* cstring = name_string.is_null() ? NULL : thisEnv->as_utf8_string(name_string);
// Create a new HotSpotNmethod instance in the peer runtime
result = peerEnv->new_HotSpotNmethod(mh, cstring, isDefault, compileIdSnapshot, JVMCI_CHECK_0);
if (nm == NULL) {
if (result.is_null()) {
// exception occurred (e.g. OOME) creating a new HotSpotNmethod
} else if (nm == NULL) {
// nmethod must have been unloaded
} else {
// Link the new HotSpotNmethod to the nmethod
@ -2450,6 +2452,13 @@ C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle))
JVMCI_THROW_MSG_0(IllegalArgumentException,
err_msg("Cannot translate object of type: %s", thisEnv->klass_name(obj)));
}
if (callPostTranslation) {
peerEnv->call_HotSpotJVMCIRuntime_postTranslation(result, JVMCI_CHECK_0);
}
// Propagate any exception that occurred while creating the translated object
if (peerEnv->transfer_pending_exception(thread, thisEnv)) {
return 0L;
}
return (jlong) peerEnv->make_global(result).as_jobject();
}
@ -2790,7 +2799,7 @@ JNINativeMethod CompilerToVM::methods[] = {
{CC "getCurrentJavaThread", CC "()J", FN_PTR(getCurrentJavaThread)},
{CC "attachCurrentThread", CC "([BZ)Z", FN_PTR(attachCurrentThread)},
{CC "detachCurrentThread", CC "()V", FN_PTR(detachCurrentThread)},
{CC "translate", CC "(" OBJECT ")J", FN_PTR(translate)},
{CC "translate", CC "(" OBJECT "Z)J", FN_PTR(translate)},
{CC "unhand", CC "(J)" OBJECT, FN_PTR(unhand)},
{CC "updateHotSpotNmethod", CC "(" HS_NMETHOD ")V", FN_PTR(updateHotSpotNmethod)},
{CC "getCode", CC "(" HS_INSTALLED_CODE ")[B", FN_PTR(getCode)},

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2022, 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
@ -278,35 +278,141 @@ void JVMCIEnv::describe_pending_exception(bool clear) {
}
}
void JVMCIEnv::translate_hotspot_exception_to_jni_exception(JavaThread* THREAD, const Handle& throwable) {
assert(!is_hotspot(), "must_be");
// Resolve HotSpotJVMCIRuntime class explicitly as HotSpotJVMCI::compute_offsets
// may not have been called.
Klass* runtimeKlass = SystemDictionary::resolve_or_fail(vmSymbols::jdk_vm_ci_hotspot_HotSpotJVMCIRuntime(), true, CHECK);
JavaCallArguments jargs;
jargs.push_oop(throwable);
JavaValue result(T_OBJECT);
JavaCalls::call_static(&result,
runtimeKlass,
vmSymbols::encodeThrowable_name(),
vmSymbols::encodeThrowable_signature(), &jargs, THREAD);
if (HAS_PENDING_EXCEPTION) {
JVMCIRuntime::fatal_exception(this, "HotSpotJVMCIRuntime.encodeThrowable should not throw an exception");
// Shared code for translating an exception from HotSpot to libjvmci or vice versa.
class ExceptionTranslation: public StackObj {
protected:
JVMCIEnv* _from_env; // Source of translation. Can be nullptr.
JVMCIEnv* _to_env; // Destination of translation. Never nullptr.
ExceptionTranslation(JVMCIEnv* from_env, JVMCIEnv* to_env) : _from_env(from_env), _to_env(to_env) {}
// 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* runtimeKlass, jlong buffer, int buffer_size) = 0;
// Decodes the exception in `buffer` in `_to_env` and throws it.
virtual void decode(JavaThread* THREAD, Klass* runtimeKlass, jlong buffer) = 0;
public:
void doit(JavaThread* THREAD) {
// Resolve HotSpotJVMCIRuntime class explicitly as HotSpotJVMCI::compute_offsets
// may not have been called.
Klass* runtimeKlass = SystemDictionary::resolve_or_fail(vmSymbols::jdk_vm_ci_hotspot_HotSpotJVMCIRuntime(), true, CHECK);
int buffer_size = 2048;
while (true) {
ResourceMark rm;
jlong buffer = (jlong) NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, jbyte, buffer_size);
int res = encode(THREAD, runtimeKlass, buffer, buffer_size);
if ((_from_env != nullptr && _from_env->has_pending_exception()) || HAS_PENDING_EXCEPTION) {
JVMCIRuntime::fatal_exception(_from_env, "HotSpotJVMCIRuntime.encodeThrowable should not throw an exception");
}
if (res < 0) {
int required_buffer_size = -res;
if (required_buffer_size > buffer_size) {
buffer_size = required_buffer_size;
}
} else {
decode(THREAD, runtimeKlass, buffer);
if (!_to_env->has_pending_exception()) {
JVMCIRuntime::fatal_exception(_to_env, "HotSpotJVMCIRuntime.decodeAndThrowThrowable should throw an exception");
}
return;
}
}
}
};
// Translates an exception on the HotSpot heap to an exception on the shared library heap.
class HotSpotToSharedLibraryExceptionTranslation : public ExceptionTranslation {
private:
const Handle& _throwable;
int encode(JavaThread* THREAD, Klass* runtimeKlass, jlong buffer, int buffer_size) {
JavaCallArguments jargs;
jargs.push_oop(_throwable);
jargs.push_long(buffer);
jargs.push_int(buffer_size);
JavaValue result(T_INT);
JavaCalls::call_static(&result,
runtimeKlass,
vmSymbols::encodeThrowable_name(),
vmSymbols::encodeThrowable_signature(), &jargs, THREAD);
return result.get_jint();
}
oop encoded_throwable_string = result.get_oop();
void decode(JavaThread* THREAD, Klass* runtimeKlass, jlong buffer) {
JNIAccessMark jni(_to_env, THREAD);
jni()->CallStaticVoidMethod(JNIJVMCI::HotSpotJVMCIRuntime::clazz(),
JNIJVMCI::HotSpotJVMCIRuntime::decodeAndThrowThrowable_method(),
buffer);
}
public:
HotSpotToSharedLibraryExceptionTranslation(JVMCIEnv* hotspot_env, JVMCIEnv* jni_env, const Handle& throwable) :
ExceptionTranslation(hotspot_env, jni_env), _throwable(throwable) {}
};
ResourceMark rm;
const char* encoded_throwable_chars = java_lang_String::as_utf8_string(encoded_throwable_string);
// Translates an exception on the shared library heap to an exception on the HotSpot heap.
class SharedLibraryToHotSpotExceptionTranslation : public ExceptionTranslation {
private:
jthrowable _throwable;
JNIAccessMark jni(this, THREAD);
jobject jni_encoded_throwable_string = jni()->NewStringUTF(encoded_throwable_chars);
jthrowable jni_throwable = (jthrowable) jni()->CallStaticObjectMethod(JNIJVMCI::HotSpotJVMCIRuntime::clazz(),
JNIJVMCI::HotSpotJVMCIRuntime::decodeThrowable_method(),
jni_encoded_throwable_string);
jni()->Throw(jni_throwable);
int encode(JavaThread* THREAD, Klass* runtimeKlass, jlong buffer, int buffer_size) {
JNIAccessMark jni(_from_env, THREAD);
return jni()->CallStaticIntMethod(JNIJVMCI::HotSpotJVMCIRuntime::clazz(),
JNIJVMCI::HotSpotJVMCIRuntime::encodeThrowable_method(),
_throwable, buffer, buffer_size);
}
void decode(JavaThread* THREAD, Klass* runtimeKlass, jlong buffer) {
JavaCallArguments jargs;
jargs.push_long(buffer);
JavaValue result(T_VOID);
JavaCalls::call_static(&result,
runtimeKlass,
vmSymbols::decodeAndThrowThrowable_name(),
vmSymbols::long_void_signature(), &jargs, THREAD);
}
public:
SharedLibraryToHotSpotExceptionTranslation(JVMCIEnv* hotspot_env, JVMCIEnv* jni_env, jthrowable throwable) :
ExceptionTranslation(jni_env, hotspot_env), _throwable(throwable) {}
};
void JVMCIEnv::translate_to_jni_exception(JavaThread* THREAD, const Handle& throwable, JVMCIEnv* hotspot_env, JVMCIEnv* jni_env) {
HotSpotToSharedLibraryExceptionTranslation(hotspot_env, jni_env, throwable).doit(THREAD);
}
void JVMCIEnv::translate_from_jni_exception(JavaThread* THREAD, jthrowable throwable, JVMCIEnv* hotspot_env, JVMCIEnv* jni_env) {
SharedLibraryToHotSpotExceptionTranslation(hotspot_env, jni_env, throwable).doit(THREAD);
}
jboolean JVMCIEnv::transfer_pending_exception(JavaThread* THREAD, JVMCIEnv* peer_env) {
if (is_hotspot()) {
if (HAS_PENDING_EXCEPTION) {
Handle throwable = Handle(THREAD, PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
translate_to_jni_exception(THREAD, throwable, this, peer_env);
return true;
}
} else {
jthrowable ex = nullptr;
{
JNIAccessMark jni(this, THREAD);
ex = jni()->ExceptionOccurred();
if (ex != nullptr) {
jni()->ExceptionClear();
}
}
if (ex != nullptr) {
translate_from_jni_exception(THREAD, ex, peer_env, this);
return true;
}
}
return false;
}
JVMCIEnv::~JVMCIEnv() {
if (_throw_to_caller) {
if (is_hotspot()) {
@ -318,7 +424,7 @@ JVMCIEnv::~JVMCIEnv() {
if (HAS_PENDING_EXCEPTION) {
Handle throwable = Handle(THREAD, PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
translate_hotspot_exception_to_jni_exception(THREAD, throwable);
translate_to_jni_exception(THREAD, throwable, nullptr, this);
}
}
}
@ -801,6 +907,23 @@ JVMCIObject JVMCIEnv::call_HotSpotJVMCIRuntime_callToString(JVMCIObject object,
}
}
void JVMCIEnv::call_HotSpotJVMCIRuntime_postTranslation(JVMCIObject object, JVMCIEnv* JVMCIENV) {
JavaThread* THREAD = JVMCI::compilation_tick(JavaThread::current()); // For exception macros.
if (is_hotspot()) {
JavaCallArguments jargs;
jargs.push_oop(Handle(THREAD, HotSpotJVMCI::resolve(object)));
JavaValue result(T_VOID);
JavaCalls::call_static(&result,
HotSpotJVMCI::HotSpotJVMCIRuntime::klass(),
vmSymbols::postTranslation_name(),
vmSymbols::object_void_signature(), &jargs, CHECK);
} else {
JNIAccessMark jni(this, THREAD);
jni()->CallStaticVoidMethod(JNIJVMCI::HotSpotJVMCIRuntime::clazz(),
JNIJVMCI::HotSpotJVMCIRuntime::postTranslation_method(),
object.as_jobject());
}
}
JVMCIObject JVMCIEnv::call_JavaConstant_forPrimitive(JVMCIObject kind, jlong value, JVMCI_TRAPS) {
JavaThread* THREAD = JVMCI::compilation_tick(JavaThread::current()); // For exception macros.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2022, 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
@ -171,11 +171,15 @@ class JVMCIEnv : public ResourceObj {
const char* _file; // The file and ...
int _line; // ... line where this JNIEnv was created
// Translates an exception on the HotSpot heap to an exception on
// the shared library heap. The translation includes the stack and
// causes of `throwable`. The translated exception is pending in the
// shared library thread upon returning.
void translate_hotspot_exception_to_jni_exception(JavaThread* THREAD, const Handle& throwable);
// Translates an exception on the HotSpot heap (i.e., hotspot_env) to an exception on
// the shared library heap (i.e., jni_env). The translation includes the stack and cause(s) of `throwable`.
// The translated exception is pending in jni_env upon returning.
static void translate_to_jni_exception(JavaThread* THREAD, const Handle& throwable, JVMCIEnv* hotspot_env, JVMCIEnv* jni_env);
// Translates an exception on the shared library heap (i.e., jni_env) to an exception on
// the HotSpot heap (i.e., hotspot_env). The translation includes the stack and cause(s) of `throwable`.
// The translated exception is pending in hotspot_env upon returning.
static void translate_from_jni_exception(JavaThread* THREAD, jthrowable throwable, JVMCIEnv* hotspot_env, JVMCIEnv* jni_env);
public:
// Opens a JVMCIEnv scope for a Java to VM call (e.g., via CompilerToVM).
@ -225,6 +229,11 @@ public:
jboolean has_pending_exception();
void clear_pending_exception();
// If this env has a pending exception, it is translated to be a pending
// exception in `peer_env` and is cleared from this env. Returns true
// if a pending exception was transferred, false otherwise.
jboolean transfer_pending_exception(JavaThread* THREAD, JVMCIEnv* peer_env);
// Prints an exception and stack trace of a pending exception.
void describe_pending_exception(bool clear);
@ -311,6 +320,8 @@ public:
jboolean call_HotSpotJVMCIRuntime_isGCSupported(JVMCIObject runtime, jint gcIdentifier);
void call_HotSpotJVMCIRuntime_postTranslation(JVMCIObject object, JVMCI_TRAPS);
BasicType kindToBasicType(JVMCIObject kind, JVMCI_TRAPS);
#define DO_THROW(name) \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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

View File

@ -349,13 +349,14 @@
objectarray_field(HotSpotJVMCIRuntime, excludeFromJVMCICompilation, "[Ljava/lang/Module;") \
jvmci_method(CallNonvirtualObjectMethod, GetMethodID, call_special, JVMCIObject, HotSpotJVMCIRuntime, compileMethod, compileMethod_signature, (JVMCIObject runtime, JVMCIObject method, int entry_bci, jlong env, int id)) \
jvmci_method(CallNonvirtualObjectMethod, GetMethodID, call_special, JVMCIObject, HotSpotJVMCIRuntime, isGCSupported, int_bool_signature, (JVMCIObject runtime, int gcIdentifier)) \
jvmci_method(CallStaticObjectMethod, GetStaticMethodID, call_static, JVMCIObject, HotSpotJVMCIRuntime, encodeThrowable, encodeThrowable_signature, (JVMCIObject throwable)) \
jvmci_method(CallStaticObjectMethod, GetStaticMethodID, call_static, JVMCIObject, HotSpotJVMCIRuntime, decodeThrowable, decodeThrowable_signature, (JVMCIObject encodedThrowable)) \
jvmci_method(CallStaticBooleanMethod, GetStaticMethodID, call_static, bool, HotSpotJVMCIRuntime, encodeThrowable, encodeThrowable_signature, (JVMCIObject throwable, jlong buffer, int buffer_size)) \
jvmci_method(CallStaticVoidMethod, GetStaticMethodID, call_static, void, HotSpotJVMCIRuntime, decodeAndThrowThrowable, long_void_signature, (jlong buffer)) \
jvmci_method(CallNonvirtualVoidMethod, GetMethodID, call_special, void, HotSpotJVMCIRuntime, bootstrapFinished, void_method_signature, (JVMCIObject runtime, JVMCI_TRAPS)) \
jvmci_method(CallNonvirtualVoidMethod, GetMethodID, call_special, void, HotSpotJVMCIRuntime, shutdown, void_method_signature, (JVMCIObject runtime)) \
jvmci_method(CallStaticObjectMethod, GetStaticMethodID, call_static, JVMCIObject, HotSpotJVMCIRuntime, runtime, runtime_signature, (JVMCI_TRAPS)) \
jvmci_method(CallObjectMethod, GetMethodID, call_virtual, JVMCIObject, HotSpotJVMCIRuntime, getCompiler, getCompiler_signature, (JVMCIObject runtime, JVMCI_TRAPS)) \
jvmci_method(CallStaticObjectMethod, GetStaticMethodID, call_static, JVMCIObject, HotSpotJVMCIRuntime, callToString, callToString_signature, (JVMCIObject object, JVMCI_TRAPS)) \
jvmci_method(CallStaticVoidMethod, GetStaticMethodID, call_static, void, HotSpotJVMCIRuntime, postTranslation, object_void_signature, (JVMCIObject object, JVMCI_TRAPS)) \
end_class \
start_class(JVMCIError, jdk_vm_ci_common_JVMCIError) \
jvmci_constructor(JVMCIError, "(Ljava/lang/String;)V") \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2022, 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
@ -105,9 +105,8 @@
template(compileMethod_signature, "(Ljdk/vm/ci/hotspot/HotSpotResolvedJavaMethod;IJI)Ljdk/vm/ci/hotspot/HotSpotCompilationRequestResult;") \
template(isGCSupported_name, "isGCSupported") \
template(encodeThrowable_name, "encodeThrowable") \
template(encodeThrowable_signature, "(Ljava/lang/Throwable;)Ljava/lang/String;") \
template(decodeThrowable_name, "decodeThrowable") \
template(decodeThrowable_signature, "(Ljava/lang/String;)Ljava/lang/Throwable;") \
template(encodeThrowable_signature, "(Ljava/lang/Throwable;JI)I") \
template(decodeAndThrowThrowable_name, "decodeAndThrowThrowable") \
template(fromMetaspace_name, "fromMetaspace") \
template(method_fromMetaspace_signature, "(J)Ljdk/vm/ci/hotspot/HotSpotResolvedJavaMethod;") \
template(constantPool_fromMetaspace_signature, "(J)Ljdk/vm/ci/hotspot/HotSpotConstantPool;") \
@ -123,6 +122,7 @@
template(getCompiler_signature, "()Ljdk/vm/ci/runtime/JVMCICompiler;") \
template(callToString_name, "callToString") \
template(callToString_signature, "(Ljava/lang/Object;)Ljava/lang/String;") \
template(postTranslation_name, "postTranslation") \
template(getName_name, "getName") \
template(bootstrapFinished_name, "bootstrapFinished") \
template(forPrimitive_name, "forPrimitive") \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -849,7 +849,7 @@ final class CompilerToVM {
/**
* @see HotSpotJVMCIRuntime#translate(Object)
*/
native long translate(Object obj);
native long translate(Object obj, boolean callPostTranslation);
/**
* @see HotSpotJVMCIRuntime#unhand(Class, long)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, 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
@ -46,6 +46,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.misc.Unsafe;
import jdk.vm.ci.code.Architecture;
@ -66,6 +68,7 @@ import jdk.vm.ci.runtime.JVMCICompiler;
import jdk.vm.ci.runtime.JVMCICompilerFactory;
import jdk.vm.ci.runtime.JVMCIRuntime;
import jdk.vm.ci.services.JVMCIServiceLocator;
import jdk.vm.ci.services.Services;
/**
* HotSpot implementation of a JVMCI runtime.
@ -199,14 +202,44 @@ public final class HotSpotJVMCIRuntime implements JVMCIRuntime {
return result;
}
/**
* Decodes the exception encoded in {@code buffer} and throws it.
*
* @param buffer a native byte buffer containing an exception encoded by
* {@link #encodeThrowable}
*/
@VMEntryPoint
static Throwable decodeThrowable(String encodedThrowable) throws Throwable {
return TranslatedException.decodeThrowable(encodedThrowable);
static void decodeAndThrowThrowable(long buffer) throws Throwable {
Unsafe unsafe = UnsafeAccess.UNSAFE;
int encodingLength = unsafe.getInt(buffer);
byte[] encoding = new byte[encodingLength];
unsafe.copyMemory(null, buffer + 4, encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, encodingLength);
throw TranslatedException.decodeThrowable(encoding);
}
/**
* If {@code bufferSize} is large enough, encodes {@code throwable} into a byte array and writes
* it to {@code buffer}. The encoding in {@code buffer} can be decoded by
* {@link #decodeAndThrowThrowable}.
*
* @param throwable the exception to encode
* @param buffer a native byte buffer
* @param bufferSize the size of {@code buffer} in bytes
* @return the number of bytes written into {@code buffer} if {@code bufferSize} is large
* enough, otherwise {@code -N} where {@code N} is the value {@code bufferSize} needs to
* be to fit the encoding
*/
@VMEntryPoint
static String encodeThrowable(Throwable throwable) throws Throwable {
return TranslatedException.encodeThrowable(throwable);
static int encodeThrowable(Throwable throwable, long buffer, int bufferSize) throws Throwable {
byte[] encoding = TranslatedException.encodeThrowable(throwable);
int requiredSize = 4 + encoding.length;
if (bufferSize < requiredSize) {
return -requiredSize;
}
Unsafe unsafe = UnsafeAccess.UNSAFE;
unsafe.putInt(buffer, encoding.length);
unsafe.copyMemory(encoding, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, buffer + 4, encoding.length);
return requiredSize;
}
@VMEntryPoint
@ -235,6 +268,10 @@ public final class HotSpotJVMCIRuntime implements JVMCIRuntime {
// Note: The following one is not used (see InitTimer.ENABLED). It is added here
// so that -XX:+JVMCIPrintProperties shows the option.
InitTimer(Boolean.class, false, "Specifies if initialization timing is enabled."),
ForceTranslateFailure(String.class, null, "Forces HotSpotJVMCIRuntime.translate to throw an exception in the context " +
"of the peer runtime. The value is a filter that can restrict the forced failure to matching translated " +
"objects. See HotSpotJVMCIRuntime.postTranslation for more details. This option exists soley to test " +
"correct handling of translation failure."),
PrintConfig(Boolean.class, false, "Prints VM configuration available via JVMCI."),
AuditHandles(Boolean.class, false, "Record stack trace along with scoped foreign object reference wrappers " +
"to debug issue with a wrapper being used after its scope has closed."),
@ -1180,7 +1217,88 @@ public final class HotSpotJVMCIRuntime implements JVMCIRuntime {
* @see "https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references"
*/
public long translate(Object obj) {
return compilerToVm.translate(obj);
return compilerToVm.translate(obj, Option.ForceTranslateFailure.getString() != null);
}
private static final Pattern FORCE_TRANSLATE_FAILURE_FILTER_RE = Pattern.compile("(?:(method|type|nmethod)/)?([^:]+)(?::(hotspot|native))?");
/**
* Forces translation failure based on {@code translatedObject} and the value of
* {@link Option#ForceTranslateFailure}. The value is zero or more filters separated by a comma.
* The syntax for a filter is:
*
* <pre>
* Filter = [ TypeSelector "/" ] Substring [ ":" JVMCIEnvSelector ] .
* TypeSelector = "type" | "method" | "nmethod"
* JVMCIEnvSelector = "native" | "hotspot"
* </pre>
*
* For example:
*
* <pre>
* -Djvmci.ForceTranslateFailure=nmethod/StackOverflowError:native,method/computeHash,execute
* </pre>
*
* will cause failure of:
* <ul>
* <li>translating a {@link HotSpotNmethod} to the libjvmci heap whose fully qualified name
* contains "StackOverflowError"</li>
* <li>translating a {@link HotSpotResolvedJavaMethodImpl} to the libjvmci or HotSpot heap whose
* fully qualified name contains "computeHash"</li>
* <li>translating a {@link HotSpotNmethod}, {@link HotSpotResolvedJavaMethodImpl} or
* {@link HotSpotResolvedObjectTypeImpl} to the libjvmci or HotSpot heap whose fully qualified
* name contains "execute"</li>
* </ul>
*/
@VMEntryPoint
static void postTranslation(Object translatedObject) {
String value = Option.ForceTranslateFailure.getString();
String toMatch;
String type;
if (translatedObject instanceof HotSpotResolvedJavaMethodImpl) {
toMatch = ((HotSpotResolvedJavaMethodImpl) translatedObject).format("%H.%n");
type = "method";
} else if (translatedObject instanceof HotSpotResolvedObjectTypeImpl) {
toMatch = ((HotSpotResolvedObjectTypeImpl) translatedObject).toJavaName();
type = "type";
} else if (translatedObject instanceof HotSpotNmethod) {
HotSpotNmethod nmethod = (HotSpotNmethod) translatedObject;
if (nmethod.getMethod() != null) {
toMatch = nmethod.getMethod().format("%H.%n");
} else {
toMatch = String.valueOf(nmethod.getName());
}
type = "nmethod";
} else {
return;
}
String[] filters = value.split(",");
for (String filter : filters) {
Matcher m = FORCE_TRANSLATE_FAILURE_FILTER_RE.matcher(filter);
if (!m.matches()) {
throw new JVMCIError(Option.ForceTranslateFailure + " filter does not match " + FORCE_TRANSLATE_FAILURE_FILTER_RE + ": " + filter);
}
String typeSelector = m.group(1);
String substring = m.group(2);
String jvmciEnvSelector = m.group(3);
if (jvmciEnvSelector != null) {
if (jvmciEnvSelector.equals("native")) {
if (!Services.IS_IN_NATIVE_IMAGE) {
continue;
}
} else {
if (Services.IS_IN_NATIVE_IMAGE) {
continue;
}
}
}
if (typeSelector != null && !typeSelector.equals(type)) {
continue;
}
if (toMatch.contains(substring)) {
throw new JVMCIError("translation of " + translatedObject + " failed due to matching " + Option.ForceTranslateFailure + " filter \"" + filter + "\"");
}
}
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2022, 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
@ -22,13 +22,20 @@
*/
package jdk.vm.ci.hotspot;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import jdk.vm.ci.common.JVMCIError;
/**
* Support for translating exceptions between different runtime heaps.
@ -36,6 +43,26 @@ import java.util.Objects;
@SuppressWarnings("serial")
final class TranslatedException extends Exception {
/**
* The value returned by {@link #encodeThrowable(Throwable)} when encoding fails due to an
* {@link OutOfMemoryError}.
*/
private static final byte[] FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES;
/**
* The value returned by {@link #encodeThrowable(Throwable)} when encoding fails for any reason
* other than {@link OutOfMemoryError}.
*/
private static final byte[] FALLBACK_ENCODED_THROWABLE_BYTES;
static {
try {
FALLBACK_ENCODED_THROWABLE_BYTES = encodeThrowable(new TranslatedException("error during encoding", "<unknown>"), false);
FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES = encodeThrowable(new OutOfMemoryError(), false);
} catch (IOException e) {
throw new JVMCIError(e);
}
}
/**
* Class name of exception that could not be instantiated.
*/
@ -110,83 +137,74 @@ final class TranslatedException extends Exception {
}
}
/**
* Encodes an exception message to distinguish a null message from an empty message.
*
* @return {@code value} with a space prepended iff {@code value != null}
*/
private static String encodeMessage(String value) {
return value != null ? ' ' + value : value;
private static String emptyIfNull(String value) {
return value == null ? "" : value;
}
private static String decodeMessage(String value) {
if (value.length() == 0) {
return null;
}
return value.substring(1);
}
private static String encodedString(String value) {
return Objects.toString(value, "").replace('|', '_');
private static String emptyAsNull(String value) {
return value.isEmpty() ? null : value;
}
/**
* Encodes {@code throwable} including its stack and causes as a string. The encoding format of
* a single exception is:
*
* <pre>
* <exception class name> '|' <exception message> '|' <stack size> '|' [ <classLoader> '|' <module> '|' <moduleVersion> '|' <class> '|' <method> '|' <file> '|' <line> '|' ]*
* </pre>
*
* Each exception is encoded before the exception it causes.
* Encodes {@code throwable} including its stack and causes as a {@linkplain GZIPOutputStream
* compressed} byte array that can be decoded by {@link #decodeThrowable}.
*/
@VMEntryPoint
static String encodeThrowable(Throwable throwable) throws Throwable {
static byte[] encodeThrowable(Throwable throwable) throws Throwable {
try {
Formatter enc = new Formatter();
return encodeThrowable(throwable, true);
} catch (OutOfMemoryError e) {
return FALLBACK_ENCODED_OUTOFMEMORYERROR_BYTES;
} catch (Throwable e) {
return FALLBACK_ENCODED_THROWABLE_BYTES;
}
}
private static byte[] encodeThrowable(Throwable throwable, boolean withCauseAndStack) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(baos))) {
List<Throwable> throwables = new ArrayList<>();
for (Throwable current = throwable; current != null; current = current.getCause()) {
throwables.add(current);
if (!withCauseAndStack) {
break;
}
}
// Encode from inner most cause outwards
Collections.reverse(throwables);
for (Throwable current : throwables) {
enc.format("%s|%s|", current.getClass().getName(), encodedString(encodeMessage(current.getMessage())));
StackTraceElement[] stackTrace = current.getStackTrace();
dos.writeUTF(current.getClass().getName());
dos.writeUTF(emptyIfNull(current.getMessage()));
StackTraceElement[] stackTrace = withCauseAndStack ? current.getStackTrace() : null;
if (stackTrace == null) {
stackTrace = new StackTraceElement[0];
}
enc.format("%d|", stackTrace.length);
dos.writeInt(stackTrace.length);
for (int i = 0; i < stackTrace.length; i++) {
StackTraceElement frame = stackTrace[i];
if (frame != null) {
enc.format("%s|%s|%s|%s|%s|%s|%d|", encodedString(frame.getClassLoaderName()),
encodedString(frame.getModuleName()), encodedString(frame.getModuleVersion()),
frame.getClassName(), frame.getMethodName(),
encodedString(frame.getFileName()), frame.getLineNumber());
dos.writeUTF(emptyIfNull(frame.getClassLoaderName()));
dos.writeUTF(emptyIfNull(frame.getModuleName()));
dos.writeUTF(emptyIfNull(frame.getModuleVersion()));
dos.writeUTF(emptyIfNull(frame.getClassName()));
dos.writeUTF(emptyIfNull(frame.getMethodName()));
dos.writeUTF(emptyIfNull(frame.getFileName()));
dos.writeInt(frame.getLineNumber());
}
}
}
return enc.toString();
} catch (Throwable e) {
assert printStackTrace(e);
try {
return e.getClass().getName() + "|" + encodedString(e.getMessage()) + "|0|";
} catch (Throwable e2) {
assert printStackTrace(e2);
return "java.lang.Throwable|too many errors during encoding|0|";
}
}
return baos.toByteArray();
}
/**
* Gets the stack of the current thread without the frames between this call and the one just
* below the frame of the first method in {@link CompilerToVM}. The chopped frames are specific
* to the implementation of {@link HotSpotJVMCIRuntime#decodeThrowable(String)}.
* below the frame of the first method in {@link CompilerToVM}. The chopped frames are for the
* VM call to {@link HotSpotJVMCIRuntime#decodeAndThrowThrowable}.
*/
private static StackTraceElement[] getStackTraceSuffix() {
private static StackTraceElement[] getMyStackTrace() {
StackTraceElement[] stack = new Exception().getStackTrace();
for (int i = 0; i < stack.length; i++) {
StackTraceElement e = stack[i];
@ -206,43 +224,47 @@ final class TranslatedException extends Exception {
* {@link #encodeThrowable}
*/
@VMEntryPoint
static Throwable decodeThrowable(String encodedThrowable) {
try {
int i = 0;
String[] parts = encodedThrowable.split("\\|");
static Throwable decodeThrowable(byte[] encodedThrowable) {
try (DataInputStream dis = new DataInputStream(new GZIPInputStream(new ByteArrayInputStream(encodedThrowable)))) {
Throwable cause = null;
Throwable throwable = null;
while (i != parts.length) {
String exceptionClassName = parts[i++];
String exceptionMessage = decodeMessage(parts[i++]);
StackTraceElement[] myStack = getMyStackTrace();
while (dis.available() != 0) {
String exceptionClassName = dis.readUTF();
String exceptionMessage = emptyAsNull(dis.readUTF());
throwable = create(exceptionClassName, exceptionMessage, cause);
int stackTraceDepth = Integer.parseInt(parts[i++]);
StackTraceElement[] suffix = getStackTraceSuffix();
StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + suffix.length];
int stackTraceDepth = dis.readInt();
StackTraceElement[] stackTrace = new StackTraceElement[stackTraceDepth + myStack.length];
int stackTraceIndex = 0;
int myStackIndex = 0;
for (int j = 0; j < stackTraceDepth; j++) {
String classLoaderName = parts[i++];
String moduleName = parts[i++];
String moduleVersion = parts[i++];
String className = parts[i++];
String methodName = parts[i++];
String fileName = parts[i++];
int lineNumber = Integer.parseInt(parts[i++]);
if (classLoaderName.isEmpty()) {
classLoaderName = null;
String classLoaderName = emptyAsNull(dis.readUTF());
String moduleName = emptyAsNull(dis.readUTF());
String moduleVersion = emptyAsNull(dis.readUTF());
String className = emptyAsNull(dis.readUTF());
String methodName = emptyAsNull(dis.readUTF());
String fileName = emptyAsNull(dis.readUTF());
int lineNumber = dis.readInt();
StackTraceElement ste = new StackTraceElement(classLoaderName, moduleName, moduleVersion, className, methodName, fileName, lineNumber);
if (ste.isNativeMethod()) {
// Best effort attempt to weave stack traces from two heaps into
// a single stack trace using native method frames as stitching points.
// This is not 100% reliable as there's no guarantee that native method
// frames only exist for calls between HotSpot and libjvmci.
while (myStackIndex < myStack.length) {
StackTraceElement suffixSTE = myStack[myStackIndex++];
if (suffixSTE.isNativeMethod()) {
break;
}
stackTrace[stackTraceIndex++] = suffixSTE;
}
}
if (moduleName.isEmpty()) {
moduleName = null;
}
if (moduleVersion.isEmpty()) {
moduleVersion = null;
}
if (fileName.isEmpty()) {
fileName = null;
}
stackTrace[j] = new StackTraceElement(classLoaderName, moduleName, moduleVersion, className, methodName, fileName, lineNumber);
stackTrace[stackTraceIndex++] = ste;
}
while (myStackIndex < myStack.length) {
stackTrace[stackTraceIndex++] = myStack[myStackIndex++];
}
System.arraycopy(suffix, 0, stackTrace, stackTraceDepth, suffix.length);
throwable.setStackTrace(stackTrace);
cause = throwable;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 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
@ -24,7 +24,8 @@
/*
* @test
* @requires vm.jvmci
* @modules jdk.internal.vm.ci/jdk.vm.ci.hotspot:open
* @modules jdk.internal.vm.ci/jdk.vm.ci.hotspot:+open
* java.base/jdk.internal.misc
* @library /compiler/jvmci/jdk.vm.ci.hotspot.test/src
* @run testng/othervm
* -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler
@ -41,6 +42,9 @@ import java.lang.reflect.Method;
import org.testng.Assert;
import org.testng.annotations.Test;
import jdk.internal.misc.Unsafe;
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
public class TestTranslatedException {
@SuppressWarnings("serial")
public static class Untranslatable extends RuntimeException {
@ -56,7 +60,7 @@ public class TestTranslatedException {
Class<?> translatedExceptionClass = Class.forName("jdk.vm.ci.hotspot.TranslatedException");
Method encode = translatedExceptionClass.getDeclaredMethod("encodeThrowable", Throwable.class);
Method decode = translatedExceptionClass.getDeclaredMethod("decodeThrowable", String.class);
Method decode = translatedExceptionClass.getDeclaredMethod("decodeThrowable", byte[].class);
encode.setAccessible(true);
decode.setAccessible(true);
@ -64,11 +68,50 @@ public class TestTranslatedException {
for (int i = 0; i < 10; i++) {
throwable = new ExceptionInInitializerError(new InvocationTargetException(new RuntimeException(String.valueOf(i), throwable), "invoke"));
}
String encoding = (String) encode.invoke(null, throwable);
byte[] encoding = (byte[]) encode.invoke(null, throwable);
Throwable decoded = (Throwable) decode.invoke(null, encoding);
assertThrowableEquals(throwable, decoded);
}
@SuppressWarnings("unchecked")
@Test
public void encodeDecodeTest2() throws Exception {
Unsafe unsafe = Unsafe.getUnsafe();
int bufferSize = 512;
long buffer = 0L;
while (true) {
buffer = unsafe.allocateMemory(bufferSize);
try {
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"));
}
Method encode = HotSpotJVMCIRuntime.class.getDeclaredMethod("encodeThrowable", Throwable.class, long.class, int.class);
Method decode = HotSpotJVMCIRuntime.class.getDeclaredMethod("decodeAndThrowThrowable", long.class);
encode.setAccessible(true);
decode.setAccessible(true);
int res = (Integer) encode.invoke(null, throwable, buffer, bufferSize);
if (res < 0) {
bufferSize = -res;
} else {
try {
decode.invoke(null, buffer);
throw new AssertionError("expected decodeAndThrowThrowable to throw an exception");
} catch (InvocationTargetException e) {
Throwable decoded = e.getCause();
assertThrowableEquals(throwable, decoded);
}
return;
}
} finally {
unsafe.freeMemory(buffer);
}
}
}
private static void assertThrowableEquals(Throwable original, Throwable decoded) {
try {
Assert.assertEquals(original == null, decoded == null);