diff --git a/lib/fb/Doxyfile b/lib/fb/Doxyfile new file mode 100644 index 00000000..b44118dd --- /dev/null +++ b/lib/fb/Doxyfile @@ -0,0 +1,15 @@ +PROJECT_NAME = "Facebook Android Support" +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +RECURSIVE = YES +EXCLUDE = tests +EXCLUDE_PATTERNS = *.cpp +GENERATE_HTML = YES +GENERATE_LATEX = NO +ENABLE_PREPROCESSING = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_SCOPE_NAMES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_UNDOC_CLASSES = YES +SHOW_INCLUDE_FILES = NO +#ENABLED_SECTIONS = INTERNAL diff --git a/lib/fb/assert.cpp b/lib/fb/assert.cpp new file mode 100644 index 00000000..db9a4315 --- /dev/null +++ b/lib/fb/assert.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#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/include/fb/ALog.h b/lib/fb/include/fb/ALog.h new file mode 100644 index 00000000..0ed1c5fd --- /dev/null +++ b/lib/fb/include/fb/ALog.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @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/include/fb/Countable.h b/lib/fb/include/fb/Countable.h new file mode 100644 index 00000000..1e402a3f --- /dev/null +++ b/lib/fb/include/fb/Countable.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#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/include/fb/Doxyfile b/lib/fb/include/fb/Doxyfile new file mode 100644 index 00000000..8b4df6a7 --- /dev/null +++ b/lib/fb/include/fb/Doxyfile @@ -0,0 +1,18 @@ +PROJECT_NAME = "Facebook JNI" +PROJECT_BRIEF = "Helper library to provide safe and convenient access to JNI with very low overhead" +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +RECURSIVE = YES +EXCLUDE = tests Asserts.h Countable.h GlobalReference.h LocalReference.h LocalString.h Registration.h WeakReference.h jni_helpers.h Environment.h +EXCLUDE_PATTERNS = *-inl.h *.cpp +GENERATE_HTML = YES +GENERATE_LATEX = NO +ENABLE_PREPROCESSING = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_SCOPE_NAMES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_UNDOC_CLASSES = YES +SHOW_INCLUDE_FILES = NO +PREDEFINED = LOG_TAG=fbjni +EXAMPLE_PATH = samples +#ENABLED_SECTIONS = INTERNAL diff --git a/lib/fb/include/fb/Environment.h b/lib/fb/include/fb/Environment.h new file mode 100644 index 00000000..64f9937a --- /dev/null +++ b/lib/fb/include/fb/Environment.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#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(); + 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(); +}; + +/** + * 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 + * keep that straight. In addition, it remembers whether it performed the attach or not, so it + * is safe to nest it with itself or with non-fbjni code that manages the attachment correctly. + * + * Potential concerns: + * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the + * app is not busy. + * - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that + * there is no Java code on the current stack or you run the risk of a crash like: + * ERROR: detaching thread with interp frames (count=18) + * (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) + * ThreadScope won't do a detach if the thread was already attached before the guard is + * instantiated, but there's probably some usage that could trip this up. + * - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language + * classes, not any of our application's classes. This will be different behavior than threads + * that were initiated on the Java side. A workaround is to pass a global reference for a + * class or instance to the new thread; this bypasses the need for the class loader. + * (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) + * If you need access to the application's classes, you can use ThreadScope::WithClassLoader. + */ +class FBEXPORT ThreadScope { + public: + ThreadScope(); + ThreadScope(ThreadScope&) = delete; + ThreadScope(ThreadScope&&) = default; + ThreadScope& operator=(ThreadScope&) = delete; + ThreadScope& operator=(ThreadScope&&) = delete; + ~ThreadScope(); + + /** + * This runs the closure in a scope with fbjni's classloader. This should be + * the same classloader as the rest of the application and thus anything + * running in the closure will have access to the same classes as in a normal + * java-create thread. + */ + static void WithClassLoader(std::function&& runnable); + + static void OnLoad(); + private: + bool attachedWithThisScope_; +}; +} +} diff --git a/lib/fb/include/fb/ProgramLocation.h b/lib/fb/include/fb/ProgramLocation.h new file mode 100644 index 00000000..36f7737f --- /dev/null +++ b/lib/fb/include/fb/ProgramLocation.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#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/include/fb/RefPtr.h b/lib/fb/include/fb/RefPtr.h new file mode 100644 index 00000000..d21fe697 --- /dev/null +++ b/lib/fb/include/fb/RefPtr.h @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#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/include/fb/StaticInitialized.h b/lib/fb/include/fb/StaticInitialized.h new file mode 100644 index 00000000..6d943972 --- /dev/null +++ b/lib/fb/include/fb/StaticInitialized.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#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/include/fb/ThreadLocal.h b/lib/fb/include/fb/ThreadLocal.h new file mode 100644 index 00000000..d86a2f0d --- /dev/null +++ b/lib/fb/include/fb/ThreadLocal.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#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/include/fb/assert.h b/lib/fb/include/fb/assert.h new file mode 100644 index 00000000..1ff0740a --- /dev/null +++ b/lib/fb/include/fb/assert.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#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/include/fb/fbjni.h b/lib/fb/include/fb/fbjni.h new file mode 100644 index 00000000..66944c1c --- /dev/null +++ b/lib/fb/include/fb/fbjni.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/lib/fb/include/fb/fbjni/Boxed.h b/lib/fb/include/fb/fbjni/Boxed.h new file mode 100644 index 00000000..5044d503 --- /dev/null +++ b/lib/fb/include/fb/fbjni/Boxed.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +namespace detail { +template +struct JPrimitive : JavaClass { + using typename JavaClass::javaobject; + using JavaClass::javaClassStatic; + static local_ref valueOf(jprim val) { + static auto cls = javaClassStatic(); + static auto method = + cls->template getStaticMethod("valueOf"); + return method(cls, val); + } + jprim value() const { + static auto method = + javaClassStatic()->template getMethod(T::kValueMethod); + return method(this->self()); + } +}; + +} // namespace detail + + +#define DEFINE_BOXED_PRIMITIVE(LITTLE, BIG) \ + struct J ## BIG : detail::JPrimitive { \ + static auto constexpr kJavaDescriptor = "Ljava/lang/" #BIG ";"; \ + static auto constexpr kValueMethod = #LITTLE "Value"; \ + j ## LITTLE LITTLE ## Value() const { \ + return value(); \ + } \ + }; \ + inline local_ref autobox(j ## LITTLE val) { \ + return J ## BIG::valueOf(val); \ + } + +DEFINE_BOXED_PRIMITIVE(boolean, Boolean) +DEFINE_BOXED_PRIMITIVE(byte, Byte) +DEFINE_BOXED_PRIMITIVE(char, Character) +DEFINE_BOXED_PRIMITIVE(short, Short) +DEFINE_BOXED_PRIMITIVE(int, Integer) +DEFINE_BOXED_PRIMITIVE(long, Long) +DEFINE_BOXED_PRIMITIVE(float, Float) +DEFINE_BOXED_PRIMITIVE(double, Double) + +#undef DEFINE_BOXED_PRIMITIVE + +inline local_ref autobox(alias_ref val) { + return make_local(val); +} + +}} + diff --git a/lib/fb/include/fb/fbjni/ByteBuffer.h b/lib/fb/include/fb/fbjni/ByteBuffer.h new file mode 100644 index 00000000..450f66a6 --- /dev/null +++ b/lib/fb/include/fb/fbjni/ByteBuffer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include "CoreClasses.h" +#include "References-forward.h" + +namespace facebook { +namespace jni { + +// JNI's NIO support has some awkward preconditions and error reporting. This +// class provides much more user-friendly access. +class FBEXPORT JByteBuffer : public JavaClass { + public: + static constexpr const char* kJavaDescriptor = "Ljava/nio/ByteBuffer;"; + + static local_ref wrapBytes(uint8_t* data, size_t size); + + bool isDirect(); + + uint8_t* getDirectBytes(); + size_t getDirectSize(); +}; + +}} diff --git a/lib/fb/include/fb/fbjni/Common.h b/lib/fb/include/fb/fbjni/Common.h new file mode 100644 index 00000000..9da51c40 --- /dev/null +++ b/lib/fb/include/fb/fbjni/Common.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @file Common.h + * + * Defining the stuff that don't deserve headers of their own... + */ + +#pragma once + +#include + +#include + +#include +#include + +#ifdef FBJNI_DEBUG_REFS +# ifdef __ANDROID__ +# include +# else +# include +# endif +#endif + +// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as +// a C++ exception. +#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \ + ::facebook::jni::throwPendingJniExceptionAsCppException() + +// If the condition is true, throws a JniException object, which wraps the pending JNI Java +// exception if any. If no pending exception is found, throws a JniException object that wraps a +// RuntimeException throwable.  +#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \ + ::facebook::jni::throwCppExceptionIf(CONDITION) + +/// @cond INTERNAL + +namespace facebook { +namespace jni { + +FBEXPORT void throwPendingJniExceptionAsCppException(); +FBEXPORT void throwCppExceptionIf(bool condition); + +[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable); +[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg); +template +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args); + + +/** + * This needs to be called at library load time, typically in your JNI_OnLoad method. + * + * The intended use is to return the result of initialize() directly + * from JNI_OnLoad and to do nothing else there. Library specific + * initialization code should go in the function passed to initialize + * (which can be, and probably should be, a C++ lambda). This approach + * provides correct error handling and translation errors during + * initialization into Java exceptions when appropriate. + * + * Failure to call this will cause your code to crash in a remarkably + * unhelpful way (typically a segfault) while trying to handle an exception + * which occurs later. + */ +FBEXPORT 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 +inline void dbglog(const char* msg, Args... args) { +# ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_VERBOSE, "fbjni_dbg", msg, args...); +# else + std::fprintf(stderr, msg, args...); +# endif +} + +#else + +template +inline void dbglog(const char*, Args...) { +} + +#endif + +}}} + +/// @endcond diff --git a/lib/fb/include/fb/fbjni/Context.h b/lib/fb/include/fb/fbjni/Context.h new file mode 100644 index 00000000..8630aa6b --- /dev/null +++ b/lib/fb/include/fb/fbjni/Context.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" +#include "File.h" + +namespace facebook { +namespace jni { + +class AContext : public JavaClass { + public: + static constexpr const char* kJavaDescriptor = "Landroid/content/Context;"; + + // Define a method that calls into the represented Java class + local_ref getCacheDir() { + static auto method = getClass()->getMethod("getCacheDir"); + return method(self()); + } + + local_ref getFilesDir() { + static auto method = getClass()->getMethod("getFilesDir"); + return method(self()); + } +}; + +} +} diff --git a/lib/fb/include/fb/fbjni/CoreClasses-inl.h b/lib/fb/include/fb/fbjni/CoreClasses-inl.h new file mode 100644 index 00000000..40fb94b7 --- /dev/null +++ b/lib/fb/include/fb/fbjni/CoreClasses-inl.h @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include + +#include "Common.h" +#include "Exceptions.h" +#include "Meta.h" +#include "MetaConvert.h" + +namespace facebook { +namespace jni { + +// jobject ///////////////////////////////////////////////////////////////////////////////////////// + +inline bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept { + return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE; +} + +inline local_ref JObject::getClass() const noexcept { + return adopt_local(internal::getEnv()->GetObjectClass(self())); +} + +inline bool JObject::isInstanceOf(alias_ref cls) const noexcept { + return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE; +} + +template +inline T JObject::getFieldValue(JField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JObject::getFieldValue(JField field) const noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JObject::setFieldValue(JField field, T value) noexcept { + field.set(self(), value); +} + +inline std::string JObject::toString() const { + static auto method = findClassLocal("java/lang/Object")->getMethod("toString"); + + return method(self())->toStdString(); +} + + +// Class is here instead of CoreClasses.h because we need +// alias_ref to be complete. +class MonitorLock { + public: + inline MonitorLock() noexcept; + inline MonitorLock(alias_ref object) noexcept; + inline ~MonitorLock() noexcept; + + inline MonitorLock(MonitorLock&& other) noexcept; + inline MonitorLock& operator=(MonitorLock&& other) noexcept; + + inline MonitorLock(const MonitorLock&) = delete; + inline MonitorLock& operator=(const MonitorLock&) = delete; + + private: + inline void reset() noexcept; + alias_ref owned_; +}; + +MonitorLock::MonitorLock() noexcept : owned_(nullptr) {} + +MonitorLock::MonitorLock(alias_ref object) noexcept + : owned_(object) { + internal::getEnv()->MonitorEnter(object.get()); +} + +void MonitorLock::reset() noexcept { + if (owned_) { + internal::getEnv()->MonitorExit(owned_.get()); + if (internal::getEnv()->ExceptionCheck()) { + abort(); // Lock mismatch + } + owned_ = nullptr; + } +} + +MonitorLock::~MonitorLock() noexcept { + reset(); +} + +MonitorLock::MonitorLock(MonitorLock&& other) noexcept + : owned_(other.owned_) +{ + other.owned_ = nullptr; +} + +MonitorLock& MonitorLock::operator=(MonitorLock&& other) noexcept { + reset(); + owned_ = other.owned_; + other.owned_ = nullptr; + return *this; +} + +inline MonitorLock JObject::lock() const noexcept { + return MonitorLock(this_); +} + +inline jobject JObject::self() const noexcept { + return this_; +} + +inline void swap(JObject& a, JObject& b) noexcept { + using std::swap; + swap(a.this_, b.this_); +} + +// JavaClass /////////////////////////////////////////////////////////////////////////////////////// + +namespace detail { +template +static local_ref newInstance(Args... args) { + static auto cls = JC::javaClassStatic(); + static auto constructor = cls->template getConstructor(); + return cls->newObject(constructor, args...); +} +} + + +template +auto JavaClass::self() const noexcept -> javaobject { + return static_cast(JObject::self()); +} + +// jclass ////////////////////////////////////////////////////////////////////////////////////////// + +namespace detail { + +// This is not a real type. It is used so people won't accidentally +// use a void* to initialize a NativeMethod. +struct NativeMethodWrapper; + +} + +struct NativeMethod { + const char* name; + std::string descriptor; + detail::NativeMethodWrapper* wrapper; +}; + +inline local_ref JClass::getSuperclass() const noexcept { + return adopt_local(internal::getEnv()->GetSuperclass(self())); +} + +inline void JClass::registerNatives(std::initializer_list methods) { + const auto env = internal::getEnv(); + + 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(); + jnimethods[i].fnPtr = reinterpret_cast(it->wrapper); + } + + auto result = env->RegisterNatives(self(), jnimethods, methods.size()); + FACEBOOK_JNI_THROW_EXCEPTION_IF(result != JNI_OK); +} + +inline bool JClass::isAssignableFrom(alias_ref other) const noexcept { + const auto env = internal::getEnv(); + const auto result = env->IsAssignableFrom(self(), other.get()); + return result; +} + +template +inline JConstructor JClass::getConstructor() const { + return getConstructor(jmethod_traits_from_cxx::constructor_descriptor().c_str()); +} + +template +inline JConstructor JClass::getConstructor(const char* descriptor) const { + constexpr auto constructor_method_name = ""; + return getMethod(constructor_method_name, descriptor); +} + +template +inline JMethod JClass::getMethod(const char* name) const { + return getMethod(name, jmethod_traits_from_cxx::descriptor().c_str()); +} + +template +inline JMethod JClass::getMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JMethod{method}; +} + +template +inline JStaticMethod JClass::getStaticMethod(const char* name) const { + return getStaticMethod(name, jmethod_traits_from_cxx::descriptor().c_str()); +} + +template +inline JStaticMethod JClass::getStaticMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetStaticMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JStaticMethod{method}; +} + +template +inline JNonvirtualMethod JClass::getNonvirtualMethod(const char* name) const { + return getNonvirtualMethod(name, jmethod_traits_from_cxx::descriptor().c_str()); +} + +template +inline JNonvirtualMethod JClass::getNonvirtualMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JNonvirtualMethod{method}; +} + +template +inline JField(), T>> +JClass::getField(const char* name) const { + return getField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JField(), T>> JClass::getField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JField{field}; +} + +template +inline JStaticField(), T>> JClass::getStaticField( + const char* name) const { + return getStaticField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JStaticField(), T>> JClass::getStaticField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetStaticFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JStaticField{field}; +} + +template +inline T JClass::getStaticFieldValue(JStaticField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JClass::getStaticFieldValue(JStaticField field) noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JClass::setStaticFieldValue(JStaticField field, T value) noexcept { + field.set(self(), value); +} + +template +inline local_ref JClass::newObject( + JConstructor constructor, + Args... args) const { + const auto env = internal::getEnv(); + auto object = env->NewObject(self(), constructor.getId(), + detail::callToJni( + detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!object); + return adopt_local(static_cast(object)); +} + +inline jclass JClass::self() const noexcept { + return static_cast(JObject::self()); +} + +inline void registerNatives(const char* name, std::initializer_list methods) { + findClassLocal(name)->registerNatives(methods); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +inline local_ref make_jstring(const std::string& modifiedUtf8) { + return make_jstring(modifiedUtf8.c_str()); +} + +namespace detail { +// convert to std::string from jstring +template <> +struct Convert { + typedef jstring jniType; + static std::string fromJni(jniType t) { + return wrap_alias(t)->toStdString(); + } + static jniType toJniRet(const std::string& t) { + return make_jstring(t).release(); + } + static local_ref toCall(const std::string& t) { + return make_jstring(t); + } +}; + +// convert return from const char* +template <> +struct Convert { + typedef jstring jniType; + // no automatic synthesis of const char*. (It can't be freed.) + static jniType toJniRet(const char* t) { + return make_jstring(t).release(); + } + static local_ref toCall(const char* t) { + return make_jstring(t); + } +}; +} + +// 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(); + return env->GetArrayLength(self()); +} +} + +namespace detail { +template +inline ElementProxy::ElementProxy( + Target* target, + size_t idx) + : target_{target}, idx_{idx} {} + +template +inline ElementProxy& ElementProxy::operator=(const T& o) { + target_->setElement(idx_, o); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref&& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(const ElementProxy& o) { + auto src = o.target_->getElement(o.idx_); + target_->setElement(idx_, src.get()); + return *this; +} + +template +inline ElementProxy::ElementProxy::operator const local_ref () const { + return target_->getElement(idx_); +} + +template +inline ElementProxy::ElementProxy::operator local_ref () { + return target_->getElement(idx_); +} +} + +template +std::string JArrayClass::get_instantiated_java_descriptor() { + return "[" + jtype_traits::descriptor(); +}; + +template +std::string JArrayClass::get_instantiated_base_name() { + return get_instantiated_java_descriptor(); +}; + +template +auto JArrayClass::newArray(size_t size) -> local_ref { + static auto elementClass = findClassStatic(jtype_traits::base_name().c_str()); + const auto env = internal::getEnv(); + auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray); + return adopt_local(static_cast(rawArray)); +} + +template +inline void JArrayClass::setElement(size_t idx, const T& value) { + const auto env = internal::getEnv(); + env->SetObjectArrayElement(this->self(), idx, value); +} + +template +inline local_ref JArrayClass::getElement(size_t idx) { + const auto env = internal::getEnv(); + auto rawElement = env->GetObjectArrayElement(this->self(), idx); + return adopt_local(static_cast(rawElement)); +} + +template +inline detail::ElementProxy> JArrayClass::operator[](size_t index) { + return detail::ElementProxy>(this, index); +} + +// 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; +} + +template +std::string JPrimitiveArray::get_instantiated_java_descriptor() { + return jtype_traits::descriptor(); +} +template +std::string JPrimitiveArray::get_instantiated_base_name() { + return JPrimitiveArray::get_instantiated_java_descriptor(); +} + +template +auto JPrimitiveArray::pin() -> PinnedPrimitiveArray> { + return PinnedPrimitiveArray>{this->self(), 0, 0}; +} + +template +auto JPrimitiveArray::pinRegion(jsize start, jsize length) + -> PinnedPrimitiveArray> { + return PinnedPrimitiveArray>{this->self(), start, length}; +} + +template +auto JPrimitiveArray::pinCritical() + -> PinnedPrimitiveArray> { + return PinnedPrimitiveArray>{this->self(), 0, 0}; +} + +template +class PinnedArrayAlloc { + public: + static void allocate( + alias_ref::array_type> array, + jsize start, + jsize length, + T** elements, + size_t* size, + jboolean* isCopy) { + (void) start; + (void) length; + *elements = array->getElements(isCopy); + *size = array->size(); + } + static void release( + alias_ref::array_type> array, + T* elements, + jint start, + jint size, + jint mode) { + (void) start; + (void) size; + array->releaseElements(elements, mode); + } +}; + +template +class PinnedCriticalAlloc { + public: + static void allocate( + alias_ref::array_type> array, + jsize start, + jsize length, + T** elements, + size_t* size, + jboolean* isCopy) { + const auto env = internal::getEnv(); + *elements = static_cast(env->GetPrimitiveArrayCritical(array.get(), isCopy)); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!elements); + *size = array->size(); + } + static void release( + alias_ref::array_type> array, + T* elements, + jint start, + jint size, + jint mode) { + const auto env = internal::getEnv(); + env->ReleasePrimitiveArrayCritical(array.get(), elements, mode); + } +}; + +template +class PinnedRegionAlloc { + public: + static void allocate( + alias_ref::array_type> array, + jsize start, + jsize length, + T** elements, + size_t* size, + jboolean* isCopy) { + auto buf = array->getRegion(start, length); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!buf); + *elements = buf.release(); + *size = length; + *isCopy = true; + } + static void release( + alias_ref::array_type> array, + T* elements, + jint start, + jint size, + jint mode) { + std::unique_ptr holder; + if (mode == 0 || mode == JNI_ABORT) { + holder.reset(elements); + } + if (mode == 0 || mode == JNI_COMMIT) { + array->setRegion(start, size, elements); + } + } +}; + +// PinnedPrimitiveArray /////////////////////////////////////////////////////////////////////////// + +template +PinnedPrimitiveArray::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) { + *this = std::move(o); +} + +template +PinnedPrimitiveArray& +PinnedPrimitiveArray::operator=(PinnedPrimitiveArray&& o) { + if (array_) { + release(); + } + array_ = std::move(o.array_); + elements_ = o.elements_; + isCopy_ = o.isCopy_; + size_ = o.size_; + start_ = o.start_; + o.clear(); + return *this; +} + +template +T* PinnedPrimitiveArray::get() { + return elements_; +} + +template +inline void PinnedPrimitiveArray::release() { + releaseImpl(0); + clear(); +} + +template +inline void PinnedPrimitiveArray::commit() { + releaseImpl(JNI_COMMIT); +} + +template +inline void PinnedPrimitiveArray::abort() { + releaseImpl(JNI_ABORT); + clear(); +} + +template +inline void PinnedPrimitiveArray::releaseImpl(jint mode) { + FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr); + Alloc::release(array_, elements_, start_, size_, mode); +} + +template +inline void PinnedPrimitiveArray::clear() noexcept { + array_ = nullptr; + elements_ = nullptr; + isCopy_ = false; + start_ = 0; + size_ = 0; +} + +template +inline T& PinnedPrimitiveArray::operator[](size_t index) { + FACEBOOK_JNI_THROW_EXCEPTION_IF(elements_ == nullptr); + return elements_[index]; +} + +template +inline bool PinnedPrimitiveArray::isCopy() const noexcept { + return isCopy_ == JNI_TRUE; +} + +template +inline size_t PinnedPrimitiveArray::size() const noexcept { + return size_; +} + +template +inline PinnedPrimitiveArray::~PinnedPrimitiveArray() noexcept { + if (elements_) { + release(); + } +} + +template +inline PinnedPrimitiveArray::PinnedPrimitiveArray(alias_ref::array_type> array, jint start, jint length) { + array_ = array; + start_ = start; + Alloc::allocate(array, start, length, &elements_, &size_, &isCopy_); +} + +template +inline alias_ref JavaClass::javaClassStatic() { + static auto cls = findClassStatic(jtype_traits::base_name().c_str()); + return cls; +} + +template +inline local_ref JavaClass::javaClassLocal() { + std::string className(jtype_traits::base_name().c_str()); + return findClassLocal(className.c_str()); +} + +}} diff --git a/lib/fb/include/fb/fbjni/CoreClasses.h b/lib/fb/include/fb/fbjni/CoreClasses.h new file mode 100644 index 00000000..9e0bb876 --- /dev/null +++ b/lib/fb/include/fb/fbjni/CoreClasses.h @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +/** @file CoreClasses.h + * + * In CoreClasses.h wrappers for the core classes (jobject, jclass, and jstring) is defined + * to provide access to corresponding JNI functions + some conveniance. + */ + +#include "References-forward.h" +#include "Meta-forward.h" +#include "TypeTraits.h" + +#include + +#include + +#include + +namespace facebook { +namespace jni { + +class JClass; +class JObject; + +/// 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 +/// Java programs). +/// +/// The most common use case for this is storing the result +/// 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); + +/// Lookup a class by name. Note this functions returns a local reference, +/// which means that it must not be stored in a static variable. +/// +/// The most common use case for this is one-time initialization +/// (like caching method ids). +/// +/// @return Returns a global reference to the class +FBEXPORT 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; + +// Together, these classes allow convenient use of any class with the fbjni +// helpers. To use: +// +// struct MyClass : public JavaClass { +// constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;"; +// }; +// +// Then, an alias_ref will be backed by an instance of +// MyClass. JavaClass provides a convenient way to add functionality to these +// smart references. +// +// For example: +// +// struct MyClass : public JavaClass { +// constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;"; +// +// void foo() { +// static auto method = javaClassStatic()->getMethod("foo"); +// method(self()); +// } +// +// static local_ref create(int i) { +// return newInstance(i); +// } +// }; +// +// auto obj = MyClass::create(10); +// obj->foo(); +// +// While users of a JavaClass-type can lookup methods and fields through the +// underlying JClass, those calls can only be checked at runtime. It is recommended +// that the JavaClass-type instead explicitly expose it's methods as in the example +// above. + +namespace detail { +template +static local_ref newInstance(Args... args); +} + +class MonitorLock; + +class FBEXPORT JObject : detail::JObjectBase { +public: + static constexpr auto kJavaDescriptor = "Ljava/lang/Object;"; + + static constexpr const char* get_instantiated_java_descriptor() { return nullptr; } + static constexpr const char* get_instantiated_base_name() { return nullptr; } + + /// Get a @ref local_ref of the object's class + local_ref getClass() const noexcept; + + /// Checks if the object is an instance of a class + bool isInstanceOf(alias_ref cls) const noexcept; + + /// Get the primitive value of a field + template + T getFieldValue(JField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + 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. + template + void setFieldValue(JField field, T value) noexcept; + + /// Convenience method to create a std::string representing the object + std::string toString() const; + + // Take this object's monitor lock + MonitorLock lock() const noexcept; + + typedef _jobject _javaobject; + typedef _javaobject* javaobject; + +protected: + jobject self() const noexcept; +private: + friend void swap(JObject& a, JObject& b) noexcept; + template + friend struct detail::ReprAccess; + template + friend class JavaClass; + + template + friend class JObjectWrapper; +}; + +// This is only to maintain backwards compatibility with things that are +// already providing a specialization of JObjectWrapper. Any such instances +// should be updated to use a JavaClass. +template<> +class JObjectWrapper : public JObject { +}; + + +namespace detail { +template +struct JTypeFor { + static_assert( + std::is_base_of< + std::remove_pointer::type, + typename std::remove_pointer::type + >::value, ""); + using _javaobject = typename std::remove_pointer::type; + using javaobject = JType; +}; + +template +struct JTypeFor { + // JNI pattern for jobject assignable pointer + struct _javaobject : Base::_javaobject { + // This allows us to map back to the defining type (in ReprType, for + // example). + typedef T JniRefRepr; + }; + using javaobject = _javaobject*; +}; +} + +// JavaClass provides a method to inform fbjni about user-defined Java types. +// Given a class: +// struct Foo : JavaClass { +// static constexpr auto kJavaDescriptor = "Lcom/example/package/Foo;"; +// }; +// fbjni can determine the java type/method signatures for Foo::javaobject and +// smart refs (like alias_ref) will hold an instance of Foo +// and provide access to it through the -> and * operators. +// +// The "Base" template argument can be used to specify the JavaClass superclass +// of this type (for instance, JString's Base is JObject). +// +// The "JType" template argument is used to provide a jni type (like jstring, +// 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 { + using JObjType = typename detail::JTypeFor; +public: + using _javaobject = typename JObjType::_javaobject; + using javaobject = typename JObjType::javaobject; + + using JavaBase = JavaClass; + + static alias_ref javaClassStatic(); + static local_ref javaClassLocal(); +protected: + /// Allocates a new object and invokes the specified constructor + /// Like JClass's getConstructor, this function can only check at runtime if + /// the class actually has a constructor that accepts the corresponding types. + /// While a JavaClass-type can expose this function directly, it is recommended + /// to instead to use this to explicitly only expose those constructors that + /// the Java class actually has (i.e. with static create() functions). + template + static local_ref newInstance(Args... args) { + return detail::newInstance(args...); + } + + javaobject self() const noexcept; +}; + +/// Wrapper to provide functionality to jclass references +struct NativeMethod; + +class FBEXPORT JClass : public JavaClass { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;"; + + /// Get a @local_ref to the super class of this class + local_ref getSuperclass() const noexcept; + + /// Register native methods for the class. Usage looks like this: + /// + /// classRef->registerNatives({ + /// makeNativeMethod("nativeMethodWithAutomaticDescriptor", + /// methodWithAutomaticDescriptor), + /// makeNativeMethod("nativeMethodWithExplicitDescriptor", + /// "(Lcom/facebook/example/MyClass;)V", + /// methodWithExplicitDescriptor), + /// }); + /// + /// By default, C++ exceptions raised will be converted to Java exceptions. + /// To avoid this and get the "standard" JNI behavior of a crash when a C++ + /// exception is crashing out of the JNI method, declare the method noexcept. + void registerNatives(std::initializer_list methods); + + /// Check to see if the class is assignable from another class + /// @pre cls != nullptr + bool isAssignableFrom(alias_ref cls) const noexcept; + + /// Convenience method to lookup the constructor with descriptor as specified by the + /// type arguments + template + JConstructor getConstructor() const; + + /// Convenience method to lookup the constructor with specified descriptor + template + JConstructor getConstructor(const char* descriptor) const; + + /// Look up the method with given name and descriptor as specified with the type arguments + template + JMethod getMethod(const char* name) const; + + /// Look up the method with given name and descriptor + template + JMethod getMethod(const char* name, const char* descriptor) const; + + /// Lookup the field with the given name and deduced descriptor + template + JField(), T>> getField(const char* name) const; + + /// Lookup the field with the given name and descriptor + template + JField(), T>> getField(const char* name, const char* descriptor) const; + + /// Lookup the static field with the given name and deduced descriptor + template + JStaticField(), T>> getStaticField(const char* name) const; + + /// Lookup the static field with the given name and descriptor + template + JStaticField(), T>> getStaticField( + const char* name, + const char* descriptor) const; + + /// Get the primitive value of a static field + template + T getStaticFieldValue(JStaticField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + 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. + template + void setStaticFieldValue(JStaticField field, T value) noexcept; + + /// Allocates a new object and invokes the specified constructor + template + local_ref newObject(JConstructor constructor, Args... args) const; + + /// Look up the static method with given name and descriptor as specified with the type arguments + template + JStaticMethod getStaticMethod(const char* name) const; + + /// Look up the static method with given name and descriptor + template + JStaticMethod getStaticMethod(const char* name, const char* descriptor) const; + + /// Look up the non virtual method with given name and descriptor as specified with the + /// type arguments + template + JNonvirtualMethod getNonvirtualMethod(const char* name) const; + + /// Look up the non virtual method with given name and descriptor + template + JNonvirtualMethod getNonvirtualMethod(const char* name, const char* descriptor) const; + +private: + jclass self() const noexcept; +}; + +// Convenience method to register methods on a class without holding +// onto the class object. +void registerNatives(const char* name, std::initializer_list methods); + +/// Wrapper to provide functionality to jstring references +class FBEXPORT 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 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); +}; + +namespace detail { +template +class ElementProxy { + private: + Target* target_; + size_t idx_; + + public: + using T = typename Target::javaentry; + ElementProxy(Target* target, size_t idx); + + ElementProxy& operator=(const T& o); + + ElementProxy& operator=(alias_ref& o); + + ElementProxy& operator=(alias_ref&& o); + + ElementProxy& operator=(const ElementProxy& o); + + operator const local_ref () const; + + operator local_ref (); +}; +} + +namespace detail { +class FBEXPORT 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 + // JArrayClass). + static constexpr const char* kJavaDescriptor = nullptr; + size_t size() const noexcept; +}; + +// 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 { + // This cannot be used in a scope that derives a descriptor (like in a method + // signature). + static constexpr const char* kJavaDescriptor = nullptr; +}; +} + +template +class JArrayClass : public JavaClass, detail::JTypeArray> { + public: + static_assert(is_plain_jni_reference(), ""); + // javaentry is the jni type of an entry in the array (i.e. jint). + using javaentry = T; + // javaobject is the jni type of the array. + using javaobject = typename JavaClass, detail::JTypeArray>::javaobject; + static constexpr const char* kJavaDescriptor = nullptr; + static std::string get_instantiated_java_descriptor(); + static std::string get_instantiated_base_name(); + + /// Allocate a new array from Java heap, for passing as a JNI parameter or return value. + /// NOTE: if using as a return value, you want to call release() instead of get() on the + /// smart pointer. + static local_ref newArray(size_t count); + + /// Assign an object to the array. + /// Typically you will use the shorthand (*ref)[idx]=value; + void setElement(size_t idx, const T& value); + + /// Read an object from the array. + /// Typically you will use the shorthand + /// T value = (*ref)[idx]; + /// If you use auto, you'll get an ElementProxy, which may need to be cast. + local_ref getElement(size_t idx); + + /// EXPERIMENTAL SUBSCRIPT SUPPORT + /// This implementation of [] returns a proxy object which then has a bunch of specializations + /// (adopt_local free function, operator= and casting overloads on the ElementProxy) that can + /// make code look like it is dealing with a T rather than an obvious proxy. In particular, the + /// proxy in this iteration does not read a value and therefore does not create a LocalRef + /// until one of these other operators is used. There are certainly holes that you may find + /// by using idioms that haven't been tried yet. Consider yourself warned. On the other hand, + /// it does make for some idiomatic assignment code; see TestBuildStringArray in fbjni_tests + /// for some examples. + detail::ElementProxy operator[](size_t idx); +}; + +template +using jtypeArray = typename JArrayClass::javaobject; + +template +local_ref::javaobject> adopt_local_array(jobjectArray ref) { + return adopt_local(static_cast::javaobject>(ref)); +} + +template +local_ref adopt_local(detail::ElementProxy elementProxy) { + return static_cast>(elementProxy); +} + +template +class PinnedPrimitiveArray; + +template class PinnedArrayAlloc; +template class PinnedRegionAlloc; +template class PinnedCriticalAlloc; + +/// Wrapper to provide functionality to jarray references. +/// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with +/// the elements of the array. +template +class FBEXPORT JPrimitiveArray : + public JavaClass, detail::JArray, JArrayType> { + static_assert(is_jni_primitive_array(), ""); + public: + static constexpr const char* kJavaDescriptor = nullptr; + static std::string get_instantiated_java_descriptor(); + static std::string get_instantiated_base_name(); + + using T = typename jtype_traits::entry_type; + + static local_ref newArray(size_t count); + + void getRegion(jsize start, jsize length, T* buf); + std::unique_ptr getRegion(jsize start, jsize length); + void setRegion(jsize start, jsize length, const T* buf); + + /// Returns a view of the underlying array. This will either be a "pinned" + /// version of the array (in which case changes to one immediately affect the + /// other) or a copy of the array (in which cases changes to the view will take + /// affect when destroyed or on calls to release()/commit()). + PinnedPrimitiveArray> pin(); + + /// Returns a view of part of the underlying array. A pinned region is always + /// backed by a copy of the region. + PinnedPrimitiveArray> pinRegion(jsize start, jsize length); + + /// Returns a view of the underlying array like pin(). However, while the pin + /// is held, the code is considered within a "critical region". In a critical + /// region, native code must not call JNI functions or make any calls that may + /// block on other Java threads. These restrictions make it more likely that + /// the view will be "pinned" rather than copied (for example, the VM may + /// suspend garbage collection within a critical region). + PinnedPrimitiveArray> pinCritical(); + +private: + friend class PinnedArrayAlloc; + T* getElements(jboolean* isCopy); + 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); + +using JArrayBoolean = JPrimitiveArray; +using JArrayByte = JPrimitiveArray; +using JArrayChar = JPrimitiveArray; +using JArrayShort = JPrimitiveArray; +using JArrayInt = JPrimitiveArray; +using JArrayLong = JPrimitiveArray; +using JArrayFloat = JPrimitiveArray; +using JArrayDouble = JPrimitiveArray; + +/// RAII class for pinned primitive arrays +/// This currently only supports read/write access to existing java arrays. You can't create a +/// primitive array this way yet. This class also pins the entire array into memory during the +/// lifetime of the PinnedPrimitiveArray. If you need to unpin the array manually, call the +/// release() or abort() functions. During a long-running block of code, you +/// should unpin the array as soon as you're done with it, to avoid holding up +/// the Java garbage collector. +template +class PinnedPrimitiveArray { + public: + static_assert(is_jni_primitive::value, + "PinnedPrimitiveArray requires primitive jni type."); + + using ArrayType = typename jtype_traits::array_type; + + PinnedPrimitiveArray(PinnedPrimitiveArray&&); + PinnedPrimitiveArray(const PinnedPrimitiveArray&) = delete; + ~PinnedPrimitiveArray() noexcept; + + PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&); + PinnedPrimitiveArray& operator=(const PinnedPrimitiveArray&) = delete; + + T* get(); + void release(); + /// Unpins the array. If the array is a copy, pending changes are discarded. + void abort(); + /// If the array is a copy, copies pending changes to the underlying java array. + void commit(); + + bool isCopy() const noexcept; + + const T& operator[](size_t index) const; + T& operator[](size_t index); + size_t size() const noexcept; + + private: + alias_ref array_; + size_t start_; + T* elements_; + jboolean isCopy_; + size_t size_; + + void allocate(alias_ref, jint start, jint length); + void releaseImpl(jint mode); + void clear() noexcept; + + PinnedPrimitiveArray(alias_ref, jint start, jint length); + + friend class JPrimitiveArray::array_type>; +}; + +#pragma push_macro("PlainJniRefMap") +#undef PlainJniRefMap +#define PlainJniRefMap(rtype, jtype) \ +namespace detail { \ +template<> \ +struct RefReprType { \ + using type = rtype; \ +}; \ +} + +PlainJniRefMap(JArrayBoolean, jbooleanArray); +PlainJniRefMap(JArrayByte, jbyteArray); +PlainJniRefMap(JArrayChar, jcharArray); +PlainJniRefMap(JArrayShort, jshortArray); +PlainJniRefMap(JArrayInt, jintArray); +PlainJniRefMap(JArrayLong, jlongArray); +PlainJniRefMap(JArrayFloat, jfloatArray); +PlainJniRefMap(JArrayDouble, jdoubleArray); +PlainJniRefMap(JObject, jobject); +PlainJniRefMap(JClass, jclass); +PlainJniRefMap(JString, jstring); +PlainJniRefMap(JThrowable, jthrowable); + +#pragma pop_macro("PlainJniRefMap") + +}} + +#include "CoreClasses-inl.h" diff --git a/lib/fb/include/fb/fbjni/Exceptions.h b/lib/fb/include/fb/fbjni/Exceptions.h new file mode 100644 index 00000000..ce2cc70b --- /dev/null +++ b/lib/fb/include/fb/fbjni/Exceptions.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * @file Exceptions.h + * + * After invoking a JNI function that can throw a Java exception, the macro + * @ref FACEBOOK_JNI_THROW_PENDING_EXCEPTION() or @ref FACEBOOK_JNI_THROW_EXCEPTION_IF() + * should be invoked. + * + * IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! + * To use these methods you MUST call initExceptionHelpers() when your library is loaded. + */ + +#pragma once + +#include +#include +#include + +#include + +#include + +#include "Common.h" +#include "References.h" +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +class JThrowable; + +class JCppException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } + + static local_ref create(const std::exception& ex) { + return newInstance(make_jstring(ex.what())); + } +}; + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +/** + * This class wraps a Java exception into a C++ exception; if the exception is routed back + * to the Java side, it can be unwrapped and just look like a pure Java interaction. The class + * is resilient to errors while creating the exception, falling back to some pre-allocated + * exceptions if a new one cannot be allocated or populated. + * + * Note: the what() method of this class is not thread-safe (t6900503). + */ +class FBEXPORT JniException : public std::exception { + public: + JniException(); + ~JniException(); + + explicit JniException(alias_ref throwable); + + JniException(JniException &&rhs); + + JniException(const JniException &other); + + local_ref getThrowable() const noexcept; + + virtual const char* what() const noexcept; + + void setJavaException() const noexcept; + + private: + global_ref throwable_; + mutable std::string what_; + mutable bool isMessageExtracted_; + const static std::string kExceptionMessageFailure_; + + void populateWhat() const noexcept; +}; + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw C++ exceptions + +static const int kMaxExceptionMessageBufferSize = 512; + +// These methods are the preferred way to throw a Java exception from +// a C++ function. They create and throw a C++ exception which wraps +// a Java exception, so the C++ flow is interrupted. Then, when +// translatePendingCppExceptionToJavaException is called at the +// topmost level of the native stack, the wrapped Java exception is +// thrown to the java caller. +template +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) { + int msgSize = snprintf(nullptr, 0, fmt, args...); + + char *msg = (char*) alloca(msgSize + 1); + snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...); + throwNewJavaException(throwableName, msg); +} + +// 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; + +// For convenience, some exception names in java.lang are available here. + +const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException"; + +}} diff --git a/lib/fb/include/fb/fbjni/File.h b/lib/fb/include/fb/fbjni/File.h new file mode 100644 index 00000000..29fc9850 --- /dev/null +++ b/lib/fb/include/fb/fbjni/File.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +class JFile : public JavaClass { + public: + static constexpr const char* kJavaDescriptor = "Ljava/io/File;"; + + // Define a method that calls into the represented Java class + std::string getAbsolutePath() { + static auto method = getClass()->getMethod("getAbsolutePath"); + return method(self())->toStdString(); + } + +}; + +} +} diff --git a/lib/fb/include/fb/fbjni/Hybrid.h b/lib/fb/include/fb/fbjni/Hybrid.h new file mode 100644 index 00000000..e272c2da --- /dev/null +++ b/lib/fb/include/fb/fbjni/Hybrid.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +namespace detail { + +class BaseHybridClass { +public: + virtual ~BaseHybridClass() {} +}; + +struct FBEXPORT HybridData : public JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;"; + void setNativePointer(std::unique_ptr new_value); + BaseHybridClass* getNativePointer(); + static local_ref create(); +}; + +template +struct HybridTraits { + // This static assert should actually always fail if we don't use one of the + // specializations below. + static_assert( + std::is_base_of::value || + std::is_base_of::value, + "The base of a HybridClass must be either another HybridClass or derived from JObject."); +}; + +template <> +struct HybridTraits { + using CxxBase = BaseHybridClass; + using JavaBase = JObject; +}; + +template +struct HybridTraits< + Base, + typename std::enable_if::value>::type> { + using CxxBase = Base; + using JavaBase = typename Base::JavaPart; +}; + +template +struct HybridTraits< + Base, + typename std::enable_if::value>::type> { + using CxxBase = BaseHybridClass; + using JavaBase = Base; +}; + +// convert to HybridClass* from jhybridobject +template +struct FBEXPORT Convert< + T, typename std::enable_if< + std::is_base_of::type>::value>::type> { + typedef typename std::remove_pointer::type::jhybridobject jniType; + static T fromJni(jniType t) { + if (t == nullptr) { + return nullptr; + } + return wrap_alias(t)->cthis(); + } + // There is no automatic return conversion for objects. +}; + +template +struct RefReprType::value, void>::type> { + static_assert(std::is_same::value, + "HybridFoo (where HybridFoo derives from HybridClass) is not supported in this context. " + "For an xxx_ref, you may want: xxx_ref or HybridFoo*."); + using Repr = T; +}; + + +} + +template +class FBEXPORT HybridClass : public detail::HybridTraits::CxxBase { +public: + struct JavaPart : JavaClass::JavaBase> { + // At this point, T is incomplete, and so we cannot access + // T::kJavaDescriptor directly. jtype_traits support this escape hatch for + // such a case. + static constexpr const char* kJavaDescriptor = nullptr; + static std::string get_instantiated_java_descriptor(); + static std::string get_instantiated_base_name(); + + using HybridType = T; + + // This will reach into the java object and extract the C++ instance from + // the mHybridData and return it. + T* cthis(); + + friend class HybridClass; + }; + + using jhybridobject = typename JavaPart::javaobject; + using javaobject = typename JavaPart::javaobject; + typedef detail::HybridData::javaobject jhybriddata; + + static alias_ref javaClassStatic() { + return JavaPart::javaClassStatic(); + } + + static local_ref javaClassLocal() { + std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2); + return findClassLocal(className.c_str()); + } + +protected: + typedef HybridClass HybridBase; + + // This ensures that a C++ hybrid part cannot be created on its own + // by default. If a hybrid wants to enable this, it can provide its + // own public ctor, or change the accessibility of this to public. + using detail::HybridTraits::CxxBase::CxxBase; + + static void registerHybrid(std::initializer_list methods) { + javaClassStatic()->registerNatives(methods); + } + + static local_ref makeHybridData(std::unique_ptr cxxPart) { + auto hybridData = detail::HybridData::create(); + hybridData->setNativePointer(std::move(cxxPart)); + return hybridData; + } + + template + static local_ref makeCxxInstance(Args&&... args) { + return makeHybridData(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 + // through java. This method requires the Java part to have a ctor + // which takes a HybridData, and for the C++ part to have a ctor + // compatible with the arguments passed here. For safety, the ctor + // can be private, and the hybrid declared a friend of its base, so + // the hybrid can only be created from here. + // + // Exception behavior: This can throw an exception if creating the + // 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); + } + + // TODO? Create reusable interface for Allocatable classes and use it to + // strengthen type-checking (and possibly provide a default + // implementation of allocate().) + template + static local_ref allocateWithCxxArgs(Args&&... args) { + auto hybridData = makeCxxInstance(std::forward(args)...); + static auto allocateMethod = + javaClassStatic()->template getStaticMethod("allocate"); + return allocateMethod(javaClassStatic(), hybridData.get()); + } + + // Factory method for creating a hybrid object where the arguments + // are passed to the java ctor. + template + static local_ref newObjectJavaArgs(Args&&... args) { + return JavaPart::newInstance(std::move(args)...); + } + + // If a hybrid class throws an exception which derives from + // std::exception, it will be passed to mapException on the hybrid + // class, or nearest ancestor. This allows boilerplate exception + // translation code (for example, calling throwNewJavaException on a + // particular java class) to be hoisted to a common function. If + // mapException returns, then the std::exception will be translated + // to Java. + static void mapException(const std::exception& ex) {} +}; + +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"); + } + // 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; +}; + +template +/* static */ inline std::string HybridClass::JavaPart::get_instantiated_java_descriptor() { + return T::kJavaDescriptor; +} + +template +/* static */ inline std::string HybridClass::JavaPart::get_instantiated_base_name() { + auto name = get_instantiated_java_descriptor(); + return name.substr(1, name.size() - 2); +} + +// Given a *_ref object which refers to a hybrid class, this will reach inside +// of it, find the mHybridData, extract the C++ instance pointer, cast it to +// the appropriate type, and return it. +template +inline auto cthis(T jthis) -> decltype(jthis->cthis()) { + return jthis->cthis(); +} + +void HybridDataOnLoad(); + +} +} diff --git a/lib/fb/include/fb/fbjni/Iterator-inl.h b/lib/fb/include/fb/fbjni/Iterator-inl.h new file mode 100644 index 00000000..206d2b4b --- /dev/null +++ b/lib/fb/include/fb/fbjni/Iterator-inl.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +namespace facebook { +namespace jni { + +namespace detail { + +template +struct IteratorHelper : public JavaClass> { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/IteratorHelper;"; + + typedef local_ref value_type; + typedef ptrdiff_t difference_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::forward_iterator_tag iterator_category; + + typedef JavaClass> JavaBase_; + + bool hasNext() const { + static auto hasNextMethod = + JavaBase_::javaClassStatic()->template getMethod("hasNext"); + return hasNextMethod(JavaBase_::self()); + } + + value_type next() { + static auto elementField = + JavaBase_::javaClassStatic()->template getField("mElement"); + return dynamic_ref_cast(JavaBase_::getFieldValue(elementField)); + } + + static void reset(value_type& v) { + v.reset(); + } +}; + +template +struct MapIteratorHelper : public JavaClass> { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/MapIteratorHelper;"; + + typedef std::pair, local_ref> value_type; + + typedef JavaClass> JavaBase_; + + bool hasNext() const { + static auto hasNextMethod = + JavaBase_::javaClassStatic()->template getMethod("hasNext"); + return hasNextMethod(JavaBase_::self()); + } + + value_type next() { + static auto keyField = JavaBase_::javaClassStatic()->template getField("mKey"); + static auto valueField = JavaBase_::javaClassStatic()->template getField("mValue"); + return std::make_pair(dynamic_ref_cast(JavaBase_::getFieldValue(keyField)), + dynamic_ref_cast(JavaBase_::getFieldValue(valueField))); + } + + static void reset(value_type& v) { + v.first.reset(); + v.second.reset(); + } +}; + +template +class Iterator { + public: + typedef typename T::value_type value_type; + typedef ptrdiff_t difference_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::input_iterator_tag iterator_category; + + // begin ctor + Iterator(global_ref&& helper) + : helper_(std::move(helper)) + , i_(-1) { + ++(*this); + } + + // end ctor + Iterator() + : i_(-1) {} + + bool operator==(const Iterator& it) const { return i_ == it.i_; } + bool operator!=(const Iterator& it) const { return !(*this == it); } + const value_type& operator*() const { assert(i_ != -1); return entry_; } + const value_type* operator->() const { assert(i_ != -1); return &entry_; } + Iterator& operator++() { // preincrement + bool hasNext = helper_->hasNext(); + if (hasNext) { + ++i_; + entry_ = helper_->next(); + } else { + i_ = -1; + helper_->reset(entry_); + } + return *this; + } + Iterator operator++(int) { // postincrement + Iterator ret; + ret.i_ = i_; + ret.entry_ = std::move(entry_); + ++(*this); + return ret; + } + + global_ref helper_; + // set to -1 at end + std::ptrdiff_t i_; + value_type entry_; +}; + +} + +template +struct JIterator::Iterator : public detail::Iterator> { + using detail::Iterator>::Iterator; +}; + +template +typename JIterator::Iterator JIterator::begin() const { + static auto ctor = detail::IteratorHelper::javaClassStatic()-> + template getConstructor::javaobject( + typename JIterator::javaobject)>(); + return Iterator( + make_global( + detail::IteratorHelper::javaClassStatic()->newObject(ctor, this->self()))); +} + +template +typename JIterator::Iterator JIterator::end() const { + return Iterator(); +} + +template +struct JIterable::Iterator : public detail::Iterator> { + using detail::Iterator>::Iterator; +}; + +template +typename JIterable::Iterator JIterable::begin() const { + static auto ctor = detail::IteratorHelper::javaClassStatic()-> + template getConstructor::javaobject( + typename JIterable::javaobject)>(); + return Iterator( + make_global( + detail::IteratorHelper::javaClassStatic()->newObject(ctor, this->self()))); +} + +template +typename JIterable::Iterator JIterable::end() const { + return Iterator(); +} + +template +size_t JCollection::size() const { + static auto sizeMethod = + JCollection::javaClassStatic()->template getMethod("size"); + return sizeMethod(this->self()); +} + +template +struct JMap::Iterator : public detail::Iterator> { + using detail::Iterator>::Iterator; +}; + +template +size_t JMap::size() const { + static auto sizeMethod = + JMap::javaClassStatic()->template getMethod("size"); + return sizeMethod(this->self()); +} + +template +typename JMap::Iterator JMap::begin() const { + static auto ctor = detail::MapIteratorHelper::javaClassStatic()-> + template getConstructor::javaobject( + typename JMap::javaobject)>(); + return Iterator( + make_global( + detail::MapIteratorHelper::javaClassStatic()->newObject(ctor, this->self()))); +} + +template +typename JMap::Iterator JMap::end() const { + return Iterator(); +} + +} +} diff --git a/lib/fb/include/fb/fbjni/Iterator.h b/lib/fb/include/fb/fbjni/Iterator.h new file mode 100644 index 00000000..aa365266 --- /dev/null +++ b/lib/fb/include/fb/fbjni/Iterator.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +/** + * JavaClass which represents a reference to a java.util.Iterator instance. It + * provides begin()/end() methods to provide C++-style iteration over the + * underlying collection. The class has a template parameter for the element + * type, which defaults to jobject. For example: + * + * alias_ref::javaobject> my_iter = ...; + * + * In the simplest case, it can be used just as alias_ref::javaobject>, + * for example in a method declaration. + */ +template +struct JIterator : JavaClass> { + constexpr static auto kJavaDescriptor = "Ljava/util/Iterator;"; + + struct Iterator; + + /** + * To iterate: + * + * for (const auto& element : *jiter) { ... } + * + * The JIterator iterator value_type is local_ref, containing a reference + * to an element instance. + * + * If the Iterator returns objects whch are not convertible to the given + * element type, iteration will throw a java ClassCastException. + * + * For example, to convert an iterator over a collection of java strings to + * an std::vector of std::strings: + * + * std::vector vs; + * for (const auto& elem : *jiter) { + * vs.push_back(elem->toStdString()); + * } + * + * Or if you prefer using std algorithms: + * + * std::vector vs; + * std::transform(jiter->begin(), jiter->end(), std::back_inserter(vs), + * [](const local_ref& elem) { return elem->toStdString(); }); + * + * The iterator is a InputIterator. + */ + Iterator begin() const; + Iterator end() const; +}; + +/** + * Similar to JIterator, except this represents any object which implements the + * java.lang.Iterable interface. It will create the Java Iterator as a part of + * begin(). + */ +template +struct JIterable : JavaClass> { + constexpr static auto kJavaDescriptor = "Ljava/lang/Iterable;"; + + struct Iterator; + + Iterator begin() const; + Iterator end() const; +}; + +/** + * JavaClass types which represent Collection, List, and Set are also provided. + * These preserve the Java class heirarchy. + */ +template +struct JCollection : JavaClass, JIterable> { + constexpr static auto kJavaDescriptor = "Ljava/util/Collection;"; + + /** + * Returns the number of elements in the collection. + */ + size_t size() const; +}; + +template +struct JList : JavaClass, JCollection> { + constexpr static auto kJavaDescriptor = "Ljava/util/List;"; +}; + +template +struct JSet : JavaClass, JCollection> { + constexpr static auto kJavaDescriptor = "Ljava/util/Set;"; +}; + +/** + * JavaClass which represents a reference to a java.util.Map instance. It adds + * wrappers around Java methods, including begin()/end() methods to provide + * C++-style iteration over the Java Map. The class has template parameters + * for the key and value types, which default to jobject. For example: + * + * alias_ref::javaobject> my_map = ...; + * + * In the simplest case, it can be used just as alias_ref::javaobject>, + * for example in a method declaration. + */ +template +struct JMap : JavaClass> { + constexpr static auto kJavaDescriptor = "Ljava/util/Map;"; + + struct Iterator; + + /** + * Returns the number of pairs in the map. + */ + size_t size() const; + + /** + * To iterate over the Map: + * + * for (const auto& entry : *jmap) { ... } + * + * The JMap iterator value_type is std::pair, local_ref> + * containing references to key and value instances. + * + * If the Map contains objects whch are not convertible to the given key and + * value types, iteration will throw a java ClassCastException. + * + * The iterator is a InputIterator. + */ + Iterator begin() const; + Iterator end() const; +}; + +} +} + +#include "Iterator-inl.h" diff --git a/lib/fb/include/fb/fbjni/JThread.h b/lib/fb/include/fb/fbjni/JThread.h new file mode 100644 index 00000000..60b3cac2 --- /dev/null +++ b/lib/fb/include/fb/fbjni/JThread.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" +#include "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/include/fb/fbjni/Meta-forward.h b/lib/fb/include/fb/fbjni/Meta-forward.h new file mode 100644 index 00000000..ff08aff4 --- /dev/null +++ b/lib/fb/include/fb/fbjni/Meta-forward.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +namespace facebook { +namespace jni { + +template +class JMethod; +template +class JStaticMethod; +template +class JNonvirtualMethod; +template +struct JConstructor; +template +class JField; +template +class JStaticField; + +/// Type traits for Java types (currently providing Java type descriptors) +template +struct jtype_traits; + +/// Type traits for Java methods (currently providing Java type descriptors) +template +struct jmethod_traits; + +}} diff --git a/lib/fb/include/fb/fbjni/Meta-inl.h b/lib/fb/include/fb/fbjni/Meta-inl.h new file mode 100644 index 00000000..45cf2d1b --- /dev/null +++ b/lib/fb/include/fb/fbjni/Meta-inl.h @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include "Common.h" +#include "Exceptions.h" +#include "MetaConvert.h" +#include "References.h" +#include "Boxed.h" + +#if defined(__ANDROID__) +#include +#endif + +namespace facebook { +namespace jni { + +// JMethod ///////////////////////////////////////////////////////////////////////////////////////// + +inline JMethodBase::JMethodBase(jmethodID method_id) noexcept + : method_id_{method_id} +{} + +inline JMethodBase::operator bool() const noexcept { + return method_id_ != nullptr; +} + +inline jmethodID JMethodBase::getId() const noexcept { + return method_id_; +} + +namespace { + +template +struct ArgsArraySetter; + +template +struct ArgsArraySetter { + static void set(alias_ref::javaobject> array, Arg arg0, Args... args) { + // TODO(xxxxxxxx): Use Convert... to do conversions like the fast path. + (*array)[idx] = autobox(arg0); + ArgsArraySetter::set(array, args...); + } +}; + +template +struct ArgsArraySetter { + static void set(alias_ref::javaobject> array) { + } +}; + +template +local_ref::javaobject> makeArgsArray(Args... args) { + auto arr = JArrayClass::newArray(sizeof...(args)); + ArgsArraySetter<0, Args...>::set(arr, 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) { + const auto env = Environment::current(); + env->CallVoidMethod( + self.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_CALL") +#undef DEFINE_PRIMITIVE_CALL +#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD, CLASS) \ +template \ +inline TYPE JMethod::operator()(alias_ref self, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->Call ## METHOD ## Method( \ + self.get(), \ + getId(), \ + detail::callToJni(detail::Convert::type>::toCall(args))...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + 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) +#pragma pop_macro("DEFINE_PRIMITIVE_CALL") + +/// JMethod specialization for references that wraps the return value in a @ref local_ref +template +class JMethod : public JMethodBase { + public: + // TODO: static_assert is jobject-derived or local_ref jobject + using JniRet = typename detail::Convert::type>::jniType; + static_assert(IsPlainJniReference(), "JniRet must be a JNI reference"); + using JMethodBase::JMethodBase; + JMethod() noexcept {}; + 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); + + friend class JClass; +}; + +template +inline auto JMethod::operator()(alias_ref self, Args... args) -> local_ref { + const auto env = Environment::current(); + auto result = env->CallObjectMethod( + self.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + +template +inline void JStaticMethod::operator()(alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + env->CallStaticVoidMethod( + cls.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_CALL") +#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(); \ + auto result = env->CallStatic ## METHOD ## Method( \ + cls.get(), \ + getId(), \ + detail::callToJni(detail::Convert::type>::toCall(args))...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_STATIC_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_STATIC_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_STATIC_CALL(jchar, Char) +DEFINE_PRIMITIVE_STATIC_CALL(jshort, Short) +DEFINE_PRIMITIVE_STATIC_CALL(jint, Int) +DEFINE_PRIMITIVE_STATIC_CALL(jlong, Long) +DEFINE_PRIMITIVE_STATIC_CALL(jfloat, Float) +DEFINE_PRIMITIVE_STATIC_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_CALL") + +/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref +template +class JStaticMethod : public JMethodBase { + + public: + using JniRet = typename detail::Convert::type>::jniType; + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + using JMethodBase::JMethodBase; + JStaticMethod() noexcept {}; + 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(); + auto result = env->CallStaticObjectMethod( + cls.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); + } + + friend class JClass; +}; + +template +inline void +JNonvirtualMethod::operator()(alias_ref self, alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + env->CallNonvirtualVoidMethod( + self.get(), + cls.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_CALL +#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(); \ + auto result = env->CallNonvirtual ## METHOD ## Method( \ + self.get(), \ + cls.get(), \ + getId(), \ + detail::callToJni(detail::Convert::type>::toCall(args))...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jchar, Char) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jshort, Short) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jint, Int) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jlong, Long) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jfloat, Float) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") + +/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref +template +class JNonvirtualMethod : public JMethodBase { + public: + using JniRet = typename detail::Convert::type>::jniType; + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + using JMethodBase::JMethodBase; + JNonvirtualMethod() noexcept {}; + 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(); + auto result = env->CallNonvirtualObjectMethod( + self.get(), + cls.get(), + getId(), + detail::callToJni(detail::Convert::type>::toCall(args))...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); + } + + friend class JClass; +}; + +template +local_ref slowCall(jmethodID method_id, alias_ref self, Args... args) { + static auto invoke = findClassStatic("java/lang/reflect/Method") + ->getMethod::javaobject)>("invoke"); + // TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod. + auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE)); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (!reflected) throw std::runtime_error("Unable to get reflected java.lang.reflect.Method"); + auto argsArray = makeArgsArray(args...); + // No need to check for exceptions since invoke is itself a JMethod that will do that for us. + return invoke(reflected, self.get(), argsArray.get()); +} + + +// JField /////////////////////////////////////////////////////////////////////////////////////// + +template +inline JField::JField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_FIELD_PRIMITIVE_GET_SET +#define DEFINE_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JField::get(jobject object) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->Get ## METHOD ## Field(object, field_id_); \ +} \ + \ +template<> \ +inline void JField::set(jobject object, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->Set ## METHOD ## Field(object, field_id_, value); \ +} + +DEFINE_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") + +template +inline T JField::get(jobject object) const noexcept { + return static_cast(internal::getEnv()->GetObjectField(object, field_id_)); +} + +template +inline void JField::set(jobject object, T value) noexcept { + internal::getEnv()->SetObjectField(object, field_id_, static_cast(value)); +} + +// JStaticField ///////////////////////////////////////////////////////////////////////////////// + +template +inline JStaticField::JStaticField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JStaticField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JStaticField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET +#define DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JStaticField::get(jclass jcls) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->GetStatic ## METHOD ## Field(jcls, field_id_); \ +} \ + \ +template<> \ +inline void JStaticField::set(jclass jcls, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->SetStatic ## METHOD ## Field(jcls, field_id_, value); \ +} + +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") + +template +inline T JStaticField::get(jclass jcls) const noexcept { + const auto env = internal::getEnv(); + 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); +} + + +// jmethod_traits ////////////////////////////////////////////////////////////////////////////////// + +// TODO(T6608405) Adapt this to implement a register natives method that requires no descriptor +namespace internal { + +template +inline std::string JavaDescriptor() { + return jtype_traits::descriptor(); +} + +template +inline std::string JavaDescriptor() { + return JavaDescriptor() + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "(" + JavaDescriptor() + ")" + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "()" + JavaDescriptor(); +} + +} // internal + +template +inline std::string jmethod_traits::descriptor() { + return internal::JMethodDescriptor(); +} + +template +inline std::string jmethod_traits::constructor_descriptor() { + return internal::JMethodDescriptor(); +} + +}} diff --git a/lib/fb/include/fb/fbjni/Meta.h b/lib/fb/include/fb/fbjni/Meta.h new file mode 100644 index 00000000..4aefb0ca --- /dev/null +++ b/lib/fb/include/fb/fbjni/Meta.h @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @file meta.h + * + * Provides wrappers for meta data such as methods and fields. + */ + +#pragma once + +#include +#include + +#include + +#include "References-forward.h" + +#ifdef __ANDROID__ +# include +# define XLOG_TAG "fb-jni" +# define XLOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, XLOG_TAG, __VA_ARGS__) +# define XLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, XLOG_TAG, __VA_ARGS__) +# define XLOGI(...) __android_log_print(ANDROID_LOG_INFO, XLOG_TAG, __VA_ARGS__) +# define XLOGW(...) __android_log_print(ANDROID_LOG_WARN, XLOG_TAG, __VA_ARGS__) +# define XLOGE(...) __android_log_print(ANDROID_LOG_ERROR, XLOG_TAG, __VA_ARGS__) +# define XLOGWTF(...) __android_log_print(ANDROID_LOG_FATAL, XLOG_TAG, __VA_ARGS__) +#endif + +namespace facebook { +namespace jni { + +// This will get the reflected Java Method from the method_id, get it's invoke +// method, and call the method via that. This shouldn't ever be needed, but +// Android 6.0 crashes when calling a method on a java.lang.Proxy via jni. +template +local_ref slowCall(jmethodID method_id, alias_ref self, Args... args); + +class JObject; + + +/// Wrapper of a jmethodID. Provides a common base for JMethod specializations +class JMethodBase { + public: + /// Verify that the method is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jmethodID getId() const noexcept; + + protected: + /// Create a wrapper of a method id + explicit JMethodBase(jmethodID method_id = nullptr) noexcept; + + private: + jmethodID method_id_; +}; + + +/// Representation of a jmethodID +template +class JMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_METHOD_CLASS + +// Defining JMethod specializations based on return value +#define DEFINE_PRIMITIVE_METHOD_CLASS(TYPE) \ +template \ +class JMethod : public JMethodBase { \ + public: \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "TYPE must be primitive or void"); \ + \ + using JMethodBase::JMethodBase; \ + JMethod() noexcept {}; \ + JMethod(const JMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, Args... args); \ + \ + friend class JClass; \ +} + +DEFINE_PRIMITIVE_METHOD_CLASS(void); +DEFINE_PRIMITIVE_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_METHOD_CLASS") +/// @endcond + + +/// Convenience type representing constructors +/// These should only be used with JClass::getConstructor and JClass::newObject. +template +struct JConstructor : private JMethod { + using JMethod::JMethod; + private: + JConstructor(const JMethod& other) : JMethod(other.getId()) {} + friend class JClass; +}; + +/// Representation of a jStaticMethodID +template +class JStaticMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_STATIC_METHOD_CLASS + +// Defining JStaticMethod specializations based on return value +#define DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(TYPE) \ +template \ +class JStaticMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JStaticMethod() noexcept {}; \ + JStaticMethod(const JStaticMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref cls, Args... args); \ + \ + friend class JClass; \ +} + +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(void); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") +/// @endcond + + +/// Representation of a jNonvirtualMethodID +template +class JNonvirtualMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS + +// Defining JNonvirtualMethod specializations based on return value +#define DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(TYPE) \ +template \ +class JNonvirtualMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JNonvirtualMethod() noexcept {}; \ + JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, alias_ref cls, Args... args); \ + \ + friend class JClass; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(void); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") +/// @endcond + + +/** + * JField represents typed fields and simplifies their access. Note that object types return + * raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jobject object) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jobject object, T value) noexcept; + + friend class JObject; +}; + + +/** + * JStaticField represents typed fields and simplifies their access. Note that object types + * return raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JStaticField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JStaticField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jclass jcls) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jclass jcls, T value) noexcept; + + friend class JClass; + friend class JObject; +}; + + +/// Template magic to provide @ref jmethod_traits +template +struct jmethod_traits { + static std::string descriptor(); + static std::string constructor_descriptor(); +}; + + +// jtype_traits //////////////////////////////////////////////////////////////////////////////////// + +template +struct jtype_traits { +private: + using Repr = ReprType; +public: + // The jni type signature (described at + // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html). + static std::string descriptor() { + std::string descriptor; + if (Repr::kJavaDescriptor == nullptr) { + descriptor = Repr::get_instantiated_java_descriptor(); + } else { + descriptor = Repr::kJavaDescriptor; + } + return descriptor; + } + + // The signature used for class lookups. See + // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName(). + static std::string base_name() { + if (Repr::kJavaDescriptor != nullptr) { + std::string base_name = Repr::kJavaDescriptor; + return base_name.substr(1, base_name.size() - 2); + } + return Repr::get_instantiated_base_name(); + } +}; + +#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") +#undef DEFINE_FIELD_AND_ARRAY_TRAIT + +#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC) \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{#DSC}; } \ + static std::string base_name() { return descriptor(); } \ + using array_type = TYPE ## Array; \ +}; \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{"[" #DSC}; } \ + static std::string base_name() { return descriptor(); } \ + using entry_type = TYPE; \ +}; + +// There is no voidArray, handle that without the macro. +template<> +struct jtype_traits { + static std::string descriptor() { return std::string{"V"}; }; +}; + +DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z) +DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte, B) +DEFINE_FIELD_AND_ARRAY_TRAIT(jchar, C) +DEFINE_FIELD_AND_ARRAY_TRAIT(jshort, S) +DEFINE_FIELD_AND_ARRAY_TRAIT(jint, I) +DEFINE_FIELD_AND_ARRAY_TRAIT(jlong, J) +DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat, F) +DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble, D) + +#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") + + +template +struct jmethod_traits_from_cxx; + +}} + +#include "Meta-inl.h" diff --git a/lib/fb/include/fb/fbjni/MetaConvert.h b/lib/fb/include/fb/fbjni/MetaConvert.h new file mode 100644 index 00000000..33027c7e --- /dev/null +++ b/lib/fb/include/fb/fbjni/MetaConvert.h @@ -0,0 +1,122 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include "Common.h" +#include "References.h" + +namespace facebook { +namespace jni { + +namespace detail { + +// In order to avoid potentially filling the jni locals table, +// temporary objects (right now, this is just jstrings) need to be +// released. This is done by returning a holder which autoconverts to +// jstring. +template +inline T callToJni(T&& t) { + return t; +} + +template +inline JniType callToJni(local_ref&& sref) { + return sref.get(); +} + +// Normally, pass through types unmolested. +template +struct Convert { + typedef T jniType; + static jniType fromJni(jniType t) { + return t; + } + static jniType toJniRet(jniType t) { + return t; + } + static jniType toCall(jniType t) { + return t; + } +}; + +// This is needed for return conversion +template <> +struct Convert { + typedef void jniType; +}; + +// jboolean is an unsigned char, not a bool. Allow it to work either way. +template<> +struct Convert { + typedef jboolean jniType; + static bool fromJni(jniType t) { + return t; + } + static jniType toJniRet(bool t) { + return t; + } + static jniType toCall(bool t) { + return t; + } +}; + +// convert to alias_ref from T +template +struct Convert> { + typedef JniType jniType; + static alias_ref fromJni(jniType t) { + return wrap_alias(t); + } + static jniType toJniRet(alias_ref t) { + return t.get(); + } + static jniType toCall(alias_ref t) { + return t.get(); + } +}; + +// convert return from local_ref +template +struct Convert> { + typedef JniType jniType; + // No automatic synthesis of local_ref + static jniType toJniRet(local_ref t) { + return t.release(); + } + static jniType toCall(local_ref t) { + return t.get(); + } +}; + +// convert return from global_ref +template +struct Convert> { + typedef JniType jniType; + // No automatic synthesis of global_ref + static jniType toJniRet(global_ref t) { + return t.get(); + } + static jniType toCall(global_ref t) { + return t.get(); + } +}; + +template struct jni_sig_from_cxx_t; +template +struct jni_sig_from_cxx_t { + using JniRet = typename Convert::type>::jniType; + using JniSig = JniRet(typename Convert::type>::jniType...); +}; + +template +using jni_sig_from_cxx = typename jni_sig_from_cxx_t::JniSig; + +} // namespace detail + +template +struct jmethod_traits_from_cxx : jmethod_traits> { +}; + +}} diff --git a/lib/fb/include/fb/fbjni/NativeRunnable.h b/lib/fb/include/fb/fbjni/NativeRunnable.h new file mode 100644 index 00000000..68da6a25 --- /dev/null +++ b/lib/fb/include/fb/fbjni/NativeRunnable.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "CoreClasses.h" +#include "Hybrid.h" +#include "Registration.h" + +#include + +namespace facebook { +namespace jni { + +struct JRunnable : public JavaClass { + static auto constexpr kJavaDescriptor = "Ljava/lang/Runnable;"; +}; + +struct JNativeRunnable : public HybridClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;"; + + JNativeRunnable(std::function&& runnable) : runnable_(std::move(runnable)) {} + + static void OnLoad() { + registerHybrid({ + makeNativeMethod("run", JNativeRunnable::run), + }); + } + + void run() { + runnable_(); + } + + private: + std::function runnable_; +}; + + +} // namespace jni +} // namespace facebook diff --git a/lib/fb/include/fb/fbjni/ReferenceAllocators-inl.h b/lib/fb/include/fb/fbjni/ReferenceAllocators-inl.h new file mode 100644 index 00000000..9106dd25 --- /dev/null +++ b/lib/fb/include/fb/fbjni/ReferenceAllocators-inl.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace jni { + +/// @cond INTERNAL +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; + + void reset() noexcept; +}; + +extern ReferenceStats g_reference_stats; +} +/// @endcond + + +// LocalReferenceAllocator ///////////////////////////////////////////////////////////////////////// + +inline jobject LocalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Local new: %p", original); + auto ref = internal::getEnv()->NewLocalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void LocalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Local release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.locals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteLocalRef(reference); + } +} + +inline bool LocalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNILocalRefType; +} + + +// GlobalReferenceAllocator //////////////////////////////////////////////////////////////////////// + +inline jobject GlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Global new: %p", original); + auto ref = internal::getEnv()->NewGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void GlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.globals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteGlobalRef(reference); + } +} + +inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIGlobalRefType; +} + + +// WeakGlobalReferenceAllocator //////////////////////////////////////////////////////////////////// + +inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Weak global new: %p", original); + auto ref = internal::getEnv()->NewWeakGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void WeakGlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Weak Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.weaks_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteWeakGlobalRef(reference); + } +} + +inline bool WeakGlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIWeakGlobalRefType; +} + +}} diff --git a/lib/fb/include/fb/fbjni/ReferenceAllocators.h b/lib/fb/include/fb/fbjni/ReferenceAllocators.h new file mode 100644 index 00000000..024ba797 --- /dev/null +++ b/lib/fb/include/fb/fbjni/ReferenceAllocators.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * @file ReferenceAllocators.h + * + * Reference allocators are used to create and delete various classes of JNI references (local, + * global, and weak global). + */ + +#pragma once + +#include + +#include "Common.h" + +namespace facebook { namespace jni { + +/// Allocator that handles local references +class FBEXPORT LocalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles global references +class FBEXPORT GlobalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles weak global references +class FBEXPORT 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. + */ +FBEXPORT bool doesGetObjectRefTypeWork(); + +} +/// @endcond + +}} + +#include "ReferenceAllocators-inl.h" diff --git a/lib/fb/include/fb/fbjni/References-forward.h b/lib/fb/include/fb/fbjni/References-forward.h new file mode 100644 index 00000000..8dabf67c --- /dev/null +++ b/lib/fb/include/fb/fbjni/References-forward.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "ReferenceAllocators.h" + +namespace facebook { +namespace jni { + +template +class JObjectWrapper; + +namespace detail { +struct JObjectBase { + jobject get() const noexcept; + void set(jobject reference) noexcept; + jobject this_; +}; + +// RefReprType maps a type to the representation used by fbjni smart references. +template +struct RefReprType; + +template +struct JavaObjectType; + +template +struct ReprAccess; +} + +// Given T, either a jobject-like type or a JavaClass-derived type, ReprType +// is the corresponding JavaClass-derived type and JniType is the +// jobject-like type. +template +using ReprType = typename detail::RefReprType::type; + +template +using JniType = typename detail::JavaObjectType::type; + +template +class base_owned_ref; + +template +class basic_strong_ref; + +template +class weak_ref; + +template +class alias_ref; + +/// A smart unique reference owning a local JNI reference +template +using local_ref = basic_strong_ref; + +/// A smart unique reference owning a global JNI reference +template +using global_ref = basic_strong_ref; + +}} // namespace facebook::jni diff --git a/lib/fb/include/fb/fbjni/References-inl.h b/lib/fb/include/fb/fbjni/References-inl.h new file mode 100644 index 00000000..f81a8432 --- /dev/null +++ b/lib/fb/include/fb/fbjni/References-inl.h @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +template +inline enable_if_t(), T> getPlainJniReference(T ref) { + return ref; +} + +template +inline JniType getPlainJniReference(alias_ref ref) { + return ref.get(); +} + +template +inline JniType getPlainJniReference(const base_owned_ref& ref) { + return ref.get(); +} + + +namespace detail { +template +struct ReprAccess { + using javaobject = JniType; + static void set(Repr& repr, javaobject obj) noexcept { + repr.JObjectBase::set(obj); + } + static javaobject get(const Repr& repr) { + return static_cast(repr.JObject::get()); + } +}; + +namespace { +template +void StaticAssertValidRepr() noexcept { + static_assert(std::is_base_of::value, + "A smart ref representation must be derived from JObject."); + static_assert(IsPlainJniReference>(), "T must be a JNI reference"); + static_assert(sizeof(Repr) == sizeof(JObjectBase), ""); + static_assert(alignof(Repr) == alignof(JObjectBase), ""); +} +} + +template +ReprStorage::ReprStorage(JniType obj) noexcept { + StaticAssertValidRepr(); + set(obj); +} + +template +void ReprStorage::set(JniType obj) noexcept { + new (&storage_) Repr; + ReprAccess::set(get(), obj); +} + +template +Repr& ReprStorage::get() noexcept { + return *reinterpret_cast(&storage_); +} + +template +const Repr& ReprStorage::get() const noexcept { + return *reinterpret_cast(&storage_); +} + +template +JniType ReprStorage::jobj() const noexcept { + ReprAccess::get(get()); + return ReprAccess::get(get()); +} + +template +void ReprStorage::swap(ReprStorage& other) noexcept { + StaticAssertValidRepr(); + using std::swap; + swap(get(), other.get()); +} + +inline void JObjectBase::set(jobject reference) noexcept { + this_ = reference; +} + +inline jobject JObjectBase::get() const noexcept { + return this_; +} + +template +enable_if_t(), plain_jni_reference_t> make_ref(const T& reference) { + auto old_reference = getPlainJniReference(reference); + if (!old_reference) { + return nullptr; + } + + auto ref = Alloc{}.newReference(old_reference); + if (!ref) { + // Note that we end up here if we pass a weak ref that refers to a collected object. + // Thus, it's hard to come up with a reason why this function should be used with + // weak references. + throw std::bad_alloc{}; + } + + return static_cast>(ref); +} + +} // namespace detail + +template +inline local_ref adopt_local(T ref) noexcept { + static_assert(IsPlainJniReference(), "T must be a plain jni reference"); + return local_ref{ref}; +} + +template +inline global_ref adopt_global(T ref) noexcept { + static_assert(IsPlainJniReference(), "T must be a plain jni reference"); + return global_ref{ref}; +} + +template +inline weak_ref adopt_weak_global(T ref) noexcept { + static_assert(IsPlainJniReference(), "T must be a plain jni reference"); + return weak_ref{ref}; +} + + +template +inline enable_if_t(), alias_ref> wrap_alias(T ref) noexcept { + return alias_ref(ref); +} + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + + +template +enable_if_t(), local_ref>> +make_local(const T& ref) { + return adopt_local(detail::make_ref(ref)); +} + +template +enable_if_t(), global_ref>> +make_global(const T& ref) { + return adopt_global(detail::make_ref(ref)); +} + +template +enable_if_t(), weak_ref>> +make_weak(const T& ref) { + return adopt_weak_global(detail::make_ref(ref)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b) { + return isSameObject(getPlainJniReference(a), getPlainJniReference(b)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b) { + return !(a == b); +} + + +// base_owned_ref /////////////////////////////////////////////////////////////////////// + +template +inline base_owned_ref::base_owned_ref() noexcept + : base_owned_ref(nullptr) +{} + +template +inline base_owned_ref::base_owned_ref(std::nullptr_t t) noexcept + : base_owned_ref(static_cast(nullptr)) +{} + +template +inline base_owned_ref::base_owned_ref(const base_owned_ref& other) + : storage_{static_cast(Alloc{}.newReference(other.get()))} +{} + +template +template +inline base_owned_ref::base_owned_ref(const base_owned_ref& other) + : storage_{static_cast(Alloc{}.newReference(other.get()))} +{ + static_assert(std::is_convertible, javaobject>::value, ""); +} + +template +inline facebook::jni::base_owned_ref::base_owned_ref( + javaobject reference) noexcept + : storage_(reference) { + assert(Alloc{}.verifyReference(reference)); + internal::dbglog("New wrapped ref=%p this=%p", get(), this); +} + +template +inline base_owned_ref::base_owned_ref( + base_owned_ref&& other) noexcept + : storage_(other.get()) { + internal::dbglog("New move from ref=%p other=%p", other.get(), &other); + internal::dbglog("New move to ref=%p this=%p", get(), this); + // JObject is a simple type and does not support move semantics so we explicitly + // clear other + other.set(nullptr); +} + +template +template +base_owned_ref::base_owned_ref(base_owned_ref&& other) noexcept + : storage_(other.get()) { + internal::dbglog("New move from ref=%p other=%p", other.get(), &other); + internal::dbglog("New move to ref=%p this=%p", get(), this); + // JObject is a simple type and does not support move semantics so we explicitly + // clear other + other.set(nullptr); +} + +template +inline base_owned_ref::~base_owned_ref() noexcept { + reset(); + internal::dbglog("Ref destruct ref=%p this=%p", get(), this); +} + +template +inline auto base_owned_ref::release() noexcept -> javaobject { + auto value = get(); + internal::dbglog("Ref release ref=%p this=%p", value, this); + set(nullptr); + return value; +} + +template +inline void base_owned_ref::reset() noexcept { + reset(nullptr); +} + +template +inline void base_owned_ref::reset(javaobject reference) noexcept { + if (get()) { + assert(Alloc{}.verifyReference(reference)); + Alloc{}.deleteReference(get()); + } + set(reference); +} + +template +inline auto base_owned_ref::get() const noexcept -> javaobject { + return storage_.jobj(); +} + +template +inline void base_owned_ref::set(javaobject ref) noexcept { + storage_.set(ref); +} + + +// weak_ref /////////////////////////////////////////////////////////////////////// + +template +inline weak_ref& weak_ref::operator=( + const weak_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline weak_ref& weak_ref::operator=( + weak_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + get(), this, other.get(), &other); + reset(other.release()); + return *this; +} + +template +local_ref weak_ref::lockLocal() const { + return adopt_local( + static_cast(LocalReferenceAllocator{}.newReference(get()))); +} + +template +global_ref weak_ref::lockGlobal() const { + return adopt_global( + static_cast(GlobalReferenceAllocator{}.newReference(get()))); +} + +template +inline void swap( + weak_ref& a, + weak_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.get(), &a, b.get(), &b); + a.storage_.swap(b.storage_); +} + + +// basic_strong_ref //////////////////////////////////////////////////////////////////////////// + +template +inline basic_strong_ref& basic_strong_ref::operator=( + const basic_strong_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline basic_strong_ref& basic_strong_ref::operator=( + basic_strong_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + get(), this, other.get(), &other); + reset(other.release()); + return *this; +} + +template +inline alias_ref basic_strong_ref::releaseAlias() noexcept { + return wrap_alias(release()); +} + +template +inline basic_strong_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline auto basic_strong_ref::operator->() noexcept -> Repr* { + return &storage_.get(); +} + +template +inline auto basic_strong_ref::operator->() const noexcept -> const Repr* { + return &storage_.get(); +} + +template +inline auto basic_strong_ref::operator*() noexcept -> Repr& { + return storage_.get(); +} + +template +inline auto basic_strong_ref::operator*() const noexcept -> const Repr& { + return storage_.get(); +} + +template +inline void swap( + basic_strong_ref& a, + basic_strong_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.get(), &a, b.get(), &b); + using std::swap; + a.storage_.swap(b.storage_); +} + + +// alias_ref ////////////////////////////////////////////////////////////////////////////// + +template +inline alias_ref::alias_ref() noexcept + : storage_{nullptr} +{} + +template +inline alias_ref::alias_ref(std::nullptr_t) noexcept + : storage_{nullptr} +{} + +template +inline alias_ref::alias_ref(const alias_ref& other) noexcept + : storage_{other.get()} +{} + +template +inline alias_ref::alias_ref(javaobject ref) noexcept + : storage_(ref) { + assert( + LocalReferenceAllocator{}.verifyReference(ref) || + GlobalReferenceAllocator{}.verifyReference(ref)); +} + +template +template +inline alias_ref::alias_ref(alias_ref other) noexcept + : storage_{other.get()} +{} + +template +template +inline alias_ref::alias_ref(const basic_strong_ref& other) noexcept + : storage_{other.get()} +{} + +template +inline alias_ref& alias_ref::operator=(alias_ref other) noexcept { + swap(*this, other); + return *this; +} + +template +inline alias_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline auto facebook::jni::alias_ref::get() const noexcept -> javaobject { + return storage_.jobj(); +} + +template +inline auto alias_ref::operator->() noexcept -> Repr* { + return &(**this); +} + +template +inline auto alias_ref::operator->() const noexcept -> const Repr* { + return &(**this); +} + +template +inline auto alias_ref::operator*() noexcept -> Repr& { + return storage_.get(); +} + +template +inline auto alias_ref::operator*() const noexcept -> const Repr& { + return storage_.get(); +} + +template +inline void alias_ref::set(javaobject ref) noexcept { + storage_.set(ref); +} + +template +inline void swap(alias_ref& a, alias_ref& b) noexcept { + a.storage_.swap(b.storage_); +} + +// Could reduce code duplication by using a pointer-to-function +// template argument. I'm not sure whether that would make the code +// more maintainable (DRY), or less (too clever/confusing.). +template +enable_if_t(), local_ref> +static_ref_cast(const local_ref& ref) noexcept +{ + T p = static_cast(ref.get()); + return make_local(p); +} + +template +enable_if_t(), global_ref> +static_ref_cast(const global_ref& ref) noexcept +{ + T p = static_cast(ref.get()); + return make_global(p); +} + +template +enable_if_t(), alias_ref> +static_ref_cast(const alias_ref& ref) noexcept +{ + T p = static_cast(ref.get()); + return wrap_alias(p); +} + +template +auto dynamic_ref_cast(const RefType& ref) -> +enable_if_t(), decltype(static_ref_cast(ref))> +{ + if (! ref) { + return decltype(static_ref_cast(ref))(); + } + + std::string target_class_name{jtype_traits::base_name()}; + + // 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)) { + throwNewJavaException("java/lang/ClassCastException", + "Tried to cast from %s to %s.", + source_class->toString().c_str(), + target_class_name.c_str()); + } + + return static_ref_cast(ref); +} + +}} diff --git a/lib/fb/include/fb/fbjni/References.h b/lib/fb/include/fb/fbjni/References.h new file mode 100644 index 00000000..8795216a --- /dev/null +++ b/lib/fb/include/fb/fbjni/References.h @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + + +/** @file References.h + * + * Functionality similar to smart pointers, but for references into the VM. Four main reference + * types are provided: local_ref, global_ref, weak_ref, and alias_ref. All are generic + * templates that and refer to objects in the jobject hierarchy. The type of the referred objects + * are specified using the template parameter. All reference types except alias_ref own their + * underlying reference, just as a std smart pointer owns the underlying raw pointer. In the context + * of std smart pointers, these references behave like unique_ptr, and have basically the same + * interface. Thus, when the reference is destructed, the plain JNI reference, i.e. the underlying + * JNI reference (like the parameters passed directly to JNI functions), is released. The alias + * references provides no ownership and is a simple wrapper for plain JNI references. + * + * All but the weak references provides access to the underlying object using dereferencing, and a + * get() method. It is also possible to convert these references to booleans to test for nullity. + * To access the underlying object of a weak reference, the reference must either be released, or + * the weak reference can be used to create a local or global reference. + * + * An owning reference is created either by moving the reference from an existing owned reference, + * by copying an existing owned reference (which creates a new underlying reference), by using the + * default constructor which initialize the reference to nullptr, or by using a helper function. The + * helper function exist in two flavors: make_XXX or adopt_XXX. + * + * Adopting takes a plain JNI reference and wrap it in an owned reference. It takes ownership of the + * plain JNI reference so be sure that no one else owns the reference when you adopt it, and make + * sure that you know what kind of reference it is. + * + * New owned references can be created from existing plain JNI references, alias references, local + * references, and global references (i.e. non-weak references) using the make_local, make_global, + * and make_weak functions. + * + * Alias references can be implicitly initialized using global, local and plain JNI references using + * the wrap_alias function. Here, we don't assume ownership of the passed-in reference, but rather + * create a separate reference that we do own, leaving the passed-in reference to its fate. + * + * Similar rules apply for assignment. An owned reference can be copy or move assigned using a smart + * reference of the same type. In the case of copy assignment a new reference is created. Alias + * reference can also be assigned new values, but since they are simple wrappers of plain JNI + * references there is no move semantics involved. + * + * Alias references are special in that they do not own the object and can therefore safely be + * converted to and from its corresponding plain JNI reference. They are useful as parameters of + * functions that do not affect the lifetime of a reference. Usage can be compared with using plain + * JNI pointers as parameters where a function does not take ownership of the underlying object. + * + * The local, global, and alias references makes it possible to access methods in the underlying + * objects. A core set of classes are implemented in CoreClasses.h, and user defined wrappers are + * supported (see example below). The wrappers also supports inheritance so a wrapper can inherit + * from another wrapper to gain access to its functionality. As an example the jstring wrapper + * inherits from the jobject wrapper, so does the jclass wrapper. That means that you can for + * example call the toString() method using the jclass wrapper, or any other class that inherits + * from the jobject wrapper. + * + * Note that the wrappers are parameterized on the static type of your (jobject) pointer, thus if + * you have a jobject that refers to a Java String you will need to cast it to jstring to get the + * jstring wrapper. This also mean that if you make a down cast that is invalid there will be no one + * stopping you and the wrappers currently does not detect this which can cause crashes. Thus, cast + * wisely. + * + * @include WrapperSample.cpp + */ + +#pragma once + +#include +#include +#include + +#include + +#include + +#include "ReferenceAllocators.h" +#include "TypeTraits.h" +#include "References-forward.h" + +namespace facebook { +namespace jni { + +/// Convenience function to wrap an existing local reference +template +local_ref adopt_local(T ref) noexcept; + +/// Convenience function to wrap an existing global reference +template +global_ref adopt_global(T ref) noexcept; + +/// Convenience function to wrap an existing weak reference +template +weak_ref adopt_weak_global(T ref) noexcept; + + +/// Swaps two owning references of the same type +template +void swap(weak_ref& a, weak_ref& b) noexcept; + +/// Swaps two owning references of the same type +template +void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; + +/** + * Retrieve the plain reference from a plain reference. + */ +template +enable_if_t(), T> getPlainJniReference(T ref); + +/** + * Retrieve the plain reference from an alias reference. + */ +template +JniType getPlainJniReference(alias_ref ref); + +/** + * Retrieve the plain JNI reference from any reference owned reference. + */ +template +JniType getPlainJniReference(const base_owned_ref& ref); + +class JObject; +class JClass; + +namespace detail { + +template +struct HasJniRefRepr : std::false_type {}; + +template +struct HasJniRefRepr::value, void>::type> : std::true_type { + using type = typename T::JniRefRepr; +}; + +template +struct RefReprType { + using type = typename std::conditional::value, typename HasJniRefRepr::type, JObjectWrapper>::type; + static_assert(std::is_base_of::value, + "Repr type missing JObject base."); + static_assert(std::is_same::type>::value, + "RefReprType not idempotent"); +}; + +template +struct RefReprType::value, void>::type> { + using type = T; + static_assert(std::is_base_of::value, + "Repr type missing JObject base."); + static_assert(std::is_same::type>::value, + "RefReprType not idempotent"); +}; + +template +struct JavaObjectType { + using type = typename RefReprType::type::javaobject; + static_assert(IsPlainJniReference(), + "JavaObjectType not a plain jni reference"); + static_assert(std::is_same::type>::value, + "JavaObjectType not idempotent"); +}; + +template +struct JavaObjectType> { + using type = T; + static_assert(IsPlainJniReference(), + "JavaObjectType not a plain jni reference"); + static_assert(std::is_same::type>::value, + "JavaObjectType not idempotent"); +}; + +template +struct JavaObjectType { + using type = T*; + static_assert(IsPlainJniReference(), + "JavaObjectType not a plain jni reference"); + static_assert(std::is_same::type>::value, + "JavaObjectType not idempotent"); +}; + +template +struct ReprStorage { + explicit ReprStorage(JniType obj) noexcept; + + void set(JniType obj) noexcept; + + Repr& get() noexcept; + const Repr& get() const noexcept; + JniType jobj() const noexcept; + + void swap(ReprStorage& other) noexcept; + private: + ReprStorage() = delete; + ReprStorage(const ReprStorage&) = delete; + ReprStorage(ReprStorage&&) = delete; + ReprStorage& operator=(const ReprStorage&) = delete; + ReprStorage& operator=(ReprStorage&&) = delete; + + using Storage = typename std::aligned_storage::type; + Storage storage_; +}; + +} // namespace detail + +/** + * Create a new local reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned local reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), local_ref>> +make_local(const T& r); + +/** + * Create a new global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned global reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), global_ref>> +make_global(const T& r); + +/** + * Create a new weak global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned weak global reference (referring to null if the input does) + * @throws std::bad_alloc if the returned reference is null + */ +template +enable_if_t(), weak_ref>> +make_weak(const T& r); + +/** + * Compare two references to see if they refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b); + +/** + * Compare two references to see if they don't refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b); + +template +class base_owned_ref { + public: + using javaobject = JniType; + + /** + * Release the ownership and set the reference to null. Thus no deleter is invoked. + * @return Returns the reference + */ + javaobject release() noexcept; + + /** + * Reset the reference to refer to nullptr. + */ + void reset() noexcept; + + protected: + using Repr = ReprType; + detail::ReprStorage storage_; + + javaobject get() const noexcept; + void set(javaobject ref) noexcept; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit base_owned_ref(javaobject reference) noexcept; + + /// Create a null reference + base_owned_ref() noexcept; + + /// Create a null reference + explicit base_owned_ref(std::nullptr_t) noexcept; + + /// Copy constructor (note creates a new reference) + base_owned_ref(const base_owned_ref& other); + template + base_owned_ref(const base_owned_ref& other); + + /// Transfers ownership of an underlying reference from one unique reference to another + base_owned_ref(base_owned_ref&& other) noexcept; + template + base_owned_ref(base_owned_ref&& other) noexcept; + + /// The delete the underlying reference if applicable + ~base_owned_ref() noexcept; + + + /// Assignment operator (note creates a new reference) + base_owned_ref& operator=(const base_owned_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + base_owned_ref& operator=(base_owned_ref&& rhs) noexcept; + + void reset(javaobject reference) noexcept; + + friend javaobject jni::getPlainJniReference<>(const base_owned_ref& ref); + + template + friend class base_owned_ref; +}; + + +/** + * A smart reference that owns its underlying JNI reference. The class provides basic + * functionality to handle a reference but gives no access to it unless the reference is + * released, thus no longer owned. The API is stolen with pride from unique_ptr and the + * semantics should be basically the same. This class should not be used directly, instead use + * @ref weak_ref + */ +template +class weak_ref : public base_owned_ref { + public: + using javaobject = JniType; + + using Allocator = WeakGlobalReferenceAllocator; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + + /// Create a null reference + weak_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + explicit weak_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + weak_ref(const weak_ref& other) + : base_owned_ref{other} {} + + // This needs to be explicit to change its visibility. + template + weak_ref(const weak_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + weak_ref(weak_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + + /// Assignment operator (note creates a new reference) + weak_ref& operator=(const weak_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + weak_ref& operator=(weak_ref&& rhs) noexcept; + + // Creates an owned local reference to the referred object or to null if the object is reclaimed + local_ref lockLocal() const; + + // Creates an owned global reference to the referred object or to null if the object is reclaimed + global_ref lockGlobal() const; + + private: + // get/release/reset on weak_ref are not exposed to users. + using base_owned_ref::get; + using base_owned_ref::release; + using base_owned_ref::reset; + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit weak_ref(javaobject reference) noexcept + : base_owned_ref{reference} {} + + template friend class weak_ref; + friend weak_ref adopt_weak_global(javaobject ref) noexcept; + friend void swap(weak_ref& a, weak_ref& b) noexcept; +}; + + +/** + * A class representing owned strong references to Java objects. This class + * should not be used directly, instead use @ref local_ref, or @ref global_ref. + */ +template +class basic_strong_ref : public base_owned_ref { + using typename base_owned_ref::Repr; + public: + using javaobject = JniType; + + using Allocator = Alloc; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + using base_owned_ref::release; + using base_owned_ref::reset; + + /// Create a null reference + basic_strong_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + explicit basic_strong_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + basic_strong_ref(const basic_strong_ref& other) + : base_owned_ref{other} {} + + // This needs to be explicit to change its visibility. + template + basic_strong_ref(const basic_strong_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + basic_strong_ref(basic_strong_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + /// Assignment operator (note creates a new reference) + basic_strong_ref& operator=(const basic_strong_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + basic_strong_ref& operator=(basic_strong_ref&& rhs) noexcept; + + /// Get the plain JNI reference + using base_owned_ref::get; + + /// Release the ownership of the reference and return the wrapped reference in an alias + alias_ref releaseAlias() noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Access the functionality provided by the object wrappers + Repr* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const Repr* operator->() const noexcept; + + /// Provide a reference to the underlying wrapper (be sure that it is non-null before invoking) + Repr& operator*() noexcept; + + /// Provide a const reference to the underlying wrapper (be sure that it is non-null + /// before invoking) + const Repr& operator*() const noexcept; + + private: + + using base_owned_ref::storage_; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit basic_strong_ref(javaobject reference) noexcept + : base_owned_ref{reference} {} + + + friend local_ref adopt_local(T ref) noexcept; + friend global_ref adopt_global(T ref) noexcept; + friend void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; +}; + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + +/// Swaps to alias reference of the same type +template +void swap(alias_ref& a, alias_ref& b) noexcept; + +/** + * A non-owning variant of the smart references (a dumb reference). These references still provide + * access to the functionality of the @ref JObjectWrapper specializations including exception + * handling and ease of use. Use this representation when you don't want to claim ownership of the + * underlying reference (compare to using raw pointers instead of smart pointers.) For symmetry use + * @ref alias_ref instead of this class. + */ +template +class alias_ref { + using Repr = ReprType; + + public: + using javaobject = JniType; + + /// Create a null reference + alias_ref() noexcept; + + /// Create a null reference + alias_ref(std::nullptr_t) noexcept; + + /// Copy constructor + alias_ref(const alias_ref& other) noexcept; + + /// Wrap an existing plain JNI reference + /* implicit */ alias_ref(javaobject ref) noexcept; + + /// Wrap an existing smart reference of any type convertible to T + template< + typename TOther, + typename = enable_if_t< + IsConvertible, javaobject>(), T> + > + alias_ref(alias_ref other) noexcept; + + /// Wrap an existing alias reference of a type convertible to T + template< + typename TOther, + typename AOther, + typename = enable_if_t< + IsConvertible, javaobject>(), T> + > + alias_ref(const basic_strong_ref& other) noexcept; + + /// Assignment operator + alias_ref& operator=(alias_ref other) noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Converts back to a plain JNI reference + javaobject get() const noexcept; + + /// Access the functionality provided by the object wrappers + Repr* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const Repr* operator->() const noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + Repr& operator*() noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + const Repr& operator*() const noexcept; + + private: + void set(javaobject ref) noexcept; + + detail::ReprStorage storage_; + + friend void swap(alias_ref& a, alias_ref& b) noexcept; +}; + + +/** + * RAII object to create a local JNI frame, using PushLocalFrame/PopLocalFrame. + * + * 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 { +public: + JniLocalScope(JNIEnv* p_env, jint capacity); + ~JniLocalScope(); + +private: + JNIEnv* env_; + bool hasFrame_; +}; + +template +enable_if_t(), local_ref> +static_ref_cast(const local_ref& ref) noexcept; + +template +enable_if_t(), global_ref> +static_ref_cast(const global_ref& ref) noexcept; + +template +enable_if_t(), alias_ref> +static_ref_cast(const alias_ref& ref) noexcept; + +template +auto dynamic_ref_cast(const RefType& ref) -> +enable_if_t(), decltype(static_ref_cast(ref))> ; + +}} + +#include "References-inl.h" diff --git a/lib/fb/include/fb/fbjni/Registration-inl.h b/lib/fb/include/fb/fbjni/Registration-inl.h new file mode 100644 index 00000000..9bcf21a2 --- /dev/null +++ b/lib/fb/include/fb/fbjni/Registration-inl.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "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)) { + 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(); +} + +} + +}} diff --git a/lib/fb/include/fb/fbjni/Registration.h b/lib/fb/include/fb/fbjni/Registration.h new file mode 100644 index 00000000..e690f96b --- /dev/null +++ b/lib/fb/include/fb/fbjni/Registration.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include "References.h" + +namespace facebook { +namespace jni { + +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)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(JNIEnv*, C, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(alias_ref, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (C::*method0)(Args... args)); + +} + +// We have to use macros here, because the func needs to be used +// as both a decltype expression argument and as a non-type template +// parameter, since C++ provides no way for translateException +// to deduce the type of its non-type template parameter. +// The empty string in the macros below ensures that name +// is always a string literal (because that syntax is only +// valid when name is a string literal). +#define makeNativeMethod2(name, func) \ + { name "", ::facebook::jni::detail::makeDescriptor(&func), \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +#define makeNativeMethod3(name, desc, func) \ + { name "", desc, \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +// Variadic template hacks to get macros with different numbers of +// arguments. Usage instructions are in CoreClasses.h. +#define makeNativeMethodN(a, b, c, count, ...) makeNativeMethod ## count +#define makeNativeMethod(...) makeNativeMethodN(__VA_ARGS__, 3, 2)(__VA_ARGS__) + +}} + +#include "Registration-inl.h" diff --git a/lib/fb/include/fb/fbjni/TypeTraits.h b/lib/fb/include/fb/fbjni/TypeTraits.h new file mode 100644 index 00000000..26472669 --- /dev/null +++ b/lib/fb/include/fb/fbjni/TypeTraits.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include "References-forward.h" + +namespace facebook { +namespace jni { + +/// Generic std::enable_if helper +template +using enable_if_t = typename std::enable_if::type; + +/// Generic std::is_convertible helper +template +constexpr bool IsConvertible() { + return std::is_convertible::value; +} + +template class TT, typename T> +struct is_instantiation_of : std::false_type {}; + +template class TT, typename... Ts> +struct is_instantiation_of> : std::true_type {}; + +template class TT, typename... Ts> +constexpr bool IsInstantiationOf() { + return is_instantiation_of::value; +} + +/// Metafunction to determine whether a type is a JNI reference or not +template +struct is_plain_jni_reference : + std::integral_constant::value && + std::is_base_of< + typename std::remove_pointer::type, + typename std::remove_pointer::type>::value> {}; + +/// Helper to simplify use of is_plain_jni_reference +template +constexpr bool IsPlainJniReference() { + return is_plain_jni_reference::value; +} + +/// Metafunction to determine whether a type is a primitive JNI type or not +template +struct is_jni_primitive : + std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> {}; + +/// Helper to simplify use of is_jni_primitive +template +constexpr bool IsJniPrimitive() { + return is_jni_primitive::value; +} + +/// Metafunction to determine whether a type is a JNI array of primitives or not +template +struct is_jni_primitive_array : + std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> {}; + +/// Helper to simplify use of is_jni_primitive_array +template +constexpr bool IsJniPrimitiveArray() { + return is_jni_primitive_array::value; +} + +/// Metafunction to determine if a type is a scalar (primitive or reference) JNI type +template +struct is_jni_scalar : + std::integral_constant::value || + is_jni_primitive::value> {}; + +/// Helper to simplify use of is_jni_scalar +template +constexpr bool IsJniScalar() { + return is_jni_scalar::value; +} + +// Metafunction to determine if a type is a JNI type +template +struct is_jni_type : + std::integral_constant::value || + std::is_void::value> {}; + +/// Helper to simplify use of is_jni_type +template +constexpr bool IsJniType() { + return is_jni_type::value; +} + +template +struct is_non_weak_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsNonWeakReference() { + return is_non_weak_reference::value; +} + +template +struct is_any_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsAnyReference() { + return is_any_reference::value; +} + +template +struct reference_traits { + using plain_jni_reference_t = JniType; + static_assert(IsPlainJniReference(), "Need a plain JNI reference"); +}; + +template