Files
flipper/libs/fbjni/cxx/fbjni/detail/Exceptions.cpp
Pritesh Nandgaonkar bcf6479a5e Solve fbjni bug to allow catching Java exception in JNI
Summary: This diff just adds the changes done here D12829542 for the OSS

Reviewed By: jknoxville

Differential Revision: D12839582

fbshipit-source-id: 0bdfa9e8e661309454709819af703499cd540ab4
2018-11-19 04:29:34 -08:00

392 lines
12 KiB
C++

/*
* Copyright (c) Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#include "CoreClasses.h"
#include "Log.h"
#ifndef FBJNI_NO_EXCEPTION_PTR
#include <lyra/lyra.h>
#include <lyra/lyra_exceptions.h>
#endif
#include <alloca.h>
#include <cstdlib>
#include <ios>
#include <stdexcept>
#include <stdio.h>
#include <string>
#include <system_error>
#include <jni.h>
namespace facebook {
namespace jni {
namespace {
class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
static local_ref<JRuntimeException> create(const char* str) {
return newInstance(make_jstring(str));
}
static local_ref<JRuntimeException> create() {
return newInstance();
}
};
class JIOException : public JavaClass<JIOException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/io/IOException;";
static local_ref<JIOException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
static local_ref<JOutOfMemoryError> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;";
static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;";
static local_ref<JUnknownCppException> create() {
return newInstance();
}
static local_ref<JUnknownCppException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;";
static local_ref<JCppSystemErrorException> create(const std::system_error& e) {
return newInstance(make_jstring(e.what()), e.code().value());
}
};
// Exception throwing & translating functions //////////////////////////////////////////////////////
// Functions that throw Java exceptions
void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
auto env = Environment::current();
if (throwable) {
env->Throw(throwable.get());
}
if (env->ExceptionCheck() != JNI_TRUE) {
FBJNI_LOGF("Failed to set Java exception");
}
}
}
// Functions that throw C++ exceptions
// TODO(T6618159) Inject the c++ stack into the exception's stack trace. One
// issue: when a java exception is created, it captures the full java stack
// across jni boundaries. lyra will only capture the c++ stack to the jni
// boundary. So, as we pass the java exception up to c++, we need to capture
// the c++ stack and then insert it into the correct place in the java stack
// trace. Then, as the exception propagates across the boundaries, we will
// slowly fill in the c++ parts of the trace.
void throwPendingJniExceptionAsCppException() {
JNIEnv* env = Environment::current();
if (env->ExceptionCheck() == JNI_FALSE) {
return;
}
auto throwable = env->ExceptionOccurred();
if (!throwable) {
throw std::runtime_error("Unable to get pending JNI exception.");
}
env->ExceptionClear();
throw JniException(adopt_local(throwable));
}
void throwCppExceptionIf(bool condition) {
if (!condition) {
return;
}
auto env = Environment::current();
if (env->ExceptionCheck() == JNI_TRUE) {
throwPendingJniExceptionAsCppException();
return;
}
throw JniException();
}
void throwNewJavaException(jthrowable throwable) {
throw JniException(wrap_alias(throwable));
}
void throwNewJavaException(const char* throwableName, const char* msg) {
// If anything of the fbjni calls fail, an exception of a suitable
// form will be thrown, which is what we want.
auto throwableClass = findClassLocal(throwableName);
auto throwable = throwableClass->newObject(
throwableClass->getConstructor<jthrowable(jstring)>(),
make_jstring(msg).release());
throwNewJavaException(throwable.get());
}
// jthrowable //////////////////////////////////////////////////////////////////////////////////////
local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
static auto meth = javaClassStatic()->getMethod<javaobject(alias_ref<javaobject>)>("initCause");
return meth(self(), cause);
}
auto JThrowable::getStackTrace() -> local_ref<JStackTrace> {
static auto meth = javaClassStatic()->getMethod<JStackTrace::javaobject()>("getStackTrace");
return meth(self());
}
void JThrowable::setStackTrace(alias_ref<JStackTrace> stack) {
static auto meth = javaClassStatic()->getMethod<void(alias_ref<JStackTrace>)>("setStackTrace");
return meth(self(), stack);
}
auto JStackTraceElement::create(
const std::string& declaringClass, const std::string& methodName, const std::string& file, int line)
-> local_ref<javaobject> {
return newInstance(declaringClass, methodName, file, line);
}
std::string JStackTraceElement::getClassName() const {
static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getClassName");
return meth(self())->toStdString();
}
std::string JStackTraceElement::getMethodName() const {
static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getMethodName");
return meth(self())->toStdString();
}
std::string JStackTraceElement::getFileName() const {
static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getFileName");
return meth(self())->toStdString();
}
int JStackTraceElement::getLineNumber() const {
static auto meth = javaClassStatic()->getMethod<jint()>("getLineNumber");
return meth(self());
}
// Translate C++ to Java Exception
namespace {
// For each exception in the chain of the exception_ptr argument, func
// will be called with that exception (in reverse order, i.e. innermost first).
#ifndef FBJNI_NO_EXCEPTION_PTR
void denest(const std::function<void(std::exception_ptr)>& func, std::exception_ptr ptr) {
FBJNI_ASSERT(ptr);
try {
std::rethrow_exception(ptr);
} catch (const std::nested_exception& e) {
denest(func, e.nested_ptr());
} catch (...) {
// ignored.
}
func(ptr);
}
#endif
} // namespace
#ifndef FBJNI_NO_EXCEPTION_PTR
local_ref<JStackTraceElement> createJStackTraceElement(const lyra::StackTraceElement& cpp) {
return JStackTraceElement::create(
"|lyra|{" + cpp.libraryName() + "}", cpp.functionName(), cpp.buildId(), cpp.libraryOffset());
}
void addCppStacktraceToJavaException(alias_ref<JThrowable> java, std::exception_ptr cpp) {
auto cppStack = lyra::getStackTraceSymbols(
(cpp == nullptr) ?
lyra::getStackTrace()
: lyra::getExceptionTrace(cpp));
auto javaStack = java->getStackTrace();
auto newStack = JThrowable::JStackTrace::newArray(javaStack->size() + cppStack.size());
size_t i = 0;
for (size_t j = 0; j < cppStack.size(); j++, i++) {
(*newStack)[i] = createJStackTraceElement(cppStack[j]);
}
for (size_t j = 0; j < javaStack->size(); j++, i++) {
(*newStack)[i] = (*javaStack)[j];
}
java->setStackTrace(newStack);
}
local_ref<JThrowable> convertCppExceptionToJavaException(std::exception_ptr ptr) {
FBJNI_ASSERT(ptr);
local_ref<JThrowable> current;
bool addCppStack = true;
try {
std::rethrow_exception(ptr);
addCppStack = false;
} catch (const JniException& ex) {
current = ex.getThrowable();
} catch (const std::ios_base::failure& ex) {
current = JIOException::create(ex.what());
} catch (const std::bad_alloc& ex) {
current = JOutOfMemoryError::create(ex.what());
} catch (const std::out_of_range& ex) {
current = JArrayIndexOutOfBoundsException::create(ex.what());
} catch (const std::system_error& ex) {
current = JCppSystemErrorException::create(ex);
} catch (const std::runtime_error& ex) {
current = JRuntimeException::create(ex.what());
} catch (const std::exception& ex) {
current = JCppException::create(ex.what());
} catch (const char* msg) {
current = JUnknownCppException::create(msg);
} catch (...) {
current = JUnknownCppException::create();
}
if (addCppStack) {
addCppStacktraceToJavaException(current, ptr);
}
return current;
}
#endif
local_ref<JThrowable> getJavaExceptionForCppBackTrace() {
return getJavaExceptionForCppBackTrace(nullptr);
}
local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg) {
local_ref<JThrowable> current =
msg ? JUnknownCppException::create(msg) : JUnknownCppException::create();
#ifndef FBJNI_NO_EXCEPTION_PTR
addCppStacktraceToJavaException(current, nullptr);
#endif
return current;
}
#ifndef FBJNI_NO_EXCEPTION_PTR
local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr) {
FBJNI_ASSERT(ptr);
local_ref<JThrowable> previous;
auto func = [&previous] (std::exception_ptr ptr) {
auto current = convertCppExceptionToJavaException(ptr);
if (previous) {
current->initCause(previous);
}
previous = current;
};
denest(func, ptr);
return previous;
}
#endif
void translatePendingCppExceptionToJavaException() {
try {
#ifndef FBJNI_NO_EXCEPTION_PTR
auto exc = getJavaExceptionForCppException(std::current_exception());
#else
auto exc = JUnknownCppException::create();
#endif
setJavaExceptionAndAbortOnFailure(exc);
} catch (...) {
#ifndef FBJNI_NO_EXCEPTION_PTR
FBJNI_LOGE(
"Unexpected error in translatePendingCppExceptionToJavaException(): %s",
lyra::toString(std::current_exception()).c_str());
#else
FBJNI_LOGE(
"Unexpected error in translatePendingCppExceptionToJavaException()");
#endif
std::terminate();
}
}
// JniException ////////////////////////////////////////////////////////////////////////////////////
const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
JniException::JniException() : JniException(JRuntimeException::create()) { }
JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) {
throwable_ = make_global(throwable);
}
JniException::JniException(JniException &&rhs)
: throwable_(std::move(rhs.throwable_)),
what_(std::move(rhs.what_)),
isMessageExtracted_(rhs.isMessageExtracted_) {
}
JniException::JniException(const JniException &rhs)
: what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
throwable_ = make_global(rhs.throwable_);
}
JniException::~JniException() {
try {
ThreadScope ts;
throwable_.reset();
} catch (...) {
FBJNI_LOGE("Exception in ~JniException()");
std::terminate();
}
}
local_ref<JThrowable> JniException::getThrowable() const noexcept {
return make_local(throwable_);
}
// TODO 6900503: consider making this thread-safe.
void JniException::populateWhat() const noexcept {
try {
ThreadScope ts;
what_ = throwable_->toString();
isMessageExtracted_ = true;
} catch(...) {
what_ = kExceptionMessageFailure_;
}
}
const char* JniException::what() const noexcept {
if (!isMessageExtracted_) {
populateWhat();
}
return what_.c_str();
}
void JniException::setJavaException() const noexcept {
setJavaExceptionAndAbortOnFailure(throwable_);
}
}}