diff --git a/java/jni/YGJNI.cpp b/java/jni/YGJNI.cpp index 4e75d17c..6a1a790a 100644 --- a/java/jni/YGJNI.cpp +++ b/java/jni/YGJNI.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include #include #include #include diff --git a/java/jni/YGJTypes.h b/java/jni/YGJTypes.h index bd8e6c7b..dfc0901b 100644 --- a/java/jni/YGJTypes.h +++ b/java/jni/YGJTypes.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include #include #include #include diff --git a/lib/fb/BUCK b/lib/fb/BUCK index 37606023..d078c1b6 100644 --- a/lib/fb/BUCK +++ b/lib/fb/BUCK @@ -32,6 +32,7 @@ yoga_cxx_library( "-Wall", "-Werror", "-Wno-unused-parameter", + "-Wno-unused-variable", "-std=c++11", ], platforms = (ANDROID,), diff --git a/lib/fb/src/main/cpp/assert.cpp b/lib/fb/src/main/cpp/assert.cpp deleted file mode 100644 index be64e787..00000000 --- a/lib/fb/src/main/cpp/assert.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include - -#include -#include - -namespace facebook { - -#define ASSERT_BUF_SIZE 4096 -static char sAssertBuf[ASSERT_BUF_SIZE]; -static AssertHandler gAssertHandler; - -void assertInternal(const char* formatstr ...) { - va_list va_args; - va_start(va_args, formatstr); - vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args); - va_end(va_args); - if (gAssertHandler != NULL) { - gAssertHandler(sAssertBuf); - } - FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf); - // crash at this specific address so that we can find our crashes easier - *(int*)0xdeadb00c = 0; - // let the compiler know we won't reach the end of the function - __builtin_unreachable(); -} - -void setAssertHandler(AssertHandler assertHandler) { - gAssertHandler = assertHandler; -} - -} // namespace facebook diff --git a/lib/fb/src/main/cpp/include/fb/ALog.h b/lib/fb/src/main/cpp/include/fb/ALog.h deleted file mode 100644 index 6ea846ee..00000000 --- a/lib/fb/src/main/cpp/include/fb/ALog.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -/** @file ALog.h - * - * Very simple android only logging. Define LOG_TAG to enable the macros. - */ - -#pragma once - -#ifdef __ANDROID__ - -#include - -namespace facebook { -namespace alog { - -template -inline void log(int level, const char* tag, const char* msg, ARGS... args) noexcept { - __android_log_print(level, tag, msg, args...); -} - -template -inline void log(int level, const char* tag, const char* msg) noexcept { - __android_log_write(level, tag, msg); -} - -template -inline void logv(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_VERBOSE, tag, msg, args...); -} - -template -inline void logd(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_DEBUG, tag, msg, args...); -} - -template -inline void logi(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_INFO, tag, msg, args...); -} - -template -inline void logw(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_WARN, tag, msg, args...); -} - -template -inline void loge(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_ERROR, tag, msg, args...); -} - -template -inline void logf(const char* tag, const char* msg, ARGS... args) noexcept { - log(ANDROID_LOG_FATAL, tag, msg, args...); -} - - -#ifdef LOG_TAG -# define ALOGV(...) ::facebook::alog::logv(LOG_TAG, __VA_ARGS__) -# define ALOGD(...) ::facebook::alog::logd(LOG_TAG, __VA_ARGS__) -# define ALOGI(...) ::facebook::alog::logi(LOG_TAG, __VA_ARGS__) -# define ALOGW(...) ::facebook::alog::logw(LOG_TAG, __VA_ARGS__) -# define ALOGE(...) ::facebook::alog::loge(LOG_TAG, __VA_ARGS__) -# define ALOGF(...) ::facebook::alog::logf(LOG_TAG, __VA_ARGS__) -#endif - -}} - -#else -# define ALOGV(...) ((void)0) -# define ALOGD(...) ((void)0) -# define ALOGI(...) ((void)0) -# define ALOGW(...) ((void)0) -# define ALOGE(...) ((void)0) -# define ALOGF(...) ((void)0) -#endif diff --git a/lib/fb/src/main/cpp/include/fb/Countable.h b/lib/fb/src/main/cpp/include/fb/Countable.h deleted file mode 100644 index b355ab67..00000000 --- a/lib/fb/src/main/cpp/include/fb/Countable.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include -#include -#include -#include - -namespace facebook { - -class Countable : public noncopyable, public nonmovable { -public: - // RefPtr expects refcount to start at 0 - Countable() : m_refcount(0) {} - virtual ~Countable() - { - FBASSERT(m_refcount == 0); - } - -private: - void ref() { - ++m_refcount; - } - - void unref() { - if (0 == --m_refcount) { - delete this; - } - } - - bool hasOnlyOneRef() const { - return m_refcount == 1; - } - - template friend class RefPtr; - std::atomic m_refcount; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/ProgramLocation.h b/lib/fb/src/main/cpp/include/fb/ProgramLocation.h deleted file mode 100644 index 57c44c1d..00000000 --- a/lib/fb/src/main/cpp/include/fb/ProgramLocation.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include -#include - -namespace facebook { - -#define FROM_HERE facebook::ProgramLocation(__FUNCTION__, __FILE__, __LINE__) - -class ProgramLocation { -public: - ProgramLocation() : m_functionName("Unspecified"), m_fileName("Unspecified"), m_lineNumber(0) {} - - ProgramLocation(const char* functionName, const char* fileName, int line) : - m_functionName(functionName), - m_fileName(fileName), - m_lineNumber(line) - {} - - const char* functionName() const { return m_functionName; } - const char* fileName() const { return m_fileName; } - int lineNumber() const { return m_lineNumber; } - - std::string asFormattedString() const { - std::stringstream str; - str << "Function " << m_functionName << " in file " << m_fileName << ":" << m_lineNumber; - return str.str(); - } - - bool operator==(const ProgramLocation& other) const { - // Assumes that the strings are static - return (m_functionName == other.m_functionName) && (m_fileName == other.m_fileName) && m_lineNumber == other.m_lineNumber; - } - -private: - const char* m_functionName; - const char* m_fileName; - int m_lineNumber; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/RefPtr.h b/lib/fb/src/main/cpp/include/fb/RefPtr.h deleted file mode 100644 index 8c22a151..00000000 --- a/lib/fb/src/main/cpp/include/fb/RefPtr.h +++ /dev/null @@ -1,271 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include - -namespace facebook { - -// Reference counting smart pointer. This is designed to work with the -// Countable class or other implementations in the future. It is designed in a -// way to be both efficient and difficult to misuse. Typical usage is very -// simple once you learn the patterns (and the compiler will help!): -// -// By default, the internal pointer is null. -// RefPtr ref; -// -// Object creation requires explicit construction: -// RefPtr ref = createNew(...); -// -// Or if the constructor is not public: -// RefPtr ref = adoptRef(new Foo(...)); -// -// But you can implicitly create from nullptr: -// RefPtr maybeRef = cond ? ref : nullptr; -// -// Move/Copy Construction/Assignment are straightforward: -// RefPtr ref2 = ref; -// ref = std::move(ref2); -// -// Destruction automatically drops the RefPtr's reference as expected. -// -// Upcasting is implicit but downcasting requires an explicit cast: -// struct Bar : public Foo {}; -// RefPtr barRef = static_cast>(ref); -// ref = barRef; -// -template -class RefPtr { -public: - constexpr RefPtr() : - m_ptr(nullptr) - {} - - // Allow implicit construction from a pointer only from nullptr - constexpr RefPtr(std::nullptr_t ptr) : - m_ptr(nullptr) - {} - - RefPtr(const RefPtr& ref) : - m_ptr(ref.m_ptr) - { - refIfNecessary(m_ptr); - } - - // Only allow implicit upcasts. A downcast will result in a compile error - // unless you use static_cast (which will end up invoking the explicit - // operator below). - template - RefPtr(const RefPtr& ref, typename std::enable_if::value, U>::type* = nullptr) : - m_ptr(ref.get()) - { - refIfNecessary(m_ptr); - } - - RefPtr(RefPtr&& ref) : - m_ptr(nullptr) - { - *this = std::move(ref); - } - - // Only allow implicit upcasts. A downcast will result in a compile error - // unless you use static_cast (which will end up invoking the explicit - // operator below). - template - RefPtr(RefPtr&& ref, typename std::enable_if::value, U>::type* = nullptr) : - m_ptr(nullptr) - { - *this = std::move(ref); - } - - ~RefPtr() { - unrefIfNecessary(m_ptr); - m_ptr = nullptr; - } - - RefPtr& operator=(const RefPtr& ref) { - if (m_ptr != ref.m_ptr) { - unrefIfNecessary(m_ptr); - m_ptr = ref.m_ptr; - refIfNecessary(m_ptr); - } - return *this; - } - - // The STL assumes rvalue references are unique and for simplicity's sake, we - // make the same assumption here, that &ref != this. - RefPtr& operator=(RefPtr&& ref) { - unrefIfNecessary(m_ptr); - m_ptr = ref.m_ptr; - ref.m_ptr = nullptr; - return *this; - } - - template - RefPtr& operator=(RefPtr&& ref) { - unrefIfNecessary(m_ptr); - m_ptr = ref.m_ptr; - ref.m_ptr = nullptr; - return *this; - } - - void reset() { - unrefIfNecessary(m_ptr); - m_ptr = nullptr; - } - - T* get() const { - return m_ptr; - } - - T* operator->() const { - return m_ptr; - } - - T& operator*() const { - return *m_ptr; - } - - template - explicit operator RefPtr () const; - - explicit operator bool() const { - return m_ptr ? true : false; - } - - bool isTheLastRef() const { - FBASSERT(m_ptr); - return m_ptr->hasOnlyOneRef(); - } - - // Creates a strong reference from a raw pointer, assuming that is already - // referenced from some other RefPtr. This should be used sparingly. - static inline RefPtr assumeAlreadyReffed(T* ptr) { - return RefPtr(ptr, ConstructionMode::External); - } - - // Creates a strong reference from a raw pointer, assuming that it points to a - // freshly-created object. See the documentation for RefPtr for usage. - static inline RefPtr adoptRef(T* ptr) { - return RefPtr(ptr, ConstructionMode::Adopted); - } - -private: - enum class ConstructionMode { - Adopted, - External - }; - - RefPtr(T* ptr, ConstructionMode mode) : - m_ptr(ptr) - { - FBASSERTMSGF(ptr, "Got null pointer in %s construction mode", mode == ConstructionMode::Adopted ? "adopted" : "external"); - ptr->ref(); - if (mode == ConstructionMode::Adopted) { - FBASSERT(ptr->hasOnlyOneRef()); - } - } - - static inline void refIfNecessary(T* ptr) { - if (ptr) { - ptr->ref(); - } - } - static inline void unrefIfNecessary(T* ptr) { - if (ptr) { - ptr->unref(); - } - } - - template friend class RefPtr; - - T* m_ptr; -}; - -// Creates a strong reference from a raw pointer, assuming that is already -// referenced from some other RefPtr and that it is non-null. This should be -// used sparingly. -template -static inline RefPtr assumeAlreadyReffed(T* ptr) { - return RefPtr::assumeAlreadyReffed(ptr); -} - -// As above, but tolerant of nullptr. -template -static inline RefPtr assumeAlreadyReffedOrNull(T* ptr) { - return ptr ? RefPtr::assumeAlreadyReffed(ptr) : nullptr; -} - -// Creates a strong reference from a raw pointer, assuming that it points to a -// freshly-created object. See the documentation for RefPtr for usage. -template -static inline RefPtr adoptRef(T* ptr) { - return RefPtr::adoptRef(ptr); -} - -template -static inline RefPtr createNew(Args&&... arguments) { - return RefPtr::adoptRef(new T(std::forward(arguments)...)); -} - -template template -RefPtr::operator RefPtr() const { - static_assert(std::is_base_of::value, "Invalid static cast"); - return assumeAlreadyReffedOrNull(static_cast(m_ptr)); -} - -template -inline bool operator==(const RefPtr& a, const RefPtr& b) { - return a.get() == b.get(); -} - -template -inline bool operator!=(const RefPtr& a, const RefPtr& b) { - return a.get() != b.get(); -} - -template -inline bool operator==(const RefPtr& ref, U* ptr) { - return ref.get() == ptr; -} - -template -inline bool operator!=(const RefPtr& ref, U* ptr) { - return ref.get() != ptr; -} - -template -inline bool operator==(U* ptr, const RefPtr& ref) { - return ref.get() == ptr; -} - -template -inline bool operator!=(U* ptr, const RefPtr& ref) { - return ref.get() != ptr; -} - -template -inline bool operator==(const RefPtr& ref, std::nullptr_t ptr) { - return ref.get() == ptr; -} - -template -inline bool operator!=(const RefPtr& ref, std::nullptr_t ptr) { - return ref.get() != ptr; -} - -template -inline bool operator==(std::nullptr_t ptr, const RefPtr& ref) { - return ref.get() == ptr; -} - -template -inline bool operator!=(std::nullptr_t ptr, const RefPtr& ref) { - return ref.get() != ptr; -} - -} diff --git a/lib/fb/src/main/cpp/include/fb/StaticInitialized.h b/lib/fb/src/main/cpp/include/fb/StaticInitialized.h deleted file mode 100644 index c8b84dea..00000000 --- a/lib/fb/src/main/cpp/include/fb/StaticInitialized.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include - -namespace facebook { - -// Class that lets you declare a global but does not add a static constructor -// to the binary. Eventually I'd like to have this auto-initialize in a -// multithreaded environment but for now it's easiest just to use manual -// initialization. -template -class StaticInitialized { -public: - constexpr StaticInitialized() : - m_instance(nullptr) - {} - - template - void initialize(Args&&... arguments) { - FBASSERT(!m_instance); - m_instance = new T(std::forward(arguments)...); - } - - T* operator->() const { - return m_instance; - } -private: - T* m_instance; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/ThreadLocal.h b/lib/fb/src/main/cpp/include/fb/ThreadLocal.h deleted file mode 100644 index 4ea9e6ca..00000000 --- a/lib/fb/src/main/cpp/include/fb/ThreadLocal.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include -#include - -#include - -namespace facebook { - -/////////////////////////////////////////////////////////////////////////////// - -/** - * A thread-local object is a "global" object within a thread. This is useful - * for writing apartment-threaded code, where nothing is actullay shared - * between different threads (hence no locking) but those variables are not - * on stack in local scope. To use it, just do something like this, - * - * ThreadLocal static_object; - * static_object->data_ = ...; - * static_object->doSomething(); - * - * ThreadLocal static_number; - * int value = *static_number; - * - * So, syntax-wise it's similar to pointers. T can be primitive types, and if - * it's a class, there has to be a default constructor. - */ -template -class ThreadLocal { -public: - /** - * Constructor that has to be called from a thread-neutral place. - */ - ThreadLocal() : - m_key(0), - m_cleanup(OnThreadExit) { - initialize(); - } - - /** - * As above but with a custom cleanup function - */ - typedef void (*CleanupFunction)(void* obj); - explicit ThreadLocal(CleanupFunction cleanup) : - m_key(0), - m_cleanup(cleanup) { - FBASSERT(cleanup); - initialize(); - } - - /** - * Access object's member or method through this operator overload. - */ - T *operator->() const { - return get(); - } - - T &operator*() const { - return *get(); - } - - T *get() const { - return (T*)pthread_getspecific(m_key); - } - - T* release() { - T* obj = get(); - pthread_setspecific(m_key, NULL); - return obj; - } - - void reset(T* other = NULL) { - T* old = (T*)pthread_getspecific(m_key); - if (old != other) { - FBASSERT(m_cleanup); - m_cleanup(old); - pthread_setspecific(m_key, other); - } - } - -private: - void initialize() { - int ret = pthread_key_create(&m_key, m_cleanup); - if (ret != 0) { - const char *msg = "(unknown error)"; - switch (ret) { - case EAGAIN: - msg = "PTHREAD_KEYS_MAX (1024) is exceeded"; - break; - case ENOMEM: - msg = "Out-of-memory"; - break; - } - (void) msg; - FBASSERTMSGF(0, "pthread_key_create failed: %d %s", ret, msg); - } - } - - static void OnThreadExit(void *obj) { - if (NULL != obj) { - delete (T*)obj; - } - } - - pthread_key_t m_key; - CleanupFunction m_cleanup; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/assert.h b/lib/fb/src/main/cpp/include/fb/assert.h deleted file mode 100644 index f6dce42b..00000000 --- a/lib/fb/src/main/cpp/include/fb/assert.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#ifndef FBASSERT_H -#define FBASSERT_H - -#include - -namespace facebook { -#define ENABLE_FBASSERT 1 - -#if ENABLE_FBASSERT -#define FBASSERTMSGF(expr, msg, ...) !(expr) ? facebook::assertInternal("Assert (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) : (void) 0 -#else -#define FBASSERTMSGF(expr, msg, ...) -#endif // ENABLE_FBASSERT - -#define FBASSERT(expr) FBASSERTMSGF(expr, "%s", #expr) - -#define FBCRASH(msg, ...) facebook::assertInternal("Fatal error (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) -#define FBUNREACHABLE() facebook::assertInternal("This code should be unreachable (%s:%d)", __FILE__, __LINE__) - -FBEXPORT void assertInternal(const char* formatstr, ...) __attribute__((noreturn)); - -// This allows storing the assert message before the current process terminates due to a crash -typedef void (*AssertHandler)(const char* message); -void setAssertHandler(AssertHandler assertHandler); - -} // namespace facebook -#endif // FBASSERT_H diff --git a/lib/fb/src/main/cpp/include/fb/fbjni.h b/lib/fb/src/main/cpp/include/fb/fbjni.h deleted file mode 100644 index 9f0da1ab..00000000 --- a/lib/fb/src/main/cpp/include/fb/fbjni.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/JThread.h b/lib/fb/src/main/cpp/include/fb/fbjni/JThread.h deleted file mode 100644 index 7ef4b63b..00000000 --- a/lib/fb/src/main/cpp/include/fb/fbjni/JThread.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include "CoreClasses.h" -#include "NativeRunnable.h" - -namespace facebook { -namespace jni { - -class JThread : public JavaClass { - public: - static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;"; - - void start() { - static auto method = javaClassStatic()->getMethod("start"); - method(self()); - } - - void join() { - static auto method = javaClassStatic()->getMethod("join"); - method(self()); - } - - static local_ref create(std::function&& runnable) { - auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable)); - return newInstance(static_ref_cast(jrunnable)); - } -}; - -} -} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Registration-inl.h b/lib/fb/src/main/cpp/include/fb/fbjni/Registration-inl.h deleted file mode 100644 index 030baace..00000000 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Registration-inl.h +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include "Exceptions.h" -#include "Hybrid.h" - -namespace facebook { -namespace jni { - -namespace detail { - -#ifdef __i386__ -// X86 ABI forces 16 byte stack allignment on calls. Unfortunately -// sometimes Dalvik chooses not to obey the ABI: -// - https://code.google.com/p/android/issues/detail?id=61012 -// - https://android.googlesource.com/platform/ndk/+/81696d2%5E!/ -// Therefore, we tell the compiler to re-align the stack on entry -// to our JNI functions. -#define JNI_ENTRY_POINT __attribute__((force_align_arg_pointer)) -#else -#define JNI_ENTRY_POINT -#endif - -// registration wrapper for legacy JNI-style functions - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(JNIEnv*, C, Args... args)) { - struct funcWrapper { - JNI_ENTRY_POINT static void call(JNIEnv* env, jobject obj, Args... args) { - // Note that if func was declared noexcept, then both gcc and clang are smart - // enough to elide the try/catch. - try { - (*func)(env, static_cast(obj), args...); - } catch (...) { - translatePendingCppExceptionToJavaException(); - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) { - struct funcWrapper { - JNI_ENTRY_POINT static R call(JNIEnv* env, jobject obj, Args... args) { - try { - return (*func)(env, static_cast>(obj), args...); - } catch (...) { - translatePendingCppExceptionToJavaException(); - return R{}; - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -// registration wrappers for functions, with autoconversion of arguments. - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(alias_ref, Args... args)) { - struct funcWrapper { - JNI_ENTRY_POINT static void call(JNIEnv*, jobject obj, - typename Convert::type>::jniType... args) { - try { - (*func)(static_cast>(obj), Convert::type>::fromJni(args)...); - } catch (...) { - translatePendingCppExceptionToJavaException(); - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref, Args... args)) { - struct funcWrapper { - - JNI_ENTRY_POINT static typename Convert::type>::jniType call(JNIEnv*, jobject obj, - typename Convert::type>::jniType... args) { - try { - return Convert::type>::toJniRet( - (*func)(static_cast>(obj), Convert::type>::fromJni(args)...)); - } catch (...) { - using jniRet = typename Convert::type>::jniType; - translatePendingCppExceptionToJavaException(); - return jniRet{}; - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -// registration wrappers for non-static methods, with autoconvertion of arguments. - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod( - void (C::*method0)(Args... args)) { - (void)method0; - struct funcWrapper { - JNI_ENTRY_POINT static void call(JNIEnv* env, jobject obj, - typename Convert::type>::jniType... args) { - try { - try { - auto aref = wrap_alias(static_cast(obj)); - // This is usually a noop, but if the hybrid object is a - // base class of other classes which register JNI methods, - // this will get the right type for the registered method. - auto cobj = static_cast(facebook::jni::cthis(aref)); - (cobj->*method)(Convert::type>::fromJni(args)...); - } catch (const std::exception& ex) { - C::mapException(ex); - throw; - } - } catch (...) { - translatePendingCppExceptionToJavaException(); - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -template -inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) { - struct funcWrapper { - - JNI_ENTRY_POINT static typename Convert::type>::jniType call(JNIEnv* env, jobject obj, - typename Convert::type>::jniType... args) { - try { - try { - auto aref = wrap_alias(static_cast(obj)); - // This is usually a noop, but if the hybrid object is a - // base class of other classes which register JNI methods, - // this will get the right type for the registered method. - auto cobj = static_cast(facebook::jni::cthis(aref)); - return Convert::type>::toJniRet( - (cobj->*method)(Convert::type>::fromJni(args)...)); - } catch (const std::exception& ex) { - C::mapException(ex); - throw; - } - } catch (...) { - using jniRet = typename Convert::type>::jniType; - translatePendingCppExceptionToJavaException(); - return jniRet{}; - } - } - }; - - // This intentionally erases the real type; JNI will do it anyway - return reinterpret_cast(&(funcWrapper::call)); -} - -template -inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) { - return jmethod_traits::descriptor(); -} - -template -inline std::string makeDescriptor(R (*)(alias_ref, Args... args)) { - return jmethod_traits_from_cxx::descriptor(); -} - -template -inline std::string makeDescriptor(R (C::*)(Args... args)) { - return jmethod_traits_from_cxx::descriptor(); -} - -template -template -JNI_ENTRY_POINT R CriticalMethod::call( - alias_ref, - Args... args) noexcept { - static_assert( - IsJniPrimitive() || std::is_void(), - "Critical Native Methods may only return primitive JNI types, or void."); - static_assert( - AreJniPrimitives(), - "Critical Native Methods may only use primitive JNI types as parameters"); - - return func(std::forward(args)...); -} - -template -template -inline std::string CriticalMethod::desc() { - return makeDescriptor(call); -} - -} - -}} diff --git a/lib/fb/src/main/cpp/include/fb/log.h b/lib/fb/src/main/cpp/include/fb/log.h deleted file mode 100644 index e26e716b..00000000 --- a/lib/fb/src/main/cpp/include/fb/log.h +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * FB Wrapper for logging functions. - * - * The android logging API uses the macro "LOG()" for its logic, which means - * that it conflicts with random other places that use LOG for their own - * purposes and doesn't work right half the places you include it - * - * FBLOG uses exactly the same semantics (FBLOGD for debug etc) but because of - * the FB prefix it's strictly better. FBLOGV also gets stripped out based on - * whether NDEBUG is set, but can be overridden by FBLOG_NDEBUG - * - * Most of the rest is a copy of with minor changes. - */ - -// -// C/C++ logging functions. See the logging documentation for API details. -// -// We'd like these to be available from C code (in case we import some from -// somewhere), so this has a C interface. -// -// The output will be correct when the log file is shared between multiple -// threads and/or multiple processes so long as the operating system -// supports O_APPEND. These calls have mutex-protected data structures -// and so are NOT reentrant. Do not use LOG in a signal handler. -// -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef ANDROID -#include -#else -// These declarations are needed for our internal use even on non-Android -// builds. -// (they are borrowed from ) - -/* - * Android log priority values, in ascending priority order. - */ -typedef enum android_LogPriority { - ANDROID_LOG_UNKNOWN = 0, - ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ - ANDROID_LOG_VERBOSE, - ANDROID_LOG_DEBUG, - ANDROID_LOG_INFO, - ANDROID_LOG_WARN, - ANDROID_LOG_ERROR, - ANDROID_LOG_FATAL, - ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ -} android_LogPriority; - -/* - * Send a simple string to the log. - */ -int __android_log_write(int prio, const char *tag, const char *text); - -/* - * Send a formatted string to the log, used like printf(fmt,...) - */ -int __android_log_print(int prio, const char *tag, const char *fmt, ...) -#if defined(__GNUC__) - __attribute__((format(printf, 3, 4))) -#endif - ; - -#endif - -// --------------------------------------------------------------------- - -/* - * Normally we strip FBLOGV (VERBOSE messages) from release builds. - * You can modify this (for example with "#define FBLOG_NDEBUG 0" - * at the top of your source file) to change that behavior. - */ -#ifndef FBLOG_NDEBUG -#ifdef NDEBUG -#define FBLOG_NDEBUG 1 -#else -#define FBLOG_NDEBUG 0 -#endif -#endif - -/* - * This is the local tag used for the following simplified - * logging macros. You can change this preprocessor definition - * before using the other macros to change the tag. - */ -#ifndef LOG_TAG -#define LOG_TAG NULL -#endif - -// --------------------------------------------------------------------- - -/* - * Simplified macro to send a verbose log message using the current LOG_TAG. - */ -#ifndef FBLOGV -#if FBLOG_NDEBUG -#define FBLOGV(...) ((void)0) -#else -#define FBLOGV(...) ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) -#endif -#endif - -#define CONDITION(cond) (__builtin_expect((cond) != 0, 0)) - -#ifndef FBLOGV_IF -#if FBLOG_NDEBUG -#define FBLOGV_IF(cond, ...) ((void)0) -#else -#define FBLOGV_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ - : (void)0) -#endif -#endif - -/* - * Simplified macro to send a debug log message using the current LOG_TAG. - */ -#ifndef FBLOGD -#define FBLOGD(...) ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef FBLOGD_IF -#define FBLOGD_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) : (void)0) -#endif - -/* - * Simplified macro to send an info log message using the current LOG_TAG. - */ -#ifndef FBLOGI -#define FBLOGI(...) ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef FBLOGI_IF -#define FBLOGI_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) : (void)0) -#endif - -/* - * Simplified macro to send a warning log message using the current LOG_TAG. - */ -#ifndef FBLOGW -#define FBLOGW(...) ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef FBLOGW_IF -#define FBLOGW_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) : (void)0) -#endif - -/* - * Simplified macro to send an error log message using the current LOG_TAG. - */ -#ifndef FBLOGE -#define FBLOGE(...) ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef FBLOGE_IF -#define FBLOGE_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) : (void)0) -#endif - -// --------------------------------------------------------------------- - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * verbose priority. - */ -#ifndef IF_FBLOGV -#if FBLOG_NDEBUG -#define IF_FBLOGV() if (false) -#else -#define IF_FBLOGV() IF_FBLOG(LOG_VERBOSE, LOG_TAG) -#endif -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * debug priority. - */ -#ifndef IF_FBLOGD -#define IF_FBLOGD() IF_FBLOG(LOG_DEBUG, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * info priority. - */ -#ifndef IF_FBLOGI -#define IF_FBLOGI() IF_FBLOG(LOG_INFO, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * warn priority. - */ -#ifndef IF_FBLOGW -#define IF_FBLOGW() IF_FBLOG(LOG_WARN, LOG_TAG) -#endif - -/* - * Conditional based on whether the current LOG_TAG is enabled at - * error priority. - */ -#ifndef IF_FBLOGE -#define IF_FBLOGE() IF_FBLOG(LOG_ERROR, LOG_TAG) -#endif - -// --------------------------------------------------------------------- - -/* - * Log a fatal error. If the given condition fails, this stops program - * execution like a normal assertion, but also generating the given message. - * It is NOT stripped from release builds. Note that the condition test - * is -inverted- from the normal assert() semantics. - */ -#define FBLOG_ALWAYS_FATAL_IF(cond, ...) \ - ((CONDITION(cond)) ? ((void)fb_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \ - : (void)0) - -#define FBLOG_ALWAYS_FATAL(...) \ - (((void)fb_printAssert(NULL, LOG_TAG, __VA_ARGS__))) - -/* - * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that - * are stripped out of release builds. - */ -#if FBLOG_NDEBUG - -#define FBLOG_FATAL_IF(cond, ...) ((void)0) -#define FBLOG_FATAL(...) ((void)0) - -#else - -#define FBLOG_FATAL_IF(cond, ...) FBLOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) -#define FBLOG_FATAL(...) FBLOG_ALWAYS_FATAL(__VA_ARGS__) - -#endif - -/* - * Assertion that generates a log message when the assertion fails. - * Stripped out of release builds. Uses the current LOG_TAG. - */ -#define FBLOG_ASSERT(cond, ...) FBLOG_FATAL_IF(!(cond), __VA_ARGS__) -//#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) - -// --------------------------------------------------------------------- - -/* - * Basic log message macro. - * - * Example: - * FBLOG(LOG_WARN, NULL, "Failed with error %d", errno); - * - * The second argument may be NULL or "" to indicate the "global" tag. - */ -#ifndef FBLOG -#define FBLOG(priority, tag, ...) \ - FBLOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) -#endif - -#ifndef FBLOG_BY_DELIMS -#define FBLOG_BY_DELIMS(priority, tag, delims, msg, ...) \ - logPrintByDelims(ANDROID_##priority, tag, delims, msg, ##__VA_ARGS__) -#endif - -/* - * Log macro that allows you to specify a number for the priority. - */ -#ifndef FBLOG_PRI -#define FBLOG_PRI(priority, tag, ...) fb_printLog(priority, tag, __VA_ARGS__) -#endif - -/* - * Log macro that allows you to pass in a varargs ("args" is a va_list). - */ -#ifndef FBLOG_PRI_VA -#define FBLOG_PRI_VA(priority, tag, fmt, args) \ - fb_vprintLog(priority, NULL, tag, fmt, args) -#endif - -/* - * Conditional given a desired logging priority and tag. - */ -#ifndef IF_FBLOG -#define IF_FBLOG(priority, tag) if (fb_testLog(ANDROID_##priority, tag)) -#endif - -typedef void (*LogHandler)(int priority, const char* tag, const char* message); -FBEXPORT void setLogHandler(LogHandler logHandler); - -/* - * =========================================================================== - * - * The stuff in the rest of this file should not be used directly. - */ -FBEXPORT int fb_printLog(int prio, const char* tag, const char* fmt, ...) -#if defined(__GNUC__) - __attribute__((format(printf, 3, 4))) -#endif - ; - -#define fb_vprintLog(prio, cond, tag, fmt...) \ - __android_log_vprint(prio, tag, fmt) - -#define fb_printAssert(cond, tag, fmt...) __android_log_assert(cond, tag, fmt) - -#define fb_writeLog(prio, tag, text) __android_log_write(prio, tag, text) - -#define fb_bWriteLog(tag, payload, len) __android_log_bwrite(tag, payload, len) -#define fb_btWriteLog(tag, type, payload, len) \ - __android_log_btwrite(tag, type, payload, len) - -#define fb_testLog(prio, tag) (1) - -/* - * FB extensions - */ -void logPrintByDelims(int priority, const char* tag, const char* delims, - const char* msg, ...); - -#ifdef __cplusplus -} -#endif diff --git a/lib/fb/src/main/cpp/include/fb/noncopyable.h b/lib/fb/src/main/cpp/include/fb/noncopyable.h deleted file mode 100644 index ec00ada5..00000000 --- a/lib/fb/src/main/cpp/include/fb/noncopyable.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -namespace facebook { - -struct noncopyable { - noncopyable(const noncopyable&) = delete; - noncopyable& operator=(const noncopyable&) = delete; -protected: - noncopyable() = default; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/nonmovable.h b/lib/fb/src/main/cpp/include/fb/nonmovable.h deleted file mode 100644 index 9b39b8c6..00000000 --- a/lib/fb/src/main/cpp/include/fb/nonmovable.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -namespace facebook { - -struct nonmovable { - nonmovable(nonmovable&&) = delete; - nonmovable& operator=(nonmovable&&) = delete; -protected: - nonmovable() = default; -}; - -} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/ByteBuffer.h b/lib/fb/src/main/cpp/include/fbjni/ByteBuffer.h similarity index 51% rename from lib/fb/src/main/cpp/include/fb/fbjni/ByteBuffer.h rename to lib/fb/src/main/cpp/include/fbjni/ByteBuffer.h index 41c7acc3..55223e46 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/ByteBuffer.h +++ b/lib/fb/src/main/cpp/include/fbjni/ByteBuffer.h @@ -6,26 +6,37 @@ */ #pragma once -#include - -#include "CoreClasses.h" -#include "References-forward.h" +#include namespace facebook { namespace jni { +class JBuffer : public JavaClass { +public: + static constexpr const char* kJavaDescriptor = "Ljava/nio/Buffer;"; + + void rewind() const; + bool isDirect() const; + void* getDirectAddress() const; + size_t getDirectCapacity() const; +}; + // JNI's NIO support has some awkward preconditions and error reporting. This // class provides much more user-friendly access. -class FBEXPORT JByteBuffer : public JavaClass { +class JByteBuffer : public JavaClass { public: static constexpr const char* kJavaDescriptor = "Ljava/nio/ByteBuffer;"; static local_ref wrapBytes(uint8_t* data, size_t size); + static local_ref allocateDirect(jint size); - bool isDirect() const; + uint8_t* getDirectBytes() const { + return static_cast(getDirectAddress()); + } - uint8_t* getDirectBytes() const; - size_t getDirectSize() const; + size_t getDirectSize() const { + return getDirectCapacity(); + } }; }} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Context.h b/lib/fb/src/main/cpp/include/fbjni/Context.h similarity index 72% rename from lib/fb/src/main/cpp/include/fb/fbjni/Context.h rename to lib/fb/src/main/cpp/include/fbjni/Context.h index cf5bcb2d..331de691 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Context.h +++ b/lib/fb/src/main/cpp/include/fbjni/Context.h @@ -6,8 +6,8 @@ */ #pragma once -#include "CoreClasses.h" -#include "File.h" +#include +#include namespace facebook { namespace jni { @@ -18,12 +18,12 @@ class AContext : public JavaClass { // Define a method that calls into the represented Java class local_ref getCacheDir() { - static auto method = getClass()->getMethod("getCacheDir"); + static const auto method = getClass()->getMethod("getCacheDir"); return method(self()); } local_ref getFilesDir() { - static auto method = getClass()->getMethod("getFilesDir"); + static const auto method = getClass()->getMethod("getFilesDir"); return method(self()); } }; diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/File.h b/lib/fb/src/main/cpp/include/fbjni/File.h similarity index 82% rename from lib/fb/src/main/cpp/include/fb/fbjni/File.h rename to lib/fb/src/main/cpp/include/fbjni/File.h index 857826cc..852202ba 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/File.h +++ b/lib/fb/src/main/cpp/include/fbjni/File.h @@ -6,7 +6,7 @@ */ #pragma once -#include "CoreClasses.h" +#include namespace facebook { namespace jni { @@ -17,7 +17,7 @@ class JFile : public JavaClass { // Define a method that calls into the represented Java class std::string getAbsolutePath() { - static auto method = getClass()->getMethod("getAbsolutePath"); + static const auto method = getClass()->getMethod("getAbsolutePath"); return method(self())->toStdString(); } diff --git a/lib/fb/src/main/cpp/include/fbjni/JThread.h b/lib/fb/src/main/cpp/include/fbjni/JThread.h new file mode 100644 index 00000000..a343e022 --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/JThread.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include +#include + +namespace facebook { +namespace jni { + +class JThread : public JavaClass { + public: + static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;"; + + void start() { + static const auto method = javaClassStatic()->getMethod("start"); + method(self()); + } + + void join() { + static const auto method = javaClassStatic()->getMethod("join"); + method(self()); + } + + static local_ref create(std::function&& runnable) { + auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable)); + return newInstance(static_ref_cast(jrunnable)); + } + + static local_ref create(std::function&& runnable, std::string&& name) { + auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable)); + return newInstance(static_ref_cast(jrunnable), make_jstring(std::move(name))); + } + + static local_ref getCurrent() { + static const auto method = javaClassStatic()->getStaticMethod()>("currentThread"); + return method(javaClassStatic()); + } + + int getPriority() { + static const auto method = getClass()->getMethod("getPriority"); + return method(self()); + } + + void setPriority(int priority) { + static const auto method = getClass()->getMethod("setPriority"); + method(self(), priority); + } +}; + +} +} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/NativeRunnable.h b/lib/fb/src/main/cpp/include/fbjni/NativeRunnable.h similarity index 92% rename from lib/fb/src/main/cpp/include/fb/fbjni/NativeRunnable.h rename to lib/fb/src/main/cpp/include/fbjni/NativeRunnable.h index f459ebf6..7bb915f4 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/NativeRunnable.h +++ b/lib/fb/src/main/cpp/include/fbjni/NativeRunnable.h @@ -6,9 +6,7 @@ */ #pragma once -#include "CoreClasses.h" -#include "Hybrid.h" -#include "Registration.h" +#include #include diff --git a/lib/fb/src/main/cpp/include/fbjni/ReadableByteChannel.h b/lib/fb/src/main/cpp/include/fbjni/ReadableByteChannel.h new file mode 100644 index 00000000..f524d26c --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/ReadableByteChannel.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include +#include + +namespace facebook { +namespace jni { + +class JReadableByteChannel : public JavaClass { +public: + static constexpr const char* kJavaDescriptor = "Ljava/nio/channels/ReadableByteChannel;"; + + int read(alias_ref dest) const; +}; + +}} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Boxed.h b/lib/fb/src/main/cpp/include/fbjni/detail/Boxed.h similarity index 80% rename from lib/fb/src/main/cpp/include/fb/fbjni/Boxed.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Boxed.h index 36ab9a31..e231b958 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Boxed.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Boxed.h @@ -17,13 +17,13 @@ struct JPrimitive : JavaClass { using typename JavaClass::javaobject; using JavaClass::javaClassStatic; static local_ref valueOf(jprim val) { - static auto cls = javaClassStatic(); - static auto method = + static const auto cls = javaClassStatic(); + static const auto method = cls->template getStaticMethod("valueOf"); return method(cls, val); } jprim value() const { - static auto method = + static const auto method = javaClassStatic()->template getMethod(T::kValueMethod); return method(this->self()); } @@ -55,9 +55,20 @@ DEFINE_BOXED_PRIMITIVE(double, Double) #undef DEFINE_BOXED_PRIMITIVE +template +inline typename std::enable_if< + (std::is_same::value || std::is_same::value) && !std::is_same::value, + local_ref +>::type autobox(T val) { + return JLong::valueOf(val); +} + +struct JVoid : public jni::JavaClass { + static auto constexpr kJavaDescriptor = "Ljava/lang/Void;"; +}; + inline local_ref autobox(alias_ref val) { return make_local(val); } }} - diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Common.h b/lib/fb/src/main/cpp/include/fbjni/detail/Common.h similarity index 78% rename from lib/fb/src/main/cpp/include/fb/fbjni/Common.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Common.h index 6025f6d6..573fcc75 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Common.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Common.h @@ -15,9 +15,6 @@ #include -#include -#include - #ifdef FBJNI_DEBUG_REFS # ifdef __ANDROID__ # include @@ -42,11 +39,11 @@ namespace facebook { namespace jni { -FBEXPORT void throwPendingJniExceptionAsCppException(); -FBEXPORT void throwCppExceptionIf(bool condition); +void throwPendingJniExceptionAsCppException(); +void throwCppExceptionIf(bool condition); -[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable); -[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg); +[[noreturn]] void throwNewJavaException(jthrowable); +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* msg); template [[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args); @@ -65,20 +62,10 @@ template * unhelpful way (typically a segfault) while trying to handle an exception * which occurs later. */ -FBEXPORT jint initialize(JavaVM*, std::function&&) noexcept; +jint initialize(JavaVM*, std::function&&) noexcept; namespace internal { -/** - * Retrieve a pointer the JNI environment of the current thread. - * - * @pre The current thread must be attached to the VM - */ -inline JNIEnv* getEnv() noexcept { - // TODO(T6594868) Benchmark against raw JNI access - return Environment::current(); -} - // Define to get extremely verbose logging of references and to enable reference stats #ifdef FBJNI_DEBUG_REFS template diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses-inl.h similarity index 87% rename from lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses-inl.h rename to lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses-inl.h index e5c86069..d8214fde 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses-inl.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses-inl.h @@ -21,15 +21,15 @@ namespace jni { // jobject ///////////////////////////////////////////////////////////////////////////////////////// inline bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept { - return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE; + return Environment::current()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE; } inline local_ref JObject::getClass() const noexcept { - return adopt_local(internal::getEnv()->GetObjectClass(self())); + return adopt_local(Environment::current()->GetObjectClass(self())); } inline bool JObject::isInstanceOf(alias_ref cls) const noexcept { - return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE; + return Environment::current()->IsInstanceOf(self(), cls.get()) != JNI_FALSE; } template @@ -47,8 +47,13 @@ inline void JObject::setFieldValue(JField field, T value) noexcept { field.set(self(), value); } +template +inline void JObject::setFieldValue(JField field, alias_ref value) noexcept { + setFieldValue(field, value.get()); +} + inline std::string JObject::toString() const { - static auto method = findClassLocal("java/lang/Object")->getMethod("toString"); + static const auto method = findClassLocal("java/lang/Object")->getMethod("toString"); return method(self())->toStdString(); } @@ -77,13 +82,13 @@ MonitorLock::MonitorLock() noexcept : owned_(nullptr) {} MonitorLock::MonitorLock(alias_ref object) noexcept : owned_(object) { - internal::getEnv()->MonitorEnter(object.get()); + Environment::current()->MonitorEnter(object.get()); } void MonitorLock::reset() noexcept { if (owned_) { - internal::getEnv()->MonitorExit(owned_.get()); - if (internal::getEnv()->ExceptionCheck()) { + Environment::current()->MonitorExit(owned_.get()); + if (Environment::current()->ExceptionCheck()) { abort(); // Lock mismatch } owned_ = nullptr; @@ -126,7 +131,7 @@ namespace detail { template static local_ref newInstance(Args... args) { static auto cls = JC::javaClassStatic(); - static auto constructor = cls->template getConstructor(); + static const auto constructor = cls->template getConstructor(); return cls->newObject(constructor, args...); } } @@ -154,17 +159,18 @@ struct NativeMethod { }; inline local_ref JClass::getSuperclass() const noexcept { - return adopt_local(internal::getEnv()->GetSuperclass(self())); + return adopt_local(Environment::current()->GetSuperclass(self())); } inline void JClass::registerNatives(std::initializer_list methods) { - const auto env = internal::getEnv(); + const auto env = Environment::current(); JNINativeMethod jnimethods[methods.size()]; size_t i = 0; for (auto it = methods.begin(); it < methods.end(); ++it, ++i) { - jnimethods[i].name = it->name; - jnimethods[i].signature = it->descriptor.c_str(); + // The JNI struct members are unnecessarily non-const. + jnimethods[i].name = const_cast(it->name); + jnimethods[i].signature = const_cast(it->descriptor.c_str()); jnimethods[i].fnPtr = reinterpret_cast(it->wrapper); } @@ -173,8 +179,13 @@ inline void JClass::registerNatives(std::initializer_list methods) } inline bool JClass::isAssignableFrom(alias_ref other) const noexcept { - const auto env = internal::getEnv(); - const auto result = env->IsAssignableFrom(self(), other.get()); + const auto env = Environment::current(); + // Ths method has behavior compatible with the + // java.lang.Class#isAssignableFrom method. The order of the + // arguments to the JNI IsAssignableFrom C function is "opposite" + // from what some might expect, which makes this code look a little + // odd, but it is correct. + const auto result = env->IsAssignableFrom(other.get(), self()); return result; } @@ -198,7 +209,7 @@ template inline JMethod JClass::getMethod( const char* name, const char* descriptor) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); const auto method = env->GetMethodID(self(), name, descriptor); FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); return JMethod{method}; @@ -213,7 +224,7 @@ template inline JStaticMethod JClass::getStaticMethod( const char* name, const char* descriptor) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); const auto method = env->GetStaticMethodID(self(), name, descriptor); FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); return JStaticMethod{method}; @@ -228,7 +239,7 @@ template inline JNonvirtualMethod JClass::getNonvirtualMethod( const char* name, const char* descriptor) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); const auto method = env->GetMethodID(self(), name, descriptor); FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); return JNonvirtualMethod{method}; @@ -244,7 +255,7 @@ template inline JField(), T>> JClass::getField( const char* name, const char* descriptor) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); auto field = env->GetFieldID(self(), name, descriptor); FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); return JField{field}; @@ -260,7 +271,7 @@ template inline JStaticField(), T>> JClass::getStaticField( const char* name, const char* descriptor) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); auto field = env->GetStaticFieldID(self(), name, descriptor); FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); return JStaticField{field}; @@ -281,11 +292,16 @@ inline void JClass::setStaticFieldValue(JStaticField field, T value) noexcept field.set(self(), value); } +template +inline void JClass::setStaticFieldValue(JStaticField field, alias_ref value) noexcept { + setStaticFieldValue(field, value.get()); +} + template inline local_ref JClass::newObject( JConstructor constructor, Args... args) const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); auto object = env->NewObject(self(), constructor.getId(), detail::callToJni( detail::Convert::type>::toCall(args))...); @@ -338,18 +354,11 @@ struct Convert { }; } -// jthrowable ////////////////////////////////////////////////////////////////////////////////////// - -inline local_ref JThrowable::initCause(alias_ref cause) { - static auto meth = javaClassStatic()->getMethod("initCause"); - return meth(self(), cause.get()); -} - // jtypeArray ////////////////////////////////////////////////////////////////////////////////////// namespace detail { inline size_t JArray::size() const noexcept { - const auto env = internal::getEnv(); + const auto env = Environment::current(); return env->GetArrayLength(self()); } } @@ -409,8 +418,8 @@ std::string JArrayClass::get_instantiated_base_name() { template auto JArrayClass::newArray(size_t size) -> local_ref { - static auto elementClass = findClassStatic(jtype_traits::base_name().c_str()); - const auto env = internal::getEnv(); + static const auto elementClass = findClassStatic(jtype_traits::base_name().c_str()); + const auto env = Environment::current(); auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr); FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray); return adopt_local(static_cast(rawArray)); @@ -418,13 +427,13 @@ auto JArrayClass::newArray(size_t size) -> local_ref { template inline void JArrayClass::setElement(size_t idx, const T& value) { - const auto env = internal::getEnv(); + const auto env = Environment::current(); env->SetObjectArrayElement(this->self(), idx, value); } template inline local_ref JArrayClass::getElement(size_t idx) { - const auto env = internal::getEnv(); + const auto env = Environment::current(); auto rawElement = env->GetObjectArrayElement(this->self(), idx); return adopt_local(static_cast(rawElement)); } @@ -434,12 +443,16 @@ inline detail::ElementProxy> JArrayClass::operator[](size_t in return detail::ElementProxy>(this, index); } +template +local_ref::javaobject> adopt_local_array(jobjectArray ref) { + return adopt_local(static_cast::javaobject>(ref)); +} + // jarray ///////////////////////////////////////////////////////////////////////////////////////// template auto JPrimitiveArray::getRegion(jsize start, jsize length) -> std::unique_ptr { - using T = typename jtype_traits::entry_type; auto buf = std::unique_ptr{new T[length]}; getRegion(start, length, buf.get()); return buf; @@ -510,7 +523,7 @@ class PinnedCriticalAlloc { jboolean* isCopy) { (void)start; (void)length; - const auto env = internal::getEnv(); + const auto env = Environment::current(); *elements = static_cast(env->GetPrimitiveArrayCritical(array.get(), isCopy)); FACEBOOK_JNI_THROW_EXCEPTION_IF(!elements); *size = array->size(); @@ -523,7 +536,7 @@ class PinnedCriticalAlloc { jint mode) { (void)start; (void)size; - const auto env = internal::getEnv(); + const auto env = Environment::current(); env->ReleasePrimitiveArrayCritical(array.get(), elements, mode); } }; diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses.h b/lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses.h similarity index 88% rename from lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses.h rename to lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses.h index 09fa8b82..53b4118b 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/CoreClasses.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/CoreClasses.h @@ -20,14 +20,19 @@ #include -#include - namespace facebook { namespace jni { class JClass; class JObject; +namespace detail { + +/// Lookup a class by name. This should only be used internally. +jclass findClass(JNIEnv* env, const char* name); + +} + /// Lookup a class by name. Note this functions returns an alias_ref that /// points to a leaked global reference. This is appropriate for classes /// that are never unloaded (which is any class in an Android app and most @@ -37,7 +42,7 @@ class JObject; /// in a "static auto" variable, or a static global. /// /// @return Returns a leaked global reference to the class -FBEXPORT alias_ref findClassStatic(const char* name); +alias_ref findClassStatic(const char* name); /// Lookup a class by name. Note this functions returns a local reference, /// which means that it must not be stored in a static variable. @@ -46,12 +51,12 @@ FBEXPORT alias_ref findClassStatic(const char* name); /// (like caching method ids). /// /// @return Returns a global reference to the class -FBEXPORT local_ref findClassLocal(const char* name); +local_ref findClassLocal(const char* name); /// Check to see if two references refer to the same object. Comparison with nullptr /// returns true if and only if compared to another nullptr. A weak reference that /// refers to a reclaimed object count as nullptr. -FBEXPORT bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept; +bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept; // Together, these classes allow convenient use of any class with the fbjni // helpers. To use: @@ -70,7 +75,7 @@ FBEXPORT bool isSameObject(alias_ref lhs, alias_ref rhs) noexc // constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;"; // // void foo() { -// static auto method = javaClassStatic()->getMethod("foo"); +// static const auto method = javaClassStatic()->getMethod("foo"); // method(self()); // } // @@ -94,7 +99,7 @@ static local_ref newInstance(Args... args); class MonitorLock; -class FBEXPORT JObject : detail::JObjectBase { +class JObject : detail::JObjectBase { public: static constexpr auto kJavaDescriptor = "Ljava/lang/Object;"; @@ -115,10 +120,12 @@ public: template local_ref getFieldValue(JField field) const noexcept; - /// Set the value of field. Any Java type is accepted, including the primitive types - /// and raw reference types. + /// Set the value of field. Any Java type is accepted. template void setFieldValue(JField field, T value) noexcept; + template(), T>::type> + void setFieldValue(JField field, alias_ref value) noexcept; /// Convenience method to create a std::string representing the object std::string toString() const; @@ -190,7 +197,7 @@ struct JTypeFor { // jthrowable) to be used as javaobject. This should only be necessary for // built-in jni types and not user-defined ones. template -class FBEXPORT JavaClass : public Base { +class JavaClass : public Base { using JObjType = typename detail::JTypeFor; public: using _javaobject = typename JObjType::_javaobject; @@ -218,7 +225,7 @@ protected: /// Wrapper to provide functionality to jclass references struct NativeMethod; -class FBEXPORT JClass : public JavaClass { +class JClass : public JavaClass { public: /// Java type descriptor static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;"; @@ -295,10 +302,12 @@ class FBEXPORT JClass : public JavaClass { template local_ref getStaticFieldValue(JStaticField field) noexcept; - /// Set the value of field. Any Java type is accepted, including the primitive types - /// and raw reference types. + /// Set the value of field. Any Java type is accepted. template void setStaticFieldValue(JStaticField field, T value) noexcept; + template(), T>::type> + void setStaticFieldValue(JStaticField field, alias_ref value) noexcept; /// Allocates a new object and invokes the specified constructor template @@ -330,27 +339,23 @@ private: void registerNatives(const char* name, std::initializer_list methods); /// Wrapper to provide functionality to jstring references -class FBEXPORT JString : public JavaClass { +class JString : public JavaClass { public: /// Java type descriptor static constexpr const char* kJavaDescriptor = "Ljava/lang/String;"; /// Convenience method to convert a jstring object to a std::string std::string toStdString() const; + + /// Convenience method to convert a jstring object to a std::u16string + std::u16string toU16String() const; }; -/// Convenience functions to convert a std::string or const char* into a @ref local_ref to a -/// jstring -FBEXPORT local_ref make_jstring(const char* modifiedUtf8); -FBEXPORT local_ref make_jstring(const std::string& modifiedUtf8); - -/// Wrapper to provide functionality to jthrowable references -class FBEXPORT JThrowable : public JavaClass { - public: - static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; - - local_ref initCause(alias_ref cause); -}; +/// Convenience functions to convert a const char*, std::string, or std::u16string +/// into a @ref local_ref to a jstring. +local_ref make_jstring(const char* modifiedUtf8); +local_ref make_jstring(const std::string& modifiedUtf8); +local_ref make_jstring(const std::u16string& utf16); namespace detail { template @@ -378,7 +383,7 @@ class ElementProxy { } namespace detail { -class FBEXPORT JArray : public JavaClass { +class JArray : public JavaClass { public: // This cannot be used in a scope that derives a descriptor (like in a method // signature). Use a more derived type instead (like JArrayInt or @@ -390,7 +395,7 @@ class FBEXPORT JArray : public JavaClass { // This is used so that the JArrayClass javaobject extends jni's // jobjectArray. This class should not be used directly. A general Object[] // should use JArrayClass. -class FBEXPORT JTypeArray : public JavaClass { +class JTypeArray : public JavaClass { // This cannot be used in a scope that derives a descriptor (like in a method // signature). static constexpr const char* kJavaDescriptor = nullptr; @@ -440,9 +445,7 @@ template using jtypeArray = typename JArrayClass::javaobject; template -local_ref::javaobject> adopt_local_array(jobjectArray ref) { - return adopt_local(static_cast::javaobject>(ref)); -} +local_ref::javaobject> adopt_local_array(jobjectArray ref); template local_ref adopt_local(detail::ElementProxy elementProxy) { @@ -460,7 +463,7 @@ template class PinnedCriticalAlloc; /// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with /// the elements of the array. template -class FBEXPORT JPrimitiveArray : +class JPrimitiveArray : public JavaClass, detail::JArray, JArrayType> { static_assert(is_jni_primitive_array(), ""); public: @@ -500,14 +503,14 @@ private: void releaseElements(T* elements, jint mode); }; -FBEXPORT local_ref make_boolean_array(jsize size); -FBEXPORT local_ref make_byte_array(jsize size); -FBEXPORT local_ref make_char_array(jsize size); -FBEXPORT local_ref make_short_array(jsize size); -FBEXPORT local_ref make_int_array(jsize size); -FBEXPORT local_ref make_long_array(jsize size); -FBEXPORT local_ref make_float_array(jsize size); -FBEXPORT local_ref make_double_array(jsize size); +local_ref make_boolean_array(jsize size); +local_ref make_byte_array(jsize size); +local_ref make_char_array(jsize size); +local_ref make_short_array(jsize size); +local_ref make_int_array(jsize size); +local_ref make_long_array(jsize size); +local_ref make_float_array(jsize size); +local_ref make_double_array(jsize size); using JArrayBoolean = JPrimitiveArray; using JArrayByte = JPrimitiveArray; @@ -569,6 +572,29 @@ class PinnedPrimitiveArray { friend class JPrimitiveArray::array_type>; }; +struct JStackTraceElement : JavaClass { + static auto constexpr kJavaDescriptor = "Ljava/lang/StackTraceElement;"; + + static local_ref create(const std::string& declaringClass, const std::string& methodName, const std::string& file, int line); + + std::string getClassName() const; + std::string getMethodName() const; + std::string getFileName() const; + int getLineNumber() const; +}; + +/// Wrapper to provide functionality to jthrowable references +class JThrowable : public JavaClass { + public: + static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; + + using JStackTrace = JArrayClass; + + local_ref initCause(alias_ref cause); + local_ref getStackTrace(); + void setStackTrace(alias_ref>); +}; + #pragma push_macro("PlainJniRefMap") #undef PlainJniRefMap #define PlainJniRefMap(rtype, jtype) \ diff --git a/lib/fb/src/main/cpp/include/fb/Environment.h b/lib/fb/src/main/cpp/include/fbjni/detail/Environment.h similarity index 58% rename from lib/fb/src/main/cpp/include/fb/Environment.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Environment.h index 53b14c80..5453e54b 100644 --- a/lib/fb/src/main/cpp/include/fb/Environment.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Environment.h @@ -9,24 +9,69 @@ #include #include -#include namespace facebook { namespace jni { // Keeps a thread-local reference to the current thread's JNIEnv. struct Environment { - // May be null if this thread isn't attached to the JVM - FBEXPORT static JNIEnv* current(); + // Throws a std::runtime_error if this thread isn't attached to the JVM + // TODO(T6594868) Benchmark against raw JNI access + static JNIEnv* current(); 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 void detachCurrentThread(); + static JNIEnv* ensureCurrentThreadIsAttached(); }; +namespace detail { + +// This will return null the thread isn't attached to the VM, or if +// fbjni has never been initialized with a VM at all. You probably +// shouldn't be using this. +JNIEnv* currentOrNull(); + +/** + * If there's thread-local data, it's a pointer to one of these. The + * instance is a member of JniEnvCacher or ThreadScope, and lives on + * the stack. + */ +struct TLData { + // This is modified only by JniEnvCacher, and is guaranteed to be + // valid if set, and refer to an env which originated from a JNI + // call into C++. + JNIEnv* env; + // This is modified only by ThreadScope, and is set only if an + // instance of ThreadScope which attached is on the stack. + bool attached; +}; + +/** + * RAII object which manages a cached JNIEnv* value. A Value is only + * cached if it is guaranteed safe, which means when C++ is called + * from a registered fbjni function. + */ +class JniEnvCacher { +public: + JniEnvCacher(JNIEnv* env); + JniEnvCacher(JniEnvCacher&) = delete; + JniEnvCacher(JniEnvCacher&&) = default; + JniEnvCacher& operator=(JniEnvCacher&) = delete; + JniEnvCacher& operator=(JniEnvCacher&&) = delete; + ~JniEnvCacher(); + +private: + // If this flag is set, then, this object needs to clear the cache. + bool thisCached_; + + // The thread local pointer may point here. + detail::TLData data_; +}; + +} + /** * RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it * exits will cause a crash, as will calling Detach an extra time, and this guard class helps @@ -48,8 +93,11 @@ struct Environment { * 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) * If you need access to the application's classes, you can use ThreadScope::WithClassLoader. + * - If fbjni has never been initialized, there will be no JavaVM object to attach with. + * In that case, a std::runtime_error will be thrown. This is only likely to happen in a + * standalone C++ application, or if Environment::initialize is not used. */ -class FBEXPORT ThreadScope { +class ThreadScope { public: ThreadScope(); ThreadScope(ThreadScope&) = delete; @@ -67,8 +115,14 @@ class FBEXPORT ThreadScope { static void WithClassLoader(std::function&& runnable); static void OnLoad(); + private: - bool attachedWithThisScope_; + // If this flag is set, then this object needs to detach. + bool thisAttached_; + + // The thread local pointer may point here. + detail::TLData data_; }; + } } diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Exceptions.h b/lib/fb/src/main/cpp/include/fbjni/detail/Exceptions.h similarity index 79% rename from lib/fb/src/main/cpp/include/fb/fbjni/Exceptions.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Exceptions.h index fe28154f..94b642b9 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Exceptions.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Exceptions.h @@ -23,12 +23,15 @@ #include -#include - #include "Common.h" #include "References.h" #include "CoreClasses.h" +#if defined(__ANDROID__) && defined(__ARM_ARCH_5TE__) && !defined(FBJNI_NO_EXCEPTION_PTR) +// ARMv5 NDK does not support exception_ptr so we cannot use that when building for it. +#define FBJNI_NO_EXCEPTION_PTR +#endif + namespace facebook { namespace jni { @@ -57,10 +60,10 @@ class JCppException : public JavaClass { * * Note: the what() method of this class is not thread-safe (t6900503). */ -class FBEXPORT JniException : public std::exception { +class JniException : public std::exception { public: JniException(); - ~JniException(); + ~JniException() override; explicit JniException(alias_ref throwable); @@ -70,7 +73,7 @@ class FBEXPORT JniException : public std::exception { local_ref getThrowable() const noexcept; - virtual const char* what() const noexcept; + const char* what() const noexcept override; void setJavaException() const noexcept; @@ -105,11 +108,23 @@ template } // 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. -FBEXPORT void translatePendingCppExceptionToJavaException() noexcept; +// be thrown, it aborts the program. +void translatePendingCppExceptionToJavaException(); + +#ifndef FBJNI_NO_EXCEPTION_PTR +local_ref getJavaExceptionForCppException(std::exception_ptr ptr); +#endif + +/*** + * The stack returned may include build ids. It may be beneficial to + * call lyra::setLibraryIdentifierFunction before calling this if + * build ids are desirable. + */ +local_ref getJavaExceptionForCppBackTrace(); + +local_ref getJavaExceptionForCppBackTrace(const char* msg); // For convenience, some exception names in java.lang are available here. - const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException"; }} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Hybrid.h b/lib/fb/src/main/cpp/include/fbjni/detail/Hybrid.h similarity index 72% rename from lib/fb/src/main/cpp/include/fb/fbjni/Hybrid.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Hybrid.h index 8db5b0b9..430d7ca3 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Hybrid.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Hybrid.h @@ -9,9 +9,6 @@ #include #include -#include -#include - #include "CoreClasses.h" namespace facebook { @@ -24,13 +21,45 @@ public: virtual ~BaseHybridClass() {} }; -struct FBEXPORT HybridData : public JavaClass { +struct HybridData : public JavaClass { constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;"; - void setNativePointer(std::unique_ptr new_value); - BaseHybridClass* getNativePointer(); static local_ref create(); }; +class HybridDestructor : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/HybridData$Destructor;"; + + detail::BaseHybridClass* getNativePointer(); + + void setNativePointer(std::unique_ptr new_value); +}; + +template +detail::BaseHybridClass* getNativePointer(T t) { + return getHolder(t)->getNativePointer(); +} + +template +void setNativePointer(T t, std::unique_ptr new_value) { + getHolder(t)->setNativePointer(std::move(new_value)); +} + +template +local_ref getHolder(T t) { + static auto holderField = t->getClass()->template getField("mDestructor"); + return t->getFieldValue(holderField); +} + +// JavaClass for HybridClassBase +struct HybridClassBase : public JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridClassBase;"; + + static bool isHybridClassBase(alias_ref jclass) { + return HybridClassBase::javaClassStatic()->isAssignableFrom(jclass); + } +}; + template struct HybridTraits { // This static assert should actually always fail if we don't use one of the @@ -65,7 +94,7 @@ struct HybridTraits< // convert to HybridClass* from jhybridobject template -struct FBEXPORT Convert< +struct Convert< T, typename std::enable_if< std::is_base_of::type>::value>::type> { typedef typename std::remove_pointer::type::jhybridobject jniType; @@ -90,7 +119,7 @@ struct RefReprType -class FBEXPORT HybridClass : public detail::HybridTraits::CxxBase { +class HybridClass : public detail::HybridTraits::CxxBase { public: struct JavaPart : JavaClass::JavaBase> { // At this point, T is incomplete, and so we cannot access @@ -107,6 +136,7 @@ public: T* cthis(); friend class HybridClass; + friend T; }; using jhybridobject = typename JavaPart::javaobject; @@ -136,7 +166,7 @@ protected: static local_ref makeHybridData(std::unique_ptr cxxPart) { auto hybridData = detail::HybridData::create(); - hybridData->setNativePointer(std::move(cxxPart)); + setNativePointer(hybridData, std::move(cxxPart)); return hybridData; } @@ -145,6 +175,11 @@ protected: return makeHybridData(std::unique_ptr(new T(std::forward(args)...))); } + template + static void setCxxInstance(alias_ref o, Args&&... args) { + setNativePointer(o, std::unique_ptr(new T(std::forward(args)...))); + } + public: // Factory method for creating a hybrid object where the arguments // are used to initialize the C++ part directly without passing them @@ -158,11 +193,23 @@ public: // C++ object fails, or any JNI methods throw. template static local_ref newObjectCxxArgs(Args&&... args) { - auto hybridData = makeCxxInstance(std::forward(args)...); - return JavaPart::newInstance(hybridData); + static bool isHybrid = detail::HybridClassBase::isHybridClassBase(javaClassStatic()); + auto cxxPart = std::unique_ptr(new T(std::forward(args)...)); + + local_ref result; + if (isHybrid) { + result = JavaPart::newInstance(); + setNativePointer(result, std::move(cxxPart)); + } + else { + auto hybridData = makeHybridData(std::move(cxxPart)); + result = JavaPart::newInstance(hybridData); + } + + return result; } - // TODO? Create reusable interface for Allocatable classes and use it to + // TODO? Create reusable interface for Allocatable classes and use it to // strengthen type-checking (and possibly provide a default // implementation of allocate().) template @@ -194,17 +241,23 @@ public: template inline T* HybridClass::JavaPart::cthis() { - static auto field = - HybridClass::JavaPart::javaClassStatic()->template getField("mHybridData"); - auto hybridData = this->getFieldValue(field); - if (!hybridData) { - throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + detail::BaseHybridClass* result = 0; + static bool isHybrid = detail::HybridClassBase::isHybridClassBase(this->getClass()); + if (isHybrid) { + result = getNativePointer(this); + } else { + static auto field = + HybridClass::JavaPart::javaClassStatic()->template getField("mHybridData"); + auto hybridData = this->getFieldValue(field); + if (!hybridData) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + + result = getNativePointer(hybridData); } + // I'd like to use dynamic_cast here, but -fno-rtti is the default. - T* value = static_cast(hybridData->getNativePointer()); - // This would require some serious programmer error. - FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field"); - return value; + return static_cast(result); }; template diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Iterator-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/Iterator-inl.h similarity index 98% rename from lib/fb/src/main/cpp/include/fb/fbjni/Iterator-inl.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Iterator-inl.h index f7c62dcc..d0df3455 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Iterator-inl.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Iterator-inl.h @@ -32,7 +32,7 @@ struct IteratorHelper : public JavaClass> { value_type next() { static auto elementField = JavaBase_::javaClassStatic()->template getField("mElement"); - return dynamic_ref_cast(JavaBase_::getFieldValue(elementField)); + return dynamic_ref_cast>(JavaBase_::getFieldValue(elementField)); } static void reset(value_type& v) { diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Iterator.h b/lib/fb/src/main/cpp/include/fbjni/detail/Iterator.h similarity index 100% rename from lib/fb/src/main/cpp/include/fb/fbjni/Iterator.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Iterator.h diff --git a/lib/fb/src/main/cpp/include/fbjni/detail/JWeakReference.h b/lib/fb/src/main/cpp/include/fbjni/detail/JWeakReference.h new file mode 100644 index 00000000..4a95fd35 --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/detail/JWeakReference.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +/** + * Wrap Java's WeakReference instead of using JNI WeakGlobalRefs. + * A WeakGlobalRef can yield a strong reference even after the object has been + * finalized. See comment in the djinni library. + * https://github.com/dropbox/djinni/blob/master/support-lib/jni/djinni_support.hpp + */ +template +class JWeakReference : public JavaClass> { + + typedef JavaClass> JavaBase_; + + public: + static constexpr const char* kJavaDescriptor = "Ljava/lang/ref/WeakReference;"; + + static local_ref> newInstance(alias_ref object) { + return JavaBase_::newInstance(static_ref_cast(object)); + } + + local_ref get() const { + static const auto method = JavaBase_::javaClassStatic()->template getMethod("get"); + return static_ref_cast(method(JavaBase_::self())); + } +}; + +} +} diff --git a/lib/fb/src/main/cpp/include/fbjni/detail/Log.h b/lib/fb/src/main/cpp/include/fbjni/detail/Log.h new file mode 100644 index 00000000..26b34aa3 --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Log.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +/** @file ALog.h + * + * Very simple (android only) logging. Define LOG_TAG to enable the macros. + */ + +#pragma once + +#ifdef __ANDROID__ + +#include + +namespace facebook { +namespace jni { +namespace log_ { +// the weird name of this namespace is to avoid a conflict with the +// function named log. + +inline void loge(const char* tag, const char* msg) noexcept { + __android_log_write(ANDROID_LOG_ERROR, tag, msg); +} + +template +inline void loge(const char* tag, const char* msg, ARGS... args) noexcept { + __android_log_print(ANDROID_LOG_ERROR, tag, msg, args...); +} + +inline void logf(const char* tag, const char* msg) noexcept { + __android_log_write(ANDROID_LOG_FATAL, tag, msg); +} + +template +inline void logf(const char* tag, const char* msg, ARGS... args) noexcept { + __android_log_print(ANDROID_LOG_FATAL, tag, msg, args...); +} + +template +[[noreturn]] +inline void logassert(const char* tag, const char* msg, ARGS... args) noexcept { + __android_log_assert(0, tag, msg, args...); +} + + +#ifdef LOG_TAG +# define FBJNI_LOGE(...) ::facebook::jni::log_::loge(LOG_TAG, __VA_ARGS__) +# define FBJNI_LOGF(...) ::facebook::jni::log_::logf(LOG_TAG, __VA_ARGS__) +# define FBJNI_ASSERT(cond) do { if (!(cond)) ::facebook::jni::log_::logassert(LOG_TAG, "%s", #cond); } while(0) +#else +# define FBJNI_LOGE(...) ::facebook::jni::log_::loge("log", __VA_ARGS__) +# define FBJNI_LOGF(...) ::facebook::jni::log_::logf("log", __VA_ARGS__) +# define FBJNI_ASSERT(cond) do { if (!(cond)) ::facebook::jni::log_::logassert("log", "%s", #cond); } while(0) +#endif + +}}} + +#else +#include + +# define FBJNI_LOGE(...) ((void)0) +# define FBJNI_LOGF(...) (abort()) +# define FBJNI_ASSERT(cond) ((void)0) +#endif diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Meta-forward.h b/lib/fb/src/main/cpp/include/fbjni/detail/Meta-forward.h similarity index 100% rename from lib/fb/src/main/cpp/include/fb/fbjni/Meta-forward.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Meta-forward.h diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Meta-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/Meta-inl.h similarity index 85% rename from lib/fb/src/main/cpp/include/fb/fbjni/Meta-inl.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Meta-inl.h index dfa2c375..a2754374 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Meta-inl.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Meta-inl.h @@ -14,10 +14,6 @@ #include "References.h" #include "Boxed.h" -#if defined(__ANDROID__) -#include -#endif - namespace facebook { namespace jni { @@ -63,31 +59,10 @@ local_ref::javaobject> makeArgsArray(Args... args) { return arr; } - -inline bool needsSlowPath(alias_ref obj) { -#if defined(__ANDROID__) - // 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, - // we resolve the method id to the corresponding java.lang.reflect.Method object - // and make the call via it's invoke() method. - static auto android_sdk = ([] { - char sdk_version_str[PROP_VALUE_MAX]; - __system_property_get("ro.build.version.sdk", sdk_version_str); - return atoi(sdk_version_str); - })(); - static auto is_bad_android = android_sdk == 23; - if (!is_bad_android) return false; - static auto proxy_class = findClassStatic("java/lang/reflect/Proxy"); - return obj->isInstanceOf(proxy_class); -#else - return false; -#endif -} - } template -inline void JMethod::operator()(alias_ref self, Args... args) { +inline void JMethod::operator()(alias_ref self, Args... args) const { const auto env = Environment::current(); env->CallVoidMethod( self.get(), @@ -98,10 +73,10 @@ inline void JMethod::operator()(alias_ref self, Args... #pragma push_macro("DEFINE_PRIMITIVE_CALL") #undef DEFINE_PRIMITIVE_CALL -#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD, CLASS) \ +#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD) \ template \ -inline TYPE JMethod::operator()(alias_ref self, Args... args) { \ - const auto env = internal::getEnv(); \ +inline TYPE JMethod::operator()(alias_ref self, Args... args) const { \ + const auto env = Environment::current(); \ auto result = env->Call ## METHOD ## Method( \ self.get(), \ getId(), \ @@ -110,14 +85,14 @@ inline TYPE JMethod::operator()(alias_ref self, Args... return result; \ } -DEFINE_PRIMITIVE_CALL(jboolean, Boolean, JBoolean) -DEFINE_PRIMITIVE_CALL(jbyte, Byte, JByte) -DEFINE_PRIMITIVE_CALL(jchar, Char, JCharacter) -DEFINE_PRIMITIVE_CALL(jshort, Short, JShort) -DEFINE_PRIMITIVE_CALL(jint, Int, JInteger) -DEFINE_PRIMITIVE_CALL(jlong, Long, JLong) -DEFINE_PRIMITIVE_CALL(jfloat, Float, JFloat) -DEFINE_PRIMITIVE_CALL(jdouble, Double, JDouble) +DEFINE_PRIMITIVE_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_CALL(jchar, Char) +DEFINE_PRIMITIVE_CALL(jshort, Short) +DEFINE_PRIMITIVE_CALL(jint, Int) +DEFINE_PRIMITIVE_CALL(jlong, Long) +DEFINE_PRIMITIVE_CALL(jfloat, Float) +DEFINE_PRIMITIVE_CALL(jdouble, Double) #pragma pop_macro("DEFINE_PRIMITIVE_CALL") /// JMethod specialization for references that wraps the return value in a @ref local_ref @@ -132,13 +107,13 @@ class JMethod : public JMethodBase { JMethod(const JMethod& other) noexcept = default; /// Invoke a method and return a local reference wrapping the result - local_ref operator()(alias_ref self, Args... args); + local_ref operator()(alias_ref self, Args... args) const; friend class JClass; }; template -inline auto JMethod::operator()(alias_ref self, Args... args) -> local_ref { +inline auto JMethod::operator()(alias_ref self, Args... args) const -> local_ref { const auto env = Environment::current(); auto result = env->CallObjectMethod( self.get(), @@ -149,8 +124,8 @@ inline auto JMethod::operator()(alias_ref self, Args... arg } template -inline void JStaticMethod::operator()(alias_ref cls, Args... args) { - const auto env = internal::getEnv(); +inline void JStaticMethod::operator()(alias_ref cls, Args... args) const { + const auto env = Environment::current(); env->CallStaticVoidMethod( cls.get(), getId(), @@ -162,8 +137,8 @@ inline void JStaticMethod::operator()(alias_ref cls, Args #undef DEFINE_PRIMITIVE_STATIC_CALL #define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD) \ template \ -inline TYPE JStaticMethod::operator()(alias_ref cls, Args... args) { \ - const auto env = internal::getEnv(); \ +inline TYPE JStaticMethod::operator()(alias_ref cls, Args... args) const { \ + const auto env = Environment::current(); \ auto result = env->CallStatic ## METHOD ## Method( \ cls.get(), \ getId(), \ @@ -194,8 +169,8 @@ class JStaticMethod : public JMethodBase { JStaticMethod(const JStaticMethod& other) noexcept = default; /// Invoke a method and return a local reference wrapping the result - local_ref operator()(alias_ref cls, Args... args) { - const auto env = internal::getEnv(); + local_ref operator()(alias_ref cls, Args... args) const { + const auto env = Environment::current(); auto result = env->CallStaticObjectMethod( cls.get(), getId(), @@ -209,8 +184,8 @@ class JStaticMethod : public JMethodBase { template inline void -JNonvirtualMethod::operator()(alias_ref self, alias_ref cls, Args... args) { - const auto env = internal::getEnv(); +JNonvirtualMethod::operator()(alias_ref self, alias_ref cls, Args... args) const { + const auto env = Environment::current(); env->CallNonvirtualVoidMethod( self.get(), cls.get(), @@ -224,8 +199,8 @@ JNonvirtualMethod::operator()(alias_ref self, alias_ref< #define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD) \ template \ inline TYPE \ -JNonvirtualMethod::operator()(alias_ref self, alias_ref cls, Args... args) { \ - const auto env = internal::getEnv(); \ +JNonvirtualMethod::operator()(alias_ref self, alias_ref cls, Args... args) const { \ + const auto env = Environment::current(); \ auto result = env->CallNonvirtual ## METHOD ## Method( \ self.get(), \ cls.get(), \ @@ -256,8 +231,8 @@ class JNonvirtualMethod : public JMethodBase { JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; /// Invoke a method and return a local reference wrapping the result - local_ref operator()(alias_ref self, alias_ref cls, Args... args){ - const auto env = internal::getEnv(); + local_ref operator()(alias_ref self, alias_ref cls, Args... args) const { + const auto env = Environment::current(); auto result = env->CallNonvirtualObjectMethod( self.get(), cls.get(), @@ -306,13 +281,13 @@ inline jfieldID JField::getId() const noexcept { #define DEFINE_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ template<> \ inline TYPE JField::get(jobject object) const noexcept { \ - const auto env = internal::getEnv(); \ + const auto env = Environment::current(); \ return env->Get ## METHOD ## Field(object, field_id_); \ } \ \ template<> \ inline void JField::set(jobject object, TYPE value) noexcept { \ - const auto env = internal::getEnv(); \ + const auto env = Environment::current(); \ env->Set ## METHOD ## Field(object, field_id_, value); \ } @@ -328,12 +303,12 @@ DEFINE_FIELD_PRIMITIVE_GET_SET(jdouble, Double) template inline T JField::get(jobject object) const noexcept { - return static_cast(internal::getEnv()->GetObjectField(object, field_id_)); + return static_cast(Environment::current()->GetObjectField(object, field_id_)); } template inline void JField::set(jobject object, T value) noexcept { - internal::getEnv()->SetObjectField(object, field_id_, static_cast(value)); + Environment::current()->SetObjectField(object, field_id_, static_cast(value)); } // JStaticField ///////////////////////////////////////////////////////////////////////////////// @@ -358,13 +333,13 @@ inline jfieldID JStaticField::getId() const noexcept { #define DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ template<> \ inline TYPE JStaticField::get(jclass jcls) const noexcept { \ - const auto env = internal::getEnv(); \ + const auto env = Environment::current(); \ return env->GetStatic ## METHOD ## Field(jcls, field_id_); \ } \ \ template<> \ inline void JStaticField::set(jclass jcls, TYPE value) noexcept { \ - const auto env = internal::getEnv(); \ + const auto env = Environment::current(); \ env->SetStatic ## METHOD ## Field(jcls, field_id_, value); \ } @@ -380,13 +355,13 @@ DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jdouble, Double) template inline T JStaticField::get(jclass jcls) const noexcept { - const auto env = internal::getEnv(); + const auto env = Environment::current(); return static_cast(env->GetStaticObjectField(jcls, field_id_)); } template inline void JStaticField::set(jclass jcls, T value) noexcept { - internal::getEnv()->SetStaticObjectField(jcls, field_id_, value); + Environment::current()->SetStaticObjectField(jcls, field_id_, value); } diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Meta.h b/lib/fb/src/main/cpp/include/fbjni/detail/Meta.h similarity index 98% rename from lib/fb/src/main/cpp/include/fb/fbjni/Meta.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Meta.h index 418dffbd..b0e95fbe 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Meta.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Meta.h @@ -80,7 +80,7 @@ class JMethod : public JMethodBase { JMethod() noexcept {}; \ JMethod(const JMethod& other) noexcept = default; \ \ - TYPE operator()(alias_ref self, Args... args); \ + TYPE operator()(alias_ref self, Args... args) const; \ \ friend class JClass; \ } @@ -130,7 +130,7 @@ class JStaticMethod : public JMethodBase { \ JStaticMethod() noexcept {}; \ JStaticMethod(const JStaticMethod& other) noexcept = default; \ \ - TYPE operator()(alias_ref cls, Args... args); \ + TYPE operator()(alias_ref cls, Args... args) const; \ \ friend class JClass; \ } @@ -170,7 +170,7 @@ class JNonvirtualMethod : public JMethodBase { \ JNonvirtualMethod() noexcept {}; \ JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; \ \ - TYPE operator()(alias_ref self, alias_ref cls, Args... args); \ + TYPE operator()(alias_ref self, alias_ref cls, Args... args) const; \ \ friend class JClass; \ } diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/MetaConvert.h b/lib/fb/src/main/cpp/include/fbjni/detail/MetaConvert.h similarity index 68% rename from lib/fb/src/main/cpp/include/fb/fbjni/MetaConvert.h rename to lib/fb/src/main/cpp/include/fbjni/detail/MetaConvert.h index 690a6164..df15bfba 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/MetaConvert.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/MetaConvert.h @@ -66,6 +66,25 @@ struct Convert { } }; +// Sometimes (64-bit Android) jlong is "long long", but int64_t is "long". +// Allow int64_t to work as jlong. +template +struct Convert::value || std::is_same::value) && !std::is_same::value + >::type> { + typedef jlong jniType; + static T fromJni(jniType t) { + return t; + } + static jniType toJniRet(T t) { + return t; + } + static jniType toCall(T t) { + return t; + } +}; + // convert to alias_ref from T template struct Convert> { @@ -99,7 +118,21 @@ template struct Convert> { typedef JniType jniType; // No automatic synthesis of global_ref - static jniType toJniRet(global_ref t) { + static jniType toJniRet(global_ref&& t) { + // If this gets called, ownership the global_ref was passed in here. (It's + // probably a copy of a persistent global_ref made when a function was + // declared to return a global_ref, but it could moved out or otherwise not + // referenced elsewhere. Doesn't matter.) Either way, the only safe way + // to return it is to make a local_ref, release it, and return the + // underlying local jobject. + auto ret = make_local(t); + return ret.release(); + } + static jniType toJniRet(const global_ref& t) { + // If this gets called, the function was declared to return const&. We + // have a ref to a global_ref whose lifetime will exceed this call, so we + // can just get the underlying jobject and return it to java without + // needing to make a local_ref. return t.get(); } static jniType toCall(global_ref t) { diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators-inl.h similarity index 73% rename from lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators-inl.h rename to lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators-inl.h index 907ff603..e630a992 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators-inl.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators-inl.h @@ -10,6 +10,8 @@ #include #include +#include "Environment.h" + namespace facebook { namespace jni { @@ -18,7 +20,8 @@ namespace internal { // Statistics mostly provided for test (only updated if FBJNI_DEBUG_REFS is defined) struct ReferenceStats { - std::atomic_uint locals_deleted, globals_deleted, weaks_deleted; + std::atomic_uint locals_created, globals_created, weaks_created, + locals_deleted, globals_deleted, weaks_deleted; void reset() noexcept; }; @@ -32,7 +35,10 @@ extern ReferenceStats g_reference_stats; inline jobject LocalReferenceAllocator::newReference(jobject original) const { internal::dbglog("Local new: %p", original); - auto ref = internal::getEnv()->NewLocalRef(original); + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.locals_created; + #endif + auto ref = Environment::current()->NewLocalRef(original); FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); return ref; } @@ -45,15 +51,12 @@ inline void LocalReferenceAllocator::deleteReference(jobject reference) const no ++internal::g_reference_stats.locals_deleted; #endif assert(verifyReference(reference)); - internal::getEnv()->DeleteLocalRef(reference); + Environment::current()->DeleteLocalRef(reference); } } inline bool LocalReferenceAllocator::verifyReference(jobject reference) const noexcept { - if (!reference || !internal::doesGetObjectRefTypeWork()) { - return true; - } - return internal::getEnv()->GetObjectRefType(reference) == JNILocalRefType; + return isObjectRefType(reference, JNILocalRefType); } @@ -61,7 +64,10 @@ inline bool LocalReferenceAllocator::verifyReference(jobject reference) const no inline jobject GlobalReferenceAllocator::newReference(jobject original) const { internal::dbglog("Global new: %p", original); - auto ref = internal::getEnv()->NewGlobalRef(original); + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.globals_created; + #endif + auto ref = Environment::current()->NewGlobalRef(original); FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); return ref; } @@ -74,15 +80,12 @@ inline void GlobalReferenceAllocator::deleteReference(jobject reference) const n ++internal::g_reference_stats.globals_deleted; #endif assert(verifyReference(reference)); - internal::getEnv()->DeleteGlobalRef(reference); + Environment::current()->DeleteGlobalRef(reference); } } inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { - if (!reference || !internal::doesGetObjectRefTypeWork()) { - return true; - } - return internal::getEnv()->GetObjectRefType(reference) == JNIGlobalRefType; + return isObjectRefType(reference, JNIGlobalRefType); } @@ -90,7 +93,10 @@ inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const n inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const { internal::dbglog("Weak global new: %p", original); - auto ref = internal::getEnv()->NewWeakGlobalRef(original); + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.weaks_created; + #endif + auto ref = Environment::current()->NewWeakGlobalRef(original); FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); return ref; } @@ -103,15 +109,12 @@ inline void WeakGlobalReferenceAllocator::deleteReference(jobject reference) con ++internal::g_reference_stats.weaks_deleted; #endif assert(verifyReference(reference)); - internal::getEnv()->DeleteWeakGlobalRef(reference); + Environment::current()->DeleteWeakGlobalRef(reference); } } inline bool WeakGlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { - if (!reference || !internal::doesGetObjectRefTypeWork()) { - return true; - } - return internal::getEnv()->GetObjectRefType(reference) == JNIWeakGlobalRefType; + return isObjectRefType(reference, JNIWeakGlobalRefType); } }} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators.h b/lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators.h similarity index 75% rename from lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators.h rename to lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators.h index e20eef9b..55028ecd 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/ReferenceAllocators.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/ReferenceAllocators.h @@ -13,14 +13,12 @@ #pragma once -#include - #include "Common.h" namespace facebook { namespace jni { /// Allocator that handles local references -class FBEXPORT LocalReferenceAllocator { +class LocalReferenceAllocator { public: jobject newReference(jobject original) const; void deleteReference(jobject reference) const noexcept; @@ -28,7 +26,7 @@ class FBEXPORT LocalReferenceAllocator { }; /// Allocator that handles global references -class FBEXPORT GlobalReferenceAllocator { +class GlobalReferenceAllocator { public: jobject newReference(jobject original) const; void deleteReference(jobject reference) const noexcept; @@ -36,23 +34,20 @@ class FBEXPORT GlobalReferenceAllocator { }; /// Allocator that handles weak global references -class FBEXPORT WeakGlobalReferenceAllocator { +class WeakGlobalReferenceAllocator { public: jobject newReference(jobject original) const; void deleteReference(jobject reference) const noexcept; bool verifyReference(jobject reference) const noexcept; }; -/// @cond INTERNAL -namespace internal { - /** - * @return true iff env->GetObjectRefType is expected to work properly. + * @return Helper based on GetObjectRefType. Since this isn't defined + * on all versions of Java or Android, if the type can't be + * determined, this returns true. If reference is nullptr, returns + * true. */ -FBEXPORT bool doesGetObjectRefTypeWork(); - -} -/// @endcond +bool isObjectRefType(jobject reference, jobjectRefType refType); }} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/References-forward.h b/lib/fb/src/main/cpp/include/fbjni/detail/References-forward.h similarity index 100% rename from lib/fb/src/main/cpp/include/fb/fbjni/References-forward.h rename to lib/fb/src/main/cpp/include/fbjni/detail/References-forward.h diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/References-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/References-inl.h similarity index 91% rename from lib/fb/src/main/cpp/include/fb/fbjni/References-inl.h rename to lib/fb/src/main/cpp/include/fbjni/detail/References-inl.h index d3329f41..eb2d834e 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/References-inl.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/References-inl.h @@ -173,6 +173,29 @@ operator!=(const T1& a, const T2& b) { return !(a == b); } +template +inline enable_if_t(), bool> +operator==(const T1& a, std::nullptr_t) { + return getPlainJniReference(a) == nullptr; +} + +template +inline enable_if_t(), bool> +operator==(std::nullptr_t, const T1& a) { + return nullptr == getPlainJniReference(a); +} + +template +inline enable_if_t(), bool> +operator!=(const T1& a, std::nullptr_t) { + return !(a == nullptr); +} + +template +inline enable_if_t(), bool> +operator!=(std::nullptr_t, const T1& a) { + return !(nullptr == getPlainJniReference(a)); +} // base_owned_ref /////////////////////////////////////////////////////////////////////// @@ -181,10 +204,11 @@ inline base_owned_ref::base_owned_ref() noexcept : base_owned_ref(nullptr) {} -template -inline base_owned_ref::base_owned_ref(std::nullptr_t array) noexcept - : base_owned_ref(static_cast(nullptr)) { - (void)array; +template +inline base_owned_ref::base_owned_ref(std::nullptr_t t) noexcept + : base_owned_ref(static_cast(nullptr)) +{ + (void)t; } template @@ -484,22 +508,25 @@ template auto dynamic_ref_cast(const RefType& ref) -> enable_if_t(), decltype(static_ref_cast(ref))> { - if (! ref) { + if (!ref) { return decltype(static_ref_cast(ref))(); } - std::string target_class_name{jtype_traits::base_name()}; + static alias_ref target_class = findClassStatic(jtype_traits::base_name().c_str()); + if (!target_class) { + throwNewJavaException("java/lang/ClassCastException", + "Could not find class %s.", + jtype_traits::base_name().c_str()); - // If not found, will throw an exception. - alias_ref target_class = findClassStatic(target_class_name.c_str()); + } local_ref source_class = ref->getClass(); - if ( ! source_class->isAssignableFrom(target_class)) { + if (!target_class->isAssignableFrom(source_class)) { throwNewJavaException("java/lang/ClassCastException", "Tried to cast from %s to %s.", source_class->toString().c_str(), - target_class_name.c_str()); + jtype_traits::base_name().c_str()); } return static_ref_cast(ref); diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/References.h b/lib/fb/src/main/cpp/include/fbjni/detail/References.h similarity index 96% rename from lib/fb/src/main/cpp/include/fb/fbjni/References.h rename to lib/fb/src/main/cpp/include/fbjni/detail/References.h index 00492501..0984134e 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/References.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/References.h @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ + /** @file References.h * * Functionality similar to smart pointers, but for references into the VM. Four main reference @@ -73,8 +74,6 @@ #include -#include - #include "ReferenceAllocators.h" #include "TypeTraits.h" #include "References-forward.h" @@ -250,6 +249,26 @@ template enable_if_t() && IsNonWeakReference(), bool> operator!=(const T1& a, const T2& b); +/** + * Compare references against nullptr + */ +template +enable_if_t(), bool> +operator==(const T1& a, std::nullptr_t); + +template +enable_if_t(), bool> +operator==(std::nullptr_t, const T1& a); + +template +enable_if_t(), bool> +operator!=(const T1& a, std::nullptr_t); + +template +enable_if_t(), bool> +operator!=(std::nullptr_t, const T1& a); + + template class base_owned_ref { public: @@ -336,7 +355,7 @@ class weak_ref : public base_owned_ref { : base_owned_ref{} {} /// Create a null reference - explicit weak_ref(std::nullptr_t) noexcept + /* implicit */ weak_ref(std::nullptr_t) noexcept : base_owned_ref{nullptr} {} /// Copy constructor (note creates a new reference) @@ -405,7 +424,7 @@ class basic_strong_ref : public base_owned_ref { : base_owned_ref{} {} /// Create a null reference - explicit basic_strong_ref(std::nullptr_t) noexcept + /* implicit */ basic_strong_ref(std::nullptr_t) noexcept : base_owned_ref{nullptr} {} /// Copy constructor (note creates a new reference) @@ -492,7 +511,7 @@ class alias_ref { alias_ref() noexcept; /// Create a null reference - alias_ref(std::nullptr_t) noexcept; + /* implicit */ alias_ref(std::nullptr_t) noexcept; /// Copy constructor alias_ref(const alias_ref& other) noexcept; @@ -553,7 +572,7 @@ class alias_ref { * This is useful when you have a call which is initiated from C++-land, and therefore * doesn't automatically get a local JNI frame managed for you by the JNI framework. */ -class FBEXPORT JniLocalScope { +class JniLocalScope { public: JniLocalScope(JNIEnv* p_env, jint capacity); ~JniLocalScope(); diff --git a/lib/fb/src/main/cpp/include/fbjni/detail/Registration-inl.h b/lib/fb/src/main/cpp/include/fbjni/detail/Registration-inl.h new file mode 100644 index 00000000..d8e2b043 --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Registration-inl.h @@ -0,0 +1,166 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include "Exceptions.h" +#include "Hybrid.h" + +namespace facebook { +namespace jni { + +namespace detail { + +#ifdef __i386__ +// X86 ABI forces 16 byte stack allignment on calls. Unfortunately +// sometimes Dalvik chooses not to obey the ABI: +// - https://code.google.com/p/android/issues/detail?id=61012 +// - https://android.googlesource.com/platform/ndk/+/81696d2%5E!/ +// Therefore, we tell the compiler to re-align the stack on entry +// to our JNI functions. +#define JNI_ENTRY_POINT __attribute__((force_align_arg_pointer)) +#else +#define JNI_ENTRY_POINT +#endif + +template +struct CreateDefault { + static R create() { + return R{}; + } +}; + +template <> +struct CreateDefault { + static void create() {} +}; + +template +using Converter = Convert::type>; + +template +struct WrapForVoidReturn { + static typename Converter::jniType call(Args&&... args) { + return Converter::toJniRet(func(std::forward(args)...)); + } +}; + +template +struct WrapForVoidReturn { + static void call(Args&&... args) { + func(std::forward(args)...); + } +}; + +// registration wrapper for legacy JNI-style functions +template +struct BareJniWrapper { + JNI_ENTRY_POINT static R call(JNIEnv* env, jobject obj, Args... args) { + detail::JniEnvCacher jec(env); + try { + return (*func)(env, static_cast>(obj), args...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return CreateDefault::create(); + } + } +}; + +// registration wrappers for functions, with autoconversion of arguments. +template +struct FunctionWrapper { + using jniRet = typename Converter::jniType; + JNI_ENTRY_POINT static jniRet call(JNIEnv* env, jobject obj, typename Converter::jniType... args) { + detail::JniEnvCacher jec(env); + try { + return WrapForVoidReturn, Args...>::call( + static_cast>(obj), Converter::fromJni(args)...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return CreateDefault::create(); + } + } +}; + +// registration wrappers for non-static methods, with autoconvertion of arguments. +template +struct MethodWrapper { + using jhybrid = typename C::jhybridobject; + static R dispatch(alias_ref ref, Args&&... args) { + try { + // This is usually a noop, but if the hybrid object is a + // base class of other classes which register JNI methods, + // this will get the right type for the registered method. + auto cobj = static_cast(ref->cthis()); + return (cobj->*method)(std::forward(args)...); + } catch (const std::exception& ex) { + C::mapException(ex); + throw; + } + } + + JNI_ENTRY_POINT static typename Converter::jniType call( + JNIEnv* env, jobject obj, typename Converter::jniType... args) { + return FunctionWrapper, Args&&...), dispatch, jhybrid, R, Args...>::call(env, obj, args...); + } +}; + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) { + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(BareJniWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref, Args... args)) { + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(FunctionWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) { + (void)method0; + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(MethodWrapper::call)); +} + +template +inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) { + return jmethod_traits::descriptor(); +} + +template +inline std::string makeDescriptor(R (*)(alias_ref, Args... args)) { + return jmethod_traits_from_cxx::descriptor(); +} + +template +inline std::string makeDescriptor(R (C::*)(Args... args)) { + return jmethod_traits_from_cxx::descriptor(); +} + +template +template +JNI_ENTRY_POINT R CriticalMethod::call(alias_ref, Args... args) noexcept { + static_assert( + IsJniPrimitive() || std::is_void(), + "Critical Native Methods may only return primitive JNI types, or void."); + static_assert( + AreJniPrimitives(), + "Critical Native Methods may only use primitive JNI types as parameters"); + + return func(std::forward(args)...); +} + +template +template +inline std::string CriticalMethod::desc() { + return makeDescriptor(call); +} + +} + +}} diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/Registration.h b/lib/fb/src/main/cpp/include/fbjni/detail/Registration.h similarity index 90% rename from lib/fb/src/main/cpp/include/fb/fbjni/Registration.h rename to lib/fb/src/main/cpp/include/fbjni/detail/Registration.h index 0c2d6eac..0808a070 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/Registration.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/Registration.h @@ -17,28 +17,14 @@ namespace detail { // This uses the real JNI function as a non-type template parameter to // cause a (static member) function to exist with the same signature, // but with try/catch exception translation. -template -NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(JNIEnv*, jobject, Args... args)); - -// Same as above, but for non-void return types. template NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(JNIEnv*, jobject, Args... args)); // Automatically wrap object argument, and don't take env explicitly. -template -NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(alias_ref, Args... args)); - -// Automatically wrap object argument, and don't take env explicitly, -// non-void return type. template NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(alias_ref, Args... args)); -// Extract C++ instance from object, and invoke given method on it. -template -NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)); - // Extract C++ instance from object, and invoke given method on it, -// non-void return type template NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)); @@ -57,15 +43,15 @@ std::string makeDescriptor(R (*func)(alias_ref, Args... args)); template std::string makeDescriptor(R (C::*method0)(Args... args)); -template +template struct CriticalMethod; -template -struct CriticalMethod { - template +template +struct CriticalMethod { + template static R call(alias_ref, Args... args) noexcept; - template + template inline static std::string desc(); }; @@ -91,6 +77,7 @@ struct CriticalMethod { #define makeNativeMethodN(a, b, c, count, ...) makeNativeMethod ## count #define makeNativeMethod(...) makeNativeMethodN(__VA_ARGS__, 3, 2)(__VA_ARGS__) + // FAST CALLS / CRITICAL CALLS // Android up to and including v7 supports "fast calls" by prefixing the method // signature with an exclamation mark. diff --git a/lib/fb/src/main/cpp/include/fb/fbjni/TypeTraits.h b/lib/fb/src/main/cpp/include/fbjni/detail/TypeTraits.h similarity index 99% rename from lib/fb/src/main/cpp/include/fb/fbjni/TypeTraits.h rename to lib/fb/src/main/cpp/include/fbjni/detail/TypeTraits.h index 95c146fc..e5ffaed9 100644 --- a/lib/fb/src/main/cpp/include/fb/fbjni/TypeTraits.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/TypeTraits.h @@ -86,6 +86,7 @@ constexpr bool AreJniPrimitives() { return are_jni_primitives::value; } + /// Metafunction to determine whether a type is a JNI array of primitives or not template struct is_jni_primitive_array : diff --git a/lib/fb/src/main/cpp/include/jni/LocalString.h b/lib/fb/src/main/cpp/include/fbjni/detail/utf8.h similarity index 77% rename from lib/fb/src/main/cpp/include/jni/LocalString.h rename to lib/fb/src/main/cpp/include/fbjni/detail/utf8.h index 34316531..ccadeccf 100644 --- a/lib/fb/src/main/cpp/include/jni/LocalString.h +++ b/lib/fb/src/main/cpp/include/fbjni/detail/utf8.h @@ -10,8 +10,6 @@ #include -#include - namespace facebook { namespace jni { @@ -42,28 +40,18 @@ std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept; // - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html // - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8 -class FBEXPORT LocalString { -public: - // Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string - // contains unicode supplementary characters. - explicit LocalString(const std::string& str); - explicit LocalString(const char* str); - jstring string() const { - return m_string; - } - ~LocalString(); -private: - jstring m_string; -}; - -// JString to UTF16 extractor using RAII idiom +// JString to UTF16 extractor using RAII idiom. Note that the +// ctor/dtor use GetStringCritical/ReleaseStringCritical, so this +// class is subject to the restrictions imposed by those functions. class JStringUtf16Extractor { public: JStringUtf16Extractor(JNIEnv* env, jstring javaString) : env_(env) , javaString_(javaString) + , length_(0) , utf16String_(nullptr) { if (env_ && javaString_) { + length_ = env_->GetStringLength(javaString_); utf16String_ = env_->GetStringCritical(javaString_, nullptr); } } @@ -74,18 +62,20 @@ public: } } - operator const jchar* () const { + const jsize length() const { + return length_; + } + + const jchar* chars() const { return utf16String_; } private: JNIEnv* env_; jstring javaString_; + jsize length_; const jchar* utf16String_; }; -// The string from JNI is converted to standard UTF-8 if the string contains supplementary -// characters. -FBEXPORT std::string fromJString(JNIEnv* env, jstring str); - -} } +} +} diff --git a/lib/fb/src/main/cpp/include/fbjni/fbjni.h b/lib/fb/src/main/cpp/include/fbjni/fbjni.h new file mode 100644 index 00000000..764c67e2 --- /dev/null +++ b/lib/fb/src/main/cpp/include/fbjni/fbjni.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/lib/fb/src/main/cpp/include/jni/Countable.h b/lib/fb/src/main/cpp/include/jni/Countable.h deleted file mode 100644 index b2708aae..00000000 --- a/lib/fb/src/main/cpp/include/jni/Countable.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include - -#include -#include -#include - -namespace facebook { -namespace jni { - -FBEXPORT const RefPtr& countableFromJava(JNIEnv* env, jobject obj); - -template RefPtr extractRefPtr(JNIEnv* env, jobject obj) { - return static_cast>(countableFromJava(env, obj)); -} - -template RefPtr extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) { - return obj ? extractRefPtr(env, obj) : nullptr; -} - -FBEXPORT void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable); - -void CountableOnLoad(JNIEnv* env); - -} } - diff --git a/lib/fb/src/main/cpp/include/jni/GlobalReference.h b/lib/fb/src/main/cpp/include/jni/GlobalReference.h deleted file mode 100644 index 9ebdfcd5..00000000 --- a/lib/fb/src/main/cpp/include/jni/GlobalReference.h +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include -#include - -#include - -#include - -namespace facebook { namespace jni { - -template -class GlobalReference { - static_assert(std::is_convertible::value, - "GlobalReference instantiated with type that is not " - "convertible to jobject"); - - public: - explicit GlobalReference(T globalReference) : - reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) { - } - - ~GlobalReference() { - reset(); - } - - GlobalReference() : - reference_(nullptr) { - } - - // enable move constructor and assignment - GlobalReference(GlobalReference&& rhs) : - reference_(std::move(rhs.reference_)) { - rhs.reference_ = nullptr; - } - - GlobalReference& operator=(GlobalReference&& rhs) { - if (this != &rhs) { - reset(); - reference_ = std::move(rhs.reference_); - rhs.reference_ = nullptr; - } - return *this; - } - - GlobalReference(const GlobalReference& rhs) : - reference_{} { - reset(rhs.get()); - } - - GlobalReference& operator=(const GlobalReference& rhs) { - if (this == &rhs) { - return *this; - } - reset(rhs.get()); - return *this; - } - - explicit operator bool() const { - return (reference_ != nullptr); - } - - T get() const { - return reinterpret_cast(reference_); - } - - void reset(T globalReference = nullptr) { - if (reference_) { - Environment::current()->DeleteGlobalRef(reference_); - } - if (globalReference) { - reference_ = Environment::current()->NewGlobalRef(globalReference); - } else { - reference_ = nullptr; - } - } - - private: - jobject reference_; -}; - -}} diff --git a/lib/fb/src/main/cpp/include/jni/LocalReference.h b/lib/fb/src/main/cpp/include/jni/LocalReference.h deleted file mode 100644 index b565ee74..00000000 --- a/lib/fb/src/main/cpp/include/jni/LocalReference.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include -#include - -#include - -#include - -namespace facebook { -namespace jni { - -template -struct LocalReferenceDeleter { - static_assert(std::is_convertible::value, - "LocalReferenceDeleter instantiated with type that is not convertible to jobject"); - void operator()(T localReference) { - if (localReference != nullptr) { - Environment::current()->DeleteLocalRef(localReference); - } - } - }; - -template -using LocalReference = - std::unique_ptr::type, LocalReferenceDeleter>; - -} } diff --git a/lib/fb/src/main/cpp/include/jni/Registration.h b/lib/fb/src/main/cpp/include/jni/Registration.h deleted file mode 100644 index 553ddaf8..00000000 --- a/lib/fb/src/main/cpp/include/jni/Registration.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include -#include - -namespace facebook { -namespace jni { - -static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list methods) { - auto result = env->RegisterNatives(cls, methods.begin(), methods.size()); - FBASSERT(result == 0); -} - -static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list list) { - registerNatives(env, env->FindClass(cls), list); -} - -} } diff --git a/lib/fb/src/main/cpp/include/jni/WeakReference.h b/lib/fb/src/main/cpp/include/jni/WeakReference.h deleted file mode 100644 index 70630b10..00000000 --- a/lib/fb/src/main/cpp/include/jni/WeakReference.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once -#include -#include -#include -#include -#include - - -namespace facebook { -namespace jni { - -class FBEXPORT WeakReference : public Countable { -public: - typedef RefPtr Ptr; - WeakReference(jobject strongRef); - ~WeakReference(); - jweak weakRef() { - return m_weakReference; - } - -private: - jweak m_weakReference; -}; - -// This class is intended to take a weak reference and turn it into a strong -// local reference. Consequently, it should only be allocated on the stack. -class FBEXPORT ResolvedWeakReference : public noncopyable { -public: - ResolvedWeakReference(jobject weakRef); - ResolvedWeakReference(const RefPtr& weakRef); - ~ResolvedWeakReference(); - - operator jobject () { - return m_strongReference; - } - - explicit operator bool () { - return m_strongReference != nullptr; - } - -private: - jobject m_strongReference; -}; - -} } - diff --git a/lib/fb/src/main/cpp/include/jni/jni_helpers.h b/lib/fb/src/main/cpp/include/jni/jni_helpers.h deleted file mode 100644 index cf6504a4..00000000 --- a/lib/fb/src/main/cpp/include/jni/jni_helpers.h +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#pragma once - -#include - -#include - -namespace facebook { - -/** - * Instructs the JNI environment to throw an exception. - * - * @param pEnv JNI environment - * @param szClassName class name to throw - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args); - -/** - * Instructs the JNI environment to throw a NoClassDefFoundError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw a RuntimeException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw a IllegalArgumentException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw a IllegalStateException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw an IOException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw an AssertionError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Instructs the JNI environment to throw an OutOfMemoryError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -FBEXPORT jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...); - -/** - * Finds the specified class. If it's not found, instructs the JNI environment to throw an - * exception. - * - * @param pEnv JNI environment - * @param szClassName the classname to find in JNI format (e.g. "java/lang/String") - * @return the class or NULL if not found (in which case a pending exception will be queued). This - * returns a global reference (JNIEnv::NewGlobalRef). - */ -FBEXPORT jclass findClassOrThrow(JNIEnv *pEnv, const char* szClassName); - -/** - * Finds the specified field of the specified class. If it's not found, instructs the JNI - * environment to throw an exception. - * - * @param pEnv JNI environment - * @param clazz the class to lookup the field in - * @param szFieldName the name of the field to find - * @param szSig the signature of the field - * @return the field or NULL if not found (in which case a pending exception will be queued) - */ -FBEXPORT jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig); - -/** - * Finds the specified method of the specified class. If it's not found, instructs the JNI - * environment to throw an exception. - * - * @param pEnv JNI environment - * @param clazz the class to lookup the method in - * @param szMethodName the name of the method to find - * @param szSig the signature of the method - * @return the method or NULL if not found (in which case a pending exception will be queued) - */ -FBEXPORT jmethodID getMethodIdOrThrow( - JNIEnv* pEnv, - jclass clazz, - const char* szMethodName, - const char* szSig); - -} // namespace facebook - diff --git a/lib/fb/src/main/cpp/include/fb/lyra.h b/lib/fb/src/main/cpp/include/lyra/lyra.h similarity index 75% rename from lib/fb/src/main/cpp/include/fb/lyra.h rename to lib/fb/src/main/cpp/include/lyra/lyra.h index 0d199e6e..02e6078d 100644 --- a/lib/fb/src/main/cpp/include/fb/lyra.h +++ b/lib/fb/src/main/cpp/include/lyra/lyra.h @@ -7,12 +7,9 @@ #pragma once #include -#include #include #include -#include - namespace facebook { namespace lyra { @@ -20,17 +17,21 @@ constexpr size_t kDefaultLimit = 64; using InstructionPointer = const void*; -class FBEXPORT StackTraceElement { +class StackTraceElement { public: StackTraceElement(InstructionPointer absoluteProgramCounter, InstructionPointer libraryBase, - InstructionPointer functionAddress, std::string libraryName, + InstructionPointer functionAddress, + std::string libraryName, std::string functionName) : absoluteProgramCounter_{absoluteProgramCounter}, libraryBase_{libraryBase}, functionAddress_{functionAddress}, libraryName_{std::move(libraryName)}, - functionName_{std::move(functionName)} {} + functionName_{std::move(functionName)}, + hasBuildId_{false}, + buildId_{} + {} InstructionPointer libraryBase() const noexcept { return libraryBase_; } @@ -67,14 +68,28 @@ class FBEXPORT StackTraceElement { return absoluteabsoluteProgramCounter - absoluteSymbol; } + std::string buildId() const; private: const InstructionPointer absoluteProgramCounter_; const InstructionPointer libraryBase_; const InstructionPointer functionAddress_; const std::string libraryName_; const std::string functionName_; + + mutable bool hasBuildId_; + mutable std::string buildId_; }; +/** + * If a library identifier function is set, it is passed a libraryName + * for the frame, and returns a library build id string, which will be + * included in the logged stack trace. The most common use for this + * will be correlating stack traces with breakpad identifiers. + */ +typedef std::string (*LibraryIdentifierFunctionType)(const std::string&); + +void setLibraryIdentifierFunction(LibraryIdentifierFunctionType func); + /** * Populate the vector with the current stack trace * @@ -91,8 +106,7 @@ class FBEXPORT StackTraceElement { * * @param skip The number of frames to skip before capturing the trace */ -FBEXPORT void getStackTrace(std::vector& stackTrace, - size_t skip = 0); +void getStackTrace(std::vector& stackTrace, size_t skip = 0); /** * Creates a vector and populates it with the current stack trace @@ -108,7 +122,7 @@ FBEXPORT void getStackTrace(std::vector& stackTrace, * * @limit The maximum number of frames captured */ -FBEXPORT inline std::vector getStackTrace( +inline std::vector getStackTrace( size_t skip = 0, size_t limit = kDefaultLimit) { auto stackTrace = std::vector{}; @@ -125,15 +139,15 @@ FBEXPORT inline std::vector getStackTrace( * * @param stackTrace The input stack trace */ -FBEXPORT void getStackTraceSymbols(std::vector& symbols, - const std::vector& trace); +void getStackTraceSymbols(std::vector& symbols, + const std::vector& trace); /** * Symbolicates a stack trace into a new vector * * @param stackTrace The input stack trace */ -FBEXPORT inline std::vector getStackTraceSymbols( +inline std::vector getStackTraceSymbols( const std::vector& trace) { auto symbols = std::vector{}; getStackTraceSymbols(symbols, trace); @@ -152,7 +166,7 @@ FBEXPORT inline std::vector getStackTraceSymbols( * * @param limit The maximum number of frames captured */ -FBEXPORT inline std::vector getStackTraceSymbols( +inline std::vector getStackTraceSymbols( size_t skip = 0, size_t limit = kDefaultLimit) { return getStackTraceSymbols(getStackTrace(skip + 1, limit)); @@ -161,12 +175,21 @@ FBEXPORT inline std::vector getStackTraceSymbols( /** * Formatting a stack trace element */ -FBEXPORT std::ostream& operator<<(std::ostream& out, const StackTraceElement& elm); +std::ostream& operator<<(std::ostream& out, const StackTraceElement& elm); /** * Formatting a stack trace */ -FBEXPORT std::ostream& operator<<(std::ostream& out, - const std::vector& trace); +std::ostream& operator<<(std::ostream& out, + const std::vector& trace); + +/** + * Log stack trace + * + * Makes it possible to log a trace without using a temporary stream when the + * underlying log API is not stream based. + */ +void logStackTrace(const std::vector& trace); + } } diff --git a/lib/fb/src/main/cpp/include/lyra/lyra_exceptions.h b/lib/fb/src/main/cpp/include/lyra/lyra_exceptions.h new file mode 100644 index 00000000..a40f1d8b --- /dev/null +++ b/lib/fb/src/main/cpp/include/lyra/lyra_exceptions.h @@ -0,0 +1,84 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#pragma once + +#include +#include +#include + +#include + +namespace facebook { +namespace lyra { + +namespace detail { + struct ExceptionTraceHolder { + ExceptionTraceHolder(); + // Need some virtual function to make this a polymorphic type. + virtual ~ExceptionTraceHolder(); + ExceptionTraceHolder(const ExceptionTraceHolder&) = delete; + ExceptionTraceHolder(ExceptionTraceHolder&&) = default; + + std::vector stackTrace_; + }; + + template + struct Holder : E, ExceptionTraceHolder { + Holder(E&& e) : E{std::forward(e)}, ExceptionTraceHolder{} {} + }; + template + struct Holder : E { + Holder(E&& e) : E{std::forward(e)} {} + }; +} + +/** + * Retrieves the stack trace of an exception + */ +const std::vector& getExceptionTrace(std::exception_ptr ptr); + +/** + * Throw an exception and store the stack trace. This works like + * std::throw_with_nested in that it will actually throw a type that is + * publicly derived from both E and detail::ExceptionTraceHolder. + */ +template +[[noreturn]] void fbthrow(E&& exception) { + throw detail::Holder::value>{std::forward(exception)}; +} + +/** + * Ensure that a terminate handler that logs traces is installed. + * setLibraryIdentifierFunction should be called first if the stack + * trace should log build ids for libraries. + */ +void ensureRegisteredTerminateHandler(); + +/** + * Helper to convert an exception to a string + */ +std::string toString(std::exception_ptr exceptionPointer); + +/** + * lyra's cxa_throw will delegate to the original cxa throw. That pointer must + * be set before lyra::cxa_throw is called. + * + * One example use would be to statically compile against something that overrides __cxa_throw. + * That would look something like: + * + * [[noreturn]] void __cxa_throw(void* obj, const std::type_info* type, void (*destructor) (void*)) { + * static auto initializer = lyra::original_cxa_throw = lookupOriginalCxaThrow(); + * lyra::cxa_throw(obj, type, destructor); + * } + */ +[[gnu::noreturn]] extern void (*original_cxa_throw)(void*, const std::type_info*, void (*) (void*)); +[[noreturn]] void cxa_throw(void* obj, const std::type_info* type, void (*destructor) (void *)); + +void enableCxaThrowHookBacktraces(bool enable); + +} +} diff --git a/lib/fb/src/main/cpp/jni/ByteBuffer.cpp b/lib/fb/src/main/cpp/jni/ByteBuffer.cpp index 9d7cbda9..df62e956 100644 --- a/lib/fb/src/main/cpp/jni/ByteBuffer.cpp +++ b/lib/fb/src/main/cpp/jni/ByteBuffer.cpp @@ -4,21 +4,51 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include #include -#include - namespace facebook { namespace jni { -namespace { -local_ref createEmpty() { - static auto cls = JByteBuffer::javaClassStatic(); - static auto meth = cls->getStaticMethod("allocateDirect"); - return meth(cls, 0); +void JBuffer::rewind() const { + static auto meth = javaClassStatic()->getMethod()>("rewind"); + meth(self()); } + +void* JBuffer::getDirectAddress() const { + if (!self()) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + void* addr = Environment::current()->GetDirectBufferAddress(self()); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (!addr) { + throw std::runtime_error( + isDirect() ? + "Attempt to get direct bytes of non-direct buffer." : + "Error getting direct bytes of buffer."); + } + return addr; +} + +size_t JBuffer::getDirectCapacity() const { + if (!self()) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + int size = Environment::current()->GetDirectBufferCapacity(self()); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (size < 0) { + throw std::runtime_error( + isDirect() ? + "Attempt to get direct size of non-direct buffer." : + "Error getting direct size of buffer."); + } + return static_cast(size); +} + +bool JBuffer::isDirect() const { + static auto meth = javaClassStatic()->getMethod("isDirect"); + return meth(self()); } local_ref JByteBuffer::wrapBytes(uint8_t* data, size_t size) { @@ -26,7 +56,7 @@ local_ref JByteBuffer::wrapBytes(uint8_t* data, size_t size) { // dalvik returns an invalid result and Android's art aborts if size == 0. // Workaround this by using a slow path through Java in that case. if (!size) { - return createEmpty(); + return allocateDirect(0); } auto res = adopt_local(static_cast(Environment::current()->NewDirectByteBuffer(data, size))); FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); @@ -36,39 +66,10 @@ local_ref JByteBuffer::wrapBytes(uint8_t* data, size_t size) { return res; } -uint8_t* JByteBuffer::getDirectBytes() const { - if (!self()) { - throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); - } - void* bytes = Environment::current()->GetDirectBufferAddress(self()); - FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); - if (!bytes) { - throw std::runtime_error( - isDirect() ? - "Attempt to get direct bytes of non-direct byte buffer." : - "Error getting direct bytes of byte buffer."); - } - return static_cast(bytes); -} - -size_t JByteBuffer::getDirectSize() const { - if (!self()) { - throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); - } - int size = Environment::current()->GetDirectBufferCapacity(self()); - FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); - if (size < 0) { - throw std::runtime_error( - isDirect() ? - "Attempt to get direct size of non-direct byte buffer." : - "Error getting direct size of byte buffer."); - } - return static_cast(size); -} - -bool JByteBuffer::isDirect() const { - static auto meth = javaClassStatic()->getMethod("isDirect"); - return meth(self()); +local_ref JByteBuffer::allocateDirect(jint size) { + static auto cls = JByteBuffer::javaClassStatic(); + static auto meth = cls->getStaticMethod("allocateDirect"); + return meth(cls, size); } }} diff --git a/lib/fb/src/main/cpp/jni/Countable.cpp b/lib/fb/src/main/cpp/jni/Countable.cpp deleted file mode 100644 index bbb6f23d..00000000 --- a/lib/fb/src/main/cpp/jni/Countable.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include -#include -#include - -namespace facebook { -namespace jni { - -static jfieldID gCountableNativePtr; - -static RefPtr* rawCountableFromJava(JNIEnv* env, jobject obj) { - FBASSERT(obj); - return reinterpret_cast*>(env->GetLongField(obj, gCountableNativePtr)); -} - -const RefPtr& countableFromJava(JNIEnv* env, jobject obj) { - FBASSERT(obj); - return *rawCountableFromJava(env, obj); -} - -void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable) { - int oldValue = env->GetLongField(obj, gCountableNativePtr); - FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue); - - FBASSERT(countable); - uintptr_t fieldValue = (uintptr_t) new RefPtr(std::move(countable)); - env->SetLongField(obj, gCountableNativePtr, fieldValue); -} - -/** - * NB: THREAD SAFETY (this comment also exists at Countable.java) - * - * This method deletes the corresponding native object on whatever thread the method is called - * on. In the common case when this is called by Countable#finalize(), this will be called on the - * system finalizer thread. If you manually call dispose on the Java object, the native object - * will be deleted synchronously on that thread. - */ -void dispose(JNIEnv* env, jobject obj) { - // Grab the pointer - RefPtr* countable = rawCountableFromJava(env, obj); - if (!countable) { - // That was easy. - return; - } - - // Clear out the old value to avoid double-frees - env->SetLongField(obj, gCountableNativePtr, 0); - - delete countable; -} - -void CountableOnLoad(JNIEnv* env) { - jclass countable = env->FindClass("com/facebook/jni/Countable"); - gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J"); - registerNatives(env, countable, { - { "dispose", "()V", (void*) dispose }, - }); -} - -} } diff --git a/lib/fb/src/main/cpp/jni/Environment.cpp b/lib/fb/src/main/cpp/jni/Environment.cpp deleted file mode 100644 index 8d37cb52..00000000 --- a/lib/fb/src/main/cpp/jni/Environment.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace facebook { -namespace jni { - -namespace { -StaticInitialized> g_env; -JavaVM* g_vm = nullptr; - -struct JThreadScopeSupport : JavaClass { - 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&& func) { - static auto method = javaClassStatic()->getStaticMethod("runStdFunction"); - method(javaClassStatic(), reinterpret_cast(&func)); - } - - static void runStdFunctionImpl(alias_ref, jlong ptr) { - (*reinterpret_cast*>(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 */ -JNIEnv* Environment::current() { - JNIEnv* env = g_env->get(); - if ((env == nullptr) && (g_vm != nullptr)) { - if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { - FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); - // TODO(cjhopman): This should throw an exception. - env = nullptr; - } else { - g_env->reset(env); - } - } - return env; -} - -/* static */ -void Environment::detachCurrentThread() { - auto env = g_env->get(); - if (env) { - FBASSERT(g_vm); - g_vm->DetachCurrentThread(); - g_env->reset(); - } -} - -struct EnvironmentInitializer { - EnvironmentInitializer(JavaVM* vm) { - FBASSERT(!g_vm); - FBASSERT(vm); - g_vm = vm; - g_env.initialize([] (void*) {}); - } -}; - -/* static */ -void Environment::initialize(JavaVM* vm) { - static EnvironmentInitializer init(vm); -} - -/* static */ -JNIEnv* Environment::ensureCurrentThreadIsAttached() { - auto env = g_env->get(); - if (!env) { - FBASSERT(g_vm); - g_vm->AttachCurrentThread(&env, nullptr); - g_env->reset(env); - } - return env; -} - -ThreadScope::ThreadScope() - : attachedWithThisScope_(false) { - JNIEnv* env = nullptr; - if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) { - return; - } - env = facebook::jni::Environment::ensureCurrentThreadIsAttached(); - FBASSERT(env); - attachedWithThisScope_ = true; -} - -ThreadScope::~ThreadScope() { - if (attachedWithThisScope_) { - Environment::detachCurrentThread(); - } -} - -/* 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&& 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)); -} - -} } - diff --git a/lib/fb/src/main/cpp/jni/Exceptions.cpp b/lib/fb/src/main/cpp/jni/Exceptions.cpp deleted file mode 100644 index 73166c04..00000000 --- a/lib/fb/src/main/cpp/jni/Exceptions.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - - -namespace facebook { -namespace jni { - -namespace { -class JRuntimeException : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; - - static local_ref create(const char* str) { - return newInstance(make_jstring(str)); - } - - static local_ref create() { - return newInstance(); - } -}; - -class JIOException : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; - - static local_ref create(const char* str) { - return newInstance(make_jstring(str)); - } -}; - -class JOutOfMemoryError : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;"; - - static local_ref create(const char* str) { - return newInstance(make_jstring(str)); - } -}; - -class JArrayIndexOutOfBoundsException : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;"; - - static local_ref create(const char* str) { - return newInstance(make_jstring(str)); - } -}; - -class JUnknownCppException : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; - - static local_ref create() { - return newInstance(); - } - - static local_ref create(const char* str) { - return newInstance(make_jstring(str)); - } -}; - -class JCppSystemErrorException : public JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;"; - - static local_ref 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 throwable) { - auto env = Environment::current(); - if (throwable) { - env->Throw(throwable.get()); - } - if (env->ExceptionCheck() != JNI_TRUE) { - std::abort(); - } -} - -} - -// Functions that throw C++ exceptions - -// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated -void throwPendingJniExceptionAsCppException() { - JNIEnv* env = Environment::current(); - if (env->ExceptionCheck() == JNI_FALSE) { - return; - } - - auto throwable = adopt_local(env->ExceptionOccurred()); - if (!throwable) { - throw std::runtime_error("Unable to get pending JNI exception."); - } - env->ExceptionClear(); - - throw JniException(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(), - make_jstring(msg).release()); - throwNewJavaException(throwable.get()); -} - -// Translate C++ to Java Exception - -namespace { - -// 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 { - 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 func) { - try { - throw; - } catch (const std::exception& e) { - try { - rethrow_if_nested(); - } catch (...) { - denest(func); - } - func(); - } catch (...) { - func(); - } -} -} - -void translatePendingCppExceptionToJavaException() noexcept { - local_ref previous; - auto func = [&previous] () { - local_ref current; - try { - throw; - } 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 (previous) { - current->initCause(previous); - } - previous = current; - }; - - try { - denest(func); - setJavaExceptionAndAbortOnFailure(previous); - } catch (std::exception& e) { - FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what()); - // std::terminate will print the message of the pending exception e - std::terminate(); - } catch (...) { - FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException"); - std::terminate(); - } -} - -// JniException //////////////////////////////////////////////////////////////////////////////////// - -const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; - -JniException::JniException() : JniException(JRuntimeException::create()) { } - -JniException::JniException(alias_ref 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() { - ThreadScope ts; - throwable_.reset(); -} - -local_ref JniException::getThrowable() const noexcept { - return make_local(throwable_); -} - -// TODO 6900503: consider making this thread-safe. -void JniException::populateWhat() const noexcept { - ThreadScope ts; - try { - 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_); -} - -}} diff --git a/lib/fb/src/main/cpp/jni/Hybrid.cpp b/lib/fb/src/main/cpp/jni/Hybrid.cpp deleted file mode 100644 index c04560a5..00000000 --- a/lib/fb/src/main/cpp/jni/Hybrid.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include "fb/fbjni.h" - - -namespace facebook { -namespace jni { - -namespace detail { - -void HybridData::setNativePointer(std::unique_ptr new_value) { - static auto pointerField = getClass()->getField("mNativePointer"); - auto* old_value = reinterpret_cast(getFieldValue(pointerField)); - if (new_value) { - // Modify should only ever be called once with a non-null - // new_value. If this happens again it's a programmer error, so - // blow up. - FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice"); - } else if (old_value == 0) { - return; - } - // delete on a null pointer is defined to be a noop. - delete old_value; - // This releases ownership from the unique_ptr, and passes the pointer, and - // ownership of it, to HybridData which is managed by the java GC. The - // finalizer on hybridData calls resetNative which will delete the object, if - // resetNative has not already been called. - setFieldValue(pointerField, reinterpret_cast(new_value.release())); -} - -BaseHybridClass* HybridData::getNativePointer() { - static auto pointerField = getClass()->getField("mNativePointer"); - auto* value = reinterpret_cast(getFieldValue(pointerField)); - if (!value) { - throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); - } - return value; -} - -local_ref HybridData::create() { - return newInstance(); -} - -} - -namespace { -void resetNative(alias_ref jthis) { - jthis->setNativePointer(nullptr); -} -} - -void HybridDataOnLoad() { - registerNatives("com/facebook/jni/HybridData", { - makeNativeMethod("resetNative", resetNative), - }); -} - -}} diff --git a/lib/fb/src/main/cpp/jni/OnLoad.cpp b/lib/fb/src/main/cpp/jni/OnLoad.cpp index a95393f8..db041f5c 100644 --- a/lib/fb/src/main/cpp/jni/OnLoad.cpp +++ b/lib/fb/src/main/cpp/jni/OnLoad.cpp @@ -4,16 +4,15 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include -#include -#include -#include +#include +#include using namespace facebook::jni; -void initialize_fbjni() { - CountableOnLoad(Environment::current()); - HybridDataOnLoad(); - JNativeRunnable::OnLoad(); - ThreadScope::OnLoad(); +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return facebook::jni::initialize(vm, [] { + HybridDataOnLoad(); + JNativeRunnable::OnLoad(); + ThreadScope::OnLoad(); + }); } diff --git a/lib/fb/src/main/cpp/jni/ReadableByteChannel.cpp b/lib/fb/src/main/cpp/jni/ReadableByteChannel.cpp new file mode 100644 index 00000000..2b030b15 --- /dev/null +++ b/lib/fb/src/main/cpp/jni/ReadableByteChannel.cpp @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +namespace facebook { +namespace jni { + +int JReadableByteChannel::read(alias_ref dest) const { + if (!self()) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + static auto method = javaClassStatic()->getMethod)>("read"); + return method(self(), dest); +} + +}} + diff --git a/lib/fb/src/main/cpp/jni/References.cpp b/lib/fb/src/main/cpp/jni/References.cpp deleted file mode 100644 index 71284b0e..00000000 --- a/lib/fb/src/main/cpp/jni/References.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include - -namespace facebook { -namespace jni { - -JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity) - : env_(env) { - hasFrame_ = false; - auto pushResult = env->PushLocalFrame(capacity); - FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0); - hasFrame_ = true; -} - -JniLocalScope::~JniLocalScope() { - if (hasFrame_) { - env_->PopLocalFrame(nullptr); - } -} - -namespace internal { - -// Default implementation always returns true. -// Platform-specific sources can override this. -bool doesGetObjectRefTypeWork() __attribute__ ((weak)); -bool doesGetObjectRefTypeWork() { - return true; -} - -} - -} -} diff --git a/lib/fb/src/main/cpp/jni/WeakReference.cpp b/lib/fb/src/main/cpp/jni/WeakReference.cpp deleted file mode 100644 index 5552a763..00000000 --- a/lib/fb/src/main/cpp/jni/WeakReference.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include - -namespace facebook { -namespace jni { - -WeakReference::WeakReference(jobject strongRef) : - m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef)) -{ -} - -WeakReference::~WeakReference() { - auto env = Environment::current(); - FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread"); - env->DeleteWeakGlobalRef(m_weakReference); -} - -ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) : - m_strongReference(Environment::current()->NewLocalRef(weakRef)) -{ -} - -ResolvedWeakReference::ResolvedWeakReference(const RefPtr& weakRef) : - m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef())) -{ -} - -ResolvedWeakReference::~ResolvedWeakReference() { - if (m_strongReference) - Environment::current()->DeleteLocalRef(m_strongReference); -} - -} } - diff --git a/lib/fb/src/main/cpp/jni/detail/Environment.cpp b/lib/fb/src/main/cpp/jni/detail/Environment.cpp new file mode 100644 index 00000000..6b35358a --- /dev/null +++ b/lib/fb/src/main/cpp/jni/detail/Environment.cpp @@ -0,0 +1,291 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +#include +#include + +namespace facebook { +namespace jni { + +namespace { + +JavaVM* g_vm = nullptr; + +struct EnvironmentInitializer { + EnvironmentInitializer(JavaVM* vm) { + FBJNI_ASSERT(!g_vm); + FBJNI_ASSERT(vm); + g_vm = vm; + } +}; + +int getEnv(JNIEnv** env) { + FBJNI_ASSERT(g_vm); + // g_vm->GetEnv() might not clear the env* in failure cases. + *env = nullptr; + jint ret = g_vm->GetEnv((void**)env, JNI_VERSION_1_6); + // Other possibilites are that JNI_VERSION_1_6 is invalid, or some + // unknown return was received. + FBJNI_ASSERT(ret == JNI_OK || ret == JNI_EDETACHED); + return ret; +} + +// Some jni.h define the first arg to AttachCurrentThread as void**, +// and some as JNIEnv**. This hack allows both to work. + +template +struct AttachTraits; + +template <> +struct AttachTraits { + using EnvType = JNIEnv*; +}; + +template <> +struct AttachTraits { + using EnvType = void*; +}; + +JNIEnv* attachCurrentThread() { + JavaVMAttachArgs args{JNI_VERSION_1_6, nullptr, nullptr}; + using AttachEnvType = + typename AttachTraits::EnvType; + AttachEnvType env; + auto result = g_vm->AttachCurrentThread(&env, &args); + FBJNI_ASSERT(result == JNI_OK); + return reinterpret_cast(env); +} + +} + +/* static */ +void Environment::initialize(JavaVM* vm) { + static EnvironmentInitializer init(vm); +} + +namespace { + +pthread_key_t makeKey() { + pthread_key_t key; + int ret = pthread_key_create(&key, nullptr); + if (ret != 0) { + FBJNI_LOGF("pthread_key_create failed: %d", ret); + } + return key; +} + +pthread_key_t getTLKey() { + static pthread_key_t key = makeKey(); + return key; +} + +inline detail::TLData* getTLData(pthread_key_t key) { + return reinterpret_cast(pthread_getspecific(key)); +} + +inline void setTLData(pthread_key_t key, detail::TLData* data) { + int ret = pthread_setspecific(key, data); + if (ret != 0) { + (void) ret; + FBJNI_LOGF("pthread_setspecific failed: %d", ret); + } +} + +// This returns non-nullptr iff the env was cached from java. So it +// can return nullptr for a thread which has been registered. +inline JNIEnv* cachedOrNull() { + detail::TLData* pdata = getTLData(getTLKey()); + return (pdata ? pdata->env : nullptr); +} + +} + +namespace detail { + +// This will return a cached env if there is one, or get one from JNI +// if the thread has already been attached some other way. If it +// returns nullptr, then the thread has never been registered, or the +// VM has never been set up for fbjni. + +JNIEnv* currentOrNull() { + if (!g_vm) { + return nullptr; + } + + detail::TLData* pdata = getTLData(getTLKey()); + if (pdata && pdata->env) { + return pdata->env; + } + + JNIEnv* env; + if (getEnv(&env) != JNI_OK) { + // If there's a ThreadScope on the stack, we should have gotten a + // JNIEnv and not ended up here. + FBJNI_ASSERT(!pdata || !pdata->attached); + } + return env; +} + +// To understand JniEnvCacher and ThreadScope, it is helpful to +// realize that if a flagged JniEnvCacher is on the stack, then a +// flagged ThreadScope cannot be after it. If a flagged ThreadCacher +// is on the stack, then a JniEnvCacher *can* be after it. So, +// ThreadScope's setup and teardown can both assume they are the +// first/last interesting objects, but this is not true of +// JniEnvCacher. + +JniEnvCacher::JniEnvCacher(JNIEnv* env) + : thisCached_(false) +{ + FBJNI_ASSERT(env); + + pthread_key_t key = getTLKey(); + detail::TLData* pdata = getTLData(key); + if (pdata && pdata->env) { + return; + } + + if (!pdata) { + pdata = &data_; + setTLData(key, pdata); + pdata->attached = false; + } else { + FBJNI_ASSERT(!pdata->env); + } + + pdata->env = env; + + thisCached_ = true; +} + +JniEnvCacher::~JniEnvCacher() { + if (!thisCached_) { + return; + } + + pthread_key_t key = getTLKey(); + TLData* pdata = getTLData(key); + FBJNI_ASSERT(pdata); + FBJNI_ASSERT(pdata->env != nullptr); + pdata->env = nullptr; + if (!pdata->attached) { + setTLData(key, nullptr); + } +} + +} + +ThreadScope::ThreadScope() + : thisAttached_(false) +{ + if (g_vm == nullptr) { + throw std::runtime_error("fbjni is uninitialized; no thread can be attached."); + } + + JNIEnv* env; + + // Check if the thread is attached somehow. + auto result = getEnv(&env); + if (result == JNI_OK) { + return; + } + + // At this point, it appears there's no thread attached and no env is + // cached, or we would have returned already. So there better not + // be TLData. + + pthread_key_t key = getTLKey(); + detail::TLData* pdata = getTLData(key); + FBJNI_ASSERT(pdata == nullptr); + setTLData(key, &data_); + + attachCurrentThread(); + + data_.env = nullptr; + data_.attached = true; + + thisAttached_ = true; +} + +ThreadScope::~ThreadScope() { + if (!thisAttached_) { + return; + } + + pthread_key_t key = getTLKey(); + detail::TLData* pdata = getTLData(key); + FBJNI_ASSERT(pdata); + FBJNI_ASSERT(pdata->env == nullptr); + FBJNI_ASSERT(pdata->attached); + FBJNI_ASSERT(g_vm); + g_vm->DetachCurrentThread(); + setTLData(key, nullptr); +} + +/* static */ +JNIEnv* Environment::current() { + FBJNI_ASSERT(g_vm); + JNIEnv* env = detail::currentOrNull(); + if (env == nullptr) { + throw std::runtime_error("Unable to retrieve jni environment. Is the thread attached?"); + } + return env; +} + +/* static */ +JNIEnv* Environment::ensureCurrentThreadIsAttached() { + FBJNI_ASSERT(g_vm); + JNIEnv* env = detail::currentOrNull(); + if (env == nullptr) { + env = attachCurrentThread(); + FBJNI_ASSERT(env); + } + return env; +} + +namespace { +struct JThreadScopeSupport : JavaClass { + 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&& func) { + static const auto method = javaClassStatic()->getStaticMethod("runStdFunction"); + method(javaClassStatic(), reinterpret_cast(&func)); + } + + static void runStdFunctionImpl(alias_ref, jlong ptr) { + (*reinterpret_cast*>(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 */ +void ThreadScope::OnLoad() { + // These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading. + JThreadScopeSupport::OnLoad(); +} + +/* static */ +void ThreadScope::WithClassLoader(std::function&& runnable) { + if (cachedOrNull() == nullptr) { + ThreadScope ts; + JThreadScopeSupport::runStdFunction(std::move(runnable)); + } else { + runnable(); + } +} + +} } diff --git a/lib/fb/src/main/cpp/jni/detail/Exceptions.cpp b/lib/fb/src/main/cpp/jni/detail/Exceptions.cpp new file mode 100644 index 00000000..77082c6f --- /dev/null +++ b/lib/fb/src/main/cpp/jni/detail/Exceptions.cpp @@ -0,0 +1,390 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include +#include + +#ifndef FBJNI_NO_EXCEPTION_PTR +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +namespace { +class JRuntimeException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } + + static local_ref create() { + return newInstance(); + } +}; + +class JIOException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JOutOfMemoryError : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JArrayIndexOutOfBoundsException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JUnknownCppException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; + + static local_ref create() { + return newInstance(); + } + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JCppSystemErrorException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;"; + + static local_ref 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 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(), + make_jstring(msg).release()); + throwNewJavaException(throwable.get()); +} + +// jthrowable ////////////////////////////////////////////////////////////////////////////////////// + +local_ref JThrowable::initCause(alias_ref cause) { + static auto meth = javaClassStatic()->getMethod)>("initCause"); + return meth(self(), cause); +} + +auto JThrowable::getStackTrace() -> local_ref { + static auto meth = javaClassStatic()->getMethod("getStackTrace"); + return meth(self()); +} + +void JThrowable::setStackTrace(alias_ref stack) { + static auto meth = javaClassStatic()->getMethod)>("setStackTrace"); + return meth(self(), stack); +} + +auto JStackTraceElement::create( + const std::string& declaringClass, const std::string& methodName, const std::string& file, int line) + -> local_ref { + return newInstance(declaringClass, methodName, file, line); +} + +std::string JStackTraceElement::getClassName() const { + static auto meth = javaClassStatic()->getMethod()>("getClassName"); + return meth(self())->toStdString(); +} + +std::string JStackTraceElement::getMethodName() const { + static auto meth = javaClassStatic()->getMethod()>("getMethodName"); + return meth(self())->toStdString(); +} + +std::string JStackTraceElement::getFileName() const { + static auto meth = javaClassStatic()->getMethod()>("getFileName"); + return meth(self())->toStdString(); +} + +int JStackTraceElement::getLineNumber() const { + static auto meth = javaClassStatic()->getMethod("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& 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 createJStackTraceElement(const lyra::StackTraceElement& cpp) { + return JStackTraceElement::create( + "|lyra|{" + cpp.libraryName() + "}", cpp.functionName(), cpp.buildId(), cpp.libraryOffset()); +} + +void addCppStacktraceToJavaException(alias_ref 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 convertCppExceptionToJavaException(std::exception_ptr ptr) { + FBJNI_ASSERT(ptr); + local_ref 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 getJavaExceptionForCppBackTrace() { + return getJavaExceptionForCppBackTrace(nullptr); +} + +local_ref getJavaExceptionForCppBackTrace(const char* msg) { + local_ref 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 getJavaExceptionForCppException(std::exception_ptr ptr) { + FBJNI_ASSERT(ptr); + local_ref 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 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 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_); +} + +}} diff --git a/lib/fb/src/main/cpp/jni/detail/Hybrid.cpp b/lib/fb/src/main/cpp/jni/detail/Hybrid.cpp new file mode 100644 index 00000000..12bcabc5 --- /dev/null +++ b/lib/fb/src/main/cpp/jni/detail/Hybrid.cpp @@ -0,0 +1,32 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +namespace facebook { +namespace jni { + +namespace detail { + +local_ref HybridData::create() { + return newInstance(); +} + +} + +namespace { +void deleteNative(alias_ref, jlong ptr) { + delete reinterpret_cast(ptr); +} +} + +void HybridDataOnLoad() { + registerNatives("com/facebook/jni/HybridData$Destructor", { + makeNativeMethod("deleteNative", deleteNative), + }); +} + +}} diff --git a/lib/fb/src/main/cpp/jni/detail/References.cpp b/lib/fb/src/main/cpp/jni/detail/References.cpp new file mode 100644 index 00000000..fa30832c --- /dev/null +++ b/lib/fb/src/main/cpp/jni/detail/References.cpp @@ -0,0 +1,79 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +namespace facebook { +namespace jni { + +JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity) : env_(env) { + hasFrame_ = false; + auto pushResult = env->PushLocalFrame(capacity); + FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0); + hasFrame_ = true; +} + +JniLocalScope::~JniLocalScope() { + if (hasFrame_) { + env_->PopLocalFrame(nullptr); + } +} + +namespace { + +#ifdef __ANDROID__ + +int32_t getAndroidApiLevel() { + // This is called from the static local initializer in + // isObjectRefType(), and creating fbjni references can call + // isObjectRefType(). So, to avoid recursively entering the block + // where the static is initialized (which is undefined behavior), we + // avoid using standard fbjni references here. + + JNIEnv* env = Environment::current(); + jclass cls = detail::findClass(env, "android/os/Build$VERSION"); + jfieldID field = env->GetStaticFieldID( + cls, "SDK_INT", jtype_traits::descriptor().c_str()); + if (!field) { + env->DeleteLocalRef(cls); + } + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + int32_t ret = env->GetStaticIntField(cls, field); + env->DeleteLocalRef(cls); + return ret; +} + +bool doesGetObjectRefTypeWork() { + auto level = getAndroidApiLevel(); + return level >= 14; +} + +#else + +bool doesGetObjectRefTypeWork() { + auto jni_version = Environment::current()->GetVersion(); + return jni_version >= JNI_VERSION_1_6; +} + +#endif + +} // namespace + +bool isObjectRefType(jobject reference, jobjectRefType refType) { + // null-check first so that we short-circuit during (safe) global + // constructors, where we won't have an Environment::current() yet + if (!reference) { + return true; + } + + static bool getObjectRefTypeWorks = doesGetObjectRefTypeWork(); + + return !getObjectRefTypeWorks || + Environment::current()->GetObjectRefType(reference) == refType; +} + +} // namespace jni +} // namespace facebook diff --git a/lib/fb/src/main/cpp/jni/LocalString.cpp b/lib/fb/src/main/cpp/jni/detail/utf8.cpp similarity index 78% rename from lib/fb/src/main/cpp/jni/LocalString.cpp rename to lib/fb/src/main/cpp/jni/detail/utf8.cpp index 9233ec02..9483c911 100644 --- a/lib/fb/src/main/cpp/jni/LocalString.cpp +++ b/lib/fb/src/main/cpp/jni/detail/utf8.cpp @@ -4,11 +4,9 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include -#include -#include +#include -#include +#include namespace facebook { namespace jni { @@ -22,7 +20,9 @@ const uint16_t kUtf16HighSubHighBoundary = 0xDC00; const uint16_t kUtf16LowSubHighBoundary = 0xE000; inline void encode3ByteUTF8(char32_t code, uint8_t* out) { - FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits"); + if ((code & 0xffff0000) != 0) { + FBJNI_LOGF("3 byte utf-8 encodings only valid for up to 16 bits"); + } out[0] = 0xE0 | (code >> 12); out[1] = 0x80 | ((code >> 6) & 0x3F); @@ -36,7 +36,9 @@ inline char32_t decode3ByteUTF8(const uint8_t* in) { } inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) { - FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits"); + if ((code & 0xfff80000) != 0) { + FBJNI_LOGF("4 byte utf-8 encodings only valid for up to 21 bits"); + } out[offset] = (char) (0xF0 | (code >> 18)); out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F)); @@ -100,9 +102,13 @@ void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size { size_t j = 0; for (size_t i = 0; i < len; ) { - FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + if (j >= modifiedBufLen) { + FBJNI_LOGF("output buffer is too short"); + } if (utf8[i] == 0) { - FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short"); + if (j + 1 >= modifiedBufLen) { + FBJNI_LOGF("output buffer is too short"); + } modified[j] = 0xc0; modified[j + 1] = 0x80; i += 1; @@ -142,14 +148,18 @@ void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size } // encode each as a 3 byte surrogate value - FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short"); + if (j + 5 >= modifiedBufLen) { + FBJNI_LOGF("output buffer is too short"); + } encode3ByteUTF8(first, modified + j); encode3ByteUTF8(second, modified + j + 3); i += 4; j += 6; } - FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + if (j >= modifiedBufLen) { + FBJNI_LOGF("output buffer is too short"); + } modified[j++] = '\0'; } @@ -264,46 +274,5 @@ std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noex } } - -LocalString::LocalString(const std::string& str) -{ - size_t modlen = detail::modifiedLength(str); - if (modlen == str.size()) { - // no supplementary characters, build jstring from input buffer - m_string = Environment::current()->NewStringUTF(str.data()); - return; - } - auto modified = std::vector(modlen + 1); // allocate extra byte for \0 - detail::utf8ToModifiedUTF8( - reinterpret_cast(str.data()), str.size(), - reinterpret_cast(modified.data()), modified.size()); - m_string = Environment::current()->NewStringUTF(modified.data()); } - -LocalString::LocalString(const char* str) -{ - size_t len; - size_t modlen = detail::modifiedLength(reinterpret_cast(str), &len); - if (modlen == len) { - // no supplementary characters, build jstring from input buffer - m_string = Environment::current()->NewStringUTF(str); - return; - } - auto modified = std::vector(modlen + 1); // allocate extra byte for \0 - detail::utf8ToModifiedUTF8( - reinterpret_cast(str), len, - reinterpret_cast(modified.data()), modified.size()); - m_string = Environment::current()->NewStringUTF(modified.data()); } - -LocalString::~LocalString() { - Environment::current()->DeleteLocalRef(m_string); -} - -std::string fromJString(JNIEnv* env, jstring str) { - auto utf16String = JStringUtf16Extractor(env, str); - auto length = env->GetStringLength(str); - return detail::utf16toUTF8(utf16String, length); -} - -} } diff --git a/lib/fb/src/main/cpp/jni/fbjni.cpp b/lib/fb/src/main/cpp/jni/fbjni.cpp index c556fabb..39cc02c1 100644 --- a/lib/fb/src/main/cpp/jni/fbjni.cpp +++ b/lib/fb/src/main/cpp/jni/fbjni.cpp @@ -4,36 +4,35 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include #include #include -#include -#include + +#include namespace facebook { namespace jni { jint initialize(JavaVM* vm, std::function&& init_fn) noexcept { - static std::once_flag flag{}; // TODO (t7832883): DTRT when we have exception pointers static auto error_msg = std::string{"Failed to initialize fbjni"}; - static auto error_occured = false; - - std::call_once(flag, [vm] { - try { - Environment::initialize(vm); - } catch (std::exception& ex) { - error_occured = true; + static bool error_occured = [vm] { + bool retVal = false; try { - error_msg = std::string{"Failed to initialize fbjni: "} + ex.what(); + Environment::initialize(vm); + } catch (std::exception& ex) { + retVal = true; + try { + error_msg = std::string{"Failed to initialize fbjni: "} + ex.what(); + } catch (...) { + // Ignore, we already have a fall back message + } } catch (...) { - // Ignore, we already have a fall back message + retVal = true; } - } catch (...) { - error_occured = true; - } - }); + return retVal; + }(); try { if (error_occured) { @@ -42,7 +41,7 @@ jint initialize(JavaVM* vm, std::function&& init_fn) noexcept { init_fn(); } catch (const std::exception& e) { - FBLOGE("error %s", e.what()); + FBJNI_LOGE("error %s", e.what()); translatePendingCppExceptionToJavaException(); } catch (...) { translatePendingCppExceptionToJavaException(); @@ -52,43 +51,54 @@ jint initialize(JavaVM* vm, std::function&& init_fn) noexcept { return JNI_VERSION_1_6; } -alias_ref findClassStatic(const char* name) { - const auto env = internal::getEnv(); +namespace detail { + +jclass findClass(JNIEnv* env, const char* name) { if (!env) { throw std::runtime_error("Unable to retrieve JNIEnv*."); } - auto cls = env->FindClass(name); + jclass cls = env->FindClass(name); FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); - auto leaking_ref = (jclass)env->NewGlobalRef(cls); - FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref); - return wrap_alias(leaking_ref); + return cls; +} + } local_ref findClassLocal(const char* name) { - const auto env = internal::getEnv(); - if (!env) { - throw std::runtime_error("Unable to retrieve JNIEnv*."); - } - auto cls = env->FindClass(name); - FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); - return adopt_local(cls); + return adopt_local(detail::findClass(detail::currentOrNull(), name)); +} + +alias_ref findClassStatic(const char* name) { + JNIEnv* env = detail::currentOrNull(); + auto cls = adopt_local(detail::findClass(env, name)); + auto leaking_ref = (jclass)env->NewGlobalRef(cls.get()); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref); + return wrap_alias(leaking_ref); } // jstring ///////////////////////////////////////////////////////////////////////////////////////// std::string JString::toStdString() const { - const auto env = internal::getEnv(); + const auto env = Environment::current(); auto utf16String = JStringUtf16Extractor(env, self()); - auto length = env->GetStringLength(self()); - return detail::utf16toUTF8(utf16String, length); + return detail::utf16toUTF8(utf16String.chars(), utf16String.length()); +} + +std::u16string JString::toU16String() const { + const auto env = Environment::current(); + auto utf16String = JStringUtf16Extractor(env, self()); + if (!utf16String.chars() || utf16String.length() == 0) { + return {}; + } + return std::u16string(reinterpret_cast(utf16String.chars()), utf16String.length()); } local_ref make_jstring(const char* utf8) { if (!utf8) { return {}; } - const auto env = internal::getEnv(); + const auto env = Environment::current(); size_t len; size_t modlen = detail::modifiedLength(reinterpret_cast(utf8), &len); jstring result; @@ -111,6 +121,18 @@ local_ref make_jstring(const char* utf8) { return adopt_local(result); } +local_ref make_jstring(const std::u16string& utf16) { + if (utf16.empty()) { + return {}; + } + const auto env = Environment::current(); + static_assert( + sizeof(jchar) == sizeof(std::u16string::value_type), + "Expecting jchar to be the same size as std::u16string::CharT"); + jstring result = env->NewString(reinterpret_cast(utf16.c_str()), utf16.size()); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(result); +} // JniPrimitiveArrayFunctions ////////////////////////////////////////////////////////////////////// @@ -119,50 +141,44 @@ local_ref make_jstring(const char* utf8) { #define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME) \ \ template<> \ -FBEXPORT \ TYPE* JPrimitiveArray::getElements(jboolean* isCopy) { \ - auto env = internal::getEnv(); \ + auto env = Environment::current(); \ TYPE* res = env->Get ## NAME ## ArrayElements(self(), isCopy); \ FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ return res; \ } \ \ template<> \ -FBEXPORT \ void JPrimitiveArray::releaseElements( \ TYPE* elements, jint mode) { \ - auto env = internal::getEnv(); \ + auto env = Environment::current(); \ env->Release ## NAME ## ArrayElements(self(), elements, mode); \ FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ } \ \ template<> \ -FBEXPORT \ void JPrimitiveArray::getRegion( \ jsize start, jsize length, TYPE* buf) { \ - auto env = internal::getEnv(); \ + auto env = Environment::current(); \ env->Get ## NAME ## ArrayRegion(self(), start, length, buf); \ FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ } \ \ template<> \ -FBEXPORT \ void JPrimitiveArray::setRegion( \ jsize start, jsize length, const TYPE* elements) { \ - auto env = internal::getEnv(); \ + auto env = Environment::current(); \ env->Set ## NAME ## ArrayRegion(self(), start, length, elements); \ FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ } \ \ -FBEXPORT \ local_ref make_ ## SMALLNAME ## _array(jsize size) { \ - auto array = internal::getEnv()->New ## NAME ## Array(size); \ + auto array = Environment::current()->New ## NAME ## Array(size); \ FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \ return adopt_local(array); \ } \ \ template<> \ -FBEXPORT \ local_ref JArray ## NAME::newArray(size_t count) { \ return make_ ## SMALLNAME ## _array(count); \ } \ @@ -178,13 +194,37 @@ DEFINE_PRIMITIVE_METHODS(jfloat, Float, float) DEFINE_PRIMITIVE_METHODS(jdouble, Double, double) #pragma pop_macro("DEFINE_PRIMITIVE_METHODS") +namespace detail { + +detail::BaseHybridClass* HybridDestructor::getNativePointer() { + static auto pointerField = javaClassStatic()->getField("mNativePointer"); + auto* value = reinterpret_cast(getFieldValue(pointerField)); + if (!value) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return value; +} + +void HybridDestructor::setNativePointer( + std::unique_ptr new_value) { + static auto pointerField = javaClassStatic()->getField("mNativePointer"); + auto old_value = std::unique_ptr( + reinterpret_cast(getFieldValue(pointerField))); + if (new_value && old_value) { + FBJNI_LOGF("Attempt to set C++ native pointer twice"); + } + setFieldValue(pointerField, reinterpret_cast(new_value.release())); +} + +} + // Internal debug ///////////////////////////////////////////////////////////////////////////////// namespace internal { -FBEXPORT ReferenceStats g_reference_stats; +ReferenceStats g_reference_stats; -FBEXPORT void facebook::jni::internal::ReferenceStats::reset() noexcept { +void facebook::jni::internal::ReferenceStats::reset() noexcept { locals_deleted = globals_deleted = weaks_deleted = 0; } diff --git a/lib/fb/src/main/cpp/jni/jni_helpers.cpp b/lib/fb/src/main/cpp/jni/jni_helpers.cpp deleted file mode 100644 index c45217bf..00000000 --- a/lib/fb/src/main/cpp/jni/jni_helpers.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include -#include - -#include - -#define MSG_SIZE 1024 - -namespace facebook { - -/** - * Instructs the JNI environment to throw an exception. - * - * @param pEnv JNI environment - * @param szClassName class name to throw - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args) { - char szMsg[MSG_SIZE]; - vsnprintf(szMsg, MSG_SIZE, szFmt, va_args); - jclass exClass = pEnv->FindClass(szClassName); - return pEnv->ThrowNew(exClass, szMsg); -} - -/** - * Instructs the JNI environment to throw a NoClassDefFoundError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/NoClassDefFoundError", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw a RuntimeException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/RuntimeException", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw an IllegalArgumentException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/IllegalArgumentException", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw an IllegalStateException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/IllegalStateException", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw an OutOfMemoryError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/OutOfMemoryError", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw an AssertionError. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/lang/AssertionError", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Instructs the JNI environment to throw an IOException. - * - * @param pEnv JNI environment - * @param szFmt sprintf-style format string - * @param ... sprintf-style args - * @return 0 on success; a negative value on failure - */ -jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...) { - va_list va_args; - va_start(va_args, szFmt); - jint ret = throwException(pEnv, "java/io/IOException", szFmt, va_args); - va_end(va_args); - return ret; -} - -/** - * Finds the specified class. If it's not found, instructs the JNI environment to throw an - * exception. - * - * @param pEnv JNI environment - * @param szClassName the classname to find in JNI format (e.g. "java/lang/String") - * @return the class or NULL if not found (in which case a pending exception will be queued). This - * returns a global reference (JNIEnv::NewGlobalRef). - */ -jclass findClassOrThrow(JNIEnv* pEnv, const char* szClassName) { - jclass clazz = pEnv->FindClass(szClassName); - if (!clazz) { - return NULL; - } - return (jclass) pEnv->NewGlobalRef(clazz); -} - -/** - * Finds the specified field of the specified class. If it's not found, instructs the JNI - * environment to throw an exception. - * - * @param pEnv JNI environment - * @param clazz the class to lookup the field in - * @param szFieldName the name of the field to find - * @param szSig the signature of the field - * @return the field or NULL if not found (in which case a pending exception will be queued) - */ -jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig) { - return pEnv->GetFieldID(clazz, szFieldName, szSig); -} - -/** - * Finds the specified method of the specified class. If it's not found, instructs the JNI - * environment to throw an exception. - * - * @param pEnv JNI environment - * @param clazz the class to lookup the method in - * @param szMethodName the name of the method to find - * @param szSig the signature of the method - * @return the method or NULL if not found (in which case a pending exception will be queued) - */ -jmethodID getMethodIdOrThrow( - JNIEnv* pEnv, - jclass clazz, - const char* szMethodName, - const char* szSig) { - return pEnv->GetMethodID(clazz, szMethodName, szSig); -} - -} // namespace facebook diff --git a/lib/fb/src/main/cpp/log.cpp b/lib/fb/src/main/cpp/log.cpp deleted file mode 100644 index 8d22bcbc..00000000 --- a/lib/fb/src/main/cpp/log.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#include -#include -#include - -#define LOG_BUFFER_SIZE 4096 -static LogHandler gLogHandler; - -void setLogHandler(LogHandler logHandler) { - gLogHandler = logHandler; -} - -int fb_printLog(int prio, const char *tag, const char *fmt, ...) { - char logBuffer[LOG_BUFFER_SIZE]; - - va_list va_args; - va_start(va_args, fmt); - int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args); - va_end(va_args); - if (gLogHandler != NULL) { - gLogHandler(prio, tag, logBuffer); - } - __android_log_write(prio, tag, logBuffer); - return result; -} - -void logPrintByDelims(int priority, const char* tag, const char* delims, - const char* msg, ...) -{ - va_list ap; - char buf[32768]; - char* context; - char* tok; - - va_start(ap, msg); - vsnprintf(buf, sizeof(buf), msg, ap); - va_end(ap); - - tok = strtok_r(buf, delims, &context); - - if (!tok) { - return; - } - - do { - __android_log_write(priority, tag, tok); - } while ((tok = strtok_r(NULL, delims, &context))); -} - -#ifndef ANDROID - -// Implementations of the basic android logging functions for non-android platforms. - -static char logTagChar(int prio) { - switch (prio) { - default: - case ANDROID_LOG_UNKNOWN: - case ANDROID_LOG_DEFAULT: - case ANDROID_LOG_SILENT: - return ' '; - case ANDROID_LOG_VERBOSE: - return 'V'; - case ANDROID_LOG_DEBUG: - return 'D'; - case ANDROID_LOG_INFO: - return 'I'; - case ANDROID_LOG_WARN: - return 'W'; - case ANDROID_LOG_ERROR: - return 'E'; - case ANDROID_LOG_FATAL: - return 'F'; - } -} - -int __android_log_write(int prio, const char *tag, const char *text) { - return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text); -} - -int __android_log_print(int prio, const char *tag, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - - int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag); - res += vfprintf(stderr, "%s\n", ap); - - va_end(ap); - return res; -} - -#endif diff --git a/lib/fb/src/main/cpp/lyra/cxa_throw.cpp b/lib/fb/src/main/cpp/lyra/cxa_throw.cpp new file mode 100644 index 00000000..bfdc241d --- /dev/null +++ b/lib/fb/src/main/cpp/lyra/cxa_throw.cpp @@ -0,0 +1,106 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace lyra { + +namespace { +std::atomic enableBacktraces{true}; +} + +void enableCxaThrowHookBacktraces(bool enable) { + enableBacktraces.store(enable, std::memory_order_relaxed); +} + +[[gnu::noreturn]] void (*original_cxa_throw)(void*, const std::type_info*, void (*) (void *)); + +#if defined(_LIBCPP_VERSION) +[[noreturn]] void cxa_throw(void* obj, const std::type_info* type, void (*destructor) (void *)) { + // lyra doesn't have support yet for libc++. + original_cxa_throw(obj, type, destructor); +} +#else + +using namespace detail; + +namespace { + +const auto traceHolderType = + static_cast(&typeid(ExceptionTraceHolder)); + +// lyra's __cxa_throw attaches stack trace information to thrown exceptions. It basically does: +// 1. capture stack trace +// 2. construct a new type_info struct that: +// a. holds the ExceptionTraceHolder +// b. supports upcasting to lyra::ExceptionTraceHolder* (by just returning the holder member) +// c. acts like the original exception type_info otherwise +// 3. call original __cxa_throw() with original exception pointer, the +// HijackedExceptionTypeInfo, and HijackedExceptionTypeInfo::destructor +// (which will both delete the constructed type info and call the original +// destructor). +struct HijackedExceptionTypeInfo : public abi::__class_type_info { + HijackedExceptionTypeInfo(void* obj, const std::type_info* base, void(*destructor)(void*)) + : abi::__class_type_info{base->name()}, base_{base}, orig_dest_{destructor} { + } + + bool __is_pointer_p() const override { + return base_->__is_pointer_p(); + } + + bool __is_function_p() const override { + return base_->__is_function_p(); + } + + bool __do_catch(const type_info *__thr_type, void **__thr_obj, unsigned __outer) const override { + return base_->__do_catch(__thr_type, __thr_obj, __outer); + } + + bool __do_upcast(const abi::__class_type_info *__target, void **__obj_ptr) const override { + if (__target == traceHolderType) { + *__obj_ptr = (void*)&stack_; + return true; + } + return base_->__do_upcast(__target, __obj_ptr); + } + + static void destructor(void* obj) { + auto exc_ptr = reinterpret_cast(&obj); + auto info = reinterpret_cast(exc_ptr->__cxa_exception_type()); + auto mutable_info = static_cast(const_cast(info)); + mutable_info->orig_dest_(obj); + delete mutable_info; + } + + private: + const std::type_info* base_; + void (*orig_dest_)(void*); + ExceptionTraceHolder stack_; +}; + +} // namespace + +[[noreturn]] void cxa_throw(void* obj, const std::type_info* type, void (*destructor) (void *)) { + if (enableBacktraces.load(std::memory_order_relaxed)) { + if (!type->__do_upcast(traceHolderType, &obj)) { + type = new HijackedExceptionTypeInfo(obj, type, destructor); + destructor = HijackedExceptionTypeInfo::destructor; + } + } + original_cxa_throw(obj, type, destructor); +} + +#endif // libc++ + +} // namespace lyra +} // namespace facebook diff --git a/lib/fb/src/main/cpp/lyra/lyra.cpp b/lib/fb/src/main/cpp/lyra/lyra.cpp index 4d125da7..599a360f 100644 --- a/lib/fb/src/main/cpp/lyra/lyra.cpp +++ b/lib/fb/src/main/cpp/lyra/lyra.cpp @@ -4,15 +4,20 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include +#include #include +#include +#include #include #include #include #include +#include + using namespace std; namespace facebook { @@ -67,6 +72,27 @@ void captureBacktrace(size_t skip, vector& stackTrace) { BacktraceState state = {skip, stackTrace}; _Unwind_Backtrace(unwindCallback, &state); } + +// this is a pointer to a function +std::atomic gLibraryIdentifierFunction{nullptr}; + +} + +void setLibraryIdentifierFunction(LibraryIdentifierFunctionType func) { + gLibraryIdentifierFunction.store(func, std::memory_order_relaxed); +} + +std::string StackTraceElement::buildId() const { + if (!hasBuildId_) { + auto getBuildId = gLibraryIdentifierFunction.load(std::memory_order_relaxed); + if (getBuildId) { + buildId_ = getBuildId(libraryName()); + } else { + buildId_ = ""; + } + hasBuildId_ = true; + } + return buildId_; } void getStackTrace(vector& stackTrace, size_t skip) { @@ -93,7 +119,6 @@ void getStackTraceSymbols(vector& symbols, ostream& operator<<(ostream& out, const StackTraceElement& elm) { IosFlagsSaver flags{out}; - // TODO(t10748683): Add build id to the output out << "{dso=" << elm.libraryName() << " offset=" << hex << showbase << elm.libraryOffset(); @@ -101,7 +126,7 @@ ostream& operator<<(ostream& out, const StackTraceElement& elm) { out << " func=" << elm.functionName() << "()+" << elm.functionOffset(); } - out << " build-id=" << hex << setw(8) << 0 + out << " build-id=" << hex << setw(8) << elm.buildId() << "}"; return out; @@ -120,5 +145,28 @@ ostream& operator<<(ostream& out, const vector& trace) { return out; } + +void logStackTrace(const vector& trace) { + auto i = 0; + FBJNI_LOGE("Backtrace:"); + for (auto& elm : trace) { + if (!elm.functionName().empty()) { + FBJNI_LOGE(" #%02d |lyra|{dso=%s offset=%#x func=%s+%#x build-id=%s}", + i++, + elm.libraryName().c_str(), + elm.libraryOffset(), + elm.functionName().c_str(), + elm.functionOffset(), + elm.buildId().c_str()); + } else { + FBJNI_LOGE(" #%02d |lyra|{dso=%s offset=%#x build-id=%s}", + i++, + elm.libraryName().c_str(), + elm.libraryOffset(), + elm.buildId().c_str()); + } + } +} + } } diff --git a/lib/fb/src/main/cpp/lyra/lyra_breakpad.cpp b/lib/fb/src/main/cpp/lyra/lyra_breakpad.cpp new file mode 100644 index 00000000..b54731a1 --- /dev/null +++ b/lib/fb/src/main/cpp/lyra/lyra_breakpad.cpp @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +namespace facebook { +namespace lyra { + +/** + * This can be overridden by an implementation capable of looking up + * the breakpad id for logging purposes. +*/ +__attribute__((weak)) +std::string getBreakpadId(const std::string& library) { + return ""; +} + +} +} diff --git a/lib/fb/src/main/cpp/lyra/lyra_exceptions.cpp b/lib/fb/src/main/cpp/lyra/lyra_exceptions.cpp new file mode 100644 index 00000000..c07e6fdb --- /dev/null +++ b/lib/fb/src/main/cpp/lyra/lyra_exceptions.cpp @@ -0,0 +1,88 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +#include + +#include +#include +#include +#include + +#include + +namespace facebook { +namespace lyra { + +using namespace detail; + +namespace { +std::terminate_handler gTerminateHandler; + +const ExceptionTraceHolder* getExceptionTraceHolder(std::exception_ptr ptr) { + try { + std::rethrow_exception(ptr); + } catch (const ExceptionTraceHolder& holder) { + return &holder; + } catch (...) { + return nullptr; + } +} + +void logExceptionAndAbort() { + if (auto ptr = std::current_exception()) { + FBJNI_LOGE("Uncaught exception: %s", toString(ptr).c_str()); + auto trace = getExceptionTraceHolder(ptr); + if (trace) { + logStackTrace(getStackTraceSymbols(trace->stackTrace_)); + } + } + if (gTerminateHandler) { + gTerminateHandler(); + } else { + FBJNI_LOGF("Uncaught exception and no gTerminateHandler set"); + } +} + +const std::vector emptyTrace; +} // namespace + +ExceptionTraceHolder::~ExceptionTraceHolder() {} + +detail::ExceptionTraceHolder::ExceptionTraceHolder() { + // TODO(cjhopman): This should be done more safely (i.e. use preallocated space, etc.). + stackTrace_.reserve(128); + getStackTrace(stackTrace_, 1); +} + + +void ensureRegisteredTerminateHandler() { + static auto initializer = (gTerminateHandler = std::set_terminate(logExceptionAndAbort)); + (void)initializer; +} + +const std::vector& getExceptionTrace(std::exception_ptr ptr) { + auto holder = getExceptionTraceHolder(ptr); + return holder ? holder->stackTrace_ : emptyTrace; +} + +std::string toString(std::exception_ptr ptr) { + if (!ptr) { + return "No exception"; + } + + try { + std::rethrow_exception(ptr); + } catch (std::exception& e) { + std::stringstream ss; + ss << typeid(e).name() << ": " << e.what(); + return ss.str(); + } catch (...) { + return "Unknown exception"; + } +} + +} +} diff --git a/lib/fb/src/main/cpp/onload.cpp b/lib/fb/src/main/cpp/onload.cpp deleted file mode 100644 index c451bcba..00000000 --- a/lib/fb/src/main/cpp/onload.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the LICENSE - * file in the root directory of this source tree. - */ -#include -#ifndef DISABLE_CPUCAP -#include -#endif -#include - -using namespace facebook::jni; - -void initialize_xplatinit(); -void initialize_fbjni(); - -JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { - return facebook::jni::initialize(vm, [] { - initialize_fbjni(); -#ifndef DISABLE_XPLAT - initialize_xplatinit(); -#endif -#ifndef DISABLE_CPUCAP - initialize_cpucapabilities(); -#endif - }); -} diff --git a/lib/fb/src/main/java/com/facebook/jni/Countable.java b/lib/fb/src/main/java/com/facebook/jni/Countable.java deleted file mode 100644 index f8e343da..00000000 --- a/lib/fb/src/main/java/com/facebook/jni/Countable.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.jni; - -import com.facebook.proguard.annotations.DoNotStrip; - -/** - * A Java Object that has native memory allocated corresponding to this instance. - * - * NB: THREAD SAFETY (this comment also exists at Countable.cpp) - * - * {@link #dispose} deletes the corresponding native object on whatever thread the method is called - * on. In the common case when this is called by Countable#finalize(), this will be called on the - * system finalizer thread. If you manually call dispose on the Java object, the native object - * will be deleted synchronously on that thread. - */ -@DoNotStrip -public class Countable { - - // Private C++ instance - @DoNotStrip - private long mInstance = 0; - - public native void dispose(); - - protected void finalize() throws Throwable { - dispose(); - super.finalize(); - } -} diff --git a/lib/fb/src/main/java/com/facebook/jni/CppException.java b/lib/fb/src/main/java/com/facebook/jni/CppException.java index 16766cc0..360d29ed 100644 --- a/lib/fb/src/main/java/com/facebook/jni/CppException.java +++ b/lib/fb/src/main/java/com/facebook/jni/CppException.java @@ -1,13 +1,12 @@ -/* +/** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; @DoNotStrip public class CppException extends RuntimeException { diff --git a/lib/fb/src/main/java/com/facebook/jni/CppSystemErrorException.java b/lib/fb/src/main/java/com/facebook/jni/CppSystemErrorException.java index 865246e5..c95a7ec5 100644 --- a/lib/fb/src/main/java/com/facebook/jni/CppSystemErrorException.java +++ b/lib/fb/src/main/java/com/facebook/jni/CppSystemErrorException.java @@ -1,13 +1,12 @@ -/* +/** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; @DoNotStrip public class CppSystemErrorException extends CppException { diff --git a/lib/fb/src/main/java/com/facebook/jni/DestructorThread.java b/lib/fb/src/main/java/com/facebook/jni/DestructorThread.java new file mode 100644 index 00000000..5cd86a90 --- /dev/null +++ b/lib/fb/src/main/java/com/facebook/jni/DestructorThread.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +package com.facebook.jni; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A thread which invokes the "destruct" routine for objects after they have been garbage collected. + * + *

An object which needs to be destructed should create a static subclass of {@link Destructor}. + * Once the referent object is garbage collected, the DestructorThread will callback to the {@link + * Destructor#destruct()} method. + * + *

The underlying thread in DestructorThread starts when the first Destructor is constructed and + * then runs indefinitely. + */ +public class DestructorThread { + + /** + * N.B The Destructor SHOULD NOT refer back to its referent object either explicitly or + * implicitly (for example, as a non-static inner class). This will create a reference cycle where + * the referent object will never be garbage collected. + */ + public abstract static class Destructor extends PhantomReference { + + private Destructor next; + private Destructor previous; + + public Destructor(Object referent) { + super(referent, sReferenceQueue); + sDestructorStack.push(this); + } + + private Destructor() { + super(null, sReferenceQueue); + } + + /** Callback which is invoked when the original object has been garbage collected. */ + protected abstract void destruct(); + } + + /** A list to keep all active Destructors in memory confined to the Destructor thread. */ + private static final DestructorList sDestructorList; + /** A thread safe stack where new Destructors are placed before being add to sDestructorList. */ + private static final DestructorStack sDestructorStack; + + private static final ReferenceQueue sReferenceQueue; + private static final Thread sThread; + + static { + sDestructorStack = new DestructorStack(); + sReferenceQueue = new ReferenceQueue(); + sDestructorList = new DestructorList(); + sThread = + new Thread("HybridData DestructorThread") { + @Override + public void run() { + while (true) { + try { + Destructor current = (Destructor) sReferenceQueue.remove(); + current.destruct(); + + // If current is in the sDestructorStack, + // transfer all the Destructors in the stack to the list. + if (current.previous == null) { + sDestructorStack.transferAllToList(); + } + + DestructorList.drop(current); + } catch (InterruptedException e) { + // Continue. This thread should never be terminated. + } + } + } + }; + + sThread.start(); + } + + private static class Terminus extends Destructor { + @Override + protected void destruct() { + throw new IllegalStateException("Cannot destroy Terminus Destructor."); + } + } + + /** This is a thread safe, lock-free Treiber-like Stack of Destructors. */ + private static class DestructorStack { + private final AtomicReference mHead = new AtomicReference<>(); + + public void push(Destructor newHead) { + Destructor oldHead; + do { + oldHead = mHead.get(); + newHead.next = oldHead; + } while (!mHead.compareAndSet(oldHead, newHead)); + } + + public void transferAllToList() { + Destructor current = mHead.getAndSet(null); + while (current != null) { + Destructor next = current.next; + sDestructorList.enqueue(current); + current = next; + } + } + } + + /** A doubly-linked list of Destructors. */ + private static class DestructorList { + private final Destructor mHead; + + public DestructorList() { + mHead = new Terminus(); + mHead.next = new Terminus(); + mHead.next.previous = mHead; + } + + public void enqueue(Destructor current) { + current.next = mHead.next; + mHead.next = current; + + current.next.previous = current; + current.previous = mHead; + } + + private static void drop(Destructor current) { + current.next.previous = current.previous; + current.previous.next = current.next; + } + } +} diff --git a/lib/fb/src/main/cpp/include/fb/visibility.h b/lib/fb/src/main/java/com/facebook/jni/HybridClassBase.java similarity index 56% rename from lib/fb/src/main/cpp/include/fb/visibility.h rename to lib/fb/src/main/java/com/facebook/jni/HybridClassBase.java index 310fbe11..6699ecbd 100644 --- a/lib/fb/src/main/cpp/include/fb/visibility.h +++ b/lib/fb/src/main/java/com/facebook/jni/HybridClassBase.java @@ -4,6 +4,9 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#pragma once +package com.facebook.jni; -#define FBEXPORT __attribute__((visibility("default"))) +import com.facebook.jni.annotations.DoNotStrip; + +@DoNotStrip +public abstract class HybridClassBase extends HybridData {} diff --git a/lib/fb/src/main/java/com/facebook/jni/HybridData.java b/lib/fb/src/main/java/com/facebook/jni/HybridData.java index 35803a56..f083604d 100644 --- a/lib/fb/src/main/java/com/facebook/jni/HybridData.java +++ b/lib/fb/src/main/java/com/facebook/jni/HybridData.java @@ -1,44 +1,77 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; /** * This object holds a native C++ member for hybrid Java/C++ objects. * - * NB: THREAD SAFETY + *

NB: THREAD SAFETY * - * {@link #dispose} deletes the corresponding native object on whatever thread - * the method is called on. In the common case when this is called by - * HybridData#finalize(), this will be called on the system finalizer - * thread. If you manually call resetNative() on the Java object, the C++ - * object will be deleted synchronously on that thread. + *

{@link #resetNative} deletes the corresponding native object synchronously on whatever thread + * the method is called on. Otherwise, deletion will occur on the {@link DestructorThread} thread. */ @DoNotStrip public class HybridData { - // Private C++ instance - @DoNotStrip - private long mNativePointer = 0; + static { + SoLoader.loadLibrary("fbjni"); + } + + @DoNotStrip private Destructor mDestructor = new Destructor(this); /** - * To explicitly delete the instance, call resetNative(). If the C++ - * instance is referenced after this is called, a NullPointerException will - * be thrown. resetNative() may be called multiple times safely. Because - * {@link #finalize} calls resetNative, the instance will not leak if this is - * not called, but timing of deletion and the thread the C++ dtor is called - * on will be at the whim of the Java GC. If you want to control the thread - * and timing of the destructor, you should call resetNative() explicitly. + * To explicitly delete the instance, call resetNative(). If the C++ instance is referenced after + * this is called, a NullPointerException will be thrown. resetNative() may be called multiple + * times safely. Because the {@link DestructorThread} also calls resetNative, the instance will + * not leak if this is not called, but timing of deletion and the thread the C++ dtor is called on + * will be at the whim of the Java GC. If you want to control the thread and timing of the + * destructor, you should call resetNative() explicitly. */ - public native void resetNative(); - - protected void finalize() throws Throwable { - resetNative(); - super.finalize(); + public synchronized void resetNative() { + mDestructor.destruct(); } + /** + * N.B. Thread safety. If you call isValid from a different thread than {@link #resetNative()} + * then be sure to do so while synchronizing on the hybrid. For example: + * + *


+   * synchronized(hybrid) {
+   *   if (hybrid.isValid) {
+   *     // Do stuff.
+   *   }
+   * }
+   * 
+ */ public boolean isValid() { - return mNativePointer != 0; + return mDestructor.mNativePointer != 0; + } + + public static class Destructor extends DestructorThread.Destructor { + + // Private C++ instance + @DoNotStrip private long mNativePointer; + + Destructor(Object referent) { + super(referent); + } + + @Override + protected void destruct() { + // When invoked from the DestructorThread instead of resetNative, + // the DestructorThread has exclusive ownership of the HybridData + // so synchronization is not necessary. + deleteNative(mNativePointer); + mNativePointer = 0; + } + + static native void deleteNative(long pointer); } } diff --git a/lib/fb/src/main/java/com/facebook/jni/IteratorHelper.java b/lib/fb/src/main/java/com/facebook/jni/IteratorHelper.java index 88e2de63..a0854dad 100644 --- a/lib/fb/src/main/java/com/facebook/jni/IteratorHelper.java +++ b/lib/fb/src/main/java/com/facebook/jni/IteratorHelper.java @@ -1,31 +1,26 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; - +import com.facebook.jni.annotations.DoNotStrip; +import java.util.Iterator; import javax.annotation.Nullable; -import java.util.Iterator; - /** - * To iterate over an Iterator from C++ requires two calls per entry: hasNext() - * and next(). This helper reduces it to one call and one field get per entry. - * It does not use a generic argument, since in C++, the types will be erased, - * anyway. This is *not* a {@link java.util.Iterator}. + * To iterate over an Iterator from C++ requires two calls per entry: hasNext() and next(). This + * helper reduces it to one call and one field get per entry. It does not use a generic argument, + * since in C++, the types will be erased, anyway. This is *not* a {@link java.util.Iterator}. */ @DoNotStrip public class IteratorHelper { private final Iterator mIterator; // This is private, but accessed via JNI. - @DoNotStrip - private @Nullable Object mElement; + @DoNotStrip private @Nullable Object mElement; @DoNotStrip public IteratorHelper(Iterator iterator) { @@ -38,8 +33,8 @@ public class IteratorHelper { } /** - * Moves the helper to the next entry in the map, if any. Returns true iff - * there is an entry to read. + * Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to + * read. */ @DoNotStrip boolean hasNext() { diff --git a/lib/fb/src/main/java/com/facebook/jni/MapIteratorHelper.java b/lib/fb/src/main/java/com/facebook/jni/MapIteratorHelper.java index e57cb9f1..2dd1c9bc 100644 --- a/lib/fb/src/main/java/com/facebook/jni/MapIteratorHelper.java +++ b/lib/fb/src/main/java/com/facebook/jni/MapIteratorHelper.java @@ -1,24 +1,21 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import javax.annotation.Nullable; - +import com.facebook.jni.annotations.DoNotStrip; import java.util.Iterator; import java.util.Map; - -import com.facebook.proguard.annotations.DoNotStrip; +import javax.annotation.Nullable; /** - * To iterate over a Map from C++ requires four calls per entry: hasNext(), - * next(), getKey(), getValue(). This helper reduces it to one call and two - * field gets per entry. It does not use a generic argument, since in C++, the - * types will be erased, anyway. This is *not* a {@link java.util.Iterator}. + * To iterate over a Map from C++ requires four calls per entry: hasNext(), next(), getKey(), + * getValue(). This helper reduces it to one call and two field gets per entry. It does not use a + * generic argument, since in C++, the types will be erased, anyway. This is *not* a {@link + * java.util.Iterator}. */ @DoNotStrip public class MapIteratorHelper { @@ -32,8 +29,8 @@ public class MapIteratorHelper { } /** - * Moves the helper to the next entry in the map, if any. Returns true iff - * there is an entry to read. + * Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to + * read. */ @DoNotStrip boolean hasNext() { diff --git a/lib/fb/src/main/java/com/facebook/jni/NativeRunnable.java b/lib/fb/src/main/java/com/facebook/jni/NativeRunnable.java index 10a2517a..0e32bd55 100644 --- a/lib/fb/src/main/java/com/facebook/jni/NativeRunnable.java +++ b/lib/fb/src/main/java/com/facebook/jni/NativeRunnable.java @@ -1,18 +1,14 @@ /** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; -/** - * A Runnable that has a native run implementation. - */ +/** A Runnable that has a native run implementation. */ @DoNotStrip public class NativeRunnable implements Runnable { diff --git a/lib/fb/src/main/java/com/facebook/jni/ThreadScopeSupport.java b/lib/fb/src/main/java/com/facebook/jni/ThreadScopeSupport.java index bda99d62..52f74ff1 100644 --- a/lib/fb/src/main/java/com/facebook/jni/ThreadScopeSupport.java +++ b/lib/fb/src/main/java/com/facebook/jni/ThreadScopeSupport.java @@ -1,11 +1,20 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; @DoNotStrip public class ThreadScopeSupport { + static { + SoLoader.loadLibrary("fbjni"); + } + // This is just used for ThreadScope::withClassLoader to have a java function // in the stack so that jni has access to the correct classloader. @DoNotStrip diff --git a/lib/fb/src/main/java/com/facebook/jni/UnknownCppException.java b/lib/fb/src/main/java/com/facebook/jni/UnknownCppException.java index de34f281..77dc3ef1 100644 --- a/lib/fb/src/main/java/com/facebook/jni/UnknownCppException.java +++ b/lib/fb/src/main/java/com/facebook/jni/UnknownCppException.java @@ -1,13 +1,12 @@ -/* +/** * Copyright (c) Facebook, Inc. and its affiliates. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. */ - package com.facebook.jni; -import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.jni.annotations.DoNotStrip; @DoNotStrip public class UnknownCppException extends CppException { diff --git a/lib/fb/src/main/java/com/facebook/jni/annotations/DoNotStrip.java b/lib/fb/src/main/java/com/facebook/jni/annotations/DoNotStrip.java new file mode 100644 index 00000000..c7649556 --- /dev/null +++ b/lib/fb/src/main/java/com/facebook/jni/annotations/DoNotStrip.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + */ +package com.facebook.jni.annotations; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Add this annotation to a class, method, or field to instruct Proguard to not strip it out. + * + * This is useful for methods called via reflection that could appear as unused to Proguard. + */ +@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR }) +@Retention(CLASS) +public @interface DoNotStrip { +} diff --git a/testutil/jni.cpp b/testutil/jni.cpp index 06e467ee..8a6a3b40 100644 --- a/testutil/jni.cpp +++ b/testutil/jni.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. */ -#include +#include #include using namespace facebook;