Reverted commit D3510867

Differential Revision: D3510867

fbshipit-source-id: 365eb22e143f1c0eec6e4b8810c93dbb0e9fbbfa
This commit is contained in:
Konstantin Raev 2016-07-02 13:37:03 -07:00 committed by Facebook Github Bot 3
parent 8b2adeaa2e
commit f78e819f20
15 changed files with 334 additions and 332 deletions

View File

@ -8,7 +8,6 @@
*/ */
#pragma once #pragma once
#include <functional>
#include <string> #include <string>
#include <jni.h> #include <jni.h>
@ -22,35 +21,44 @@ struct Environment {
// May be null if this thread isn't attached to the JVM // May be null if this thread isn't attached to the JVM
FBEXPORT static JNIEnv* current(); FBEXPORT static JNIEnv* current();
static void initialize(JavaVM* vm); static void initialize(JavaVM* vm);
// There are subtle issues with calling the next functions directly. It is
// much better to always use a ThreadScope to manage attaching/detaching for
// you.
FBEXPORT static JNIEnv* ensureCurrentThreadIsAttached(); FBEXPORT static JNIEnv* ensureCurrentThreadIsAttached();
FBEXPORT static void detachCurrentThread(); FBEXPORT static void detachCurrentThread();
}; };
/** /**
* RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it * RAII Object that attaches a thread to the JVM. Failing to detach from a
* exits will cause a crash, as will calling Detach an extra time, and this guard class helps * thread before it
* keep that straight. In addition, it remembers whether it performed the attach or not, so it * exits will cause a crash, as will calling Detach an extra time, and this
* is safe to nest it with itself or with non-fbjni code that manages the attachment correctly. * guard class helps
* keep that straight. In addition, it remembers whether it performed the attach
* or not, so it
* is safe to nest it with itself or with non-fbjni code that manages the
* attachment correctly.
* *
* Potential concerns: * Potential concerns:
* - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would
* attach while the
* app is not busy. * app is not busy.
* - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that * - Having a thread detach at arbitrary points is not safe in Dalvik; you need
* there is no Java code on the current stack or you run the risk of a crash like: * to be sure that
* there is no Java code on the current stack or you run the risk of a crash
* like:
* ERROR: detaching thread with interp frames (count=18) * ERROR: detaching thread with interp frames (count=18)
* (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) * (More detail at
* ThreadScope won't do a detach if the thread was already attached before the guard is * https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo)
* ThreadScope won't do a detach if the thread was already attached before
* the guard is
* instantiated, but there's probably some usage that could trip this up. * instantiated, but there's probably some usage that could trip this up.
* - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language * - Newly attached C++ threads only get the bootstrap class loader -- i.e.
* classes, not any of our application's classes. This will be different behavior than threads * java language
* that were initiated on the Java side. A workaround is to pass a global reference for a * classes, not any of our application's classes. This will be different
* class or instance to the new thread; this bypasses the need for the class loader. * behavior than threads
* (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) * that were initiated on the Java side. A workaround is to pass a global
* If you need access to the application's classes, you can use ThreadScope::WithClassLoader. * reference for a
* class or instance to the new thread; this bypasses the need for the class
* loader.
* (See
* http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread)
*/ */
class FBEXPORT ThreadScope { class FBEXPORT ThreadScope {
public: public:
@ -61,15 +69,6 @@ class FBEXPORT ThreadScope {
ThreadScope& operator=(ThreadScope&&) = delete; ThreadScope& operator=(ThreadScope&&) = delete;
~ThreadScope(); ~ThreadScope();
/**
* This runs the closure in a scope with fbjni's classloader. This should be
* the same classloader as the rest of the application and thus anything
* running in the closure will have access to the same classes as in a normal
* java-create thread.
*/
static void WithClassLoader(std::function<void()>&& runnable);
static void OnLoad();
private: private:
bool attachedWithThisScope_; bool attachedWithThisScope_;
}; };

View File

@ -29,31 +29,11 @@
# endif # endif
#endif #endif
// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as
// a C++ exception.
#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \
::facebook::jni::throwPendingJniExceptionAsCppException()
// If the condition is true, throws a JniException object, which wraps the pending JNI Java
// exception if any. If no pending exception is found, throws a JniException object that wraps a
// RuntimeException throwable. 
#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \
::facebook::jni::throwCppExceptionIf(CONDITION)
/// @cond INTERNAL /// @cond INTERNAL
namespace facebook { namespace facebook {
namespace jni { namespace jni {
FBEXPORT void throwPendingJniExceptionAsCppException();
FBEXPORT void throwCppExceptionIf(bool condition);
[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable);
[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg);
template<typename... Args>
[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args);
/** /**
* This needs to be called at library load time, typically in your JNI_OnLoad method. * This needs to be called at library load time, typically in your JNI_OnLoad method.
* *

View File

@ -25,10 +25,6 @@ class AContext : public JavaClass<AContext> {
return method(self()); return method(self());
} }
local_ref<JFile::javaobject> getFilesDir() {
static auto method = getClass()->getMethod<JFile::javaobject()>("getFilesDir");
return method(self());
}
}; };
} }

View File

@ -341,13 +341,6 @@ struct Convert<const char*> {
}; };
} }
// jthrowable //////////////////////////////////////////////////////////////////////////////////////
inline local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
static auto meth = javaClassStatic()->getMethod<javaobject(javaobject)>("initCause");
return meth(self(), cause.get());
}
// jtypeArray ////////////////////////////////////////////////////////////////////////////////////// // jtypeArray //////////////////////////////////////////////////////////////////////////////////////
namespace detail { namespace detail {

View File

@ -212,7 +212,7 @@ protected:
/// the Java class actually has (i.e. with static create() functions). /// the Java class actually has (i.e. with static create() functions).
template<typename... Args> template<typename... Args>
static local_ref<T> newInstance(Args... args) { static local_ref<T> newInstance(Args... args) {
return detail::newInstance<T>(args...); return detail::newInstance<JavaClass>(args...);
} }
javaobject self() const noexcept; javaobject self() const noexcept;
@ -344,8 +344,6 @@ FBEXPORT local_ref<JString> make_jstring(const std::string& modifiedUtf8);
class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> { class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
public: public:
static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
local_ref<JThrowable> initCause(alias_ref<JThrowable> cause);
}; };
namespace detail { namespace detail {

View File

@ -29,26 +29,30 @@
#include <fb/visibility.h> #include <fb/visibility.h>
#include "Common.h" #include "Common.h"
#include "References.h"
#include "CoreClasses.h" // If a pending JNI Java exception is found, wraps it in a JniException object and throws it as
// a C++ exception.
#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \
::facebook::jni::throwPendingJniExceptionAsCppException()
// If the condition is true, throws a JniException object, which wraps the pending JNI Java
// exception if any. If no pending exception is found, throws a JniException object that wraps a
// RuntimeException throwable. 
#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \
::facebook::jni::throwCppExceptionIf(CONDITION)
namespace facebook { namespace facebook {
namespace jni { namespace jni {
class JThrowable; namespace internal {
void initExceptionHelpers();
}
class JCppException : public JavaClass<JCppException, JThrowable> { /**
public: * Before using any of the state initialized above, call this. It
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;"; * will assert if initialization has not yet occurred.
*/
static local_ref<JCppException> create(const char* str) { FBEXPORT void assertIfExceptionsNotInitialized();
return newInstance(make_jstring(str));
}
static local_ref<JCppException> create(const std::exception& ex) {
return newInstance(make_jstring(ex.what()));
}
};
// JniException //////////////////////////////////////////////////////////////////////////////////// // JniException ////////////////////////////////////////////////////////////////////////////////////
@ -63,22 +67,23 @@ class JCppException : public JavaClass<JCppException, JThrowable> {
class FBEXPORT JniException : public std::exception { class FBEXPORT JniException : public std::exception {
public: public:
JniException(); JniException();
~JniException();
explicit JniException(alias_ref<jthrowable> throwable); explicit JniException(jthrowable throwable);
JniException(JniException &&rhs); JniException(JniException &&rhs);
JniException(const JniException &other); JniException(const JniException &other);
local_ref<JThrowable> getThrowable() const noexcept; ~JniException() noexcept;
jthrowable getThrowable() const noexcept;
virtual const char* what() const noexcept; virtual const char* what() const noexcept;
void setJavaException() const noexcept; void setJavaException() const noexcept;
private: private:
global_ref<JThrowable> throwable_; jthrowable throwableGlobalRef_;
mutable std::string what_; mutable std::string what_;
mutable bool isMessageExtracted_; mutable bool isMessageExtracted_;
const static std::string kExceptionMessageFailure_; const static std::string kExceptionMessageFailure_;
@ -90,8 +95,16 @@ class FBEXPORT JniException : public std::exception {
// Functions that throw C++ exceptions // Functions that throw C++ exceptions
FBEXPORT void throwPendingJniExceptionAsCppException();
FBEXPORT void throwCppExceptionIf(bool condition);
static const int kMaxExceptionMessageBufferSize = 512; static const int kMaxExceptionMessageBufferSize = 512;
[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable);
[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg);
// These methods are the preferred way to throw a Java exception from // These methods are the preferred way to throw a Java exception from
// a C++ function. They create and throw a C++ exception which wraps // a C++ function. They create and throw a C++ exception which wraps
// a Java exception, so the C++ flow is interrupted. Then, when // a Java exception, so the C++ flow is interrupted. Then, when
@ -100,6 +113,7 @@ static const int kMaxExceptionMessageBufferSize = 512;
// thrown to the java caller. // thrown to the java caller.
template<typename... Args> template<typename... Args>
[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) { [[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) {
assertIfExceptionsNotInitialized();
int msgSize = snprintf(nullptr, 0, fmt, args...); int msgSize = snprintf(nullptr, 0, fmt, args...);
char *msg = (char*) alloca(msgSize + 1); char *msg = (char*) alloca(msgSize + 1);
@ -109,7 +123,7 @@ template<typename... Args>
// Identifies any pending C++ exception and throws it as a Java exception. If the exception can't // Identifies any pending C++ exception and throws it as a Java exception. If the exception can't
// be thrown, it aborts the program. This is a noexcept function at C++ level. // be thrown, it aborts the program. This is a noexcept function at C++ level.
FBEXPORT void translatePendingCppExceptionToJavaException() noexcept; void translatePendingCppExceptionToJavaException() noexcept;
// For convenience, some exception names in java.lang are available here. // For convenience, some exception names in java.lang are available here.

View File

@ -66,7 +66,7 @@ local_ref<JArrayClass<jobject>::javaobject> makeArgsArray(Args... args) {
} }
inline bool needsSlowPath(alias_ref<jobject> obj) { bool needsSlowPath(alias_ref<jobject> obj) {
#if defined(__ANDROID__) #if defined(__ANDROID__)
// On Android 6.0, art crashes when attempting to call a function on a Proxy. // On Android 6.0, art crashes when attempting to call a function on a Proxy.
// So, when we detect that case we must use the safe, slow workaround. That is, // So, when we detect that case we must use the safe, slow workaround. That is,
@ -88,6 +88,19 @@ inline bool needsSlowPath(alias_ref<jobject> obj) {
} }
template <typename... Args>
local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) {
static auto invoke = findClassStatic("java/lang/reflect/Method")
->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke");
// TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod.
auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE));
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (!reflected) throw JniException();
auto argsArray = makeArgsArray(args...);
// No need to check for exceptions since invoke is itself a JMethod that will do that for us.
return invoke(reflected, self.get(), argsArray.get());
}
template<typename... Args> template<typename... Args>
inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) { inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) {
const auto env = Environment::current(); const auto env = Environment::current();
@ -272,19 +285,6 @@ class JNonvirtualMethod<R(Args...)> : public JMethodBase {
friend class JClass; friend class JClass;
}; };
template <typename... Args>
local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) {
static auto invoke = findClassStatic("java/lang/reflect/Method")
->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke");
// TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod.
auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE));
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
if (!reflected) throw std::runtime_error("Unable to get reflected java.lang.reflect.Method");
auto argsArray = makeArgsArray(args...);
// No need to check for exceptions since invoke is itself a JMethod that will do that for us.
return invoke(reflected, self.get(), argsArray.get());
}
// JField<T> /////////////////////////////////////////////////////////////////////////////////////// // JField<T> ///////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#pragma once
#include "CoreClasses.h"
#include "Hybrid.h"
#include "Registration.h"
#include <functional>
namespace facebook {
namespace jni {
struct JNativeRunnable : public HybridClass<JNativeRunnable> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;";
JNativeRunnable(std::function<void()>&& runnable) : runnable_(std::move(runnable)) {}
static void OnLoad() {
registerHybrid({
makeNativeMethod("run", JNativeRunnable::run),
});
}
void run() {
runnable_();
}
private:
std::function<void()> runnable_;
};
} // namespace jni
} // namespace facebook

View File

@ -13,6 +13,8 @@
#include <new> #include <new>
#include <atomic> #include <atomic>
#include "Exceptions.h"
namespace facebook { namespace facebook {
namespace jni { namespace jni {

View File

@ -23,8 +23,8 @@ namespace detail {
void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength); void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength);
size_t modifiedLength(const std::string& str); size_t modifiedLength(const std::string& str);
size_t modifiedLength(const uint8_t* str, size_t* length); size_t modifiedLength(const uint8_t* str, size_t* length);
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept; std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len);
std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept; std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len);
} }

View File

@ -12,40 +12,12 @@
#include <fb/StaticInitialized.h> #include <fb/StaticInitialized.h>
#include <fb/ThreadLocal.h> #include <fb/ThreadLocal.h>
#include <fb/Environment.h> #include <fb/Environment.h>
#include <fb/fbjni/CoreClasses.h>
#include <fb/fbjni/NativeRunnable.h>
#include <functional>
namespace facebook { namespace facebook {
namespace jni { namespace jni {
namespace { static StaticInitialized<ThreadLocal<JNIEnv>> g_env;
StaticInitialized<ThreadLocal<JNIEnv>> g_env; static JavaVM* g_vm = nullptr;
JavaVM* g_vm = nullptr;
struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;";
// These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead.
static void runStdFunction(std::function<void()>&& func) {
static auto method = javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction");
method(javaClassStatic(), reinterpret_cast<jlong>(&func));
}
static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) {
(*reinterpret_cast<std::function<void()>*>(ptr))();
}
static void OnLoad() {
// We need the javaClassStatic so that the class lookup is cached and that
// runStdFunction can be called from a ThreadScope-attached thread.
javaClassStatic()->registerNatives({
makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl),
});
}
};
}
/* static */ /* static */
JNIEnv* Environment::current() { JNIEnv* Environment::current() {
@ -53,7 +25,6 @@ JNIEnv* Environment::current() {
if ((env == nullptr) && (g_vm != nullptr)) { if ((env == nullptr) && (g_vm != nullptr)) {
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM");
// TODO(cjhopman): This should throw an exception.
env = nullptr; env = nullptr;
} else { } else {
g_env->reset(env); g_env->reset(env);
@ -114,19 +85,5 @@ ThreadScope::~ThreadScope() {
} }
} }
/* static */
void ThreadScope::OnLoad() {
// These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading.
JThreadScopeSupport::OnLoad();
}
/* static */
void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
// TODO(cjhopman): If the classloader is already available in this scope, we
// shouldn't have to jump through java.
ThreadScope ts;
JThreadScopeSupport::runStdFunction(std::move(runnable));
}
} } } }

View File

@ -7,10 +7,9 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#include <fb/fbjni/CoreClasses.h> #include "fb/fbjni.h"
#include <fb/assert.h> #include <fb/assert.h>
#include <fb/log.h>
#include <alloca.h> #include <alloca.h>
#include <cstdlib> #include <cstdlib>
@ -22,113 +21,234 @@
#include <jni.h> #include <jni.h>
namespace facebook { namespace facebook {
namespace jni { namespace jni {
namespace { // CommonJniExceptions /////////////////////////////////////////////////////////////////////////////
class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
class FBEXPORT CommonJniExceptions {
public: public:
static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; static void init();
static local_ref<JRuntimeException> create(const char* str) { static jclass getThrowableClass() {
return newInstance(make_jstring(str)); return throwableClass_;
} }
static local_ref<JRuntimeException> create() { static jclass getUnknownCppExceptionClass() {
return newInstance(); return unknownCppExceptionClass_;
} }
static jthrowable getUnknownCppExceptionObject() {
return unknownCppExceptionObject_;
}
static jthrowable getRuntimeExceptionObject() {
return runtimeExceptionObject_;
}
private:
static jclass throwableClass_;
static jclass unknownCppExceptionClass_;
static jthrowable unknownCppExceptionObject_;
static jthrowable runtimeExceptionObject_;
}; };
class JIOException : public JavaClass<JIOException, JThrowable> { // The variables in this class are all JNI global references and are intentionally leaked because
public: // we assume this library cannot be unloaded. These global references are created manually instead
static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; // of using global_ref from References.h to avoid circular dependency.
jclass CommonJniExceptions::throwableClass_ = nullptr;
jclass CommonJniExceptions::unknownCppExceptionClass_ = nullptr;
jthrowable CommonJniExceptions::unknownCppExceptionObject_ = nullptr;
jthrowable CommonJniExceptions::runtimeExceptionObject_ = nullptr;
static local_ref<JIOException> create(const char* str) {
return newInstance(make_jstring(str));
}
};
class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> { // Variable to guarantee that fallback exceptions have been initialized early. We don't want to
public: // do pure dynamic initialization -- we want to warn programmers early that they need to run the
static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;"; // helpers at library load time instead of lazily getting them when the exception helpers are
// first used.
static std::atomic<bool> gIsInitialized(false);
static local_ref<JOutOfMemoryError> create(const char* str) { void CommonJniExceptions::init() {
return newInstance(make_jstring(str)); JNIEnv* env = internal::getEnv();
} FBASSERTMSGF(env, "Could not get JNI Environment");
};
class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> { // Throwable class
public: jclass localThrowableClass = env->FindClass("java/lang/Throwable");
static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;"; FBASSERT(localThrowableClass);
throwableClass_ = static_cast<jclass>(env->NewGlobalRef(localThrowableClass));
FBASSERT(throwableClass_);
env->DeleteLocalRef(localThrowableClass);
static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) { // UnknownCppException class
return newInstance(make_jstring(str)); jclass localUnknownCppExceptionClass = env->FindClass("com/facebook/jni/UnknownCppException");
} FBASSERT(localUnknownCppExceptionClass);
}; jmethodID unknownCppExceptionConstructorMID = env->GetMethodID(
localUnknownCppExceptionClass,
"<init>",
"()V");
FBASSERT(unknownCppExceptionConstructorMID);
unknownCppExceptionClass_ = static_cast<jclass>(env->NewGlobalRef(localUnknownCppExceptionClass));
FBASSERT(unknownCppExceptionClass_);
env->DeleteLocalRef(localUnknownCppExceptionClass);
class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> { // UnknownCppException object
public: jthrowable localUnknownCppExceptionObject = static_cast<jthrowable>(env->NewObject(
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; unknownCppExceptionClass_,
unknownCppExceptionConstructorMID));
FBASSERT(localUnknownCppExceptionObject);
unknownCppExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef(
localUnknownCppExceptionObject));
FBASSERT(unknownCppExceptionObject_);
env->DeleteLocalRef(localUnknownCppExceptionObject);
static local_ref<JUnknownCppException> create() { // RuntimeException object
return newInstance(); jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException");
} FBASSERT(localRuntimeExceptionClass);
static local_ref<JUnknownCppException> create(const char* str) { jmethodID runtimeExceptionConstructorMID = env->GetMethodID(
return newInstance(make_jstring(str)); localRuntimeExceptionClass,
} "<init>",
}; "()V");
FBASSERT(runtimeExceptionConstructorMID);
jthrowable localRuntimeExceptionObject = static_cast<jthrowable>(env->NewObject(
localRuntimeExceptionClass,
runtimeExceptionConstructorMID));
FBASSERT(localRuntimeExceptionObject);
runtimeExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef(localRuntimeExceptionObject));
FBASSERT(runtimeExceptionObject_);
class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> { env->DeleteLocalRef(localRuntimeExceptionClass);
public: env->DeleteLocalRef(localRuntimeExceptionObject);
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()); // initExceptionHelpers() //////////////////////////////////////////////////////////////////////////
}
}; void internal::initExceptionHelpers() {
CommonJniExceptions::init();
gIsInitialized.store(true, std::memory_order_seq_cst);
}
void assertIfExceptionsNotInitialized() {
// Use relaxed memory order because we don't need memory barriers.
// The real init-once enforcement is done by the compiler for the
// "static" in initExceptionHelpers.
FBASSERTMSGF(gIsInitialized.load(std::memory_order_relaxed),
"initExceptionHelpers was never called!");
}
// Exception throwing & translating functions ////////////////////////////////////////////////////// // Exception throwing & translating functions //////////////////////////////////////////////////////
// Functions that throw Java exceptions // Functions that throw Java exceptions
void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) { namespace {
auto env = Environment::current();
void setJavaExceptionAndAbortOnFailure(jthrowable throwable) noexcept {
assertIfExceptionsNotInitialized();
JNIEnv* env = internal::getEnv();
if (throwable) { if (throwable) {
env->Throw(throwable.get()); env->Throw(throwable);
} }
if (env->ExceptionCheck() != JNI_TRUE) { if (env->ExceptionCheck() != JNI_TRUE) {
std::abort(); std::abort();
} }
} }
void setDefaultException() noexcept {
assertIfExceptionsNotInitialized();
setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getRuntimeExceptionObject());
}
void setCppSystemErrorExceptionInJava(const std::system_error& ex) noexcept {
assertIfExceptionsNotInitialized();
JNIEnv* env = internal::getEnv();
jclass cppSystemErrorExceptionClass = env->FindClass(
"com/facebook/jni/CppSystemErrorException");
if (!cppSystemErrorExceptionClass) {
setDefaultException();
return;
}
jmethodID constructorMID = env->GetMethodID(
cppSystemErrorExceptionClass,
"<init>",
"(Ljava/lang/String;I)V");
if (!constructorMID) {
setDefaultException();
return;
}
jthrowable cppSystemErrorExceptionObject = static_cast<jthrowable>(env->NewObject(
cppSystemErrorExceptionClass,
constructorMID,
env->NewStringUTF(ex.what()),
ex.code().value()));
setJavaExceptionAndAbortOnFailure(cppSystemErrorExceptionObject);
}
template<typename... ARGS>
void setNewJavaException(jclass exceptionClass, const char* fmt, ARGS... args) {
assertIfExceptionsNotInitialized();
int msgSize = snprintf(nullptr, 0, fmt, args...);
JNIEnv* env = internal::getEnv();
try {
char *msg = (char*) alloca(msgSize + 1);
snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...);
env->ThrowNew(exceptionClass, msg);
} catch (...) {
env->ThrowNew(exceptionClass, "");
}
if (env->ExceptionCheck() != JNI_TRUE) {
setDefaultException();
}
}
void setNewJavaException(jclass exceptionClass, const char* msg) {
assertIfExceptionsNotInitialized();
setNewJavaException(exceptionClass, "%s", msg);
}
template<typename... ARGS>
void setNewJavaException(const char* className, const char* fmt, ARGS... args) {
assertIfExceptionsNotInitialized();
JNIEnv* env = internal::getEnv();
jclass exceptionClass = env->FindClass(className);
if (env->ExceptionCheck() != JNI_TRUE && !exceptionClass) {
// If FindClass() has failed but no exception has been thrown, throw a default exception.
setDefaultException();
return;
}
setNewJavaException(exceptionClass, fmt, args...);
}
} }
// Functions that throw C++ exceptions // Functions that throw C++ exceptions
// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated // TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated
void throwPendingJniExceptionAsCppException() { FBEXPORT void throwPendingJniExceptionAsCppException() {
JNIEnv* env = Environment::current(); assertIfExceptionsNotInitialized();
JNIEnv* env = internal::getEnv();
if (env->ExceptionCheck() == JNI_FALSE) { if (env->ExceptionCheck() == JNI_FALSE) {
return; return;
} }
auto throwable = adopt_local(env->ExceptionOccurred()); jthrowable throwable = env->ExceptionOccurred();
if (!throwable) { if (!throwable) {
throw std::runtime_error("Unable to get pending JNI exception."); throw std::runtime_error("Unable to get pending JNI exception.");
} }
env->ExceptionClear();
env->ExceptionClear();
throw JniException(throwable); throw JniException(throwable);
} }
void throwCppExceptionIf(bool condition) { void throwCppExceptionIf(bool condition) {
assertIfExceptionsNotInitialized();
if (!condition) { if (!condition) {
return; return;
} }
auto env = Environment::current(); JNIEnv* env = internal::getEnv();
if (env->ExceptionCheck() == JNI_TRUE) { if (env->ExceptionCheck() == JNI_TRUE) {
throwPendingJniExceptionAsCppException(); throwPendingJniExceptionAsCppException();
return; return;
@ -137,11 +257,13 @@ void throwCppExceptionIf(bool condition) {
throw JniException(); throw JniException();
} }
void throwNewJavaException(jthrowable throwable) { FBEXPORT void throwNewJavaException(jthrowable throwable) {
throw JniException(wrap_alias(throwable)); throw JniException(throwable);
} }
void throwNewJavaException(const char* throwableName, const char* msg) { FBEXPORT void throwNewJavaException(
const char* throwableName,
const char* msg) {
// If anything of the fbjni calls fail, an exception of a suitable // If anything of the fbjni calls fail, an exception of a suitable
// form will be thrown, which is what we want. // form will be thrown, which is what we want.
auto throwableClass = findClassLocal(throwableName); auto throwableClass = findClassLocal(throwableName);
@ -153,80 +275,34 @@ void throwNewJavaException(const char* throwableName, const char* msg) {
// Translate C++ to Java Exception // Translate C++ to Java Exception
namespace { FBEXPORT void translatePendingCppExceptionToJavaException() noexcept {
assertIfExceptionsNotInitialized();
// The implementation std::rethrow_if_nested uses a dynamic_cast to determine
// if the exception is a nested_exception. If the exception is from a library
// built with -fno-rtti, then that will crash. This avoids that.
void rethrow_if_nested() {
try { try {
throw;
} catch (const std::nested_exception& e) {
e.rethrow_nested();
} catch (...) {
}
}
// For each exception in the chain of the currently handled exception, func
// will be called with that exception as the currently handled exception (in
// reverse order, i.e. innermost first).
void denest(std::function<void()> func) {
try {
throw;
} catch (const std::exception& e) {
try {
rethrow_if_nested();
} catch (...) {
denest(func);
}
func();
} catch (...) {
func();
}
}
}
void translatePendingCppExceptionToJavaException() noexcept {
local_ref<JThrowable> previous;
auto func = [&previous] () {
local_ref<JThrowable> current;
try { try {
throw; throw;
} catch(const JniException& ex) { } catch(const JniException& ex) {
current = ex.getThrowable(); ex.setJavaException();
} catch(const std::ios_base::failure& ex) { } catch(const std::ios_base::failure& ex) {
current = JIOException::create(ex.what()); setNewJavaException("java/io/IOException", ex.what());
} catch(const std::bad_alloc& ex) { } catch(const std::bad_alloc& ex) {
current = JOutOfMemoryError::create(ex.what()); setNewJavaException("java/lang/OutOfMemoryError", ex.what());
} catch(const std::out_of_range& ex) { } catch(const std::out_of_range& ex) {
current = JArrayIndexOutOfBoundsException::create(ex.what()); setNewJavaException("java/lang/ArrayIndexOutOfBoundsException", ex.what());
} catch(const std::system_error& ex) { } catch(const std::system_error& ex) {
current = JCppSystemErrorException::create(ex); setCppSystemErrorExceptionInJava(ex);
} catch(const std::runtime_error& ex) { } catch(const std::runtime_error& ex) {
current = JRuntimeException::create(ex.what()); setNewJavaException("java/lang/RuntimeException", ex.what());
} catch(const std::exception& ex) { } catch(const std::exception& ex) {
current = JCppException::create(ex.what()); setNewJavaException("com/facebook/jni/CppException", ex.what());
} catch(const char* msg) { } catch(const char* msg) {
current = JUnknownCppException::create(msg); setNewJavaException(CommonJniExceptions::getUnknownCppExceptionClass(), msg);
} catch(...) { } catch(...) {
current = JUnknownCppException::create(); setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getUnknownCppExceptionObject());
} }
if (previous) { } catch(...) {
current->initCause(previous); // This block aborts the program, if something bad happens when handling exceptions, thus
} // keeping this function noexcept.
previous = current; std::abort();
};
try {
denest(func);
setJavaExceptionAndAbortOnFailure(previous);
} catch (std::exception& e) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what());
// rethrow the exception and let the noexcept handling abort.
throw;
} catch (...) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException");
throw;
} }
} }
@ -234,41 +310,79 @@ void translatePendingCppExceptionToJavaException() noexcept {
const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
JniException::JniException() : JniException(JRuntimeException::create()) { } JniException::JniException() : JniException(CommonJniExceptions::getRuntimeExceptionObject()) { }
JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) { JniException::JniException(jthrowable throwable) : isMessageExtracted_(false) {
throwable_ = make_global(throwable); assertIfExceptionsNotInitialized();
throwableGlobalRef_ = static_cast<jthrowable>(internal::getEnv()->NewGlobalRef(throwable));
if (!throwableGlobalRef_) {
throw std::bad_alloc();
}
} }
JniException::JniException(JniException &&rhs) JniException::JniException(JniException &&rhs)
: throwable_(std::move(rhs.throwable_)), : throwableGlobalRef_(std::move(rhs.throwableGlobalRef_)),
what_(std::move(rhs.what_)), what_(std::move(rhs.what_)),
isMessageExtracted_(rhs.isMessageExtracted_) { isMessageExtracted_(rhs.isMessageExtracted_) {
rhs.throwableGlobalRef_ = nullptr;
} }
JniException::JniException(const JniException &rhs) JniException::JniException(const JniException &rhs)
: what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) { : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
throwable_ = make_global(rhs.throwable_); JNIEnv* env = internal::getEnv();
if (rhs.getThrowable()) {
throwableGlobalRef_ = static_cast<jthrowable>(env->NewGlobalRef(rhs.getThrowable()));
if (!throwableGlobalRef_) {
throw std::bad_alloc();
}
} else {
throwableGlobalRef_ = nullptr;
}
} }
JniException::~JniException() { JniException::~JniException() noexcept {
ThreadScope ts; if (throwableGlobalRef_) {
throwable_.reset(); internal::getEnv()->DeleteGlobalRef(throwableGlobalRef_);
}
} }
local_ref<JThrowable> JniException::getThrowable() const noexcept { jthrowable JniException::getThrowable() const noexcept {
return make_local(throwable_); return throwableGlobalRef_;
} }
// TODO 6900503: consider making this thread-safe. // TODO 6900503: consider making this thread-safe.
void JniException::populateWhat() const noexcept { void JniException::populateWhat() const noexcept {
ThreadScope ts; JNIEnv* env = internal::getEnv();
jmethodID toStringMID = env->GetMethodID(
CommonJniExceptions::getThrowableClass(),
"toString",
"()Ljava/lang/String;");
jstring messageJString = (jstring) env->CallObjectMethod(
throwableGlobalRef_,
toStringMID);
isMessageExtracted_ = true;
if (env->ExceptionCheck()) {
env->ExceptionClear();
what_ = kExceptionMessageFailure_;
return;
}
const char* chars = env->GetStringUTFChars(messageJString, nullptr);
if (!chars) {
what_ = kExceptionMessageFailure_;
return;
}
try { try {
what_ = throwable_->toString(); what_ = std::string(chars);
isMessageExtracted_ = true;
} catch(...) { } catch(...) {
what_ = kExceptionMessageFailure_; what_ = kExceptionMessageFailure_;
} }
env->ReleaseStringUTFChars(messageJString, chars);
} }
const char* JniException::what() const noexcept { const char* JniException::what() const noexcept {
@ -279,7 +393,7 @@ const char* JniException::what() const noexcept {
} }
void JniException::setJavaException() const noexcept { void JniException::setJavaException() const noexcept {
setJavaExceptionAndAbortOnFailure(throwable_); setJavaExceptionAndAbortOnFailure(throwableGlobalRef_);
} }
}} }}

