Commit d155107a authored by Andrew Twyman's avatar Andrew Twyman Committed by Jacob Potter

Framework for JNI exception propagation through C++

parent 28d70153
...@@ -20,6 +20,12 @@ ...@@ -20,6 +20,12 @@
static_assert(sizeof(jlong) >= sizeof(void*), "must be able to fit a void* into a jlong"); static_assert(sizeof(jlong) >= sizeof(void*), "must be able to fit a void* into a jlong");
#ifdef _MSC_VER // weak attribute not supported by MSVC
#define DJINNI_WEAK_DEFINITION
#else
#define DJINNI_WEAK_DEFINITION __attribute__((weak))
#endif
namespace djinni { namespace djinni {
// Set only once from JNI_OnLoad before any other JNI calls, so no lock needed. // Set only once from JNI_OnLoad before any other JNI calls, so no lock needed.
...@@ -32,7 +38,11 @@ void jniInit(JavaVM * jvm) { ...@@ -32,7 +38,11 @@ void jniInit(JavaVM * jvm) {
for (const auto & kv : JniClassInitializer::Registration::get_all()) { for (const auto & kv : JniClassInitializer::Registration::get_all()) {
kv.second->init(); kv.second->init();
} }
} catch (const jni_exception_pending &) {} } catch (const std::exception & e) {
// Default exception handling only, since non-default might not be safe if init
// is incomplete.
jniDefaultSetPendingFromCurrent(jniGetThreadEnv(), __func__);
}
} }
void jniShutdown() { void jniShutdown() {
...@@ -89,15 +99,28 @@ void LocalRefDeleter::operator() (jobject localRef) noexcept { ...@@ -89,15 +99,28 @@ void LocalRefDeleter::operator() (jobject localRef) noexcept {
} }
} }
void jni_exception::set_as_pending(JNIEnv * env) const noexcept {
assert(env);
env->Throw(java_exception());
}
void jniExceptionCheck(JNIEnv * env) { void jniExceptionCheck(JNIEnv * env) {
if (!env) { if (!env) {
abort(); abort();
} }
if (env->ExceptionCheck()) { const LocalRef<jthrowable> e(env->ExceptionOccurred());
throw jni_exception_pending(); if (e) {
env->ExceptionClear();
jniThrowCppFromJavaException(env, e.get());
} }
} }
DJINNI_WEAK_DEFINITION __attribute__((noreturn))
void jniThrowCppFromJavaException(JNIEnv * env, jthrowable java_exception) {
throw jni_exception { env, java_exception };
}
namespace { // anonymous namespace to guard the struct below namespace { // anonymous namespace to guard the struct below
struct SystemClassInfo { struct SystemClassInfo {
// This is a singleton class - an instance will be constructed by // This is a singleton class - an instance will be constructed by
...@@ -141,13 +164,18 @@ void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const cha ...@@ -141,13 +164,18 @@ void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const cha
snprintf(buf, sizeof buf, "djinni (%s:%d): %s", file_basename, line, check); snprintf(buf, sizeof buf, "djinni (%s:%d): %s", file_basename, line, check);
#endif #endif
jclass cassert = env->FindClass("java/lang/AssertionError"); const jclass cassert = env->FindClass("java/lang/Error");
assert(cassert); assert(cassert);
env->ThrowNew(cassert, buf); env->ThrowNew(cassert, buf);
assert(env->ExceptionCheck()); assert(env->ExceptionCheck());
const jthrowable e = env->ExceptionOccurred();
assert(e);
env->ExceptionClear();
env->DeleteLocalRef(cassert); env->DeleteLocalRef(cassert);
throw jni_exception_pending {}; env->DeleteLocalRef(e);
jniThrowCppFromJavaException(env, e);
} }
GlobalRef<jclass> jniFindClass(const char * name) { GlobalRef<jclass> jniFindClass(const char * name) {
...@@ -416,19 +444,24 @@ std::string jniUTF8FromString(JNIEnv * env, const jstring jstr) { ...@@ -416,19 +444,24 @@ std::string jniUTF8FromString(JNIEnv * env, const jstring jstr) {
return out; return out;
} }
#ifdef _MSC_VER DJINNI_WEAK_DEFINITION
// weak attribute not supported by MSVC void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept {
#else jniDefaultSetPendingFromCurrent(env, ctx);
__attribute__((weak)) }
#endif
void jniSetPendingFromCurrent(JNIEnv * env, const char * /*ctx*/) noexcept { void jniDefaultSetPendingFromCurrent(JNIEnv * env, const char * /*ctx*/) noexcept {
assert(env);
try { try {
throw; throw;
} catch (const jni_exception_pending &) { } catch (const jni_exception & e) {
e.set_as_pending(env);
return; return;
} catch (const std::exception & e) { } catch (const std::exception & e) {
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what()); env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what());
} }
// noexcept will call terminate() for anything not caught above (i.e.
// exceptions which aren't std::exception subclasses).
} }
struct JavaProxyCacheState { struct JavaProxyCacheState {
......
...@@ -51,34 +51,6 @@ void jniShutdown(); ...@@ -51,34 +51,6 @@ void jniShutdown();
*/ */
JNIEnv * jniGetThreadEnv(); JNIEnv * jniGetThreadEnv();
/*
* Exception to indicate that a Java exception is pending in the JVM.
*/
class jni_exception_pending : public std::exception {};
/*
* Throw jni_exception_pending if any Java exception is pending in the JVM.
*/
void jniExceptionCheck(JNIEnv * env);
/*
* Set an AssertionError in env with message message, and then throw jni_exception_pending.
*/
#ifdef _MSC_VER
__declspec(noreturn)
#else
__attribute__((noreturn))
#endif
void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const char * check);
#define DJINNI_ASSERT(check, env) \
do { \
djinni::jniExceptionCheck(env); \
const bool check__res = bool(check); \
djinni::jniExceptionCheck(env); \
if (!check__res) { \
djinni::jniThrowAssertionError(env, __FILE__, __LINE__, #check); \
} \
} while(false)
/* /*
* Global and local reference guard objects. * Global and local reference guard objects.
* *
...@@ -122,7 +94,69 @@ public: ...@@ -122,7 +94,69 @@ public:
}; };
/* /*
* Helper for JniClassInitializer. Copied from Oxygen. * Exception to indicate that a Java exception is pending in the JVM.
*/
class jni_exception : public std::exception {
GlobalRef<jthrowable> m_java_exception;
public:
jni_exception(JNIEnv * env, jthrowable java_exception)
: m_java_exception(env, java_exception) {
assert(java_exception);
}
jthrowable java_exception() const { return m_java_exception.get(); }
/*
* Sets the pending JNI exception using this Java exception.
*/
void set_as_pending(JNIEnv * env) const noexcept;
};
/*
* Throw if any Java exception is pending in the JVM.
*
* If an exception is pending, this function will clear the
* pending state, and pass the exception to
* jniThrowCppFromJavaException().
*/
void jniExceptionCheck(JNIEnv * env);
/*
* Throws a C++ exception based on the given Java exception.
*
* java_exception is a local reference to a Java throwable, which
* must not be null, and should no longer set as "pending" in the JVM.
* This is called to handle errors in other JNI processing, including
* by jniExceptionCheck().
*
* The default implementation is defined with __attribute__((weak)) so you
* can replace it by defining your own version. The default implementation
* will throw a jni_exception containing the given jthrowable.
*/
__attribute__((noreturn))
void jniThrowCppFromJavaException(JNIEnv * env, jthrowable java_exception);
/*
* Set an AssertionError in env with message message, and then throw via jniExceptionCheck.
*/
#ifdef _MSC_VER
__declspec(noreturn)
#else
__attribute__((noreturn))
#endif
void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const char * check);
#define DJINNI_ASSERT(check, env) \
do { \
djinni::jniExceptionCheck(env); \
const bool check__res = bool(check); \
djinni::jniExceptionCheck(env); \
if (!check__res) { \
djinni::jniThrowAssertionError(env, __FILE__, __LINE__, #check); \
} \
} while(false)
/*
* Helper for JniClassInitializer.
*/ */
template <class Key, class T> template <class Key, class T>
class static_registration { class static_registration {
...@@ -377,7 +411,11 @@ public: ...@@ -377,7 +411,11 @@ public:
} }
static const std::shared_ptr<T> & get(jlong handle) { static const std::shared_ptr<T> & get(jlong handle) {
return reinterpret_cast<const CppProxyHandle<T> *>(handle)->m_obj; assert(handle);
assert(handle > 4096);
const auto & ret = reinterpret_cast<const CppProxyHandle<T> *>(handle)->m_obj;
assert(ret);
return ret;
} }
private: private:
...@@ -577,9 +615,35 @@ private: ...@@ -577,9 +615,35 @@ private:
#define DJINNI_FUNCTION_PROLOGUE0(env_) #define DJINNI_FUNCTION_PROLOGUE0(env_)
#define DJINNI_FUNCTION_PROLOGUE1(env_, arg1_) #define DJINNI_FUNCTION_PROLOGUE1(env_, arg1_)
// Helper for JNI_TRANSLATE_EXCEPTIONS_RETURN. Do not call directly. /*
* Helper for JNI_TRANSLATE_EXCEPTIONS_RETURN.
*
* Must be called in a catch block. Responsible for setting the pending
* exception in JNI based on the current C++ exception.
*
* The default implementation is defined with __attribute__((weak)) so you
* can replace it by defining your own version. The default implementation
* will call jniDefaultSetPendingFromCurrent(), which will propagate a
* jni_exception directly into Java, or throw a RuntimeException for any
* other std::exception.
*/
void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept; void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept;
/*
* Helper for JNI_TRANSLATE_EXCEPTIONS_RETURN.
*
* Must be called in a catch block. Responsible for setting the pending
* exception in JNI based on the current C++ exception.
*
* This will call jniSetPendingFrom(env, jni_exception) if the current exception
* is a jni_exception, or otherwise will set a RuntimeException from any
* other std::exception. Any non-std::exception will result in a call
* to terminate().
*
* This is called by the default implementation of jniSetPendingFromCurrent.
*/
void jniDefaultSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept;
/* Catch C++ exceptions and translate them to Java exceptions. /* Catch C++ exceptions and translate them to Java exceptions.
* *
* All functions called by Java must be fully wrapped by an outer try...catch block like so: * All functions called by Java must be fully wrapped by an outer try...catch block like so:
...@@ -599,4 +663,23 @@ void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept; ...@@ -599,4 +663,23 @@ void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept;
return ret; \ return ret; \
} }
/* Catch jni_exception and translate it back to a Java exception, without catching
* any other C++ exceptions. Can be used to wrap code which might cause JNI
* exceptions like so:
*
* try {
* ...
* } JNI_TRANSLATE_JAVA_EXCEPTIONS_RETURN(env, 0)
* ... or JNI_TRANSLATE_JAVA_EXCEPTIONS_RETURN(env, ) for functions returning void
*
* The second parameter is a default return value to be used if an exception is caught and
* converted. (For JNI outer-layer calls, this result will always be ignored by JNI, so
* it can safely be 0 for any function with a non-void return value.)
*/
#define JNI_TRANSLATE_JNI_EXCEPTIONS_RETURN(env, ret) \
catch (const ::djinni::jni_exception & e) { \
e.set_as_pending(env); \
return ret; \
}
} // namespace djinni } // namespace djinni
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment