From 7f790dc0de1ecc9f232e304abcc5e38b9fae98f0 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Wed, 6 Jul 2016 12:57:38 -0700 Subject: [PATCH] Pull an updated version of fbjni into RN OSS Differential Revision: D3521227 fbshipit-source-id: 57db97ea2af2b2c9e55f380ce05d9e78a5f9d48c --- ReactAndroid/build.gradle | 2 +- .../src/main/java/com/facebook/jni/BUCK | 4 +- .../main/java/com/facebook/jni/Countable.java | 19 +- .../java/com/facebook/jni/CppException.java | 20 - .../java/com/facebook/jni/HybridData.java | 23 +- .../java/com/facebook/jni/IteratorHelper.java | 56 +++ .../com/facebook/jni/MapIteratorHelper.java | 53 +++ ...rrorException.java => NativeRunnable.java} | 21 +- .../java/com/facebook/jni/Prerequisites.java | 65 --- .../com/facebook/jni/ThreadScopeSupport.java | 22 + .../com/facebook/jni/UnknownCppException.java | 25 -- .../src/main/java/com/facebook/jni/fbjni.pro | 11 + .../src/main/jni/first-party/fb/Android.mk | 2 +- .../first-party/fb/include/fb/Environment.h | 57 +-- .../first-party/fb/include/fb/fbjni/Common.h | 20 + .../first-party/fb/include/fb/fbjni/Context.h | 4 + .../fb/include/fb/fbjni/CoreClasses-inl.h | 7 + .../fb/include/fb/fbjni/CoreClasses.h | 4 +- .../fb/include/fb/fbjni/Exceptions.h | 54 +-- .../first-party/fb/include/fb/fbjni/JThread.h | 39 ++ .../fb/include/fb/fbjni/Meta-inl.h | 28 +- .../fb/include/fb/fbjni/NativeRunnable.h | 47 +++ .../fb/fbjni/ReferenceAllocators-inl.h | 2 - .../first-party/fb/include/jni/LocalString.h | 4 +- .../jni/first-party/fb/jni/Environment.cpp | 47 ++- .../jni/first-party/fb/jni/Exceptions.cpp | 382 ++++++------------ .../jni/first-party/fb/jni/LocalString.cpp | 4 +- .../main/jni/first-party/fb/jni/OnLoad.cpp | 3 + .../src/main/jni/first-party/fb/jni/fbjni.cpp | 7 +- .../src/main/jni/first-party/fb/lyra/lyra.cpp | 20 + .../src/main/jni/first-party/fb/onload.cpp | 6 + 31 files changed, 577 insertions(+), 481 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/jni/CppException.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java rename ReactAndroid/src/main/java/com/facebook/jni/{CppSystemErrorException.java => NativeRunnable.java} (57%) delete mode 100644 ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java create mode 100644 ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro create mode 100644 ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h create mode 100644 ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 50d7219ca..407316d02 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -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' } } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/jni/BUCK index 220371dba..a5eb40afc 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/jni/BUCK @@ -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( diff --git a/ReactAndroid/src/main/java/com/facebook/jni/Countable.java b/ReactAndroid/src/main/java/com/facebook/jni/Countable.java index 75892f48c..a319e187a 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/Countable.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/Countable.java @@ -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 { diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java deleted file mode 100644 index a0c845dd6..000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java +++ /dev/null @@ -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); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java index fcb4ca336..e0b864b08 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/HybridData.java @@ -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; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java b/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java new file mode 100644 index 000000000..aca1cb50f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/IteratorHelper.java @@ -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; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java b/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java new file mode 100644 index 000000000..aa9283ed2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/MapIteratorHelper.java @@ -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 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; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java b/ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java similarity index 57% rename from ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java rename to ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java index 13090a18c..151cc8ad8 100644 --- a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java +++ b/ReactAndroid/src/main/java/com/facebook/jni/NativeRunnable.java @@ -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(); } diff --git a/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java b/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java deleted file mode 100644 index 5f279d0b2..000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/Prerequisites.java +++ /dev/null @@ -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; - } -} - diff --git a/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java b/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java new file mode 100644 index 000000000..89610c437 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/ThreadScopeSupport.java @@ -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); +} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java deleted file mode 100644 index 45e9bfe0c..000000000 --- a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java +++ /dev/null @@ -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); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro b/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro new file mode 100644 index 000000000..5b5b6454d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/jni/fbjni.pro @@ -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 *; + (com.facebook.jni.HybridData); +} + +-keepclasseswithmembers class * { + com.facebook.jni.HybridData *; +} diff --git a/ReactAndroid/src/main/jni/first-party/fb/Android.mk b/ReactAndroid/src/main/jni/first-party/fb/Android.mk index 6eb6eeab9..aaf198bce 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/Android.mk +++ b/ReactAndroid/src/main/jni/first-party/fb/Android.mk @@ -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 diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h index bf6c36162..64f9937a6 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/Environment.h @@ -8,6 +8,7 @@ */ #pragma once +#include #include #include @@ -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&& runnable); + + static void OnLoad(); private: bool attachedWithThisScope_; }; diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h index 5db529304..9da51c406 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Common.h @@ -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 +[[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. * diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h index 136ca82f1..8630aa6bf 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Context.h @@ -25,6 +25,10 @@ class AContext : public JavaClass { return method(self()); } + local_ref getFilesDir() { + static auto method = getClass()->getMethod("getFilesDir"); + return method(self()); + } }; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h index f5f861ba3..40fb94b7b 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses-inl.h @@ -341,6 +341,13 @@ struct Convert { }; } +// jthrowable ////////////////////////////////////////////////////////////////////////////////////// + +inline local_ref JThrowable::initCause(alias_ref cause) { + static auto meth = javaClassStatic()->getMethod("initCause"); + return meth(self(), cause.get()); +} + // jtypeArray ////////////////////////////////////////////////////////////////////////////////////// namespace detail { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h index 62b8bb7fe..9e0bb8764 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h @@ -212,7 +212,7 @@ protected: /// the Java class actually has (i.e. with static create() functions). template static local_ref newInstance(Args... args) { - return detail::newInstance(args...); + return detail::newInstance(args...); } javaobject self() const noexcept; @@ -344,6 +344,8 @@ FBEXPORT local_ref make_jstring(const std::string& modifiedUtf8); class FBEXPORT JThrowable : public JavaClass { public: static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; + + local_ref initCause(alias_ref cause); }; namespace detail { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h index 8edb871c2..ce2cc70bd 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Exceptions.h @@ -29,30 +29,26 @@ #include #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 { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } + + static local_ref 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 throwable); JniException(JniException &&rhs); JniException(const JniException &other); - ~JniException() noexcept; - - jthrowable getThrowable() const noexcept; + local_ref getThrowable() const noexcept; virtual const char* what() const noexcept; void setJavaException() const noexcept; private: - jthrowable throwableGlobalRef_; + global_ref 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 [[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 // 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. diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h new file mode 100644 index 000000000..60b3cac20 --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/JThread.h @@ -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 { + public: + static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;"; + + void start() { + static auto method = javaClassStatic()->getMethod("start"); + method(self()); + } + + void join() { + static auto method = javaClassStatic()->getMethod("join"); + method(self()); + } + + static local_ref create(std::function&& runnable) { + auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable)); + return newInstance(static_ref_cast(jrunnable)); + } +}; + +} +} diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h index dda35925d..45cf2d1bd 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/Meta-inl.h @@ -66,7 +66,7 @@ local_ref::javaobject> makeArgsArray(Args... args) { } -bool needsSlowPath(alias_ref obj) { +inline bool needsSlowPath(alias_ref 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 obj) { } -template -local_ref slowCall(jmethodID method_id, alias_ref self, Args... args) { - static auto invoke = findClassStatic("java/lang/reflect/Method") - ->getMethod::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 inline void JMethod::operator()(alias_ref self, Args... args) { const auto env = Environment::current(); @@ -285,6 +272,19 @@ class JNonvirtualMethod : public JMethodBase { friend class JClass; }; +template +local_ref slowCall(jmethodID method_id, alias_ref self, Args... args) { + static auto invoke = findClassStatic("java/lang/reflect/Method") + ->getMethod::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 /////////////////////////////////////////////////////////////////////////////////////// diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h new file mode 100644 index 000000000..68da6a25f --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/NativeRunnable.h @@ -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 + +namespace facebook { +namespace jni { + +struct JRunnable : public JavaClass { + static auto constexpr kJavaDescriptor = "Ljava/lang/Runnable;"; +}; + +struct JNativeRunnable : public HybridClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;"; + + JNativeRunnable(std::function&& runnable) : runnable_(std::move(runnable)) {} + + static void OnLoad() { + registerHybrid({ + makeNativeMethod("run", JNativeRunnable::run), + }); + } + + void run() { + runnable_(); + } + + private: + std::function runnable_; +}; + + +} // namespace jni +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h index 5f69a3b41..9106dd253 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/ReferenceAllocators-inl.h @@ -13,8 +13,6 @@ #include #include -#include "Exceptions.h" - namespace facebook { namespace jni { diff --git a/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h b/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h index 2763e62be..7278fc848 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h +++ b/ReactAndroid/src/main/jni/first-party/fb/include/jni/LocalString.h @@ -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; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp index 60f63ef6b..e8ea51dc0 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/Environment.cpp @@ -12,12 +12,40 @@ #include #include #include +#include +#include + +#include namespace facebook { namespace jni { -static StaticInitialized> g_env; -static JavaVM* g_vm = nullptr; +namespace { +StaticInitialized> g_env; +JavaVM* g_vm = nullptr; + +struct JThreadScopeSupport : JavaClass { + 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&& func) { + static auto method = javaClassStatic()->getStaticMethod("runStdFunction"); + method(javaClassStatic(), reinterpret_cast(&func)); + } + + static void runStdFunctionImpl(alias_ref, jlong ptr) { + (*reinterpret_cast*>(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&& 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)); +} + } } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp index e7d38054c..bfc99214a 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/Exceptions.cpp @@ -7,9 +7,10 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#include "fb/fbjni.h" +#include #include +#include #include #include @@ -21,234 +22,113 @@ #include + namespace facebook { namespace jni { -// CommonJniExceptions ///////////////////////////////////////////////////////////////////////////// - -class FBEXPORT CommonJniExceptions { +namespace { +class JRuntimeException : public JavaClass { public: - static void init(); + static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; - static jclass getThrowableClass() { - return throwableClass_; + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); } - static jclass getUnknownCppExceptionClass() { - return unknownCppExceptionClass_; + static local_ref 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 { + public: + static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; + static local_ref 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 gIsInitialized(false); +class JOutOfMemoryError : public JavaClass { + 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 create(const char* str) { + return newInstance(make_jstring(str)); + } +}; - // Throwable class - jclass localThrowableClass = env->FindClass("java/lang/Throwable"); - FBASSERT(localThrowableClass); - throwableClass_ = static_cast(env->NewGlobalRef(localThrowableClass)); - FBASSERT(throwableClass_); - env->DeleteLocalRef(localThrowableClass); +class JArrayIndexOutOfBoundsException : public JavaClass { + 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, - "", - "()V"); - FBASSERT(unknownCppExceptionConstructorMID); - unknownCppExceptionClass_ = static_cast(env->NewGlobalRef(localUnknownCppExceptionClass)); - FBASSERT(unknownCppExceptionClass_); - env->DeleteLocalRef(localUnknownCppExceptionClass); + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; - // UnknownCppException object - jthrowable localUnknownCppExceptionObject = static_cast(env->NewObject( - unknownCppExceptionClass_, - unknownCppExceptionConstructorMID)); - FBASSERT(localUnknownCppExceptionObject); - unknownCppExceptionObject_ = static_cast(env->NewGlobalRef( - localUnknownCppExceptionObject)); - FBASSERT(unknownCppExceptionObject_); - env->DeleteLocalRef(localUnknownCppExceptionObject); +class JUnknownCppException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; - // RuntimeException object - jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException"); - FBASSERT(localRuntimeExceptionClass); + static local_ref create() { + return newInstance(); + } - jmethodID runtimeExceptionConstructorMID = env->GetMethodID( - localRuntimeExceptionClass, - "", - "()V"); - FBASSERT(runtimeExceptionConstructorMID); - jthrowable localRuntimeExceptionObject = static_cast(env->NewObject( - localRuntimeExceptionClass, - runtimeExceptionConstructorMID)); - FBASSERT(localRuntimeExceptionObject); - runtimeExceptionObject_ = static_cast(env->NewGlobalRef(localRuntimeExceptionObject)); - FBASSERT(runtimeExceptionObject_); + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; - env->DeleteLocalRef(localRuntimeExceptionClass); - env->DeleteLocalRef(localRuntimeExceptionObject); -} +class JCppSystemErrorException : public JavaClass { + 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 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 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, - "", - "(Ljava/lang/String;I)V"); - if (!constructorMID) { - setDefaultException(); - return; - } - jthrowable cppSystemErrorExceptionObject = static_cast(env->NewObject( - cppSystemErrorExceptionClass, - constructorMID, - env->NewStringUTF(ex.what()), - ex.code().value())); - setJavaExceptionAndAbortOnFailure(cppSystemErrorExceptionObject); -} - -template -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 -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 func) { + try { + throw; + } catch (const std::exception& e) { + try { + rethrow_if_nested(); + } catch (...) { + denest(func); + } + func(); + } catch (...) { + func(); + } +} +} + +void translatePendingCppExceptionToJavaException() noexcept { + local_ref previous; + auto func = [&previous] () { + local_ref 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(internal::getEnv()->NewGlobalRef(throwable)); - if (!throwableGlobalRef_) { - throw std::bad_alloc(); - } +JniException::JniException(alias_ref 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(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 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_); } }} diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp index 1a0e8d703..2e197f075 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/LocalString.cpp @@ -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 ""; } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp index d50416133..0334729c8 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/OnLoad.cpp @@ -10,10 +10,13 @@ #include #include #include +#include using namespace facebook::jni; void initialize_fbjni() { CountableOnLoad(Environment::current()); HybridDataOnLoad(); + JNativeRunnable::OnLoad(); + ThreadScope::OnLoad(); } diff --git a/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp b/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp index e2bd795fa..de9423c20 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/jni/fbjni.cpp @@ -26,7 +26,6 @@ jint initialize(JavaVM* vm, std::function&& 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&& init_fn) noexcept { alias_ref 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 findClassStatic(const char* name) { local_ref 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); diff --git a/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp b/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp index c3d25df1a..f915ecef2 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/lyra/lyra.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -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& stackTrace; @@ -71,6 +87,8 @@ void getStackTraceSymbols(vector& 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& trace) { + IosFlagsSaver flags{out}; + auto i = 0; out << "Backtrace:\n"; for (auto& elm : trace) { diff --git a/ReactAndroid/src/main/jni/first-party/fb/onload.cpp b/ReactAndroid/src/main/jni/first-party/fb/onload.cpp index 9357940a4..2caebc37a 100644 --- a/ReactAndroid/src/main/jni/first-party/fb/onload.cpp +++ b/ReactAndroid/src/main/jni/first-party/fb/onload.cpp @@ -8,6 +8,9 @@ */ #include +#ifndef DISABLE_CPUCAP +#include +#endif #include 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 }); }