Copy fbjni library from react-native

This commit is contained in:
Emil Sjolander
2016-08-31 16:15:15 +01:00
parent b51e832e4e
commit 57725e849a
66 changed files with 8653 additions and 0 deletions

77
lib/fb/jni/ByteBuffer.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}
}
}

View 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
View 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;
}
}
}}

View 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);
}
}

View 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;
}
}

View 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
View 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