Pull an updated version of fbjni into RN OSS
Differential Revision: D3521227 fbshipit-source-id: 57db97ea2af2b2c9e55f380ce05d9e78a5f9d48c
This commit is contained in:
parent
7795918eb4
commit
7f790dc0de
|
@ -240,7 +240,7 @@ android {
|
|||
jniLibs.srcDir "$buildDir/react-ndk/exported"
|
||||
res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell', 'src/main/res/views/modal']
|
||||
java {
|
||||
srcDirs = ['src/main/java', 'src/main/libraries/soloader/java']
|
||||
srcDirs = ['src/main/java', 'src/main/libraries/soloader/java', 'src/main/jni/first-party/fb/jni/java']
|
||||
exclude 'com/facebook/react/processing'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,15 @@ include_defs('//ReactAndroid/DEFS')
|
|||
android_library(
|
||||
name = 'jni',
|
||||
srcs = glob(['**/*.java']),
|
||||
proguard_config = 'fbjni.pro',
|
||||
deps = [
|
||||
react_native_dep('java/com/facebook/proguard/annotations:annotations'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
project_config(
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* A Java Object that has native memory allocated corresponding to this instance.
|
||||
|
@ -23,14 +17,15 @@ import com.facebook.proguard.annotations.DoNotStrip;
|
|||
*/
|
||||
@DoNotStrip
|
||||
public class Countable {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary("fb");
|
||||
}
|
||||
|
||||
// Private C++ instance
|
||||
@DoNotStrip
|
||||
private long mInstance = 0;
|
||||
|
||||
public Countable() {
|
||||
Prerequisites.ensure();
|
||||
}
|
||||
|
||||
public native void dispose();
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* This object holds a native C++ member for hybrid Java/C++ objects.
|
||||
|
@ -24,14 +18,15 @@ import com.facebook.proguard.annotations.DoNotStrip;
|
|||
*/
|
||||
@DoNotStrip
|
||||
public class HybridData {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary("fb");
|
||||
}
|
||||
|
||||
// Private C++ instance
|
||||
@DoNotStrip
|
||||
private long mNativePointer = 0;
|
||||
|
||||
public HybridData() {
|
||||
Prerequisites.ensure();
|
||||
}
|
||||
|
||||
/**
|
||||
* To explicitly delete the instance, call resetNative(). If the C++
|
||||
* instance is referenced after this is called, a NullPointerException will
|
||||
|
@ -47,4 +42,8 @@ public class HybridData {
|
|||
resetNative();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return mNativePointer != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* To iterate over an Iterator from C++ requires two calls per entry: hasNext()
|
||||
* and next(). This helper reduces it to one call and one field get per entry.
|
||||
* It does not use a generic argument, since in C++, the types will be erased,
|
||||
* anyway. This is *not* a {@link java.util.Iterator}.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class IteratorHelper {
|
||||
private final Iterator mIterator;
|
||||
|
||||
// This is private, but accessed via JNI.
|
||||
@DoNotStrip
|
||||
private @Nullable Object mElement;
|
||||
|
||||
@DoNotStrip
|
||||
public IteratorHelper(Iterator iterator) {
|
||||
mIterator = iterator;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public IteratorHelper(Iterable iterable) {
|
||||
mIterator = iterable.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the helper to the next entry in the map, if any. Returns true iff
|
||||
* there is an entry to read.
|
||||
*/
|
||||
@DoNotStrip
|
||||
boolean hasNext() {
|
||||
if (mIterator.hasNext()) {
|
||||
mElement = mIterator.next();
|
||||
return true;
|
||||
} else {
|
||||
mElement = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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 javax.annotation.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* To iterate over a Map from C++ requires four calls per entry: hasNext(),
|
||||
* next(), getKey(), getValue(). This helper reduces it to one call and two
|
||||
* field gets per entry. It does not use a generic argument, since in C++, the
|
||||
* types will be erased, anyway. This is *not* a {@link java.util.Iterator}.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class MapIteratorHelper {
|
||||
@DoNotStrip private final Iterator<Map.Entry> mIterator;
|
||||
@DoNotStrip private @Nullable Object mKey;
|
||||
@DoNotStrip private @Nullable Object mValue;
|
||||
|
||||
@DoNotStrip
|
||||
public MapIteratorHelper(Map map) {
|
||||
mIterator = map.entrySet().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the helper to the next entry in the map, if any. Returns true iff
|
||||
* there is an entry to read.
|
||||
*/
|
||||
@DoNotStrip
|
||||
boolean hasNext() {
|
||||
if (mIterator.hasNext()) {
|
||||
Map.Entry entry = mIterator.next();
|
||||
mKey = entry.getKey();
|
||||
mValue = entry.getValue();
|
||||
return true;
|
||||
} else {
|
||||
mKey = null;
|
||||
mValue = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
|
@ -9,19 +9,20 @@
|
|||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* A Runnable that has a native run implementation.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CppSystemErrorException extends CppException {
|
||||
int errorCode;
|
||||
public class NativeRunnable implements Runnable {
|
||||
|
||||
@DoNotStrip
|
||||
public CppSystemErrorException(String message, int errorCode) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
private final HybridData mHybridData;
|
||||
|
||||
private NativeRunnable(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
public native void run();
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
|
||||
public class Prerequisites {
|
||||
private static final int EGL_OPENGL_ES2_BIT = 0x0004;
|
||||
|
||||
public static void ensure() {
|
||||
SoLoader.loadLibrary("fb");
|
||||
}
|
||||
|
||||
// Code is simplified version of getDetectedVersion()
|
||||
// from cts/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java
|
||||
static public boolean supportsOpenGL20() {
|
||||
EGL10 egl = (EGL10) EGLContext.getEGL();
|
||||
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
||||
int[] numConfigs = new int[1];
|
||||
|
||||
if (egl.eglInitialize(display, null)) {
|
||||
try {
|
||||
if (egl.eglGetConfigs(display, null, 0, numConfigs)) {
|
||||
EGLConfig[] configs = new EGLConfig[numConfigs[0]];
|
||||
if (egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
|
||||
int[] value = new int[1];
|
||||
for (int i = 0; i < numConfigs[0]; i++) {
|
||||
if (egl.eglGetConfigAttrib(display, configs[i],
|
||||
EGL10.EGL_RENDERABLE_TYPE, value)) {
|
||||
if ((value[0] & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
egl.eglTerminate(display);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.jni;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
public class ThreadScopeSupport {
|
||||
static {
|
||||
SoLoader.loadLibrary("fb");
|
||||
}
|
||||
|
||||
// This is just used for ThreadScope::withClassLoader to have a java function
|
||||
// in the stack so that jni has access to the correct classloader.
|
||||
@DoNotStrip
|
||||
private static void runStdFunction(long ptr) {
|
||||
runStdFunctionImpl(ptr);
|
||||
}
|
||||
|
||||
private static native void runStdFunctionImpl(long ptr);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# For common use cases for the hybrid pattern, keep symbols which may
|
||||
# be referenced only from C++.
|
||||
|
||||
-keepclassmembers class * {
|
||||
com.facebook.jni.HybridData *;
|
||||
<init>(com.facebook.jni.HybridData);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
com.facebook.jni.HybridData *;
|
||||
}
|
|
@ -22,7 +22,7 @@ LOCAL_SRC_FILES:= \
|
|||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
|
||||
|
||||
LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" -DDISABLE_XPLAT -fexceptions -frtti
|
||||
LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" -DDISABLE_CPUCAP -DDISABLE_XPLAT -fexceptions -frtti
|
||||
LOCAL_CFLAGS += -Wall -Werror
|
||||
# include/utils/threads.h has unused parameters
|
||||
LOCAL_CFLAGS += -Wno-unused-parameter
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <jni.h>
|
||||
|
||||
|
@ -21,44 +22,35 @@ struct Environment {
|
|||
// May be null if this thread isn't attached to the JVM
|
||||
FBEXPORT static JNIEnv* current();
|
||||
static void initialize(JavaVM* vm);
|
||||
|
||||
// There are subtle issues with calling the next functions directly. It is
|
||||
// much better to always use a ThreadScope to manage attaching/detaching for
|
||||
// you.
|
||||
FBEXPORT static JNIEnv* ensureCurrentThreadIsAttached();
|
||||
FBEXPORT static void detachCurrentThread();
|
||||
};
|
||||
|
||||
/**
|
||||
* RAII Object that attaches a thread to the JVM. Failing to detach from a
|
||||
* thread before it
|
||||
* exits will cause a crash, as will calling Detach an extra time, and this
|
||||
* guard class helps
|
||||
* keep that straight. In addition, it remembers whether it performed the attach
|
||||
* or not, so it
|
||||
* is safe to nest it with itself or with non-fbjni code that manages the
|
||||
* attachment correctly.
|
||||
* RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it
|
||||
* exits will cause a crash, as will calling Detach an extra time, and this guard class helps
|
||||
* keep that straight. In addition, it remembers whether it performed the attach or not, so it
|
||||
* is safe to nest it with itself or with non-fbjni code that manages the attachment correctly.
|
||||
*
|
||||
* Potential concerns:
|
||||
* - Attaching to the JVM is fast (~100us on MotoG), but ideally you would
|
||||
* attach while the
|
||||
* - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the
|
||||
* app is not busy.
|
||||
* - Having a thread detach at arbitrary points is not safe in Dalvik; you need
|
||||
* to be sure that
|
||||
* there is no Java code on the current stack or you run the risk of a crash
|
||||
* like:
|
||||
* - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that
|
||||
* there is no Java code on the current stack or you run the risk of a crash like:
|
||||
* ERROR: detaching thread with interp frames (count=18)
|
||||
* (More detail at
|
||||
* https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo)
|
||||
* ThreadScope won't do a detach if the thread was already attached before
|
||||
* the guard is
|
||||
* (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo)
|
||||
* ThreadScope won't do a detach if the thread was already attached before the guard is
|
||||
* instantiated, but there's probably some usage that could trip this up.
|
||||
* - Newly attached C++ threads only get the bootstrap class loader -- i.e.
|
||||
* java language
|
||||
* classes, not any of our application's classes. This will be different
|
||||
* behavior than threads
|
||||
* that were initiated on the Java side. A workaround is to pass a global
|
||||
* reference for a
|
||||
* class or instance to the new thread; this bypasses the need for the class
|
||||
* loader.
|
||||
* (See
|
||||
* http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread)
|
||||
* - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language
|
||||
* classes, not any of our application's classes. This will be different behavior than threads
|
||||
* that were initiated on the Java side. A workaround is to pass a global reference for a
|
||||
* class or instance to the new thread; this bypasses the need for the class loader.
|
||||
* (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread)
|
||||
* If you need access to the application's classes, you can use ThreadScope::WithClassLoader.
|
||||
*/
|
||||
class FBEXPORT ThreadScope {
|
||||
public:
|
||||
|
@ -69,6 +61,15 @@ class FBEXPORT ThreadScope {
|
|||
ThreadScope& operator=(ThreadScope&&) = delete;
|
||||
~ThreadScope();
|
||||
|
||||
/**
|
||||
* This runs the closure in a scope with fbjni's classloader. This should be
|
||||
* the same classloader as the rest of the application and thus anything
|
||||
* running in the closure will have access to the same classes as in a normal
|
||||
* java-create thread.
|
||||
*/
|
||||
static void WithClassLoader(std::function<void()>&& runnable);
|
||||
|
||||
static void OnLoad();
|
||||
private:
|
||||
bool attachedWithThisScope_;
|
||||
};
|
||||
|
|
|
@ -29,11 +29,31 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as
|
||||
// a C++ exception.
|
||||
#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \
|
||||
::facebook::jni::throwPendingJniExceptionAsCppException()
|
||||
|
||||
// If the condition is true, throws a JniException object, which wraps the pending JNI Java
|
||||
// exception if any. If no pending exception is found, throws a JniException object that wraps a
|
||||
// RuntimeException throwable.
|
||||
#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \
|
||||
::facebook::jni::throwCppExceptionIf(CONDITION)
|
||||
|
||||
/// @cond INTERNAL
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
FBEXPORT void throwPendingJniExceptionAsCppException();
|
||||
FBEXPORT void throwCppExceptionIf(bool condition);
|
||||
|
||||
[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable);
|
||||
[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg);
|
||||
template<typename... Args>
|
||||
[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args);
|
||||
|
||||
|
||||
/**
|
||||
* This needs to be called at library load time, typically in your JNI_OnLoad method.
|
||||
*
|
||||
|
|
|
@ -25,6 +25,10 @@ class AContext : public JavaClass<AContext> {
|
|||
return method(self());
|
||||
}
|
||||
|
||||
local_ref<JFile::javaobject> getFilesDir() {
|
||||
static auto method = getClass()->getMethod<JFile::javaobject()>("getFilesDir");
|
||||
return method(self());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -341,6 +341,13 @@ struct Convert<const char*> {
|
|||
};
|
||||
}
|
||||
|
||||
// jthrowable //////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
|
||||
static auto meth = javaClassStatic()->getMethod<javaobject(javaobject)>("initCause");
|
||||
return meth(self(), cause.get());
|
||||
}
|
||||
|
||||
// jtypeArray //////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
|
|
@ -212,7 +212,7 @@ protected:
|
|||
/// the Java class actually has (i.e. with static create() functions).
|
||||
template<typename... Args>
|
||||
static local_ref<T> newInstance(Args... args) {
|
||||
return detail::newInstance<JavaClass>(args...);
|
||||
return detail::newInstance<T>(args...);
|
||||
}
|
||||
|
||||
javaobject self() const noexcept;
|
||||
|
@ -344,6 +344,8 @@ FBEXPORT local_ref<JString> make_jstring(const std::string& modifiedUtf8);
|
|||
class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
|
||||
public:
|
||||
static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
|
||||
|
||||
local_ref<JThrowable> initCause(alias_ref<JThrowable> cause);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
|
|
@ -29,30 +29,26 @@
|
|||
#include <fb/visibility.h>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as
|
||||
// a C++ exception.
|
||||
#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \
|
||||
::facebook::jni::throwPendingJniExceptionAsCppException()
|
||||
|
||||
// If the condition is true, throws a JniException object, which wraps the pending JNI Java
|
||||
// exception if any. If no pending exception is found, throws a JniException object that wraps a
|
||||
// RuntimeException throwable.
|
||||
#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \
|
||||
::facebook::jni::throwCppExceptionIf(CONDITION)
|
||||
#include "References.h"
|
||||
#include "CoreClasses.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
namespace internal {
|
||||
void initExceptionHelpers();
|
||||
}
|
||||
class JThrowable;
|
||||
|
||||
/**
|
||||
* Before using any of the state initialized above, call this. It
|
||||
* will assert if initialization has not yet occurred.
|
||||
*/
|
||||
FBEXPORT void assertIfExceptionsNotInitialized();
|
||||
class JCppException : public JavaClass<JCppException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;";
|
||||
|
||||
static local_ref<JCppException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
|
||||
static local_ref<JCppException> create(const std::exception& ex) {
|
||||
return newInstance(make_jstring(ex.what()));
|
||||
}
|
||||
};
|
||||
|
||||
// JniException ////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -67,23 +63,22 @@ FBEXPORT void assertIfExceptionsNotInitialized();
|
|||
class FBEXPORT JniException : public std::exception {
|
||||
public:
|
||||
JniException();
|
||||
~JniException();
|
||||
|
||||
explicit JniException(jthrowable throwable);
|
||||
explicit JniException(alias_ref<jthrowable> throwable);
|
||||
|
||||
JniException(JniException &&rhs);
|
||||
|
||||
JniException(const JniException &other);
|
||||
|
||||
~JniException() noexcept;
|
||||
|
||||
jthrowable getThrowable() const noexcept;
|
||||
local_ref<JThrowable> getThrowable() const noexcept;
|
||||
|
||||
virtual const char* what() const noexcept;
|
||||
|
||||
void setJavaException() const noexcept;
|
||||
|
||||
private:
|
||||
jthrowable throwableGlobalRef_;
|
||||
global_ref<JThrowable> throwable_;
|
||||
mutable std::string what_;
|
||||
mutable bool isMessageExtracted_;
|
||||
const static std::string kExceptionMessageFailure_;
|
||||
|
@ -95,16 +90,8 @@ class FBEXPORT JniException : public std::exception {
|
|||
|
||||
// Functions that throw C++ exceptions
|
||||
|
||||
FBEXPORT void throwPendingJniExceptionAsCppException();
|
||||
|
||||
FBEXPORT void throwCppExceptionIf(bool condition);
|
||||
|
||||
static const int kMaxExceptionMessageBufferSize = 512;
|
||||
|
||||
[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable);
|
||||
|
||||
[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg);
|
||||
|
||||
// These methods are the preferred way to throw a Java exception from
|
||||
// a C++ function. They create and throw a C++ exception which wraps
|
||||
// a Java exception, so the C++ flow is interrupted. Then, when
|
||||
|
@ -113,7 +100,6 @@ static const int kMaxExceptionMessageBufferSize = 512;
|
|||
// thrown to the java caller.
|
||||
template<typename... Args>
|
||||
[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
int msgSize = snprintf(nullptr, 0, fmt, args...);
|
||||
|
||||
char *msg = (char*) alloca(msgSize + 1);
|
||||
|
@ -123,7 +109,7 @@ template<typename... Args>
|
|||
|
||||
// Identifies any pending C++ exception and throws it as a Java exception. If the exception can't
|
||||
// be thrown, it aborts the program. This is a noexcept function at C++ level.
|
||||
void translatePendingCppExceptionToJavaException() noexcept;
|
||||
FBEXPORT void translatePendingCppExceptionToJavaException() noexcept;
|
||||
|
||||
// For convenience, some exception names in java.lang are available here.
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreClasses.h"
|
||||
#include "NativeRunnable.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
class JThread : public JavaClass<JThread> {
|
||||
public:
|
||||
static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;";
|
||||
|
||||
void start() {
|
||||
static auto method = javaClassStatic()->getMethod<void()>("start");
|
||||
method(self());
|
||||
}
|
||||
|
||||
void join() {
|
||||
static auto method = javaClassStatic()->getMethod<void()>("join");
|
||||
method(self());
|
||||
}
|
||||
|
||||
static local_ref<JThread> create(std::function<void()>&& runnable) {
|
||||
auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable));
|
||||
return newInstance(static_ref_cast<JRunnable::javaobject>(jrunnable));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ local_ref<JArrayClass<jobject>::javaobject> makeArgsArray(Args... args) {
|
|||
}
|
||||
|
||||
|
||||
bool needsSlowPath(alias_ref<jobject> obj) {
|
||||
inline bool needsSlowPath(alias_ref<jobject> obj) {
|
||||
#if defined(__ANDROID__)
|
||||
// On Android 6.0, art crashes when attempting to call a function on a Proxy.
|
||||
// So, when we detect that case we must use the safe, slow workaround. That is,
|
||||
|
@ -88,19 +88,6 @@ bool needsSlowPath(alias_ref<jobject> obj) {
|
|||
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) {
|
||||
static auto invoke = findClassStatic("java/lang/reflect/Method")
|
||||
->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke");
|
||||
// TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod.
|
||||
auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE));
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
if (!reflected) throw JniException();
|
||||
auto argsArray = makeArgsArray(args...);
|
||||
// No need to check for exceptions since invoke is itself a JMethod that will do that for us.
|
||||
return invoke(reflected, self.get(), argsArray.get());
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) {
|
||||
const auto env = Environment::current();
|
||||
|
@ -285,6 +272,19 @@ class JNonvirtualMethod<R(Args...)> : public JMethodBase {
|
|||
friend class JClass;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) {
|
||||
static auto invoke = findClassStatic("java/lang/reflect/Method")
|
||||
->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke");
|
||||
// TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod.
|
||||
auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE));
|
||||
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
|
||||
if (!reflected) throw std::runtime_error("Unable to get reflected java.lang.reflect.Method");
|
||||
auto argsArray = makeArgsArray(args...);
|
||||
// No need to check for exceptions since invoke is itself a JMethod that will do that for us.
|
||||
return invoke(reflected, self.get(), argsArray.get());
|
||||
}
|
||||
|
||||
|
||||
// JField<T> ///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreClasses.h"
|
||||
#include "Hybrid.h"
|
||||
#include "Registration.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
struct JRunnable : public JavaClass<JRunnable> {
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/Runnable;";
|
||||
};
|
||||
|
||||
struct JNativeRunnable : public HybridClass<JNativeRunnable, JRunnable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;";
|
||||
|
||||
JNativeRunnable(std::function<void()>&& runnable) : runnable_(std::move(runnable)) {}
|
||||
|
||||
static void OnLoad() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("run", JNativeRunnable::run),
|
||||
});
|
||||
}
|
||||
|
||||
void run() {
|
||||
runnable_();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> runnable_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace jni
|
||||
} // namespace facebook
|
|
@ -13,8 +13,6 @@
|
|||
#include <new>
|
||||
#include <atomic>
|
||||
|
||||
#include "Exceptions.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ namespace detail {
|
|||
void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength);
|
||||
size_t modifiedLength(const std::string& str);
|
||||
size_t modifiedLength(const uint8_t* str, size_t* length);
|
||||
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len);
|
||||
std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len);
|
||||
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept;
|
||||
std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,40 @@
|
|||
#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 {
|
||||
|
||||
static StaticInitialized<ThreadLocal<JNIEnv>> g_env;
|
||||
static JavaVM* g_vm = nullptr;
|
||||
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() {
|
||||
|
@ -25,6 +53,7 @@ JNIEnv* Environment::current() {
|
|||
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);
|
||||
|
@ -85,5 +114,19 @@ ThreadScope::~ThreadScope() {
|
|||
}
|
||||
}
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "fb/fbjni.h"
|
||||
#include <fb/fbjni/CoreClasses.h>
|
||||
|
||||
#include <fb/assert.h>
|
||||
#include <fb/log.h>
|
||||
|
||||
#include <alloca.h>
|
||||
#include <cstdlib>
|
||||
|
@ -21,234 +22,113 @@
|
|||
|
||||
#include <jni.h>
|
||||
|
||||
|
||||
namespace facebook {
|
||||
namespace jni {
|
||||
|
||||
// CommonJniExceptions /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class FBEXPORT CommonJniExceptions {
|
||||
namespace {
|
||||
class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
|
||||
public:
|
||||
static void init();
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
|
||||
|
||||
static jclass getThrowableClass() {
|
||||
return throwableClass_;
|
||||
static local_ref<JRuntimeException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
|
||||
static jclass getUnknownCppExceptionClass() {
|
||||
return unknownCppExceptionClass_;
|
||||
static local_ref<JRuntimeException> create() {
|
||||
return newInstance();
|
||||
}
|
||||
|
||||
static jthrowable getUnknownCppExceptionObject() {
|
||||
return unknownCppExceptionObject_;
|
||||
}
|
||||
|
||||
static jthrowable getRuntimeExceptionObject() {
|
||||
return runtimeExceptionObject_;
|
||||
}
|
||||
|
||||
private:
|
||||
static jclass throwableClass_;
|
||||
static jclass unknownCppExceptionClass_;
|
||||
static jthrowable unknownCppExceptionObject_;
|
||||
static jthrowable runtimeExceptionObject_;
|
||||
};
|
||||
|
||||
// The variables in this class are all JNI global references and are intentionally leaked because
|
||||
// we assume this library cannot be unloaded. These global references are created manually instead
|
||||
// of using global_ref from References.h to avoid circular dependency.
|
||||
jclass CommonJniExceptions::throwableClass_ = nullptr;
|
||||
jclass CommonJniExceptions::unknownCppExceptionClass_ = nullptr;
|
||||
jthrowable CommonJniExceptions::unknownCppExceptionObject_ = nullptr;
|
||||
jthrowable CommonJniExceptions::runtimeExceptionObject_ = nullptr;
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
// Variable to guarantee that fallback exceptions have been initialized early. We don't want to
|
||||
// do pure dynamic initialization -- we want to warn programmers early that they need to run the
|
||||
// helpers at library load time instead of lazily getting them when the exception helpers are
|
||||
// first used.
|
||||
static std::atomic<bool> gIsInitialized(false);
|
||||
class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
|
||||
|
||||
void CommonJniExceptions::init() {
|
||||
JNIEnv* env = internal::getEnv();
|
||||
FBASSERTMSGF(env, "Could not get JNI Environment");
|
||||
static local_ref<JOutOfMemoryError> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
// Throwable class
|
||||
jclass localThrowableClass = env->FindClass("java/lang/Throwable");
|
||||
FBASSERT(localThrowableClass);
|
||||
throwableClass_ = static_cast<jclass>(env->NewGlobalRef(localThrowableClass));
|
||||
FBASSERT(throwableClass_);
|
||||
env->DeleteLocalRef(localThrowableClass);
|
||||
class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;";
|
||||
|
||||
// UnknownCppException class
|
||||
jclass localUnknownCppExceptionClass = env->FindClass("com/facebook/jni/UnknownCppException");
|
||||
FBASSERT(localUnknownCppExceptionClass);
|
||||
jmethodID unknownCppExceptionConstructorMID = env->GetMethodID(
|
||||
localUnknownCppExceptionClass,
|
||||
"<init>",
|
||||
"()V");
|
||||
FBASSERT(unknownCppExceptionConstructorMID);
|
||||
unknownCppExceptionClass_ = static_cast<jclass>(env->NewGlobalRef(localUnknownCppExceptionClass));
|
||||
FBASSERT(unknownCppExceptionClass_);
|
||||
env->DeleteLocalRef(localUnknownCppExceptionClass);
|
||||
static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
// UnknownCppException object
|
||||
jthrowable localUnknownCppExceptionObject = static_cast<jthrowable>(env->NewObject(
|
||||
unknownCppExceptionClass_,
|
||||
unknownCppExceptionConstructorMID));
|
||||
FBASSERT(localUnknownCppExceptionObject);
|
||||
unknownCppExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef(
|
||||
localUnknownCppExceptionObject));
|
||||
FBASSERT(unknownCppExceptionObject_);
|
||||
env->DeleteLocalRef(localUnknownCppExceptionObject);
|
||||
class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;";
|
||||
|
||||
// RuntimeException object
|
||||
jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException");
|
||||
FBASSERT(localRuntimeExceptionClass);
|
||||
static local_ref<JUnknownCppException> create() {
|
||||
return newInstance();
|
||||
}
|
||||
|
||||
jmethodID runtimeExceptionConstructorMID = env->GetMethodID(
|
||||
localRuntimeExceptionClass,
|
||||
"<init>",
|
||||
"()V");
|
||||
FBASSERT(runtimeExceptionConstructorMID);
|
||||
jthrowable localRuntimeExceptionObject = static_cast<jthrowable>(env->NewObject(
|
||||
localRuntimeExceptionClass,
|
||||
runtimeExceptionConstructorMID));
|
||||
FBASSERT(localRuntimeExceptionObject);
|
||||
runtimeExceptionObject_ = static_cast<jthrowable>(env->NewGlobalRef(localRuntimeExceptionObject));
|
||||
FBASSERT(runtimeExceptionObject_);
|
||||
static local_ref<JUnknownCppException> create(const char* str) {
|
||||
return newInstance(make_jstring(str));
|
||||
}
|
||||
};
|
||||
|
||||
env->DeleteLocalRef(localRuntimeExceptionClass);
|
||||
env->DeleteLocalRef(localRuntimeExceptionObject);
|
||||
}
|
||||
class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> {
|
||||
public:
|
||||
static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;";
|
||||
|
||||
|
||||
// initExceptionHelpers() //////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void internal::initExceptionHelpers() {
|
||||
CommonJniExceptions::init();
|
||||
gIsInitialized.store(true, std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
void assertIfExceptionsNotInitialized() {
|
||||
// Use relaxed memory order because we don't need memory barriers.
|
||||
// The real init-once enforcement is done by the compiler for the
|
||||
// "static" in initExceptionHelpers.
|
||||
FBASSERTMSGF(gIsInitialized.load(std::memory_order_relaxed),
|
||||
"initExceptionHelpers was never called!");
|
||||
}
|
||||
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
|
||||
|
||||
namespace {
|
||||
|
||||
void setJavaExceptionAndAbortOnFailure(jthrowable throwable) noexcept {
|
||||
assertIfExceptionsNotInitialized();
|
||||
JNIEnv* env = internal::getEnv();
|
||||
void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
|
||||
auto env = Environment::current();
|
||||
if (throwable) {
|
||||
env->Throw(throwable);
|
||||
env->Throw(throwable.get());
|
||||
}
|
||||
if (env->ExceptionCheck() != JNI_TRUE) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void setDefaultException() noexcept {
|
||||
assertIfExceptionsNotInitialized();
|
||||
setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getRuntimeExceptionObject());
|
||||
}
|
||||
|
||||
void setCppSystemErrorExceptionInJava(const std::system_error& ex) noexcept {
|
||||
assertIfExceptionsNotInitialized();
|
||||
JNIEnv* env = internal::getEnv();
|
||||
jclass cppSystemErrorExceptionClass = env->FindClass(
|
||||
"com/facebook/jni/CppSystemErrorException");
|
||||
if (!cppSystemErrorExceptionClass) {
|
||||
setDefaultException();
|
||||
return;
|
||||
}
|
||||
jmethodID constructorMID = env->GetMethodID(
|
||||
cppSystemErrorExceptionClass,
|
||||
"<init>",
|
||||
"(Ljava/lang/String;I)V");
|
||||
if (!constructorMID) {
|
||||
setDefaultException();
|
||||
return;
|
||||
}
|
||||
jthrowable cppSystemErrorExceptionObject = static_cast<jthrowable>(env->NewObject(
|
||||
cppSystemErrorExceptionClass,
|
||||
constructorMID,
|
||||
env->NewStringUTF(ex.what()),
|
||||
ex.code().value()));
|
||||
setJavaExceptionAndAbortOnFailure(cppSystemErrorExceptionObject);
|
||||
}
|
||||
|
||||
template<typename... ARGS>
|
||||
void setNewJavaException(jclass exceptionClass, const char* fmt, ARGS... args) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
int msgSize = snprintf(nullptr, 0, fmt, args...);
|
||||
JNIEnv* env = internal::getEnv();
|
||||
|
||||
try {
|
||||
char *msg = (char*) alloca(msgSize + 1);
|
||||
snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...);
|
||||
env->ThrowNew(exceptionClass, msg);
|
||||
} catch (...) {
|
||||
env->ThrowNew(exceptionClass, "");
|
||||
}
|
||||
|
||||
if (env->ExceptionCheck() != JNI_TRUE) {
|
||||
setDefaultException();
|
||||
}
|
||||
}
|
||||
|
||||
void setNewJavaException(jclass exceptionClass, const char* msg) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
setNewJavaException(exceptionClass, "%s", msg);
|
||||
}
|
||||
|
||||
template<typename... ARGS>
|
||||
void setNewJavaException(const char* className, const char* fmt, ARGS... args) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
JNIEnv* env = internal::getEnv();
|
||||
jclass exceptionClass = env->FindClass(className);
|
||||
if (env->ExceptionCheck() != JNI_TRUE && !exceptionClass) {
|
||||
// If FindClass() has failed but no exception has been thrown, throw a default exception.
|
||||
setDefaultException();
|
||||
return;
|
||||
}
|
||||
setNewJavaException(exceptionClass, fmt, args...);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Functions that throw C++ exceptions
|
||||
|
||||
// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated
|
||||
FBEXPORT void throwPendingJniExceptionAsCppException() {
|
||||
assertIfExceptionsNotInitialized();
|
||||
JNIEnv* env = internal::getEnv();
|
||||
void throwPendingJniExceptionAsCppException() {
|
||||
JNIEnv* env = Environment::current();
|
||||
if (env->ExceptionCheck() == JNI_FALSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
jthrowable throwable = env->ExceptionOccurred();
|
||||
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) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv* env = internal::getEnv();
|
||||
auto env = Environment::current();
|
||||
if (env->ExceptionCheck() == JNI_TRUE) {
|
||||
throwPendingJniExceptionAsCppException();
|
||||
return;
|
||||
|
@ -257,13 +137,11 @@ void throwCppExceptionIf(bool condition) {
|
|||
throw JniException();
|
||||
}
|
||||
|
||||
FBEXPORT void throwNewJavaException(jthrowable throwable) {
|
||||
throw JniException(throwable);
|
||||
void throwNewJavaException(jthrowable throwable) {
|
||||
throw JniException(wrap_alias(throwable));
|
||||
}
|
||||
|
||||
FBEXPORT void throwNewJavaException(
|
||||
const char* throwableName,
|
||||
const char* msg) {
|
||||
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);
|
||||
|
@ -275,34 +153,80 @@ FBEXPORT void throwNewJavaException(
|
|||
|
||||
// Translate C++ to Java Exception
|
||||
|
||||
FBEXPORT void translatePendingCppExceptionToJavaException() noexcept {
|
||||
assertIfExceptionsNotInitialized();
|
||||
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) {
|
||||
ex.setJavaException();
|
||||
current = ex.getThrowable();
|
||||
} catch(const std::ios_base::failure& ex) {
|
||||
setNewJavaException("java/io/IOException", ex.what());
|
||||
current = JIOException::create(ex.what());
|
||||
} catch(const std::bad_alloc& ex) {
|
||||
setNewJavaException("java/lang/OutOfMemoryError", ex.what());
|
||||
current = JOutOfMemoryError::create(ex.what());
|
||||
} catch(const std::out_of_range& ex) {
|
||||
setNewJavaException("java/lang/ArrayIndexOutOfBoundsException", ex.what());
|
||||
current = JArrayIndexOutOfBoundsException::create(ex.what());
|
||||
} catch(const std::system_error& ex) {
|
||||
setCppSystemErrorExceptionInJava(ex);
|
||||
current = JCppSystemErrorException::create(ex);
|
||||
} catch(const std::runtime_error& ex) {
|
||||
setNewJavaException("java/lang/RuntimeException", ex.what());
|
||||
current = JRuntimeException::create(ex.what());
|
||||
} catch(const std::exception& ex) {
|
||||
setNewJavaException("com/facebook/jni/CppException", ex.what());
|
||||
current = JCppException::create(ex.what());
|
||||
} catch(const char* msg) {
|
||||
setNewJavaException(CommonJniExceptions::getUnknownCppExceptionClass(), msg);
|
||||
current = JUnknownCppException::create(msg);
|
||||
} catch(...) {
|
||||
setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getUnknownCppExceptionObject());
|
||||
current = JUnknownCppException::create();
|
||||
}
|
||||
} catch(...) {
|
||||
// This block aborts the program, if something bad happens when handling exceptions, thus
|
||||
// keeping this function noexcept.
|
||||
std::abort();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,79 +234,41 @@ FBEXPORT void translatePendingCppExceptionToJavaException() noexcept {
|
|||
|
||||
const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
|
||||
|
||||
JniException::JniException() : JniException(CommonJniExceptions::getRuntimeExceptionObject()) { }
|
||||
JniException::JniException() : JniException(JRuntimeException::create()) { }
|
||||
|
||||
JniException::JniException(jthrowable throwable) : isMessageExtracted_(false) {
|
||||
assertIfExceptionsNotInitialized();
|
||||
throwableGlobalRef_ = static_cast<jthrowable>(internal::getEnv()->NewGlobalRef(throwable));
|
||||
if (!throwableGlobalRef_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) {
|
||||
throwable_ = make_global(throwable);
|
||||
}
|
||||
|
||||
JniException::JniException(JniException &&rhs)
|
||||
: throwableGlobalRef_(std::move(rhs.throwableGlobalRef_)),
|
||||
: throwable_(std::move(rhs.throwable_)),
|
||||
what_(std::move(rhs.what_)),
|
||||
isMessageExtracted_(rhs.isMessageExtracted_) {
|
||||
rhs.throwableGlobalRef_ = nullptr;
|
||||
}
|
||||
|
||||
JniException::JniException(const JniException &rhs)
|
||||
: what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
|
||||
JNIEnv* env = internal::getEnv();
|
||||
if (rhs.getThrowable()) {
|
||||
throwableGlobalRef_ = static_cast<jthrowable>(env->NewGlobalRef(rhs.getThrowable()));
|
||||
if (!throwableGlobalRef_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
} else {
|
||||
throwableGlobalRef_ = nullptr;
|
||||
}
|
||||
throwable_ = make_global(rhs.throwable_);
|
||||
}
|
||||
|
||||
JniException::~JniException() noexcept {
|
||||
if (throwableGlobalRef_) {
|
||||
internal::getEnv()->DeleteGlobalRef(throwableGlobalRef_);
|
||||
}
|
||||
JniException::~JniException() {
|
||||
ThreadScope ts;
|
||||
throwable_.reset();
|
||||
}
|
||||
|
||||
jthrowable JniException::getThrowable() const noexcept {
|
||||
return throwableGlobalRef_;
|
||||
local_ref<JThrowable> JniException::getThrowable() const noexcept {
|
||||
return make_local(throwable_);
|
||||
}
|
||||
|
||||
// TODO 6900503: consider making this thread-safe.
|
||||
void JniException::populateWhat() const noexcept {
|
||||
JNIEnv* env = internal::getEnv();
|
||||
|
||||
jmethodID toStringMID = env->GetMethodID(
|
||||
CommonJniExceptions::getThrowableClass(),
|
||||
"toString",
|
||||
"()Ljava/lang/String;");
|
||||
jstring messageJString = (jstring) env->CallObjectMethod(
|
||||
throwableGlobalRef_,
|
||||
toStringMID);
|
||||
|
||||
isMessageExtracted_ = true;
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
what_ = kExceptionMessageFailure_;
|
||||
return;
|
||||
}
|
||||
|
||||
const char* chars = env->GetStringUTFChars(messageJString, nullptr);
|
||||
if (!chars) {
|
||||
what_ = kExceptionMessageFailure_;
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadScope ts;
|
||||
try {
|
||||
what_ = std::string(chars);
|
||||
what_ = throwable_->toString();
|
||||
isMessageExtracted_ = true;
|
||||
} catch(...) {
|
||||
what_ = kExceptionMessageFailure_;
|
||||
}
|
||||
|
||||
env->ReleaseStringUTFChars(messageJString, chars);
|
||||
}
|
||||
|
||||
const char* JniException::what() const noexcept {
|
||||
|
@ -393,7 +279,7 @@ const char* JniException::what() const noexcept {
|
|||
}
|
||||
|
||||
void JniException::setJavaException() const noexcept {
|
||||
setJavaExceptionAndAbortOnFailure(throwableGlobalRef_);
|
||||
setJavaExceptionAndAbortOnFailure(throwable_);
|
||||
}
|
||||
|
||||
}}
|
||||
|
|
|
@ -156,7 +156,7 @@ void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size
|
|||
modified[j++] = '\0';
|
||||
}
|
||||
|
||||
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) {
|
||||
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;
|
||||
|
@ -230,7 +230,7 @@ size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
|
|||
return utf8StringLen;
|
||||
}
|
||||
|
||||
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) {
|
||||
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
|
||||
if (!utf16String || utf16StringLen <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -10,10 +10,13 @@
|
|||
#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();
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
|
|||
std::call_once(flag, [vm] {
|
||||
try {
|
||||
Environment::initialize(vm);
|
||||
internal::initExceptionHelpers();
|
||||
} catch (std::exception& ex) {
|
||||
error_occured = true;
|
||||
try {
|
||||
|
@ -58,6 +57,9 @@ jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
|
|||
|
||||
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);
|
||||
|
@ -67,6 +69,9 @@ alias_ref<JClass> findClassStatic(const char* name) {
|
|||
|
||||
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);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <fb/lyra.h>
|
||||
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -15,6 +16,21 @@ namespace lyra {
|
|||
|
||||
namespace {
|
||||
|
||||
class IosFlagsSaver {
|
||||
ios_base& ios_;
|
||||
ios_base::fmtflags flags_;
|
||||
|
||||
public:
|
||||
IosFlagsSaver(ios_base& ios)
|
||||
: ios_(ios),
|
||||
flags_(ios.flags())
|
||||
{}
|
||||
|
||||
~IosFlagsSaver() {
|
||||
ios_.flags(flags_);
|
||||
}
|
||||
};
|
||||
|
||||
struct BacktraceState {
|
||||
size_t skip;
|
||||
vector<InstructionPointer>& stackTrace;
|
||||
|
@ -71,6 +87,8 @@ void getStackTraceSymbols(vector<StackTraceElement>& symbols,
|
|||
}
|
||||
|
||||
ostream& operator<<(ostream& out, const StackTraceElement& elm) {
|
||||
IosFlagsSaver flags{out};
|
||||
|
||||
// TODO(t10748683): Add build id to the output
|
||||
out << "{dso=" << elm.libraryName() << " offset=" << hex
|
||||
<< showbase << elm.libraryOffset();
|
||||
|
@ -88,6 +106,8 @@ ostream& operator<<(ostream& out, const StackTraceElement& elm) {
|
|||
// TODO(t10737667): The implement a tool that parse the stack trace and
|
||||
// symbolicate it
|
||||
ostream& operator<<(ostream& out, const vector<StackTraceElement>& trace) {
|
||||
IosFlagsSaver flags{out};
|
||||
|
||||
auto i = 0;
|
||||
out << "Backtrace:\n";
|
||||
for (auto& elm : trace) {
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#ifndef DISABLE_CPUCAP
|
||||
#include <fb/CpuCapabilities.h>
|
||||
#endif
|
||||
#include <fb/fbjni.h>
|
||||
|
||||
using namespace facebook::jni;
|
||||
|
@ -20,6 +23,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||
initialize_fbjni();
|
||||
#ifndef DISABLE_XPLAT
|
||||
initialize_xplatinit();
|
||||
#endif
|
||||
#ifndef DISABLE_CPUCAP
|
||||
initialize_cpucapabilities();
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue