Copy fbjni library from react-native
This commit is contained in:
77
lib/fb/jni/ByteBuffer.cpp
Normal file
77
lib/fb/jni/ByteBuffer.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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/ByteBuffer.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fb/fbjni/References.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
namespace {
|
||||
local_ref<JByteBuffer> createEmpty() {
|
||||
static auto cls = JByteBuffer::javaClassStatic();
|
||||
static auto meth = cls->getStaticMethod<JByteBuffer::javaobject(int)>("allocateDirect");
|
||||
return meth(cls, 0);
|
||||
}
|
||||
}
|
||||
|
||||
local_ref<JByteBuffer> JByteBuffer::wrapBytes(uint8_t* data, size_t size) {
|
||||
// env->NewDirectByteBuffer requires that size is positive. Android's
|
||||
// dalvik returns an invalid result and Android's art aborts if size == 0.
|
||||
// Workaround this by using a slow path through Java in that case.
|
||||
if (!size) {
|
||||
return createEmpty();
|
||||
}
|
||||
auto res = adopt_local(static_cast<javaobject>(Environment::current()->NewDirectByteBuffer(data, size)));
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
if (!res) {
|
||||
throw std::runtime_error("Direct byte buffers are unsupported.");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t* JByteBuffer::getDirectBytes() {
|
||||
if (!self()) {
|
||||
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
|
||||
}
|
||||
void* bytes = Environment::current()->GetDirectBufferAddress(self());
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
if (!bytes) {
|
||||
throw std::runtime_error(
|
||||
isDirect() ?
|
||||
"Attempt to get direct bytes of non-direct byte buffer." :
|
||||
"Error getting direct bytes of byte buffer.");
|
||||
}
|
||||
return static_cast<uint8_t*>(bytes);
|
||||
}
|
||||
|
||||
size_t JByteBuffer::getDirectSize() {
|
||||
if (!self()) {
|
||||
throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
|
||||
}
|
||||
int size = Environment::current()->GetDirectBufferCapacity(self());
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
if (size < 0) {
|
||||
throw std::runtime_error(
|
||||
isDirect() ?
|
||||
"Attempt to get direct size of non-direct byte buffer." :
|
||||
"Error getting direct size of byte buffer.");
|
||||
}
|
||||
return static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
bool JByteBuffer::isDirect() {
|
||||
static auto meth = javaClassStatic()->getMethod<jboolean()>("isDirect");
|
||||
return meth(self());
|
||||
}
|
||||
|
||||
}}
|
69
lib/fb/jni/Countable.cpp
Normal file
69
lib/fb/jni/Countable.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
#include <jni/Countable.h>
|
||||
#include <fb/Environment.h>
|
||||
#include <jni/Registration.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
static jfieldID gCountableNativePtr;
|
||||
|
||||
static RefPtr<Countable>* rawCountableFromJava(JNIEnv* env, jobject obj) {
|
||||
FBASSERT(obj);
|
||||
return reinterpret_cast<RefPtr<Countable>*>(env->GetLongField(obj, gCountableNativePtr));
|
||||
}
|
||||
|
||||
const RefPtr<Countable>& countableFromJava(JNIEnv* env, jobject obj) {
|
||||
FBASSERT(obj);
|
||||
return *rawCountableFromJava(env, obj);
|
||||
}
|
||||
|
||||
void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable) {
|
||||
int oldValue = env->GetLongField(obj, gCountableNativePtr);
|
||||
FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue);
|
||||
|
||||
FBASSERT(countable);
|
||||
uintptr_t fieldValue = (uintptr_t) new RefPtr<Countable>(std::move(countable));
|
||||
env->SetLongField(obj, gCountableNativePtr, fieldValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: THREAD SAFETY (this comment also exists at Countable.java)
|
||||
*
|
||||
* This method deletes the corresponding native object on whatever thread the method is called
|
||||
* on. In the common case when this is called by Countable#finalize(), this will be called on the
|
||||
* system finalizer thread. If you manually call dispose on the Java object, the native object
|
||||
* will be deleted synchronously on that thread.
|
||||
*/
|
||||
void dispose(JNIEnv* env, jobject obj) {
|
||||
// Grab the pointer
|
||||
RefPtr<Countable>* countable = rawCountableFromJava(env, obj);
|
||||
if (!countable) {
|
||||
// That was easy.
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear out the old value to avoid double-frees
|
||||
env->SetLongField(obj, gCountableNativePtr, 0);
|
||||
|
||||
delete countable;
|
||||
}
|
||||
|
||||
void CountableOnLoad(JNIEnv* env) {
|
||||
jclass countable = env->FindClass("com/facebook/jni/Countable");
|
||||
gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J");
|
||||
registerNatives(env, countable, {
|
||||
{ "dispose", "()V", (void*) dispose },
|
||||
});
|
||||
}
|
||||
|
||||
} }
|
132
lib/fb/jni/Environment.cpp
Normal file
132
lib/fb/jni/Environment.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 <pthread.h>
|
||||
#include <fb/log.h>
|
||||
#include <fb/StaticInitialized.h>
|
||||
#include <fb/ThreadLocal.h>
|
||||
#include <fb/Environment.h>
|
||||
#include <fb/fbjni/CoreClasses.h>
|
||||
#include <fb/fbjni/NativeRunnable.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
namespace {
|
||||
StaticInitialized<ThreadLocal<JNIEnv>> g_env;
|
||||
JavaVM* g_vm = nullptr;
|
||||
|
||||
struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;";
|
||||
|
||||
// These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead.
|
||||
static void runStdFunction(std::function<void()>&& func) {
|
||||
static auto method = javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction");
|
||||
method(javaClassStatic(), reinterpret_cast<jlong>(&func));
|
||||
}
|
||||
|
||||
static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) {
|
||||
(*reinterpret_cast<std::function<void()>*>(ptr))();
|
||||
}
|
||||
|
||||
static void OnLoad() {
|
||||
// We need the javaClassStatic so that the class lookup is cached and that
|
||||
// runStdFunction can be called from a ThreadScope-attached thread.
|
||||
javaClassStatic()->registerNatives({
|
||||
makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* static */
|
||||
JNIEnv* Environment::current() {
|
||||
JNIEnv* env = g_env->get();
|
||||
if ((env == nullptr) && (g_vm != nullptr)) {
|
||||
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM");
|
||||
// TODO(cjhopman): This should throw an exception.
|
||||
env = nullptr;
|
||||
} else {
|
||||
g_env->reset(env);
|
||||
}
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void Environment::detachCurrentThread() {
|
||||
auto env = g_env->get();
|
||||
if (env) {
|
||||
FBASSERT(g_vm);
|
||||
g_vm->DetachCurrentThread();
|
||||
g_env->reset();
|
||||
}
|
||||
}
|
||||
|
||||
struct EnvironmentInitializer {
|
||||
EnvironmentInitializer(JavaVM* vm) {
|
||||
FBASSERT(!g_vm);
|
||||
FBASSERT(vm);
|
||||
g_vm = vm;
|
||||
g_env.initialize([] (void*) {});
|
||||
}
|
||||
};
|
||||
|
||||
/* static */
|
||||
void Environment::initialize(JavaVM* vm) {
|
||||
static EnvironmentInitializer init(vm);
|
||||
}
|
||||
|
||||
/* static */
|
||||
JNIEnv* Environment::ensureCurrentThreadIsAttached() {
|
||||
auto env = g_env->get();
|
||||
if (!env) {
|
||||
FBASSERT(g_vm);
|
||||
g_vm->AttachCurrentThread(&env, nullptr);
|
||||
g_env->reset(env);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
ThreadScope::ThreadScope()
|
||||
: attachedWithThisScope_(false) {
|
||||
JNIEnv* env = nullptr;
|
||||
if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) {
|
||||
return;
|
||||
}
|
||||
env = facebook::jni::Environment::ensureCurrentThreadIsAttached();
|
||||
FBASSERT(env);
|
||||
attachedWithThisScope_ = true;
|
||||
}
|
||||
|
||||
ThreadScope::~ThreadScope() {
|
||||
if (attachedWithThisScope_) {
|
||||
Environment::detachCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ThreadScope::OnLoad() {
|
||||
// These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading.
|
||||
JThreadScopeSupport::OnLoad();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
|
||||
// TODO(cjhopman): If the classloader is already available in this scope, we
|
||||
// shouldn't have to jump through java.
|
||||
ThreadScope ts;
|
||||
JThreadScopeSupport::runStdFunction(std::move(runnable));
|
||||
}
|
||||
|
||||
} }
|
||||
|
285
lib/fb/jni/Exceptions.cpp
Normal file
285
lib/fb/jni/Exceptions.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* 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 <fb/fbjni/CoreClasses.h>
|
||||
|
||||
#include <fb/assert.h>
|
||||
#include <fb/log.h>
|
||||
|
||||
#include <alloca.h>
|
||||
#include <cstdlib>
|
||||
#include <ios>
|
||||
#include <stdexcept>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
namespace {
|
||||
class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
|
||||
|
||||
static local_ref<JRuntimeException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
|
||||
static local_ref<JRuntimeException> create() {
|
||||
return newInstance();
|
||||
}
|
||||
};
|
||||
|
||||
class JIOException : public JavaClass<JIOException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/io/IOException;";
|
||||
|
||||
static local_ref<JIOException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
|
||||
|
||||
static local_ref<JOutOfMemoryError> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;";
|
||||
|
||||
static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;";
|
||||
|
||||
static local_ref<JUnknownCppException> create() {
|
||||
return newInstance();
|
||||
}
|
||||
|
||||
static local_ref<JUnknownCppException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;";
|
||||
|
||||
static local_ref<JCppSystemErrorException> create(const std::system_error& e) {
|
||||
return newInstance(make_jstring(e.what()), e.code().value());
|
||||
}
|
||||
};
|
||||
|
||||
// Exception throwing & translating functions //////////////////////////////////////////////////////
|
||||
|
||||
// Functions that throw Java exceptions
|
||||
|
||||
void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
|
||||
auto env = Environment::current();
|
||||
if (throwable) {
|
||||
env->Throw(throwable.get());
|
||||
}
|
||||
if (env->ExceptionCheck() != JNI_TRUE) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Functions that throw C++ exceptions
|
||||
|
||||
// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated
|
||||
void throwPendingJniExceptionAsCppException() {
|
||||
JNIEnv* env = Environment::current();
|
||||
if (env->ExceptionCheck() == JNI_FALSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto throwable = adopt_local(env->ExceptionOccurred());
|
||||
if (!throwable) {
|
||||
throw std::runtime_error("Unable to get pending JNI exception.");
|
||||
}
|
||||
env->ExceptionClear();
|
||||
|
||||
throw JniException(throwable);
|
||||
}
|
||||
|
||||
void throwCppExceptionIf(bool condition) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto env = Environment::current();
|
||||
if (env->ExceptionCheck() == JNI_TRUE) {
|
||||
throwPendingJniExceptionAsCppException();
|
||||
return;
|
||||
}
|
||||
|
||||
throw JniException();
|
||||
}
|
||||
|
||||
void throwNewJavaException(jthrowable throwable) {
|
||||
throw JniException(wrap_alias(throwable));
|
||||
}
|
||||
|
||||
void throwNewJavaException(const char* throwableName, const char* msg) {
|
||||
// If anything of the fbjni calls fail, an exception of a suitable
|
||||
// form will be thrown, which is what we want.
|
||||
auto throwableClass = findClassLocal(throwableName);
|
||||
auto throwable = throwableClass->newObject(
|
||||
throwableClass->getConstructor<jthrowable(jstring)>(),
|
||||
make_jstring(msg).release());
|
||||
throwNewJavaException(throwable.get());
|
||||
}
|
||||
|
||||
// Translate C++ to Java Exception
|
||||
|
||||
namespace {
|
||||
|
||||
// The implementation std::rethrow_if_nested uses a dynamic_cast to determine
|
||||
// if the exception is a nested_exception. If the exception is from a library
|
||||
// built with -fno-rtti, then that will crash. This avoids that.
|
||||
void rethrow_if_nested() {
|
||||
try {
|
||||
throw;
|
||||
} catch (const std::nested_exception& e) {
|
||||
e.rethrow_nested();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
// For each exception in the chain of the currently handled exception, func
|
||||
// will be called with that exception as the currently handled exception (in
|
||||
// reverse order, i.e. innermost first).
|
||||
void denest(std::function<void()> func) {
|
||||
try {
|
||||
throw;
|
||||
} catch (const std::exception& e) {
|
||||
try {
|
||||
rethrow_if_nested();
|
||||
} catch (...) {
|
||||
denest(func);
|
||||
}
|
||||
func();
|
||||
} catch (...) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void translatePendingCppExceptionToJavaException() noexcept {
|
||||
local_ref<JThrowable> previous;
|
||||
auto func = [&previous] () {
|
||||
local_ref<JThrowable> current;
|
||||
try {
|
||||
throw;
|
||||
} catch(const JniException& ex) {
|
||||
current = ex.getThrowable();
|
||||
} catch(const std::ios_base::failure& ex) {
|
||||
current = JIOException::create(ex.what());
|
||||
} catch(const std::bad_alloc& ex) {
|
||||
current = JOutOfMemoryError::create(ex.what());
|
||||
} catch(const std::out_of_range& ex) {
|
||||
current = JArrayIndexOutOfBoundsException::create(ex.what());
|
||||
} catch(const std::system_error& ex) {
|
||||
current = JCppSystemErrorException::create(ex);
|
||||
} catch(const std::runtime_error& ex) {
|
||||
current = JRuntimeException::create(ex.what());
|
||||
} catch(const std::exception& ex) {
|
||||
current = JCppException::create(ex.what());
|
||||
} catch(const char* msg) {
|
||||
current = JUnknownCppException::create(msg);
|
||||
} catch(...) {
|
||||
current = JUnknownCppException::create();
|
||||
}
|
||||
if (previous) {
|
||||
current->initCause(previous);
|
||||
}
|
||||
previous = current;
|
||||
};
|
||||
|
||||
try {
|
||||
denest(func);
|
||||
setJavaExceptionAndAbortOnFailure(previous);
|
||||
} catch (std::exception& e) {
|
||||
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what());
|
||||
// rethrow the exception and let the noexcept handling abort.
|
||||
throw;
|
||||
} catch (...) {
|
||||
FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// JniException ////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
|
||||
|
||||
JniException::JniException() : JniException(JRuntimeException::create()) { }
|
||||
|
||||
JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) {
|
||||
throwable_ = make_global(throwable);
|
||||
}
|
||||
|
||||
JniException::JniException(JniException &&rhs)
|
||||
: throwable_(std::move(rhs.throwable_)),
|
||||
what_(std::move(rhs.what_)),
|
||||
isMessageExtracted_(rhs.isMessageExtracted_) {
|
||||
}
|
||||
|
||||
JniException::JniException(const JniException &rhs)
|
||||
: what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
|
||||
throwable_ = make_global(rhs.throwable_);
|
||||
}
|
||||
|
||||
JniException::~JniException() {
|
||||
ThreadScope ts;
|
||||
throwable_.reset();
|
||||
}
|
||||
|
||||
local_ref<JThrowable> JniException::getThrowable() const noexcept {
|
||||
return make_local(throwable_);
|
||||
}
|
||||
|
||||
// TODO 6900503: consider making this thread-safe.
|
||||
void JniException::populateWhat() const noexcept {
|
||||
ThreadScope ts;
|
||||
try {
|
||||
what_ = throwable_->toString();
|
||||
isMessageExtracted_ = true;
|
||||
} catch(...) {
|
||||
what_ = kExceptionMessageFailure_;
|
||||
}
|
||||
}
|
||||
|
||||
const char* JniException::what() const noexcept {
|
||||
if (!isMessageExtracted_) {
|
||||
populateWhat();
|
||||
}
|
||||
return what_.c_str();
|
||||
}
|
||||
|
||||
void JniException::setJavaException() const noexcept {
|
||||
setJavaExceptionAndAbortOnFailure(throwable_);
|
||||
}
|
||||
|
||||
}}
|
65
lib/fb/jni/Hybrid.cpp
Normal file
65
lib/fb/jni/Hybrid.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 "fb/fbjni.h"
|
||||
|
||||
|
||||
namespace facebook {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
void resetNative(alias_ref<detail::HybridData> jthis) {
|
||||
jthis->setNativePointer(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void HybridDataOnLoad() {
|
||||
registerNatives("com/facebook/jni/HybridData", {
|
||||
makeNativeMethod("resetNative", resetNative),
|
||||
});
|
||||
}
|
||||
|
||||
}}
|
312
lib/fb/jni/LocalString.cpp
Normal file
312
lib/fb/jni/LocalString.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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 <jni/LocalString.h>
|
||||
#include <fb/Environment.h>
|
||||
#include <fb/assert.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint16_t kUtf8OneByteBoundary = 0x80;
|
||||
const uint16_t kUtf8TwoBytesBoundary = 0x800;
|
||||
const uint16_t kUtf16HighSubLowBoundary = 0xD800;
|
||||
const uint16_t kUtf16HighSubHighBoundary = 0xDC00;
|
||||
const uint16_t kUtf16LowSubHighBoundary = 0xE000;
|
||||
|
||||
inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
|
||||
FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
|
||||
|
||||
out[0] = 0xE0 | (code >> 12);
|
||||
out[1] = 0x80 | ((code >> 6) & 0x3F);
|
||||
out[2] = 0x80 | (code & 0x3F);
|
||||
}
|
||||
|
||||
inline char32_t decode3ByteUTF8(const uint8_t* in) {
|
||||
return (((in[0] & 0x0f) << 12) |
|
||||
((in[1] & 0x3f) << 6) |
|
||||
( in[2] & 0x3f));
|
||||
}
|
||||
|
||||
inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) {
|
||||
FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits");
|
||||
|
||||
out[offset] = (char) (0xF0 | (code >> 18));
|
||||
out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F));
|
||||
out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F));
|
||||
out[offset + 3] = (char) (0x80 | (code & 0x3F));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool isFourByteUTF8Encoding(const T* utf8) {
|
||||
return ((*utf8 & 0xF8) == 0xF0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
size_t modifiedLength(const std::string& str) {
|
||||
// Scan for supplementary characters
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < str.size(); ) {
|
||||
if (str[i] == 0) {
|
||||
i += 1;
|
||||
j += 2;
|
||||
} else if (i + 4 > str.size() ||
|
||||
!isFourByteUTF8Encoding(&(str[i]))) {
|
||||
// See the code in utf8ToModifiedUTF8 for what's happening here.
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else {
|
||||
i += 4;
|
||||
j += 6;
|
||||
}
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
// returns modified utf8 length; *length is set to strlen(str)
|
||||
size_t modifiedLength(const uint8_t* str, size_t* length) {
|
||||
// NUL-terminated: Scan for length and supplementary characters
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
while (str[i] != 0) {
|
||||
if (str[i + 1] == 0 ||
|
||||
str[i + 2] == 0 ||
|
||||
str[i + 3] == 0 ||
|
||||
!isFourByteUTF8Encoding(&(str[i]))) {
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else {
|
||||
i += 4;
|
||||
j += 6;
|
||||
}
|
||||
}
|
||||
|
||||
*length = i;
|
||||
return j;
|
||||
}
|
||||
|
||||
void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen)
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; ) {
|
||||
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
|
||||
if (utf8[i] == 0) {
|
||||
FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short");
|
||||
modified[j] = 0xc0;
|
||||
modified[j + 1] = 0x80;
|
||||
i += 1;
|
||||
j += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 4 > len ||
|
||||
!isFourByteUTF8Encoding(utf8 + i)) {
|
||||
// If the input is too short for this to be a four-byte
|
||||
// encoding, or it isn't one for real, just copy it on through.
|
||||
modified[j] = utf8[i];
|
||||
i++;
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert 4 bytes of input to 2 * 3 bytes of output
|
||||
char32_t code = (((utf8[i] & 0x07) << 18) |
|
||||
((utf8[i + 1] & 0x3f) << 12) |
|
||||
((utf8[i + 2] & 0x3f) << 6) |
|
||||
( utf8[i + 3] & 0x3f));
|
||||
char32_t first;
|
||||
char32_t second;
|
||||
|
||||
if (code > 0x10ffff) {
|
||||
// These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit
|
||||
// limit on that representation. Encode two replacement characters, so the expected output
|
||||
// length lines up.
|
||||
const char32_t kUnicodeReplacementChar = 0xfffd;
|
||||
first = kUnicodeReplacementChar;
|
||||
second = kUnicodeReplacementChar;
|
||||
} else {
|
||||
// split into surrogate pair
|
||||
first = ((code - 0x010000) >> 10) | 0xd800;
|
||||
second = ((code - 0x010000) & 0x3ff) | 0xdc00;
|
||||
}
|
||||
|
||||
// encode each as a 3 byte surrogate value
|
||||
FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short");
|
||||
encode3ByteUTF8(first, modified + j);
|
||||
encode3ByteUTF8(second, modified + j + 3);
|
||||
i += 4;
|
||||
j += 6;
|
||||
}
|
||||
|
||||
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
|
||||
modified[j++] = '\0';
|
||||
}
|
||||
|
||||
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept {
|
||||
// Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
|
||||
std::string utf8(len, 0);
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; ) {
|
||||
// surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
|
||||
// encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx
|
||||
|
||||
if (len >= i + 6 &&
|
||||
modified[i] == 0xed &&
|
||||
(modified[i + 1] & 0xf0) == 0xa0 &&
|
||||
modified[i + 3] == 0xed &&
|
||||
(modified[i + 4] & 0xf0) == 0xb0) {
|
||||
// Valid surrogate pair
|
||||
char32_t pair1 = decode3ByteUTF8(modified + i);
|
||||
char32_t pair2 = decode3ByteUTF8(modified + i + 3);
|
||||
char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) |
|
||||
( pair2 & 0x3ff));
|
||||
encode4ByteUTF8(ch, utf8, j);
|
||||
i += 6;
|
||||
j += 4;
|
||||
continue;
|
||||
} else if (len >= i + 2 &&
|
||||
modified[i] == 0xc0 &&
|
||||
modified[i + 1] == 0x80) {
|
||||
utf8[j] = 0;
|
||||
i += 2;
|
||||
j += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid
|
||||
// encoding of some sort, but garbage in garbage out is ok.
|
||||
|
||||
utf8[j] = (char) modified[i];
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
utf8.resize(j);
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
||||
// Calculate how many bytes are needed to convert an UTF16 string into UTF8
|
||||
// UTF16 string
|
||||
size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
|
||||
if (!utf16String || utf16StringLen == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t utf8StringLen = 0;
|
||||
auto utf16StringEnd = utf16String + utf16StringLen;
|
||||
auto idx16 = utf16String;
|
||||
while (idx16 < utf16StringEnd) {
|
||||
auto ch = *idx16++;
|
||||
if (ch < kUtf8OneByteBoundary) {
|
||||
utf8StringLen++;
|
||||
} else if (ch < kUtf8TwoBytesBoundary) {
|
||||
utf8StringLen += 2;
|
||||
} else if (
|
||||
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
|
||||
(idx16 < utf16StringEnd) &&
|
||||
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
|
||||
utf8StringLen += 4;
|
||||
idx16++;
|
||||
} else {
|
||||
utf8StringLen += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return utf8StringLen;
|
||||
}
|
||||
|
||||
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
|
||||
if (!utf16String || utf16StringLen <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
|
||||
auto idx8 = utf8String.begin();
|
||||
auto idx16 = utf16String;
|
||||
auto utf16StringEnd = utf16String + utf16StringLen;
|
||||
while (idx16 < utf16StringEnd) {
|
||||
auto ch = *idx16++;
|
||||
if (ch < kUtf8OneByteBoundary) {
|
||||
*idx8++ = (ch & 0x7F);
|
||||
} else if (ch < kUtf8TwoBytesBoundary) {
|
||||
*idx8++ = 0b11000000 | (ch >> 6);
|
||||
*idx8++ = 0b10000000 | (ch & 0x3F);
|
||||
} else if (
|
||||
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
|
||||
(idx16 < utf16StringEnd) &&
|
||||
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
|
||||
auto ch2 = *idx16++;
|
||||
uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
|
||||
*idx8++ = 0b11110000 | (trunc_byte >> 2);
|
||||
*idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
|
||||
*idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
|
||||
*idx8++ = 0b10000000 | (ch2 & 0x3F);
|
||||
} else {
|
||||
*idx8++ = 0b11100000 | (ch >> 12);
|
||||
*idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
|
||||
*idx8++ = 0b10000000 | (ch & 0x3F);
|
||||
}
|
||||
}
|
||||
|
||||
return utf8String;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LocalString::LocalString(const std::string& str)
|
||||
{
|
||||
size_t modlen = detail::modifiedLength(str);
|
||||
if (modlen == str.size()) {
|
||||
// no supplementary characters, build jstring from input buffer
|
||||
m_string = Environment::current()->NewStringUTF(str.data());
|
||||
return;
|
||||
}
|
||||
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
|
||||
detail::utf8ToModifiedUTF8(
|
||||
reinterpret_cast<const uint8_t*>(str.data()), str.size(),
|
||||
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
|
||||
m_string = Environment::current()->NewStringUTF(modified.data());
|
||||
}
|
||||
|
||||
LocalString::LocalString(const char* str)
|
||||
{
|
||||
size_t len;
|
||||
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(str), &len);
|
||||
if (modlen == len) {
|
||||
// no supplementary characters, build jstring from input buffer
|
||||
m_string = Environment::current()->NewStringUTF(str);
|
||||
return;
|
||||
}
|
||||
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
|
||||
detail::utf8ToModifiedUTF8(
|
||||
reinterpret_cast<const uint8_t*>(str), len,
|
||||
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
|
||||
m_string = Environment::current()->NewStringUTF(modified.data());
|
||||
}
|
||||
|
||||
LocalString::~LocalString() {
|
||||
Environment::current()->DeleteLocalRef(m_string);
|
||||
}
|
||||
|
||||
std::string fromJString(JNIEnv* env, jstring str) {
|
||||
auto utf16String = JStringUtf16Extractor(env, str);
|
||||
auto length = env->GetStringLength(str);
|
||||
return detail::utf16toUTF8(utf16String, length);
|
||||
}
|
||||
|
||||
} }
|
22
lib/fb/jni/OnLoad.cpp
Normal file
22
lib/fb/jni/OnLoad.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 <jni/Countable.h>
|
||||
#include <fb/Environment.h>
|
||||
#include <fb/fbjni.h>
|
||||
#include <fb/fbjni/NativeRunnable.h>
|
||||
|
||||
using namespace facebook::jni;
|
||||
|
||||
void initialize_fbjni() {
|
||||
CountableOnLoad(Environment::current());
|
||||
HybridDataOnLoad();
|
||||
JNativeRunnable::OnLoad();
|
||||
ThreadScope::OnLoad();
|
||||
}
|
41
lib/fb/jni/References.cpp
Normal file
41
lib/fb/jni/References.cpp
Normal file
@@ -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 <fb/fbjni/References.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity)
|
||||
: env_(env) {
|
||||
hasFrame_ = false;
|
||||
auto pushResult = env->PushLocalFrame(capacity);
|
||||
FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0);
|
||||
hasFrame_ = true;
|
||||
}
|
||||
|
||||
JniLocalScope::~JniLocalScope() {
|
||||
if (hasFrame_) {
|
||||
env_->PopLocalFrame(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Default implementation always returns true.
|
||||
// Platform-specific sources can override this.
|
||||
bool doesGetObjectRefTypeWork() __attribute__ ((weak));
|
||||
bool doesGetObjectRefTypeWork() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
43
lib/fb/jni/WeakReference.cpp
Normal file
43
lib/fb/jni/WeakReference.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 <fb/Environment.h>
|
||||
#include <jni/WeakReference.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
WeakReference::WeakReference(jobject strongRef) :
|
||||
m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef))
|
||||
{
|
||||
}
|
||||
|
||||
WeakReference::~WeakReference() {
|
||||
auto env = Environment::current();
|
||||
FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread");
|
||||
env->DeleteWeakGlobalRef(m_weakReference);
|
||||
}
|
||||
|
||||
ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) :
|
||||
m_strongReference(Environment::current()->NewLocalRef(weakRef))
|
||||
{
|
||||
}
|
||||
|
||||
ResolvedWeakReference::ResolvedWeakReference(const RefPtr<WeakReference>& weakRef) :
|
||||
m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef()))
|
||||
{
|
||||
}
|
||||
|
||||
ResolvedWeakReference::~ResolvedWeakReference() {
|
||||
if (m_strongReference)
|
||||
Environment::current()->DeleteLocalRef(m_strongReference);
|
||||
}
|
||||
|
||||
} }
|
||||
|
196
lib/fb/jni/fbjni.cpp
Normal file
196
lib/fb/jni/fbjni.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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 <fb/fbjni.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <jni/LocalString.h>
|
||||
#include <fb/log.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
|
||||
static std::once_flag flag{};
|
||||
// TODO (t7832883): DTRT when we have exception pointers
|
||||
static auto error_msg = std::string{"Failed to initialize fbjni"};
|
||||
static auto error_occured = false;
|
||||
|
||||
std::call_once(flag, [vm] {
|
||||
try {
|
||||
Environment::initialize(vm);
|
||||
} catch (std::exception& ex) {
|
||||
error_occured = true;
|
||||
try {
|
||||
error_msg = std::string{"Failed to initialize fbjni: "} + ex.what();
|
||||
} catch (...) {
|
||||
// Ignore, we already have a fall back message
|
||||
}
|
||||
} catch (...) {
|
||||
error_occured = true;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if (error_occured) {
|
||||
throw std::runtime_error(error_msg);
|
||||
}
|
||||
|
||||
init_fn();
|
||||
} catch (const std::exception& e) {
|
||||
FBLOGE("error %s", e.what());
|
||||
translatePendingCppExceptionToJavaException();
|
||||
} catch (...) {
|
||||
translatePendingCppExceptionToJavaException();
|
||||
// So Java will handle the translated exception, fall through and
|
||||
// return a good version number.
|
||||
}
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
alias_ref<JClass> findClassStatic(const char* name) {
|
||||
const auto env = internal::getEnv();
|
||||
if (!env) {
|
||||
throw std::runtime_error("Unable to retrieve JNIEnv*.");
|
||||
}
|
||||
auto cls = env->FindClass(name);
|
||||
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
|
||||
auto leaking_ref = (jclass)env->NewGlobalRef(cls);
|
||||
FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref);
|
||||
return wrap_alias(leaking_ref);
|
||||
}
|
||||
|
||||
local_ref<JClass> findClassLocal(const char* name) {
|
||||
const auto env = internal::getEnv();
|
||||
if (!env) {
|
||||
throw std::runtime_error("Unable to retrieve JNIEnv*.");
|
||||
}
|
||||
auto cls = env->FindClass(name);
|
||||
FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
|
||||
return adopt_local(cls);
|
||||
}
|
||||
|
||||
|
||||
// jstring /////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
local_ref<JString> make_jstring(const char* utf8) {
|
||||
if (!utf8) {
|
||||
return {};
|
||||
}
|
||||
const auto env = internal::getEnv();
|
||||
size_t len;
|
||||
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(utf8), &len);
|
||||
jstring result;
|
||||
if (modlen == len) {
|
||||
// The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars
|
||||
// and '\0' that is encoded on 2 bytes.
|
||||
//
|
||||
// Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we
|
||||
// know that if those two strings are of the same length we don't need to do any
|
||||
// conversion -> no 4-byte chars nor '\0'.
|
||||
result = env->NewStringUTF(utf8);
|
||||
} else {
|
||||
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
|
||||
detail::utf8ToModifiedUTF8(
|
||||
reinterpret_cast<const uint8_t*>(utf8), len,
|
||||
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
|
||||
result = env->NewStringUTF(modified.data());
|
||||
}
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
return adopt_local(result);
|
||||
}
|
||||
|
||||
|
||||
// JniPrimitiveArrayFunctions //////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma push_macro("DEFINE_PRIMITIVE_METHODS")
|
||||
#undef DEFINE_PRIMITIVE_METHODS
|
||||
#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME) \
|
||||
\
|
||||
template<> \
|
||||
FBEXPORT \
|
||||
TYPE* JPrimitiveArray<TYPE ## Array>::getElements(jboolean* isCopy) { \
|
||||
auto env = internal::getEnv(); \
|
||||
TYPE* res = env->Get ## NAME ## ArrayElements(self(), isCopy); \
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
|
||||
return res; \
|
||||
} \
|
||||
\
|
||||
template<> \
|
||||
FBEXPORT \
|
||||
void JPrimitiveArray<TYPE ## Array>::releaseElements( \
|
||||
TYPE* elements, jint mode) { \
|
||||
auto env = internal::getEnv(); \
|
||||
env->Release ## NAME ## ArrayElements(self(), elements, mode); \
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
|
||||
} \
|
||||
\
|
||||
template<> \
|
||||
FBEXPORT \
|
||||
void JPrimitiveArray<TYPE ## Array>::getRegion( \
|
||||
jsize start, jsize length, TYPE* buf) { \
|
||||
auto env = internal::getEnv(); \
|
||||
env->Get ## NAME ## ArrayRegion(self(), start, length, buf); \
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
|
||||
} \
|
||||
\
|
||||
template<> \
|
||||
FBEXPORT \
|
||||
void JPrimitiveArray<TYPE ## Array>::setRegion( \
|
||||
jsize start, jsize length, const TYPE* elements) { \
|
||||
auto env = internal::getEnv(); \
|
||||
env->Set ## NAME ## ArrayRegion(self(), start, length, elements); \
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
|
||||
} \
|
||||
\
|
||||
FBEXPORT \
|
||||
local_ref<TYPE ## Array> make_ ## SMALLNAME ## _array(jsize size) { \
|
||||
auto array = internal::getEnv()->New ## NAME ## Array(size); \
|
||||
FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \
|
||||
return adopt_local(array); \
|
||||
} \
|
||||
\
|
||||
template<> \
|
||||
FBEXPORT \
|
||||
local_ref<TYPE ## Array> JArray ## NAME::newArray(size_t count) { \
|
||||
return make_ ## SMALLNAME ## _array(count); \
|
||||
} \
|
||||
\
|
||||
|
||||
DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean)
|
||||
DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte)
|
||||
DEFINE_PRIMITIVE_METHODS(jchar, Char, char)
|
||||
DEFINE_PRIMITIVE_METHODS(jshort, Short, short)
|
||||
DEFINE_PRIMITIVE_METHODS(jint, Int, int)
|
||||
DEFINE_PRIMITIVE_METHODS(jlong, Long, long)
|
||||
DEFINE_PRIMITIVE_METHODS(jfloat, Float, float)
|
||||
DEFINE_PRIMITIVE_METHODS(jdouble, Double, double)
|
||||
#pragma pop_macro("DEFINE_PRIMITIVE_METHODS")
|
||||
|
||||
// Internal debug /////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace internal {
|
||||
|
||||
FBEXPORT ReferenceStats g_reference_stats;
|
||||
|
||||
FBEXPORT void facebook::jni::internal::ReferenceStats::reset() noexcept {
|
||||
locals_deleted = globals_deleted = weaks_deleted = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}}
|
20
lib/fb/jni/java/CppException.java
Normal file
20
lib/fb/jni/java/CppException.java
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public class CppException extends RuntimeException {
|
||||
@DoNotStrip
|
||||
public CppException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
27
lib/fb/jni/java/CppSystemErrorException.java
Normal file
27
lib/fb/jni/java/CppSystemErrorException.java
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public class CppSystemErrorException extends CppException {
|
||||
int errorCode;
|
||||
|
||||
@DoNotStrip
|
||||
public CppSystemErrorException(String message, int errorCode) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
25
lib/fb/jni/java/UnknownCppException.java
Normal file
25
lib/fb/jni/java/UnknownCppException.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public class UnknownCppException extends CppException {
|
||||
@DoNotStrip
|
||||
public UnknownCppException() {
|
||||
super("Unknown");
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public UnknownCppException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
197
lib/fb/jni/jni_helpers.cpp
Normal file
197
lib/fb/jni/jni_helpers.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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 <jni.h>
|
||||
#include <stddef.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include <jni/jni_helpers.h>
|
||||
|
||||
#define MSG_SIZE 1024
|
||||
|
||||
namespace facebook {
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an exception.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szClassName class name to throw
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args) {
|
||||
char szMsg[MSG_SIZE];
|
||||
vsnprintf(szMsg, MSG_SIZE, szFmt, va_args);
|
||||
jclass exClass = pEnv->FindClass(szClassName);
|
||||
return pEnv->ThrowNew(exClass, szMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw a NoClassDefFoundError.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/NoClassDefFoundError", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw a RuntimeException.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/RuntimeException", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an IllegalArgumentException.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/IllegalArgumentException", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an IllegalStateException.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/IllegalStateException", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an OutOfMemoryError.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/OutOfMemoryError", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an AssertionError.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/lang/AssertionError", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the JNI environment to throw an IOException.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szFmt sprintf-style format string
|
||||
* @param ... sprintf-style args
|
||||
* @return 0 on success; a negative value on failure
|
||||
*/
|
||||
jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...) {
|
||||
va_list va_args;
|
||||
va_start(va_args, szFmt);
|
||||
jint ret = throwException(pEnv, "java/io/IOException", szFmt, va_args);
|
||||
va_end(va_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the specified class. If it's not found, instructs the JNI environment to throw an
|
||||
* exception.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param szClassName the classname to find in JNI format (e.g. "java/lang/String")
|
||||
* @return the class or NULL if not found (in which case a pending exception will be queued). This
|
||||
* returns a global reference (JNIEnv::NewGlobalRef).
|
||||
*/
|
||||
jclass findClassOrThrow(JNIEnv* pEnv, const char* szClassName) {
|
||||
jclass clazz = pEnv->FindClass(szClassName);
|
||||
if (!clazz) {
|
||||
return NULL;
|
||||
}
|
||||
return (jclass) pEnv->NewGlobalRef(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the specified field of the specified class. If it's not found, instructs the JNI
|
||||
* environment to throw an exception.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param clazz the class to lookup the field in
|
||||
* @param szFieldName the name of the field to find
|
||||
* @param szSig the signature of the field
|
||||
* @return the field or NULL if not found (in which case a pending exception will be queued)
|
||||
*/
|
||||
jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig) {
|
||||
return pEnv->GetFieldID(clazz, szFieldName, szSig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the specified method of the specified class. If it's not found, instructs the JNI
|
||||
* environment to throw an exception.
|
||||
*
|
||||
* @param pEnv JNI environment
|
||||
* @param clazz the class to lookup the method in
|
||||
* @param szMethodName the name of the method to find
|
||||
* @param szSig the signature of the method
|
||||
* @return the method or NULL if not found (in which case a pending exception will be queued)
|
||||
*/
|
||||
jmethodID getMethodIdOrThrow(
|
||||
JNIEnv* pEnv,
|
||||
jclass clazz,
|
||||
const char* szMethodName,
|
||||
const char* szSig) {
|
||||
return pEnv->GetMethodID(clazz, szMethodName, szSig);
|
||||
}
|
||||
|
||||
} // namespace facebook
|
Reference in New Issue
Block a user