Sync fbjni to React Native

Reviewed By: mhorowitz

Differential Revision: D5086537

fbshipit-source-id: a95863113b3c63530a2550d29dfdc9626be86dc0
This commit is contained in:
Pieter De Baets 2017-05-19 04:04:27 -07:00 committed by Facebook Github Bot
parent c6dd3d137b
commit 99f8c5df37
26 changed files with 890 additions and 361 deletions

View File

@ -0,0 +1,26 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <stdlib.h>
#if defined(__ANDROID__)
# include <sys/system_properties.h>
#endif
namespace facebook {
namespace build {
struct Build {
static int getAndroidSdk() {
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);
})();
return android_sdk;
}
};
} // build
} // facebook

View File

@ -17,6 +17,10 @@
namespace facebook {
namespace jni {
namespace internal {
struct CacheEnvTag {};
}
// 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
@ -70,7 +74,16 @@ class FBEXPORT ThreadScope {
static void WithClassLoader(std::function<void()>&& runnable);
static void OnLoad();
// This constructor is only used internally by fbjni.
ThreadScope(JNIEnv*, internal::CacheEnvTag);
private:
friend struct Environment;
ThreadScope* previous_;
// If the JNIEnv* is set, it is guaranteed to be valid at least through the
// lifetime of this ThreadScope. The only case where that guarantee can be
// made is when there is a java frame in the stack below this.
JNIEnv* env_;
bool attachedWithThisScope_;
};
}

View File

@ -58,9 +58,12 @@ DEFINE_BOXED_PRIMITIVE(double, Double)
#undef DEFINE_BOXED_PRIMITIVE
struct JVoid : public jni::JavaClass<JVoid> {
static auto constexpr kJavaDescriptor = "Ljava/lang/Void;";
};
inline local_ref<jobject> autobox(alias_ref<jobject> val) {
return make_local(val);
}
}}

View File