View File

@ -156,7 +156,7 @@ void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size
modified[j++] = '\0'; modified[j++] = '\0';
} }
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept { std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) {
// Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
std::string utf8(len, 0); std::string utf8(len, 0);
size_t j = 0; size_t j = 0;
@ -230,7 +230,7 @@ size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
return utf8StringLen; return utf8StringLen;
} }
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept { std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) {
if (!utf16String || utf16StringLen <= 0) { if (!utf16String || utf16StringLen <= 0) {
return ""; return "";
} }

View File

@ -10,13 +10,10 @@
#include <jni/Countable.h> #include <jni/Countable.h>
#include <fb/Environment.h> #include <fb/Environment.h>
#include <fb/fbjni.h> #include <fb/fbjni.h>
#include <fb/fbjni/NativeRunnable.h>
using namespace facebook::jni; using namespace facebook::jni;
void initialize_fbjni() { void initialize_fbjni() {
CountableOnLoad(Environment::current()); CountableOnLoad(Environment::current());
HybridDataOnLoad(); HybridDataOnLoad();
JNativeRunnable::OnLoad();
ThreadScope::OnLoad();
} }

View File

@ -26,6 +26,7 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
std::call_once(flag, [vm] { std::call_once(flag, [vm] {
try { try {
Environment::initialize(vm); Environment::initialize(vm);
internal::initExceptionHelpers();
} catch (std::exception& ex) { } catch (std::exception& ex) {
error_occured = true; error_occured = true;
try { try {
@ -57,9 +58,6 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
alias_ref<JClass> findClassStatic(const char* name) { alias_ref<JClass> findClassStatic(const char* name) {
const auto env = internal::getEnv(); const auto env = internal::getEnv();
if (!env) {
throw std::runtime_error("Unable to retrieve JNIEnv*.");
}
auto cls = env->FindClass(name); auto cls = env->FindClass(name);
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
auto leaking_ref = (jclass)env->NewGlobalRef(cls); auto leaking_ref = (jclass)env->NewGlobalRef(cls);
@ -69,9 +67,6 @@ alias_ref<JClass> findClassStatic(const char* name) {
local_ref<JClass> findClassLocal(const char* name) { local_ref<JClass> findClassLocal(const char* name) {
const auto env = internal::getEnv(); const auto env = internal::getEnv();
if (!env) {
throw std::runtime_error("Unable to retrieve JNIEnv*.");
}
auto cls = env->FindClass(name); auto cls = env->FindClass(name);
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
return adopt_local(cls); return adopt_local(cls);