Skip to content

Commit

Permalink
[GR-57170] Polyglot.engine.TraceCompilation=true triggers uncaught ex…
Browse files Browse the repository at this point in the history
…ception during deoptimization.

PullRequest: graal/18829
  • Loading branch information
tzezula committed Sep 19, 2024
2 parents 9d3bfe0 + 51b6f7c commit 245d8e1
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -591,7 +591,8 @@ private void generateHSToNativeEndMethod(CodeBuilder builder, MethodData methodD
builder.methodStart(EnumSet.of(Modifier.STATIC), methodName, endMethodReturnType, params, Collections.emptyList());
builder.indent();
String scopeName = targetClassSimpleName + "::" + methodName;
String scopeInit = new CodeBuilder(builder).write(typeCache.jniMethodScope).write(" scope = ").newInstance(typeCache.jniMethodScope, "\"" + scopeName + "\"", jniEnvVariable).build();
String scopeInit = new CodeBuilder(builder).write(typeCache.jniMethodScope).write(" scope = ").invokeStatic(typeCache.foreignException, "openJNIMethodScope", "\"" + scopeName + "\"",
jniEnvVariable).build();
if (objectReturnType) {
builder.lineStart(scopeInit).lineEnd(";");
scopeInit = new CodeBuilder(builder).write(typeCache.jniMethodScope).write(" sc = scope").build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -49,10 +49,14 @@
import org.graalvm.jniutils.JNI.JThrowable;
import org.graalvm.jniutils.JNI.JValue;
import org.graalvm.jniutils.JNIExceptionWrapper;
import org.graalvm.jniutils.JNIExceptionWrapper.ExceptionHandler;
import org.graalvm.jniutils.JNIExceptionWrapper.ExceptionHandlerContext;
import org.graalvm.jniutils.JNIMethodScope;
import org.graalvm.jniutils.JNIUtil;
import org.graalvm.nativebridge.BinaryOutput.ByteArrayBinaryOutput;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.word.WordFactory;

import static org.graalvm.jniutils.JNIUtil.GetStaticMethodID;
import static org.graalvm.nativeimage.c.type.CTypeConversion.toCString;
Expand All @@ -70,6 +74,8 @@ public final class ForeignException extends RuntimeException {
private static final ThreadLocal<ForeignException> pendingException = new ThreadLocal<>();
private static final JNIMethodResolver CreateForeignException = new JNIMethodResolver("createForeignException", "([B)Ljava/lang/Throwable;");
private static final JNIMethodResolver ToByteArray = new JNIMethodResolver("toByteArray", "(Lorg/graalvm/nativebridge/ForeignException;)[B");
private static final JNIMethodResolver GetStackOverflowErrorClass = new JNIMethodResolver("getStackOverflowErrorClass", "()Ljava/lang/Class;");

/**
* Pre-allocated {@code ForeignException} for double failure.
*/
Expand Down Expand Up @@ -198,12 +204,40 @@ public static ForeignException forThrowable(Throwable exception, BinaryMarshalle
public static JNICalls getJNICalls() {
JNICalls res = jniCalls;
if (res == null) {
res = createJNICalls();
jniCalls = res;
synchronized (ForeignException.class) {
res = jniCalls;
if (res == null) {
JNIMethodScope scope = JNIMethodScope.scopeOrNull();
JNIEnv env = scope != null ? scope.getEnv() : WordFactory.nullPointer();
res = createJNICalls(env);
if (scope != null) {
jniCalls = res;
}
}
}
}
return res;
}

/**
* Opens a {@link JNIMethodScope} for a call from HotSpot to a native method. On the first
* invocation, it loads any host exceptions that may be thrown by JNI APIs during native-to-Java
* transitions. This ensures proper exception handling when a JNI API exception occurs within
* the thread's yellow zone, where it cannot be inspected via a host call.
*
* @since 24.2
*/
public static JNIMethodScope openJNIMethodScope(String scopeName, JNIEnv env) {
if (jniCalls == null) {
synchronized (ForeignException.class) {
if (jniCalls == null) {
jniCalls = createJNICalls(env);
}
}
}
return new JNIMethodScope(scopeName, env);
}

byte[] toByteArray() {
return rawData;
}
Expand All @@ -223,16 +257,9 @@ private static <T extends Throwable> T silenceException(Class<T> type, Throwable
throw (T) t;
}

private static JNICalls createJNICalls() {
return JNICalls.createWithExceptionHandler(context -> {
if (ForeignException.class.getName().equals(context.getThrowableClassName())) {
JNIEnv env = context.getEnv();
byte[] marshalledData = JNIUtil.createArray(env, callToByteArray(env, context.getThrowable()));
throw ForeignException.create(marshalledData, ForeignException.GUEST_TO_HOST);
} else {
context.throwJNIExceptionWrapper();
}
});
private static JNICalls createJNICalls(JNIEnv env) {
JClass stackOverflowError = env.isNonNull() ? JNIUtil.NewGlobalRef(env, callGetStackOverflowErrorClass(env), StackOverflowError.class.getName()) : WordFactory.nullPointer();
return JNICalls.createWithExceptionHandler(new ForeignExceptionHandler(stackOverflowError));
}

private static JThrowable callCreateForeignException(JNIEnv env, JByteArray rawValue) {
Expand All @@ -247,6 +274,10 @@ private static JByteArray callToByteArray(JNIEnv env, JObject p0) {
return JNICalls.getDefault().callStaticJObject(env, ToByteArray.getEntryPoints(env), ToByteArray.resolve(env), args);
}

private static JClass callGetStackOverflowErrorClass(JNIEnv env) {
return JNICalls.getDefault().callStaticJObject(env, GetStackOverflowErrorClass.getEntryPoints(env), GetStackOverflowErrorClass.resolve(env), WordFactory.nullPointer());
}

private static final class JNIMethodResolver implements JNICalls.JNIMethod {

private final String methodName;
Expand Down Expand Up @@ -293,4 +324,43 @@ public String getDisplayName() {
return methodName;
}
}

private static final class ForeignExceptionHandler implements ExceptionHandler {

private static final StackOverflowError JNI_STACK_OVERFLOW_ERROR = new StackOverflowError("Stack overflow in transition from Native to Java.") {
@Override
@SuppressWarnings("sync-override")
public Throwable fillInStackTrace() {
return this;
}
};

private final JClass stackOverflowError;

ForeignExceptionHandler(JClass stackOverflowError) {
this.stackOverflowError = stackOverflowError;
}

@Override
public void handleException(ExceptionHandlerContext context) {
JNIEnv env = context.getEnv();
JThrowable exception = context.getThrowable();
if (stackOverflowError.isNonNull() && JNIUtil.IsSameObject(env, stackOverflowError, JNIUtil.GetObjectClass(env, exception))) {
/*
* The JavaVM lacks sufficient stack space to transition to `_thread_in_Java`.
* Calling a JNI API results in a `StackOverflowError` being set as a pending
* exception. We throw a pre-allocated `StackOverflowError`. Unlike a
* `StackOverflowError` occurring in user code, the one thrown by a VM transition is
* not wrapped in a `ForeignException`. This distinction can be used to identify the
* situation.
*/
throw JNI_STACK_OVERFLOW_ERROR;
} else if (ForeignException.class.getName().equals(context.getThrowableClassName())) {
byte[] marshalledData = JNIUtil.createArray(env, callToByteArray(env, exception));
throw ForeignException.create(marshalledData, ForeignException.GUEST_TO_HOST);
} else {
context.throwJNIExceptionWrapper();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -66,4 +66,9 @@ static Throwable createForeignException(byte[] rawValue) {
static byte[] toByteArray(ForeignException exception) {
return exception.toByteArray();
}

@JNIEntryPoint
static Class<?> getStackOverflowErrorClass() {
return StackOverflowError.class;
}
}

0 comments on commit 245d8e1

Please sign in to comment.