@ -177,7 +177,12 @@ inline void JClass::registerNatives(std::initializer_list<NativeMethod> methods)
inline bool JClass::isAssignableFrom(alias_ref<JClass> other) const noexcept {
const auto env = internal::getEnv();
const auto result = env->IsAssignableFrom(self(), other.get());
// Ths method has behavior compatible with the
// java.lang.Class#isAssignableFrom method. The order of the
// arguments to the JNI IsAssignableFrom C function is "opposite"
// from what some might expect, which makes this code look a little
// odd, but it is correct.
const auto result = env->IsAssignableFrom(other.get(), self());
return result;
}
@ -341,13 +346,6 @@ struct Convert<const char*> {
};
}
// jthrowable //////////////////////////////////////////////////////////////////////////////////////
inline local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
static auto meth = javaClassStatic()->getMethod<javaobject(javaobject)>("initCause");
return meth(self(), cause.get());
}
// jtypeArray //////////////////////////////////////////////////////////////////////////////////////
namespace detail {

View File

@ -340,14 +340,6 @@ class FBEXPORT JString : public JavaClass<JString, JObject, jstring> {
FBEXPORT local_ref<JString> make_jstring(const char* modifiedUtf8);
FBEXPORT local_ref<JString> make_jstring(const std::string& modifiedUtf8);
/// Wrapper to provide functionality to jthrowable references
class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
public:
static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
local_ref<JThrowable> initCause(alias_ref<JThrowable> cause);
};
namespace detail {
template<typename Target>
class ElementProxy {
@ -565,6 +557,24 @@ class PinnedPrimitiveArray {
friend class JPrimitiveArray<typename jtype_traits<T>::array_type>;
};
struct JStackTraceElement : JavaClass<JStackTraceElement> {
static auto constexpr kJavaDescriptor = "Ljava/lang/StackTraceElement;";
static local_ref<javaobject> create(const std::string& declaringClass, const std::string& methodName, const std::string& file, int line);
};
/// Wrapper to provide functionality to jthrowable references
class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
public:
static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
using JStackTrace = JArrayClass<JStackTraceElement::javaobject>;
local_ref<JThrowable> initCause(alias_ref<JThrowable> cause);
local_ref<JStackTrace> getStackTrace();
void setStackTrace(alias_ref<JArrayClass<JStackTraceElement::javaobject>>);
};
#pragma push_macro("PlainJniRefMap")
#undef PlainJniRefMap
#define PlainJniRefMap(rtype, jtype) \

View File

@ -32,6 +32,11 @@
#include "References.h"
#include "CoreClasses.h"
#if defined(__ANDROID__) && defined(__ARM_ARCH_5TE__) && !defined(FBJNI_NO_EXCEPTION_PTR)
// ARMv5 NDK does not support exception_ptr so we cannot use that when building for it.
#define FBJNI_NO_EXCEPTION_PTR
#endif
namespace facebook {
namespace jni {
@ -108,9 +113,16 @@ template<typename... Args>
}
// Identifies any pending C++ exception and throws it as a Java exception. If the exception can't
// be thrown, it aborts the program. This is a noexcept function at C++ level.
FBEXPORT void translatePendingCppExceptionToJavaException() noexcept;
// be thrown, it aborts the program.
FBEXPORT void translatePendingCppExceptionToJavaException();
#ifndef FBJNI_NO_EXCEPTION_PTR
FBEXPORT local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr);
#endif
FBEXPORT local_ref<JThrowable> getJavaExceptionForCppBackTrace();
FBEXPORT local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg);
// For convenience, some exception names in java.lang are available here.
const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException";

View File

@ -29,11 +29,59 @@ public:
struct FBEXPORT HybridData : public JavaClass<HybridData> {
constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;";
void setNativePointer(std::unique_ptr<BaseHybridClass> new_value);
BaseHybridClass* getNativePointer();
static local_ref<HybridData> create();
};
class HybridDestructor : public JavaClass<HybridDestructor> {
public:
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/HybridData$Destructor;";
template <typename T=detail::BaseHybridClass>
T* getNativePointer() {
static auto pointerField = javaClassStatic()->getField<jlong>("mNativePointer");
auto* value = reinterpret_cast<detail::BaseHybridClass*>(getFieldValue(pointerField));
if (!value) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
return value;
}
template <typename T=detail::BaseHybridClass>
void setNativePointer(std::unique_ptr<T> new_value) {
static auto pointerField = javaClassStatic()->getField<jlong>("mNativePointer");
auto old_value = std::unique_ptr<T>(reinterpret_cast<T*>(getFieldValue(pointerField)));
if (new_value && old_value) {
FBCRASH("Attempt to set C++ native pointer twice");
}
setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
}
};
template<typename T>
detail::BaseHybridClass* getNativePointer(T t) {
return getHolder(t)->getNativePointer();
}
template<typename T>
void setNativePointer(T t, std::unique_ptr<detail::BaseHybridClass> new_value) {
getHolder(t)->setNativePointer(std::move(new_value));
}
template<typename T>
local_ref<HybridDestructor> getHolder(T t) {
static auto holderField = t->getClass()->template getField<HybridDestructor::javaobject>("mDestructor");
return t->getFieldValue(holderField);
}
// JavaClass for HybridClassBase
struct FBEXPORT HybridClassBase : public JavaClass<HybridClassBase> {
constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridClassBase;";
static bool isHybridClassBase(alias_ref<jclass> jclass) {
return HybridClassBase::javaClassStatic()->isAssignableFrom(jclass);
}
};
template <typename Base, typename Enabled = void>
struct HybridTraits {
// This static assert should actually always fail if we don't use one of the
@ -139,7 +187,7 @@ protected:
static local_ref<detail::HybridData> makeHybridData(std::unique_ptr<T> cxxPart) {
auto hybridData = detail::HybridData::create();
hybridData->setNativePointer(std::move(cxxPart));
setNativePointer(hybridData, std::move(cxxPart));
return hybridData;
}
@ -148,6 +196,11 @@ protected:
return makeHybridData(std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
}
template <typename... Args>
static void setCxxInstance(alias_ref<jhybridobject> o, Args&&... args) {
setNativePointer(o, std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
}
public:
// Factory method for creating a hybrid object where the arguments
// are used to initialize the C++ part directly without passing them
@ -161,11 +214,23 @@ public:
// C++ object fails, or any JNI methods throw.
template <typename... Args>
static local_ref<JavaPart> newObjectCxxArgs(Args&&... args) {
auto hybridData = makeCxxInstance(std::forward<Args>(args)...);
return JavaPart::newInstance(hybridData);
static bool isHybrid = detail::HybridClassBase::isHybridClassBase(javaClassStatic());
auto cxxPart = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
local_ref<JavaPart> result;
if (isHybrid) {
result = JavaPart::newInstance();
setNativePointer(result, std::move(cxxPart));
}
else {
auto hybridData = makeHybridData(std::move(cxxPart));
result = JavaPart::newInstance(hybridData);
}
return result;
}
// TODO? Create reusable interface for Allocatable classes and use it to
// TODO? Create reusable interface for Allocatable classes and use it to
// strengthen type-checking (and possibly provide a default
// implementation of allocate().)
template <typename... Args>
@ -195,17 +260,25 @@ public:
template <typename T, typename B>
inline T* HybridClass<T, B>::JavaPart::cthis() {
static auto field =
HybridClass<T, B>::JavaPart::javaClassStatic()->template getField<detail::HybridData::javaobject>("mHybridData");
auto hybridData = this->getFieldValue(field);
if (!hybridData) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
detail::BaseHybridClass* result = 0;
static bool isHybrid = detail::HybridClassBase::isHybridClassBase(this->getClass());
if (isHybrid) {
result = getNativePointer(this);
} else {
static auto field =
HybridClass<T, B>::JavaPart::javaClassStatic()->template getField<detail::HybridData::javaobject>("mHybridData");
auto hybridData = this->getFieldValue(field);
if (!hybridData) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
result = getNativePointer(hybridData);
}
// I'd like to use dynamic_cast here, but -fno-rtti is the default.
T* value = static_cast<T*>(hybridData->getNativePointer());
// This would require some serious programmer error.
FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field");
return value;
FBASSERTMSGF(result != 0, "Incorrect C++ type in hybrid field");
// I'd like to use dynamic_cast here, but -fno-rtti is the default.
return static_cast<T*>(result);
};
template <typename T, typename B>

View File

@ -0,0 +1,37 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <fb/visibility.h>
#include "CoreClasses.h"
namespace facebook {
namespace jni {
/**
* Wrap Java's WeakReference instead of using JNI WeakGlobalRefs.
* A WeakGlobalRef can yield a strong reference even after the object has been
* finalized. See comment in the djinni library.
* https://github.com/dropbox/djinni/blob/master/support-lib/jni/djinni_support.hpp
*/
template<typename T = jobject>
class JWeakReference : public JavaClass<JWeakReference<T>> {
typedef JavaClass<JWeakReference<T>> JavaBase_;
public:
static constexpr const char* kJavaDescriptor = "Ljava/lang/ref/WeakReference;";
static local_ref<JWeakReference<T>> newInstance(alias_ref<T> object) {
return JavaBase_::newInstance(static_ref_cast<jobject>(object));
}
local_ref<T> get() const {
static auto method = JavaBase_::javaClassStatic()->template getMethod<jobject()>("get");
return static_ref_cast<T>(method(JavaBase_::self()));
}
};
}
}

View File

@ -18,7 +18,7 @@
#include "Boxed.h"
#if defined(__ANDROID__)
#include <sys/system_properties.h>
# include <fb/Build.h>
#endif
namespace facebook {
@ -72,12 +72,8 @@ inline bool needsSlowPath(alias_ref<jobject> obj) {
// 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;
static auto is_bad_android = build::Build::getAndroidSdk() == 23;
if (!is_bad_android) return false;
static auto proxy_class = findClassStatic("java/lang/reflect/Proxy");
return obj->isInstanceOf(proxy_class);
@ -100,7 +96,7 @@ inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args...
#pragma push_macro("DEFINE_PRIMITIVE_CALL")
#undef DEFINE_PRIMITIVE_CALL
#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD, CLASS) \
#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD) \
template<typename... Args> \
inline TYPE JMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, Args... args) { \
const auto env = internal::getEnv(); \
@ -112,14 +108,14 @@ inline TYPE JMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, Args...
return result; \
}
DEFINE_PRIMITIVE_CALL(jboolean, Boolean, JBoolean)
DEFINE_PRIMITIVE_CALL(jbyte, Byte, JByte)
DEFINE_PRIMITIVE_CALL(jchar, Char, JCharacter)
DEFINE_PRIMITIVE_CALL(jshort, Short, JShort)
DEFINE_PRIMITIVE_CALL(jint, Int, JInteger)
DEFINE_PRIMITIVE_CALL(jlong, Long, JLong)
DEFINE_PRIMITIVE_CALL(jfloat, Float, JFloat)
DEFINE_PRIMITIVE_CALL(jdouble, Double, JDouble)
DEFINE_PRIMITIVE_CALL(jboolean, Boolean)
DEFINE_PRIMITIVE_CALL(jbyte, Byte)
DEFINE_PRIMITIVE_CALL(jchar, Char)
DEFINE_PRIMITIVE_CALL(jshort, Short)
DEFINE_PRIMITIVE_CALL(jint, Int)
DEFINE_PRIMITIVE_CALL(jlong, Long)
DEFINE_PRIMITIVE_CALL(jfloat, Float)
DEFINE_PRIMITIVE_CALL(jdouble, Double)
#pragma pop_macro("DEFINE_PRIMITIVE_CALL")
/// JMethod specialization for references that wraps the return value in a @ref local_ref

View File

@ -95,7 +95,21 @@ template <typename T>
struct Convert<global_ref<T>> {
typedef JniType<T> jniType;
// No automatic synthesis of global_ref
static jniType toJniRet(global_ref<jniType> t) {
static jniType toJniRet(global_ref<jniType>&& t) {
// If this gets called, ownership the global_ref was passed in here. (It's
// probably a copy of a persistent global_ref made when a function was
// declared to return a global_ref, but it could moved out or otherwise not
// referenced elsewhere. Doesn't matter.) Either way, the only safe way
// to return it is to make a local_ref, release it, and return the
// underlying local jobject.
auto ret = make_local(t);
return ret.release();
}
static jniType toJniRet(const global_ref<jniType>& t) {
// If this gets called, the function was declared to return const&. We
// have a ref to a global_ref whose lifetime will exceed this call, so we
// can just get the underlying jobject and return it to java without
// needing to make a local_ref.
return t.get();
}
static jniType toCall(global_ref<jniType> t) {

View File

@ -21,7 +21,8 @@ namespace internal {
// Statistics mostly provided for test (only updated if FBJNI_DEBUG_REFS is defined)
struct ReferenceStats {
std::atomic_uint locals_deleted, globals_deleted, weaks_deleted;
std::atomic_uint locals_created, globals_created, weaks_created,
locals_deleted, globals_deleted, weaks_deleted;
void reset() noexcept;
};
@ -35,6 +36,9 @@ extern ReferenceStats g_reference_stats;
inline jobject LocalReferenceAllocator::newReference(jobject original) const {
internal::dbglog("Local new: %p", original);
#ifdef FBJNI_DEBUG_REFS
++internal::g_reference_stats.locals_created;
#endif
auto ref = internal::getEnv()->NewLocalRef(original);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return ref;
@ -64,6 +68,9 @@ inline bool LocalReferenceAllocator::verifyReference(jobject reference) const no
inline jobject GlobalReferenceAllocator::newReference(jobject original) const {
internal::dbglog("Global new: %p", original);
#ifdef FBJNI_DEBUG_REFS
++internal::g_reference_stats.globals_created;
#endif
auto ref = internal::getEnv()->NewGlobalRef(original);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return ref;
@ -93,6 +100,9 @@ inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const n
inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const {
internal::dbglog("Weak global new: %p", original);
#ifdef FBJNI_DEBUG_REFS
++internal::g_reference_stats.weaks_created;
#endif
auto ref = internal::getEnv()->NewWeakGlobalRef(original);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return ref;

View File

@ -486,22 +486,25 @@ template<typename T, typename RefType>
auto dynamic_ref_cast(const RefType& ref) ->
enable_if_t<IsPlainJniReference<T>(), decltype(static_ref_cast<T>(ref))>
{
if (! ref) {
if (!ref) {
return decltype(static_ref_cast<T>(ref))();
}
std::string target_class_name{jtype_traits<T>::base_name()};
static alias_ref<jclass> target_class = findClassStatic(jtype_traits<T>::base_name().c_str());
if (!target_class) {
throwNewJavaException("java/lang/ClassCastException",
"Could not find class %s.",
jtype_traits<T>::base_name().c_str());
// If not found, will throw an exception.
alias_ref<jclass> target_class = findClassStatic(target_class_name.c_str());
}
local_ref<jclass> source_class = ref->getClass();
if ( ! source_class->isAssignableFrom(target_class)) {
if (!target_class->isAssignableFrom(source_class)) {
throwNewJavaException("java/lang/ClassCastException",
"Tried to cast from %s to %s.",
source_class->toString().c_str(),
target_class_name.c_str());
jtype_traits<T>::base_name().c_str());
}
return static_ref_cast<T>(ref);

View File

@ -340,7 +340,7 @@ class weak_ref : public base_owned_ref<T, WeakGlobalReferenceAllocator> {
: base_owned_ref<T, Allocator>{} {}
/// Create a null reference
explicit weak_ref(std::nullptr_t) noexcept
/* implicit */ weak_ref(std::nullptr_t) noexcept
: base_owned_ref<T, Allocator>{nullptr} {}
/// Copy constructor (note creates a new reference)
@ -409,7 +409,7 @@ class basic_strong_ref : public base_owned_ref<T, Alloc> {
: base_owned_ref<T, Alloc>{} {}
/// Create a null reference
explicit basic_strong_ref(std::nullptr_t) noexcept
/* implicit */ basic_strong_ref(std::nullptr_t) noexcept
: base_owned_ref<T, Alloc>{nullptr} {}
/// Copy constructor (note creates a new reference)
@ -496,7 +496,7 @@ class alias_ref {
alias_ref() noexcept;
/// Create a null reference
alias_ref(std::nullptr_t) noexcept;
/* implicit */ alias_ref(std::nullptr_t) noexcept;
/// Copy constructor
alias_ref(const alias_ref& other) noexcept;

View File

@ -29,141 +29,104 @@ namespace detail {
#define JNI_ENTRY_POINT
#endif
template <typename R>
struct CreateDefault {
static R create() {
return R{};
}
};
template <>
struct CreateDefault<void> {
static void create() {}
};
template <typename R>
using Converter = Convert<typename std::decay<R>::type>;
template <typename F, F func, typename R, typename... Args>
struct WrapForVoidReturn {
static typename Converter<R>::jniType call(Args&&... args) {
return Converter<R>::toJniRet(func(std::forward<Args>(args)...));
}
};
template <typename F, F func, typename... Args>
struct WrapForVoidReturn<F, func, void, Args...> {
static void call(Args&&... args) {
func(std::forward<Args>(args)...);
}
};
// registration wrapper for legacy JNI-style functions
template<typename F, F func, typename C, typename... Args>
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<C>(obj), args...);
} catch (...) {
translatePendingCppExceptionToJavaException();
}
template<typename F, F func, typename C, typename R, typename... Args>
struct BareJniWrapper {
JNI_ENTRY_POINT static R call(JNIEnv* env, jobject obj, Args... args) {
ThreadScope ts(env, internal::CacheEnvTag{});
try {
return (*func)(env, static_cast<JniType<C>>(obj), args...);
} catch (...) {
translatePendingCppExceptionToJavaException();
return CreateDefault<R>::create();
}
};
}
};
// This intentionally erases the real type; JNI will do it anyway
return reinterpret_cast<NativeMethodWrapper*>(&(funcWrapper::call));
}
// registration wrappers for functions, with autoconversion of arguments.
template<typename F, F func, typename C, typename R, typename... Args>
struct FunctionWrapper {
using jniRet = typename Converter<R>::jniType;
JNI_ENTRY_POINT static jniRet call(JNIEnv* env, jobject obj, typename Converter<Args>::jniType... args) {
ThreadScope ts(env, internal::CacheEnvTag{});
try {
return WrapForVoidReturn<F, func, R, JniType<C>, Args...>::call(
static_cast<JniType<C>>(obj), Converter<Args>::fromJni(args)...);
} catch (...) {
translatePendingCppExceptionToJavaException();
return CreateDefault<jniRet>::create();
}
}
};
// registration wrappers for non-static methods, with autoconvertion of arguments.
template<typename M, M method, typename C, typename R, typename... Args>
struct MethodWrapper {
using jhybrid = typename C::jhybridobject;
static R dispatch(alias_ref<jhybrid> ref, Args&&... args) {
try {
// This is usually a noop, but if the hybrid object is a
// base class of other classes which register JNI methods,
// this will get the right type for the registered method.
auto cobj = static_cast<C*>(ref->cthis());
return (cobj->*method)(std::forward<Args>(args)...);
} catch (const std::exception& ex) {
C::mapException(ex);
throw;
}
}
JNI_ENTRY_POINT static typename Converter<R>::jniType call(
JNIEnv* env, jobject obj, typename Converter<Args>::jniType... args) {
return FunctionWrapper<R(*)(alias_ref<jhybrid>, Args&&...), dispatch, jhybrid, R, Args...>::call(env, obj, args...);
}
};
template<typename F, F func, typename C, typename R, typename... Args>
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<JniType<C>>(obj), args...);
} catch (...) {
translatePendingCppExceptionToJavaException();
return R{};
}
}
};
// This intentionally erases the real type; JNI will do it anyway
return reinterpret_cast<NativeMethodWrapper*>(&(funcWrapper::call));
}
// registration wrappers for functions, with autoconversion of arguments.
template<typename F, F func, typename C, typename... Args>
inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(alias_ref<C>, Args... args)) {
struct funcWrapper {
JNI_ENTRY_POINT static void call(JNIEnv*, jobject obj,
typename Convert<typename std::decay<Args>::type>::jniType... args) {
try {
(*func)(static_cast<JniType<C>>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...);
} catch (...) {
translatePendingCppExceptionToJavaException();
}
}
};
// This intentionally erases the real type; JNI will do it anyway
return reinterpret_cast<NativeMethodWrapper*>(&(funcWrapper::call));
return reinterpret_cast<NativeMethodWrapper*>(&(BareJniWrapper<F, func, C, R, Args...>::call));
}
template<typename F, F func, typename C, typename R, typename... Args>
inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref<C>, Args... args)) {
struct funcWrapper {
JNI_ENTRY_POINT static typename Convert<typename std::decay<R>::type>::jniType call(JNIEnv*, jobject obj,
typename Convert<typename std::decay<Args>::type>::jniType... args) {
try {
return Convert<typename std::decay<R>::type>::toJniRet(
(*func)(static_cast<JniType<C>>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...));
} catch (...) {
using jniRet = typename Convert<typename std::decay<R>::type>::jniType;
translatePendingCppExceptionToJavaException();
return jniRet{};
}
}
};
// This intentionally erases the real type; JNI will do it anyway
return reinterpret_cast<NativeMethodWrapper*>(&(funcWrapper::call));
}
// registration wrappers for non-static methods, with autoconvertion of arguments.
template<typename M, M method, typename C, typename... Args>
inline NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)) {
struct funcWrapper {
JNI_ENTRY_POINT static void call(JNIEnv* env, jobject obj,
typename Convert<typename std::decay<Args>::type>::jniType... args) {
try {
try {
auto aref = wrap_alias(static_cast<typename C::jhybridobject>(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<C*>(facebook::jni::cthis(aref));
(cobj->*method)(Convert<typename std::decay<Args>::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<NativeMethodWrapper*>(&(funcWrapper::call));
return reinterpret_cast<NativeMethodWrapper*>(&(FunctionWrapper<F, func, C, R, Args...>::call));
}
template<typename M, M method, typename C, typename R, typename... Args>
inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) {
struct funcWrapper {
JNI_ENTRY_POINT static typename Convert<typename std::decay<R>::type>::jniType call(JNIEnv* env, jobject obj,
typename Convert<typename std::decay<Args>::type>::jniType... args) {
try {
try {
auto aref = wrap_alias(static_cast<typename C::jhybridobject>(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<C*>(facebook::jni::cthis(aref));
return Convert<typename std::decay<R>::type>::toJniRet(
(cobj->*method)(Convert<typename std::decay<Args>::type>::fromJni(args)...));
} catch (const std::exception& ex) {
C::mapException(ex);
throw;
}
} catch (...) {
using jniRet = typename Convert<typename std::decay<R>::type>::jniType;
translatePendingCppExceptionToJavaException();
return jniRet{};
}
}
};
// This intentionally erases the real type; JNI will do it anyway
return reinterpret_cast<NativeMethodWrapper*>(&(funcWrapper::call));
return reinterpret_cast<NativeMethodWrapper*>(&(MethodWrapper<M, method, C, R, Args...>::call));
}
template<typename R, typename C, typename... Args>

View File

@ -20,28 +20,14 @@ namespace detail {
// This uses the real JNI function as a non-type template parameter to
// cause a (static member) function to exist with the same signature,
// but with try/catch exception translation.
template<typename F, F func, typename C, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(JNIEnv*, jobject, Args... args));
// Same as above, but for non-void return types.
template<typename F, F func, typename C, typename R, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(JNIEnv*, jobject, Args... args));
// Automatically wrap object argument, and don't take env explicitly.
template<typename F, F func, typename C, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(alias_ref<C>, Args... args));
// Automatically wrap object argument, and don't take env explicitly,
// non-void return type.
template<typename F, F func, typename C, typename R, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(alias_ref<C>, Args... args));
// Extract C++ instance from object, and invoke given method on it.
template<typename M, M method, typename C, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args));
// Extract C++ instance from object, and invoke given method on it,
// non-void return type
template<typename M, M method, typename C, typename R, typename... Args>
NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args));

View File

@ -0,0 +1,18 @@
/*
* 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 <fb/visibility.h>
namespace facebook {
namespace jni {
void FBEXPORT installTerminateHandler();
}};

View File

@ -65,8 +65,10 @@ public:
JStringUtf16Extractor(JNIEnv* env, jstring javaString)
: env_(env)
, javaString_(javaString)
, length_(0)
, utf16String_(nullptr) {
if (env_ && javaString_) {
length_ = env_->GetStringLength(javaString_);
utf16String_ = env_->GetStringCritical(javaString_, nullptr);
}
}
@ -77,13 +79,18 @@ public:
}
}
operator const jchar* () const {
const jsize length() const {
return length_;
}
const jchar* chars() const {
return utf16String_;
}
private:
JNIEnv* env_;
jstring javaString_;
jsize length_;
const jchar* utf16String_;
};

View File

@ -7,9 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <pthread.h>
#include <fb/log.h>
#include <fb/StaticInitialized.h>
#include <fb/ThreadLocal.h>
#include <fb/Environment.h>
#include <fb/fbjni/CoreClasses.h>
@ -21,9 +19,143 @@ namespace facebook {
namespace jni {
namespace {
StaticInitialized<ThreadLocal<JNIEnv>> g_env;
ThreadLocal<ThreadScope>& scopeStorage() {
// We don't want the ThreadLocal to delete the ThreadScopes.
static ThreadLocal<ThreadScope> scope([] (void*) {});
return scope;
}
ThreadScope* currentScope() {
return scopeStorage().get();
}
JavaVM* g_vm = nullptr;
struct EnvironmentInitializer {
EnvironmentInitializer(JavaVM* vm) {
FBASSERT(!g_vm);
FBASSERT(vm);
g_vm = vm;
}
};
int getEnv(JNIEnv** env) {
FBASSERT(g_vm);
// g_vm->GetEnv() might not clear the env* in failure cases.
*env = nullptr;
return g_vm->GetEnv((void**)env, JNI_VERSION_1_6);
}
JNIEnv* attachCurrentThread() {
JavaVMAttachArgs args{JNI_VERSION_1_6, nullptr, nullptr};
JNIEnv* env = nullptr;
auto result = g_vm->AttachCurrentThread(&env, &args);
FBASSERT(result == JNI_OK);
return env;
}
}
/* static */
void Environment::initialize(JavaVM* vm) {
static EnvironmentInitializer init(vm);
}
/* static */
JNIEnv* Environment::current() {
auto scope = currentScope();
if (scope && scope->env_) {
return scope->env_;
}
JNIEnv* env;
if (getEnv(&env) != JNI_OK) {
// If there's a ThreadScope in the stack, we should be attached and able to
// retrieve a JNIEnv*.
FBASSERT(!scope);
// TODO(cjhopman): this should probably be a hard failure, too.
FBLOGE("Unable to retrieve jni environment. Is the thread attached?");
}
return env;
}
/* static */
void Environment::detachCurrentThread() {
FBASSERT(g_vm);
// The thread shouldn't be detached while a ThreadScope is in the stack.
FBASSERT(!currentScope());
g_vm->DetachCurrentThread();
}
/* static */
JNIEnv* Environment::ensureCurrentThreadIsAttached() {
auto scope = currentScope();
if (scope && scope->env_) {
return scope->env_;
}
JNIEnv* env;
// We should be able to just get the JNIEnv* by just calling
// AttachCurrentThread, but the spec is unclear (and using getEnv is probably
// generally more reliable).
auto result = getEnv(&env);
// We don't know how to deal with anything other than JNI_OK or JNI_DETACHED.
FBASSERT(result == JNI_OK || result == JNI_EDETACHED);
if (result == JNI_EDETACHED) {
// The thread should not be detached while a ThreadScope is in the stack.
FBASSERT(!scope);
env = attachCurrentThread();
}
FBASSERT(env);
return env;
}
ThreadScope::ThreadScope() : ThreadScope(nullptr, internal::CacheEnvTag{}) {}
ThreadScope::ThreadScope(JNIEnv* env, internal::CacheEnvTag)
: previous_(nullptr), env_(nullptr), attachedWithThisScope_(false) {
auto& storage = scopeStorage();
previous_ = storage.get();
storage.reset(this);
if (previous_ && previous_->env_) {
FBASSERT(!env || env == previous_->env_);
env = previous_->env_;
}
env_ = env;
if (env_) {
return;
}
// Check if the thread is attached by someone else.
auto result = getEnv(&env);
if (result == JNI_OK) {
return;
}
// We don't know how to deal with anything other than JNI_OK or JNI_DETACHED.
FBASSERT(result == JNI_EDETACHED);
// If there's already a ThreadScope on the stack, then the thread should be attached.
FBASSERT(!previous_);
attachCurrentThread();
attachedWithThisScope_ = true;
}
ThreadScope::~ThreadScope() {
auto& storage = scopeStorage();
// ThreadScopes should be destroyed in the reverse order they are created
// (that is, just put them on the stack).
FBASSERT(this == storage.get());
storage.reset(previous_);
if (attachedWithThisScope_) {
Environment::detachCurrentThread();
}
}
namespace {
struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;";
@ -47,73 +179,6 @@ struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
};
}
/* static */
JNIEnv* Environment::current() {
JNIEnv* env = g_env->get();
if ((env == nullptr) && (g_vm != nullptr)) {
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM");
// TODO(cjhopman): This should throw an exception.
env = nullptr;
} else {
g_env->reset(env);
}
}
return env;
}
/* static */
void Environment::detachCurrentThread() {
auto env = g_env->get();
if (env) {
FBASSERT(g_vm);
g_vm->DetachCurrentThread();
g_env->reset();
}
}
struct EnvironmentInitializer {
EnvironmentInitializer(JavaVM* vm) {
FBASSERT(!g_vm);
FBASSERT(vm);
g_vm = vm;
g_env.initialize([] (void*) {});
}
};
/* static */
void Environment::initialize(JavaVM* vm) {
static EnvironmentInitializer init(vm);
}
/* static */
JNIEnv* Environment::ensureCurrentThreadIsAttached() {
auto env = g_env->get();
if (!env) {
FBASSERT(g_vm);
g_vm->AttachCurrentThread(&env, nullptr);
g_env->reset(env);
}
return env;
}
ThreadScope::ThreadScope()
: attachedWithThisScope_(false) {
JNIEnv* env = nullptr;
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) {
return;
}
env = facebook::jni::Environment::ensureCurrentThreadIsAttached();
FBASSERT(env);
attachedWithThisScope_ = true;
}
ThreadScope::~ThreadScope() {
if (attachedWithThisScope_) {
Environment::detachCurrentThread();
}
}
/* static */
void ThreadScope::OnLoad() {
// These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading.
@ -123,7 +188,8 @@ void ThreadScope::OnLoad() {
/* static */
void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
// TODO(cjhopman): If the classloader is already available in this scope, we
// shouldn't have to jump through java.
// shouldn't have to jump through java. It should be enough to check if the
// attach state env* is set.
ThreadScope ts;
JThreadScopeSupport::runStdFunction(std::move(runnable));
}

View File

@ -12,6 +12,11 @@
#include <fb/assert.h>
#include <fb/log.h>
#ifdef USE_LYRA
#include <fb/lyra.h>
#include <fb/lyra_exceptions.h>
#endif
#include <alloca.h>
#include <cstdlib>
#include <ios>
@ -22,7 +27,6 @@
#include <jni.h>
namespace facebook {
namespace jni {
@ -107,7 +111,13 @@ void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
// Functions that throw C++ exceptions
// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated
// TODO(T6618159) Inject the c++ stack into the exception's stack trace. One
// issue: when a java exception is created, it captures the full java stack
// across jni boundaries. lyra will only capture the c++ stack to the jni
// boundary. So, as we pass the java exception up to c++, we need to capture
// the c++ stack and then insert it into the correct place in the java stack
// trace. Then, as the exception propagates across the boundaries, we will
// slowly fill in the c++ parts of the trace.
void throwPendingJniExceptionAsCppException() {
JNIEnv* env = Environment::current();
if (env->ExceptionCheck() == JNI_FALSE) {
@ -151,82 +161,158 @@ void throwNewJavaException(const char* throwableName, const char* msg) {
throwNewJavaException(throwable.get());
}
// jthrowable //////////////////////////////////////////////////////////////////////////////////////
local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
static auto meth = javaClassStatic()->getMethod<javaobject(alias_ref<javaobject>)>("initCause");
return meth(self(), cause);
}
auto JThrowable::getStackTrace() -> local_ref<JStackTrace> {
static auto meth = javaClassStatic()->getMethod<JStackTrace::javaobject()>("getStackTrace");
return meth(self());
}
void JThrowable::setStackTrace(alias_ref<JStackTrace> stack) {
static auto meth = javaClassStatic()->getMethod<void(alias_ref<JStackTrace>)>("setStackTrace");
return meth(self(), stack);
}
auto JStackTraceElement::create(
const std::string& declaringClass, const std::string& methodName, const std::string& file, int line)
-> local_ref<javaobject> {
return newInstance(declaringClass, methodName, file, line);
}
// Translate C++ to Java Exception
namespace {
// The implementation std::rethrow_if_nested uses a dynamic_cast to determine
// if the exception is a nested_exception. If the exception is from a library
// built with -fno-rtti, then that will crash. This avoids that.
void rethrow_if_nested() {
// For each exception in the chain of the exception_ptr argument, func
// will be called with that exception (in reverse order, i.e. innermost first).
#ifndef FBJNI_NO_EXCEPTION_PTR
void denest(const std::function<void(std::exception_ptr)>& func, std::exception_ptr ptr) {
FBASSERT(ptr);
try {
throw;
std::rethrow_exception(ptr);
} catch (const std::nested_exception& e) {
e.rethrow_nested();
denest(func, e.nested_ptr());
} catch (...) {
// ignored.
}
func(ptr);
}
#endif
} // namespace
#ifdef USE_LYRA
local_ref<JStackTraceElement> createJStackTraceElement(const lyra::StackTraceElement& cpp) {
return JStackTraceElement::create(
"|lyra|{" + cpp.libraryName() + "}", cpp.functionName(), cpp.buildId(), cpp.libraryOffset());
}
#endif
#ifndef FBJNI_NO_EXCEPTION_PTR
void addCppStacktraceToJavaException(alias_ref<JThrowable> java, std::exception_ptr cpp) {
#ifdef USE_LYRA
auto cppStack = lyra::getStackTraceSymbols(
(cpp == nullptr) ?
lyra::getStackTrace()
: lyra::getExceptionTrace(cpp));
auto javaStack = java->getStackTrace();
auto newStack = JThrowable::JStackTrace::newArray(javaStack->size() + cppStack.size());
size_t i = 0;
for (size_t j = 0; j < cppStack.size(); j++, i++) {
(*newStack)[i] = createJStackTraceElement(cppStack[j]);
}
for (size_t j = 0; j < javaStack->size(); j++, i++) {
(*newStack)[i] = (*javaStack)[j];
}
java->setStackTrace(newStack);
#endif
}
// For each exception in the chain of the currently handled exception, func
// will be called with that exception as the currently handled exception (in
// reverse order, i.e. innermost first).
void denest(std::function<void()> func) {
local_ref<JThrowable> convertCppExceptionToJavaException(std::exception_ptr ptr) {
FBASSERT(ptr);
local_ref<JThrowable> current;
bool addCppStack = true;
try {
throw;
} catch (const std::exception& e) {
try {
rethrow_if_nested();
} catch (...) {
denest(func);
}
func();
std::rethrow_exception(ptr);
addCppStack = false;
} catch (const JniException& ex) {
current = ex.getThrowable();
} catch (const std::ios_base::failure& ex) {
current = JIOException::create(ex.what());
} catch (const std::bad_alloc& ex) {
current = JOutOfMemoryError::create(ex.what());
} catch (const std::out_of_range& ex) {
current = JArrayIndexOutOfBoundsException::create(ex.what());
} catch (const std::system_error& ex) {
current = JCppSystemErrorException::create(ex);
} catch (const std::runtime_error& ex) {
current = JRuntimeException::create(ex.what());
} catch (const std::exception& ex) {
current = JCppException::create(ex.what());
} catch (const char* msg) {
current = JUnknownCppException::create(msg);
} catch (...) {
func();
current = JUnknownCppException::create();
}
}
if (addCppStack) {
addCppStacktraceToJavaException(current, ptr);
}
return current;
}
#endif
local_ref<JThrowable> getJavaExceptionForCppBackTrace() {
return getJavaExceptionForCppBackTrace(nullptr);
}
void translatePendingCppExceptionToJavaException() noexcept {
local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg) {
local_ref<JThrowable> current =
msg ? JUnknownCppException::create(msg) : JUnknownCppException::create();
#ifndef FBJNI_NO_EXCEPTION_PTR
addCppStacktraceToJavaException(current, nullptr);
#endif
return current;
}
#ifndef FBJNI_NO_EXCEPTION_PTR
local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr) {
FBASSERT(ptr);
local_ref<JThrowable> previous;
auto func = [&previous] () {
local_ref<JThrowable> current;
try {
throw;
} catch(const JniException& ex) {
current = ex.getThrowable();
} catch(const std::ios_base::failure& ex) {
current = JIOException::create(ex.what());
} catch(const std::bad_alloc& ex) {
current = JOutOfMemoryError::create(ex.what());
} catch(const std::out_of_range& ex) {
current = JArrayIndexOutOfBoundsException::create(ex.what());
} catch(const std::system_error& ex) {
current = JCppSystemErrorException::create(ex);
} catch(const std::runtime_error& ex) {
current = JRuntimeException::create(ex.what());
} catch(const std::exception& ex) {
current = JCppException::create(ex.what());
} catch(const char* msg) {
current = JUnknownCppException::create(msg);
} catch(...) {
current = JUnknownCppException::create();
}
auto func = [&previous] (std::exception_ptr ptr) {
auto current = convertCppExceptionToJavaException(ptr);
if (previous) {
current->initCause(previous);
}
previous = current;
};
denest(func, ptr);
return previous;
}
#endif
void translatePendingCppExceptionToJavaException() {
try {
denest(func);
setJavaExceptionAndAbortOnFailure(previous);
} catch (std::exception& e) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what());
// rethrow the exception and let the noexcept handling abort.
throw;
#ifndef FBJNI_NO_EXCEPTION_PTR
auto exc = getJavaExceptionForCppException(std::current_exception());
#else
auto exc = JUnknownCppException::create();
#endif
setJavaExceptionAndAbortOnFailure(exc);
} catch (...) {
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException");
throw;
#ifdef USE_LYRA
FBLOGE("Unexpected error in translatePendingCppExceptionToJavaException(): %s",
lyra::toString(std::current_exception()).c_str());
#endif
std::terminate();
}
}
@ -252,8 +338,13 @@ JniException::JniException(const JniException &rhs)
}
JniException::~JniException() {
ThreadScope ts;
throwable_.reset();
try {
ThreadScope ts;
throwable_.reset();
} catch (...) {
FBLOGE("Exception in ~JniException()");
std::terminate();
}
}
local_ref<JThrowable> JniException::getThrowable() const noexcept {
@ -262,8 +353,8 @@ local_ref<JThrowable> JniException::getThrowable() const noexcept {
// TODO 6900503: consider making this thread-safe.
void JniException::populateWhat() const noexcept {
ThreadScope ts;
try {
ThreadScope ts;
what_ = throwable_->toString();
isMessageExtracted_ = true;
} catch(...) {

View File

@ -15,35 +15,6 @@ namespace jni {
namespace detail {
void HybridData::setNativePointer(std::unique_ptr<BaseHybridClass> new_value) {
static auto pointerField = getClass()->getField<jlong>("mNativePointer");
auto* old_value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
if (new_value) {
// Modify should only ever be called once with a non-null
// new_value. If this happens again it's a programmer error, so
// blow up.
FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice");
} else if (old_value == 0) {
return;
}
// delete on a null pointer is defined to be a noop.
delete old_value;
// This releases ownership from the unique_ptr, and passes the pointer, and
// ownership of it, to HybridData which is managed by the java GC. The
// finalizer on hybridData calls resetNative which will delete the object, if
// resetNative has not already been called.
setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
}
BaseHybridClass* HybridData::getNativePointer() {
static auto pointerField = getClass()->getField<jlong>("mNativePointer");
auto* value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
if (!value) {
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
}
return value;
}
local_ref<HybridData> HybridData::create() {
return newInstance();
}
@ -51,14 +22,14 @@ local_ref<HybridData> HybridData::create() {
}
namespace {
void resetNative(alias_ref<detail::HybridData> jthis) {
jthis->setNativePointer(nullptr);
void deleteNative(alias_ref<jclass>, jlong ptr) {
delete reinterpret_cast<detail::BaseHybridClass*>(ptr);
}
}
void HybridDataOnLoad() {
registerNatives("com/facebook/jni/HybridData", {
makeNativeMethod("resetNative", resetNative),
registerNatives("com/facebook/jni/HybridData$Destructor", {
makeNativeMethod("deleteNative", deleteNative),
});
}

View File

@ -305,8 +305,7 @@ LocalString::~LocalString() {
std::string fromJString(JNIEnv* env, jstring str) {
auto utf16String = JStringUtf16Extractor(env, str);
auto length = env->GetStringLength(str);
return detail::utf16toUTF8(utf16String, length);
return detail::utf16toUTF8(utf16String.chars(), utf16String.length());
}
} }

View File

@ -0,0 +1,119 @@
/*
* 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.
*/
#include <fb/fbjni.h>
#include <jni/GlobalReference.h>
#include <jni/NativeSoftError.h>
#include <jni/LocalString.h>
#include <jni/Registration.h>
namespace facebook {
namespace jni {
namespace softerror {
using facebook::jni::findClassStatic;
using facebook::jni::LocalString;
static alias_ref<jclass> softErrorClass_;
bool checkSoftErrorClassRef() {
if (!softErrorClass_) {
softErrorClass_ = findClassStatic("com/facebook/jni/NativeSoftErrorReporterProxy");
}
return softErrorClass_ ? true : false;
}
namespace {
void reportSoftErrorInternal(
Severity severity,
const char* category,
const char* error_msg,
#ifndef JNI_NO_EXCEPTION_PTR
std::exception_ptr cause,
#endif
unsigned int samplingFrequency) {
// Need to ensure we have access to our classes even in an unattached thread.
ThreadScope::WithClassLoader([&] {
if (!checkSoftErrorClassRef()) {
return;
}
static auto softReport =
softErrorClass_->getStaticMethod<void(jint, jstring, jstring, jthrowable, jint)>("softReport");
LocalString jstrCategory(category);
LocalString jstrErrorMsg(error_msg);
softReport(softErrorClass_,
severity,
jstrCategory.string(),
jstrErrorMsg.string(),
#ifndef JNI_NO_EXCEPTION_PTR
(cause == nullptr) ? getJavaExceptionForCppBackTrace().get() : getJavaExceptionForCppException(cause).get(),
#else
getJavaExceptionForCppBackTrace().get(),
#endif
samplingFrequency);
});
}
}
void reportSoftError(
Severity severity,
const char* category,
const char* error_msg,
unsigned int samplingFrequency) {
reportSoftErrorInternal(
severity,
category,
error_msg,
#ifndef JNI_NO_EXCEPTION_PTR
nullptr,
#endif
samplingFrequency);
}
#ifndef JNI_NO_EXCEPTION_PTR
void FBEXPORT reportSoftError(
Severity severity,
const char* category,
const char* error_msg,
std::exception_ptr cause,
unsigned int samplingFrequency) {
reportSoftErrorInternal(
severity,
category,
error_msg,
cause,
samplingFrequency);
}
#endif
void generateNativeSoftError() {
reportSoftError(Severity::MUST_FIX, "SoftErrorTest_1", "Reporting MUST_FIX");
#ifndef JNI_NO_EXCEPTION_PTR
reportSoftError(
Severity::WARNING,
"SoftErrorTest_2",
"Reporting WARNING with cause",
make_exception_ptr(std::invalid_argument("Fake exception")));
#endif
}
void SoftErrorOnLoad(JNIEnv* env) {
if(!checkSoftErrorClassRef()) {
return;
}
registerNatives(env, softErrorClass_.get(), {
{ "generateNativeSoftError", "()V", (void*) generateNativeSoftError }
});
}
} } }

View File

@ -0,0 +1,70 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <fb/CpuCapabilities.h>
#include <cpu-features.h>
#include <fb/Environment.h>
#include <glog/logging.h>
#include <jni/Registration.h>
namespace facebook { namespace jni {
// =========================================
// returns true if this device supports NEON calls, false otherwise
jboolean nativeDeviceSupportsNeon(JNIEnv* env, jobject obj) {
if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {
VLOG(2) << "NEON disabled, not an ARM CPU";
return false;
}
uint64_t cpufeatures = android_getCpuFeatures();
if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {
VLOG(2) << "NEON disabled, not an ARMv7 CPU";
return false;
}
if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {
VLOG(2) << "NEON disabled, not supported";
return false;
}
VLOG(2) << "NEON supported and enabled";
return true;
}
// =========================================
// returns true if this device supports VFP_FP16, false otherwise.
jboolean nativeDeviceSupportsVFPFP16(JNIEnv *env, jobject obj) {
uint64_t cpufeatures = android_getCpuFeatures();
if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_VFP_FP16) == 0) {
VLOG(2) << "VPF_FP16 disabled, not supported";
return false;
}
VLOG(2) << "VFP_FP16 supported and enabled";
return true;
}
// =========================================
// returns true if this device is x86 based, false otherwise
jboolean nativeDeviceSupportsX86(JNIEnv* env, jobject obj) {
return (android_getCpuFamily() == ANDROID_CPU_FAMILY_X86);
}
// =========================================
// register native methods
void initialize_cpucapabilities() {
facebook::jni::registerNatives(
Environment::current(),
"com/facebook/jni/CpuCapabilitiesJni",
{
{ "nativeDeviceSupportsNeon",
"()Z",
(void*) nativeDeviceSupportsNeon },
{ "nativeDeviceSupportsVFPFP16",
"()Z",
(void*) nativeDeviceSupportsVFPFP16 },
{ "nativeDeviceSupportsX86",
"()Z",
(void*) nativeDeviceSupportsX86 },
}
);
}
} } // namespace facebook::jni

View File

@ -0,0 +1,37 @@
/*
* 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 __ANDROID__
#error "This file should only be compiled for Android."
#endif
#include <fb/fbjni/References.h>
#include <fb/fbjni/CoreClasses.h>
namespace facebook {
namespace jni {
namespace internal {
static int32_t getApiLevel() {
auto cls = findClassLocal("android/os/Build$VERSION");
auto fld = cls->getStaticField<int32_t>("SDK_INT");
if (fld) {
return cls->getStaticFieldValue(fld);
}
return 0;
}
bool doesGetObjectRefTypeWork() {
static auto level = getApiLevel();
return level >= 14;
}
}
}
}

View File

@ -60,9 +60,9 @@ alias_ref<JClass> findClassStatic(const char* name) {
if (!env) {
throw std::runtime_error("Unable to retrieve JNIEnv*.");
}
auto cls = env->FindClass(name);
local_ref<jclass> cls = adopt_local(env->FindClass(name));
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
auto leaking_ref = (jclass)env->NewGlobalRef(cls);
auto leaking_ref = (jclass)env->NewGlobalRef(cls.get());
FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref);
return wrap_alias(leaking_ref);
}
@ -83,8 +83,7 @@ local_ref<JClass> findClassLocal(const char* name) {
std::string JString::toStdString() const {
const auto env = internal::getEnv();
auto utf16String = JStringUtf16Extractor(env, self());
auto length = env->GetStringLength(self());
return detail::utf16toUTF8(utf16String, length);
return detail::utf16toUTF8(utf16String.chars(), utf16String.length());
}
local_ref<JString> make_jstring(const char* utf8) {

View File

@ -0,0 +1,8 @@
java_library(
name = "java",
srcs = glob(["**/*.java"]),
visibility = ["PUBLIC"],
deps = [
"//java/com/facebook/proguard/annotations:annotations",
],
)