Add Scoped Local and Global Ref

Summary: Add ScopedLocalRef, ScopedGlobalRef and some common methods to be used later.

Reviewed By: amir-shalem

Differential Revision: D17711284

fbshipit-source-id: be43d5e246bc2406765057783be11854877c41f1
This commit is contained in:
Sidharth Guglani 2019-10-08 17:48:32 -07:00 коммит произвёл Facebook Github Bot
Родитель 67a3ad38e3
Коммит 4a69b3e636
8 изменённых файлов: 601 добавлений и 4 удалений

Просмотреть файл

@ -9,10 +9,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := yoga
LOCAL_SRC_FILES := \
jni/YGJNI.cpp \
jni/YGJTypes.cpp \
jni/YGJNIVanilla.cpp
LOCAL_SRC_FILES := $(wildcard jni/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/jni

Просмотреть файл

@ -0,0 +1,140 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#pragma once
#include <jni.h>
#include <cstddef>
#include <type_traits>
#include "corefunctions.h"
namespace facebook {
namespace yoga {
namespace vanillajni {
/**
* ScopedGlobalRef is a sort of smart reference that allows us to control the
* lifespan of a JNI global reference.
*
* This class is designed so that when a ScopedGlobalRef goes out of scoped, its
* destructor will delete -JNIEnv->DeleteGlobalRef()- the underlying JNI
* reference.
*
* This class should be used to wrap all the global references we create during
* normal JNI operations if we want reference to eventually go away (in JNI it
* is a common operation to cache some global references throughout the lifespan
* of a process, in which case using this class does not really make sense). The
* idea behind this is that in JNI we should be very explicit about the lifespan
* of global references. Global references can quickly get out of control if not
* freed properly, and the developer should always be very aware of the lifespan
* of each global reference that is created in JNI so that leaks are prevented.
*
* This class is very explicit in its behavior, and it does not allow to perform
* unexpected conversions or unexpected ownership transfer. In practice, this
* class acts as a unique pointer where the underying JNI reference can have one
* and just one owner. Transfering ownership is allowed but it is an explicit
* operation (implemneted via move semantics and also via explicity API calls).
*
* Note that this class doesn't receive an explicit JNIEnv at construction time.
* At destruction time it uses vanillajni::getCurrentEnv() to retrieve the
* JNIEnv.
*
* It is OK to cache a ScopedGlobalRef between different JNI native
* method calls.
*/
template <typename T>
class ScopedGlobalRef {
static_assert(
std::is_same<T, jclass>() || std::is_same<T, jobject>() ||
std::is_same<T, jstring>() || std::is_same<T, jthrowable>() ||
std::is_same<T, jbyteArray>() || std::is_same<T, jintArray>() ||
std::is_same<T, jshortArray>() || std::is_same<T, jcharArray>() ||
std::is_same<T, jlongArray>() || std::is_same<T, jfloatArray>() ||
std::is_same<T, jdoubleArray>() || std::is_same<T, jobjectArray>() ||
std::is_same<T, jbooleanArray>(),
"ScopedGlobalRef instantiated for invalid type");
public:
/**
* Constructs a ScopedGlobalRef with a JNI global reference.
*
* @param globalRef the global reference to wrap. Can be NULL.
*/
ScopedGlobalRef(T globalRef) : mGlobalRef(globalRef) {}
/**
* Equivalent to ScopedGlobalRef(NULL)
*/
explicit ScopedGlobalRef() : mGlobalRef(NULL) {}
/**
* Move construction is allowed.
*/
ScopedGlobalRef(ScopedGlobalRef&& s) : mGlobalRef(s.release()) {}
/**
* Move assignment is allowed.
*/
ScopedGlobalRef& operator=(ScopedGlobalRef&& s) {
reset(s.release());
return *this;
}
~ScopedGlobalRef() {
reset();
}
/**
* Deletes the currently held reference and reassigns a new one to the
* ScopedGlobalRef.
*/
void reset(T ptr = NULL) {
if (ptr != mGlobalRef) {
if (mGlobalRef != NULL) {
vanillajni::getCurrentEnv()->DeleteGlobalRef(mGlobalRef);
}
mGlobalRef = ptr;
}
}
/**
* Makes this ScopedGlobalRef not own the underlying JNI global reference.
* After calling this method, the ScopedGlobalRef will not delete the JNI
* global reference when the ScopedGlobalRef goes out of scope.
*/
T release() {
T globalRef = mGlobalRef;
mGlobalRef = NULL;
return globalRef;
}
/**
* Returns the underlying JNI global reference.
*/
T get() const { return mGlobalRef; }
/**
* Returns true if the underlying JNI reference is not NULL.
*/
operator bool() const {
return mGlobalRef != NULL;
}
ScopedGlobalRef(const ScopedGlobalRef& ref) = delete;
ScopedGlobalRef& operator=(const ScopedGlobalRef& other) = delete;
private:
T mGlobalRef;
};
template <typename T>
ScopedGlobalRef<T> make_global_ref(T globalRef) {
return ScopedGlobalRef<T>(globalRef);
}
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,140 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
/**
* This is a modified version of Android's ScopedLocalRef class that can be
* found in the Android's JNI code.
*/
#pragma once
#include <jni.h>
#include <cstddef>
#include <type_traits>
namespace facebook {
namespace yoga {
namespace vanillajni {
/**
* ScopedLocalRef is a sort of smart reference that allows us to control the
* lifespan of a JNI local reference.
*
* This class is designed so that when a ScopedLocalRef goes out of scope, its
* destructor will delete -JNIEnv->DeleteLocalRef()- the underlying JNI
* reference.
*
* This class should be used to wrap all the local references that JNI
* gives us other than those that are passed to native methods at
* invocation time. The idea behind this is that in JNI we should be very
* explicit about the lifespan of local references. Local references can quickly
* get out of control, and the developer should always be very aware of the
* lifespan of each local reference that is created in JNI so that leaks are
* prevented.
*
* This class is very explicit in its behavior, and it does not allow to perform
* unexpected conversions or unexpected ownership transfer. In practice, this
* class acts as a unique pointer where the underying JNI reference can have one
* and just one owner. Transfering ownership is allowed but it is an explicit
* operation (implemneted via move semantics and also via explicity API calls).
*
* As with standard JNI local references it is not a valid operation to keep a
* reference around between different native method calls.
*/
template <typename T>
class ScopedLocalRef {
static_assert(
std::is_same<T, jclass>() || std::is_same<T, jobject>() ||
std::is_same<T, jstring>() || std::is_same<T, jthrowable>() ||
std::is_same<T, jbyteArray>() || std::is_same<T, jintArray>() ||
std::is_same<T, jshortArray>() || std::is_same<T, jcharArray>() ||
std::is_same<T, jlongArray>() || std::is_same<T, jfloatArray>() ||
std::is_same<T, jdoubleArray>() || std::is_same<T, jobjectArray>() ||
std::is_same<T, jbooleanArray>(),
"ScopedLocalRef instantiated for invalid type");
public:
/**
* Constructs a ScopedLocalRef with a JNI local reference.
*
* @param localRef the local reference to wrap. Can be NULL.
*/
ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) {}
/**
* Equivalent to ScopedLocalRef(env, NULL)
*/
explicit ScopedLocalRef(JNIEnv* env) : mEnv(env), mLocalRef(NULL) {}
/**
* Move construction is allowed.
*/
ScopedLocalRef(ScopedLocalRef&& s) : mEnv(s.mEnv), mLocalRef(s.release()) {}
/**
* Move assignment is allowed.
*/
ScopedLocalRef& operator=(ScopedLocalRef&& s) {
reset(s.release());
mEnv = s.mEnv;
return *this;
}
~ScopedLocalRef() {
reset();
}
/**
* Deletes the currently held reference and reassigns a new one to the
* ScopedLocalRef.
*/
void reset(T ptr = NULL) {
if (ptr != mLocalRef) {
if (mLocalRef != NULL) {
mEnv->DeleteLocalRef(mLocalRef);
}
mLocalRef = ptr;
}
}
/**
* Makes this ScopedLocalRef not own the underlying JNI local reference. After
* calling this method, the ScopedLocalRef will not delete the JNI local
* reference when the ScopedLocalRef goes out of scope.
*/
T release() {
T localRef = mLocalRef;
mLocalRef = NULL;
return localRef;
}
/**
* Returns the underlying JNI local reference.
*/
T get() const { return mLocalRef; }
/**
* Returns true if the underlying JNI reference is not NULL.
*/
operator bool() const {
return mLocalRef != NULL;
}
ScopedLocalRef(const ScopedLocalRef& ref) = delete;
ScopedLocalRef& operator=(const ScopedLocalRef& other) = delete;
private:
JNIEnv* mEnv;
T mLocalRef;
};
template <typename T>
ScopedLocalRef<T> make_local_ref(JNIEnv* env, T localRef) {
return ScopedLocalRef<T>(env, localRef);
}
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,100 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include "common.h"
namespace facebook {
namespace yoga {
namespace vanillajni {
void registerNatives(
JNIEnv* env,
const char* className,
const JNINativeMethod methods[],
size_t numMethods) {
jclass clazz = env->FindClass(className);
assertNoPendingJniException(env);
env->RegisterNatives(clazz, methods, numMethods);
assertNoPendingJniException(env);
}
jmethodID getStaticMethodId(
JNIEnv* env,
jclass clazz,
const char* methodName,
const char* methodDescriptor) {
jmethodID methodId =
env->GetStaticMethodID(clazz, methodName, methodDescriptor);
assertNoPendingJniException(env);
return methodId;
}
jmethodID getMethodId(
JNIEnv* env,
jclass clazz,
const char* methodName,
const char* methodDescriptor) {
jmethodID methodId = env->GetMethodID(clazz, methodName, methodDescriptor);
assertNoPendingJniException(env);
return methodId;
}
jfieldID getFieldId(
JNIEnv* env,
jclass clazz,
const char* fieldName,
const char* fieldSignature) {
jfieldID fieldId = env->GetFieldID(clazz, fieldName, fieldSignature);
assertNoPendingJniException(env);
return fieldId;
}
#define DEFINE_CALL_METHOD_FOR_PRIMITIVE_IMPLEMENTATION(jnitype, readableType) \
DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(jnitype, readableType) { \
va_list args; \
va_start(args, methodId); \
jnitype result = env->Call##readableType##MethodV(obj, methodId, args); \
va_end(args); \
assertNoPendingJniException(env); \
return result; \
}
DEFINE_CALL_METHOD_FOR_PRIMITIVE_IMPLEMENTATION(jlong, Long);
DEFINE_CALL_METHOD_FOR_PRIMITIVE_IMPLEMENTATION(jfloat, Float);
DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(void, Void) {
va_list args;
va_start(args, methodId);
env->CallVoidMethodV(obj, methodId, args);
va_end(args);
assertNoPendingJniException(env);
}
ScopedLocalRef<jobject>
callStaticObjectMethod(JNIEnv* env, jclass clazz, jmethodID methodId, ...) {
va_list args;
va_start(args, methodId);
jobject result = env->CallStaticObjectMethodV(clazz, methodId, args);
va_end(args);
assertNoPendingJniException(env);
return make_local_ref(env, result);
}
ScopedGlobalRef<jobject> newGlobalRef(JNIEnv* env, jobject obj) {
jobject result = env->NewGlobalRef(obj);
if (!result) {
logErrorMessageAndDie("Could not obtain global reference from object");
}
return make_global_ref(result);
}
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,73 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#pragma once
#include "ScopedGlobalRef.h"
#include "ScopedLocalRef.h"
namespace facebook {
namespace yoga {
namespace vanillajni {
/**
* Registers a set of methods for a JNI class. Aborts if registration fails.
*/
void registerNatives(
JNIEnv* env,
const char* className,
const JNINativeMethod methods[],
size_t numMethods);
/**
* Returns a jmethodID for a class static method. Aborts if any error happens.
*/
jmethodID getStaticMethodId(
JNIEnv* env,
jclass clazz,
const char* methodName,
const char* methodDescriptor);
/**
* Returns a jmethodID for a class non-static method. Aborts if any error
* happens.
*/
jmethodID getMethodId(
JNIEnv* env,
jclass clazz,
const char* methodName,
const char* methodDescriptor);
/**
* Returns a class non-static field ID. Aborts if any error happens.
*/
jfieldID getFieldId(
JNIEnv* env,
jclass clazz,
const char* fieldName,
const char* fieldSignature);
// Helper methods to call a non-static method on an object depending on the
// return type. Each method will abort the execution if an error
// (such as a Java pending exception) is detected after invoking the
// Java method.
#define DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(jnitype, readableType) \
jnitype call##readableType##Method( \
JNIEnv* env, jobject obj, jmethodID methodId, ...)
DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(void, Void);
DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(jlong, Long);
DEFINE_CALL_METHOD_FOR_PRIMITIVE_INTERFACE(jfloat, Float);
ScopedLocalRef<jobject>
callStaticObjectMethod(JNIEnv* env, jclass clazz, jmethodID methodId, ...);
/**
* Given a local or a global reference, this method creates a new global
* reference out of it. If any error happens, aborts the process.
*/
ScopedGlobalRef<jobject> newGlobalRef(JNIEnv* env, jobject obj);
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,74 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#include "corefunctions.h"
#include "macros.h"
namespace facebook {
namespace yoga {
namespace vanillajni {
namespace {
JavaVM* globalVm = NULL;
struct JavaVMInitializer {
JavaVMInitializer(JavaVM* vm) {
if (!vm) {
logErrorMessageAndDie(
"You cannot pass a NULL JavaVM to ensureInitialized");
}
globalVm = vm;
}
};
} // namespace
jint ensureInitialized(JNIEnv** env, JavaVM* vm) {
static JavaVMInitializer init(vm);
if (!env) {
logErrorMessageAndDie(
"Need to pass a valid JNIEnv pointer to vanillajni initialization "
"routine");
}
if (vm->GetEnv(reinterpret_cast<void**>(env), JNI_VERSION_1_6) != JNI_OK) {
logErrorMessageAndDie(
"Error retrieving JNIEnv during initialization of vanillajni");
}
return JNI_VERSION_1_6;
}
JNIEnv* getCurrentEnv() {
JNIEnv* env;
jint ret = globalVm->GetEnv((void**) &env, JNI_VERSION_1_6);
if (ret != JNI_OK) {
logErrorMessageAndDie(
"There was an error retrieving the current JNIEnv. Make sure the "
"current thread is attached");
}
return env;
}
void logErrorMessageAndDie(const char* message) {
VANILLAJNI_LOG_ERROR(
"VanillaJni",
"Aborting due to error detected in native code: %s",
message);
VANILLAJNI_DIE();
}
void assertNoPendingJniException(JNIEnv* env) {
// This method cannot call any other method of the library, since other
// methods of the library use it to check for exceptions too
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
logErrorMessageAndDie("Aborting due to pending Java exception in JNI");
}
}
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,51 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#pragma once
#include <jni.h>
#include <cstddef>
namespace facebook {
namespace yoga {
namespace vanillajni {
/**
* This method has to be called before using the vanillajni library. This method
* is typically called when doing initialization in the "on load" JNI hook of a
* particular library.
*
* This method is thread safe, and after the first time it's called it has no
* initialization effect.
*
* @param env use this output parameter to get a JNIEnv to use for things such
* as registering native methods and such.
* @param vm the VM instance passed by JNI. This is usually the VM instance
* that is passed to the "on load" JNI hook.
* @return an integer value to return from the "on load" hook.
*/
jint ensureInitialized(JNIEnv** env, JavaVM* vm);
/**
* Returns a JNIEnv* suitable for the current thread. If the current thread is
* not attached to the Java VM, this method aborts execution.
*/
JNIEnv* getCurrentEnv();
/**
* Logs an error message and aborts the current process.
*/
void logErrorMessageAndDie(const char* message);
/**
* Checks whether there is a pending JNI exception. If so, it logs an error
* message and aborts the current process. Otherwise it does nothing.
*/
void assertNoPendingJniException(JNIEnv* env);
} // namespace vanillajni
} // namespace yoga
} // namespace facebook

Просмотреть файл

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#pragma once
#include <cstdlib>
#ifdef __ANDROID__
#include <android/log.h>
#endif
#ifdef __ANDROID__
#define VANILLAJNI_LOG_ERROR(tag, format, ...) \
__android_log_print(ANDROID_LOG_ERROR, tag, format, ##__VA_ARGS__)
#else
#define VANILLAJNI_LOG_ERROR(tag, format, ...)
#endif
#define VANILLAJNI_DIE() std::abort()