diff --git a/src/js_list.cpp b/src/js_list.cpp index 9b01c15e..e3bce0c9 100644 --- a/src/js_list.cpp +++ b/src/js_list.cpp @@ -6,7 +6,7 @@ #include "js_object.hpp" #include "js_util.hpp" #include "object_accessor.hpp" - +laksdlasd using RJSAccessor = realm::NativeAccessor; using namespace realm; diff --git a/tests/react-test-app/android/app/build.gradle b/tests/react-test-app/android/app/build.gradle index fbd478e3..7608946c 100644 --- a/tests/react-test-app/android/app/build.gradle +++ b/tests/react-test-app/android/app/build.gradle @@ -1,79 +1,271 @@ -apply plugin: "com.android.application" +// Copyright 2015-present Facebook. All Rights Reserved. -/** - * The react.gradle file registers two tasks: bundleDebugJsAndAssets and bundleReleaseJsAndAssets. - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation - * entryFile: "index.android.js", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"] - * ] - */ +apply plugin: 'com.android.library' +apply plugin: 'maven' -apply from: "react.gradle" +apply plugin: 'de.undercouch.download' + +import de.undercouch.gradle.tasks.download.Download +import org.apache.tools.ant.taskdefs.condition.Os +import org.apache.tools.ant.filters.ReplaceTokens + +// We download various C++ open-source dependencies into downloads. +// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk. +// After that we build native code from src/main/jni with module path pointing at third-party-ndk. + +def downloadsDir = new File("$buildDir/downloads") +def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") + +task createNativeDepsDirectories { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() +} + +task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { + // Use ZIP version as it's faster this way to selectively extract some parts of the archive + src 'https://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.zip' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'boost_1_57_0.zip') +} + +task prepareBoost(dependsOn: downloadBoost) { + inputs.files downloadBoost.dest, 'src/main/jni/third-party/boost/Android.mk' + outputs.dir "$thirdPartyNdkDir/boost" + doLast { + copy { + from { zipTree(downloadBoost.dest) } + from 'src/main/jni/third-party/boost/Android.mk' + include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk' + into "$thirdPartyNdkDir/boost" + } + } +} + +task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/double-conversion/archive/v1.1.1.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'double-conversion-1.1.1.tar.gz') +} + +task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { + from tarTree(downloadDoubleConversion.dest) + from 'src/main/jni/third-party/double-conversion/Android.mk' + include 'double-conversion-1.1.1/src/**/*', 'Android.mk' + filesMatching('*/src/**/*', {fname -> fname.path = "double-conversion/${fname.name}"}) + includeEmptyDirs = false + into "$thirdPartyNdkDir/double-conversion" +} + +task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/facebook/folly/archive/v0.50.0.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'folly-0.50.0.tar.gz'); +} + +task prepareFolly(dependsOn: downloadFolly, type: Copy) { + from tarTree(downloadFolly.dest) + from 'src/main/jni/third-party/folly/Android.mk' + include 'folly-0.50.0/folly/**/*', 'Android.mk' + eachFile {fname -> fname.path = (fname.path - "folly-0.50.0/")} + includeEmptyDirs = false + into "$thirdPartyNdkDir/folly" +} + +task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/glog/archive/v0.3.3.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'glog-0.3.3.tar.gz') +} + +// Prepare glog sources to be compiled, this task will perform steps that normally shoudl've been +// executed by automake. This way we can avoid dependencies on make/automake +task prepareGlog(dependsOn: downloadGlog, type: Copy) { + from tarTree(downloadGlog.dest) + from 'src/main/jni/third-party/glog/' + include 'glog-0.3.3/src/**/*', 'Android.mk', 'config.h' + includeEmptyDirs = false + filesMatching('**/*.h.in') { + filter(ReplaceTokens, tokens: [ + ac_cv_have_unistd_h: '1', + ac_cv_have_stdint_h: '1', + ac_cv_have_systypes_h: '1', + ac_cv_have_inttypes_h: '1', + ac_cv_have_libgflags: '0', + ac_google_start_namespace: 'namespace google {', + ac_cv_have_uint16_t: '1', + ac_cv_have_u_int16_t: '1', + ac_cv_have___uint16: '0', + ac_google_end_namespace: '}', + ac_cv_have___builtin_expect: '1', + ac_google_namespace: 'google', + ac_cv___attribute___noinline: '__attribute__ ((noinline))', + ac_cv___attribute___noreturn: '__attribute__ ((noreturn))', + ac_cv___attribute___printf_4_5: '__attribute__((__format__ (__printf__, 4, 5)))' + ]) + it.path = (it.name - '.in') + } + into "$thirdPartyNdkDir/glog" +} + +task downloadJSCHeaders(type: Download) { + def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/!svn/bc/174650/trunk/Source/JavaScriptCore/API/' + def jscHeaderFiles = ['JSBase.h', 'JSContextRef.h', 'JSObjectRef.h', 'JSRetainPtr.h', 'JSStringRef.h', 'JSValueRef.h', 'WebKitAvailability.h', 'JavaScriptCore.h', 'JavaScript.h', 'JSStringRefCF.h'] + def output = new File(downloadsDir, 'jsc') + output.mkdirs() + src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" }) + onlyIfNewer true + overwrite false + dest output +} + +// Create Android.mk library module based on so files from mvn + include headers fetched from webkit.org +task prepareJSC(dependsOn: downloadJSCHeaders) << { + copy { + from zipTree(configurations.compile.fileCollection { dep -> dep.name == 'android-jsc' }.singleFile) + from {downloadJSCHeaders.dest} + from 'src/main/jni/third-party/jsc/Android.mk' + include 'jni/**/*.so', '*.h', 'Android.mk' + filesMatching('*.h', { fname -> fname.path = "JavaScriptCore/${fname.path}"}) + into "$thirdPartyNdkDir/jsc"; + } +} + +def getNdkBuildName() { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + return "ndk-build.cmd" + } else { + return "ndk-build" + } +} + +def findNdkBuildFullPath() { + // we allow to provide full path to ndk-build tool + if (hasProperty('ndk.command')) { + return property('ndk.command') + } + // or just a path to the containing directory + if (hasProperty('ndk.path')) { + def ndkDir = property('ndk.path') + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + if (System.getenv('ANDROID_NDK') != null) { + def ndkDir = System.getenv('ANDROID_NDK') + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + def ndkDir = android.hasProperty('plugin') ? android.plugin.ndkFolder : + plugins.getPlugin('com.android.library').sdkHandler.getNdkFolder() + if (ndkDir) { + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + return null +} + +def getNdkBuildFullPath() { + def ndkBuildFullPath = findNdkBuildFullPath() + if (ndkBuildFullPath == null) { + throw new GradleScriptException( + "ndk-build binary cannot be found, check if you've set " + + "\$ANDROID_NDK environment variable correctly or if ndk.dir is " + + "setup in local.properties", + null) + } + if (!new File(ndkBuildFullPath).canExecute()) { + throw new GradleScriptException( + "ndk-build binary " + ndkBuildFullPath + " doesn't exist or isn't executable.\n" + + "Check that the \$ANDROID_NDK environment variable, or ndk.dir in local.proerties, is set correctly.\n" + + "(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)", + null) + } + return ndkBuildFullPath +} + +task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) { + inputs.file('src/main/jni') + outputs.dir("$buildDir/react-ndk/all") + commandLine getNdkBuildFullPath(), + 'NDK_PROJECT_PATH=null', + "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk", + 'NDK_OUT=' + temporaryDir, + "NDK_LIBS_OUT=$buildDir/react-ndk/all", + "THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk", + '-C', file('src/main/jni').absolutePath, + '-B', + 'NDK_LOG=1', + '--jobs', Runtime.runtime.availableProcessors() +} + +task cleanReactNdkLib(type: Exec) { + commandLine getNdkBuildFullPath(), + '-C', file('src/main/jni').absolutePath, + 'clean' +} + +task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) { + from "$buildDir/react-ndk/all" + exclude '**/libjsc.so' + into "$buildDir/react-ndk/exported" +} android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { - applicationId "com.reacttests" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" + ndk { - moduleName = "realm-react-android" - abiFilters "armeabi-v7a", "x86" + moduleName "reactnativejni" } + + buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false' } - buildTypes { - release { - minifyEnabled false // Set this to true to enable Proguard - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } + + sourceSets.main { + jni.srcDirs = [] + jniLibs.srcDir "$buildDir/react-ndk/exported" + res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell'] + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn packageReactNdkLibs + } + + clean.dependsOn cleanReactNdkLib + + lintOptions { + abortOnError false } } dependencies { - compile fileTree(dir: "libs", include: ["*.jar"]) - compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.14.+" + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.0.1' + compile 'com.android.support:recyclerview-v7:23.0.1' + compile 'com.facebook.fresco:fresco:0.8.1' + compile 'com.facebook.fresco:imagepipeline-okhttp:0.8.1' + compile 'com.facebook.stetho:stetho:1.2.0' + compile 'com.facebook.stetho:stetho-okhttp:1.2.0' + compile 'com.fasterxml.jackson.core:jackson-core:2.2.3' + compile 'com.google.code.findbugs:jsr305:3.0.0' + compile 'com.squareup.okhttp:okhttp:2.5.0' + compile 'com.squareup.okhttp:okhttp-ws:2.5.0' + compile 'com.squareup.okio:okio:1.6.0' + compile 'org.webkit:android-jsc:r174650' + + testCompile "junit:junit:${JUNIT_VERSION}" + testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}" + testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}" + testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}" + testCompile("org.robolectric:robolectric:${ROBOLECTRIC_VERSION}") } + +apply from: 'release.gradle' diff --git a/tests/react-test-app/android/app/gradle.properties b/tests/react-test-app/android/app/gradle.properties new file mode 100644 index 00000000..e038115d --- /dev/null +++ b/tests/react-test-app/android/app/gradle.properties @@ -0,0 +1,14 @@ +VERSION_NAME=0.12.0-SNAPSHOT +GROUP=com.facebook.react + +POM_NAME=ReactNative +POM_ARTIFACT_ID=react-native +POM_PACKAGING=aar + +android.useDeprecatedNdk=true + +MOCKITO_CORE_VERSION=1.+ +POWERMOCK_VERSION=1.6.2 +ROBOLECTRIC_VERSION=3.0 +JUNIT_VERSION=4.12 +FEST_ASSERT_CORE_VERSION=2.0M10 diff --git a/tests/react-test-app/android/app/release.gradle b/tests/react-test-app/android/app/release.gradle new file mode 100644 index 00000000..9b8cd35a --- /dev/null +++ b/tests/react-test-app/android/app/release.gradle @@ -0,0 +1,128 @@ +// Copyright 2015-present Facebook. All Rights Reserved. + +apply plugin: 'maven' +apply plugin: 'signing' + +// Gradle tasks for publishing to maven +// 1) To install in local maven repo use :installArchives task +// 2) To upload artifact to maven central use: :uploadArchives (you'd need to have the permission to do that) + +def isReleaseBuild() { + return VERSION_NAME.contains('SNAPSHOT') == false +} + +def getRepositoryUrl() { + return hasProperty('repositoryUrl') ? property('repositoryUrl') : 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' +} + +def getRepositoryUsername() { + return hasProperty('repositoryUsername') ? property('repositoryUsername') : '' +} + +def getRepositoryPassword() { + return hasProperty('repositoryPassword') ? property('repositoryPassword') : '' +} + +def configureReactNativePom(def pom) { + pom.project { + name POM_NAME + artifactId POM_ARTIFACT_ID + packaging POM_PACKAGING + description 'A framework for building native apps with React' + url 'https://github.com/facebook/react-native' + + scm { + url 'https://github.com/facebook/react-native.git' + connection 'scm:git:https://github.com/facebook/react-native.git' + developerConnection 'scm:git:git@github.com:facebook/react-native.git' + } + + licenses { + license { + name 'BSD License' + url 'https://github.com/facebook/react-native/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'facebook' + name 'Facebook' + } + } + } +} + +afterEvaluate { project -> + + task androidJavadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += files(android.bootClasspath) + classpath += files(project.getConfigurations().getByName('compile').asList()) + include '**/*.java' + exclude '**/ReactBuildConfig.java' + } + + task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { + classifier = 'javadoc' + from androidJavadoc.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs + include '**/*.java' + } + + android.libraryVariants.all { variant -> + def name = variant.name.capitalize() + task "jar${name}"(type: Jar, dependsOn: variant.javaCompile) { + from variant.javaCompile.destinationDir + } + } + + artifacts { + archives androidSourcesJar + // TODO Make Javadoc generation work with Java 1.8, currently only works with 1.7 + // archives androidJavadocJar + } + + version = VERSION_NAME + group = GROUP + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask('uploadArchives') } + sign configurations.archives + } + + uploadArchives { + configuration = configurations.archives + repositories.mavenDeployer { + beforeDeployment { + MavenDeployment deployment -> signing.signPom(deployment) + } + + repository(url: getRepositoryUrl()) { + authentication( + userName: getRepositoryUsername(), + password: getRepositoryPassword()) + + } + + configureReactNativePom pom + } + } + + task installArchives(type: Upload) { + configuration = configurations.archives + repositories.mavenDeployer { + beforeDeployment { + MavenDeployment deployment -> signing.signPom(deployment) + } + + repository url: "file://${System.properties['user.home']}/.m2/repository" + configureReactNativePom pom + } + } +} diff --git a/tests/react-test-app/android/app/src/main/AndroidManifest.xml b/tests/react-test-app/android/app/src/main/AndroidManifest.xml index 6e884d83..3f4f9d4e 100644 --- a/tests/react-test-app/android/app/src/main/AndroidManifest.xml +++ b/tests/react-test-app/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - @@ -19,4 +19,11 @@ + --> + + + + + + diff --git a/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java b/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java index ebd573a3..6dc85930 100644 --- a/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java +++ b/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java @@ -1,78 +1,78 @@ -package com.reacttests; +// package com.reacttests; -import android.app.Activity; -import android.os.Bundle; -import android.view.KeyEvent; +// import android.app.Activity; +// import android.os.Bundle; +// import android.view.KeyEvent; -import com.facebook.react.LifecycleState; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactRootView; -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; -import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; +// import com.facebook.react.LifecycleState; +// import com.facebook.react.ReactInstanceManager; +// import com.facebook.react.ReactRootView; +// import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +// import com.facebook.react.shell.MainReactPackage; +// import com.facebook.soloader.SoLoader; -public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { +// public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { - private ReactInstanceManager mReactInstanceManager; - private ReactRootView mReactRootView; +// private ReactInstanceManager mReactInstanceManager; +// private ReactRootView mReactRootView; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mReactRootView = new ReactRootView(this); +// @Override +// protected void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// mReactRootView = new ReactRootView(this); - mReactInstanceManager = ReactInstanceManager.builder() - .setApplication(getApplication()) - .setBundleAssetName("index.android.bundle") - .setJSMainModuleName("index.android") - .addPackage(new MainReactPackage()) - .setUseDeveloperSupport(BuildConfig.DEBUG) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); +// mReactInstanceManager = ReactInstanceManager.builder() +// .setApplication(getApplication()) +// .setBundleAssetName("index.android.bundle") +// .setJSMainModuleName("index.android") +// .addPackage(new MainReactPackage()) +// .setUseDeveloperSupport(BuildConfig.DEBUG) +// .setInitialLifecycleState(LifecycleState.RESUMED) +// .build(); - mReactRootView.startReactApplication(mReactInstanceManager, "ReactTests", null); +// mReactRootView.startReactApplication(mReactInstanceManager, "ReactTests", null); - setContentView(mReactRootView); - } +// setContentView(mReactRootView); +// } - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { - mReactInstanceManager.showDevOptionsDialog(); - return true; - } - return super.onKeyUp(keyCode, event); - } +// @Override +// public boolean onKeyUp(int keyCode, KeyEvent event) { +// if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { +// mReactInstanceManager.showDevOptionsDialog(); +// return true; +// } +// return super.onKeyUp(keyCode, event); +// } - @Override - public void onBackPressed() { - if (mReactInstanceManager != null) { - mReactInstanceManager.onBackPressed(); - } else { - super.onBackPressed(); - } - } +// @Override +// public void onBackPressed() { +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onBackPressed(); +// } else { +// super.onBackPressed(); +// } +// } - @Override - public void invokeDefaultOnBackPressed() { - super.onBackPressed(); - } +// @Override +// public void invokeDefaultOnBackPressed() { +// super.onBackPressed(); +// } - @Override - protected void onPause() { - super.onPause(); +// @Override +// protected void onPause() { +// super.onPause(); - if (mReactInstanceManager != null) { - mReactInstanceManager.onPause(); - } - } +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onPause(); +// } +// } - @Override - protected void onResume() { - super.onResume(); +// @Override +// protected void onResume() { +// super.onResume(); - if (mReactInstanceManager != null) { - mReactInstanceManager.onResume(this); - } - } -} +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onResume(this); +// } +// } +// } diff --git a/tests/react-test-app/android/app/src/main/jni/Android.mk b/tests/react-test-app/android/app/src/main/jni/Android.mk index 7264683c..0227515b 100644 --- a/tests/react-test-app/android/app/src/main/jni/Android.mk +++ b/tests/react-test-app/android/app/src/main/jni/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE := librealmreact +LOCAL_MODULE := nabil LOCAL_SRC_FILES := \ js_list.cpp \ @@ -10,15 +10,9 @@ LOCAL_SRC_FILES := \ js_init.cpp \ js_realm.cpp \ js_util.cpp \ - realm-react-android.c \ js_object.cpp \ js_schema.cpp \ - rpc.cpp \ - -LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) - -LOCAL_CFLAGS := \ + rpc.cpp \ LOCAL_CFLAGS += -Wall -Werror -fexceptions CXX11_FLAGS := -std=c++11 @@ -27,7 +21,7 @@ LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) LOCAL_SHARED_LIBRARIES := libjsc -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_SHARED_LIBRARY) $(call import-module,jsc) diff --git a/tests/react-test-app/android/app/src/main/jni/Application.mk b/tests/react-test-app/android/app/src/main/jni/Application.mk new file mode 100644 index 00000000..f403e5ba --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/Application.mk @@ -0,0 +1,15 @@ +APP_BUILD_SCRIPT := Android.mk + +APP_ABI := armeabi-v7a x86 +APP_PLATFORM := android-9 + +APP_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +NDK_MODULE_PATH := $(APP_MK_DIR)$(HOST_DIRSEP)$(THIRD_PARTY_NDK_DIR)$(HOST_DIRSEP)$(APP_MK_DIR)first-party + +APP_STL := gnustl_shared + +# Make sure every shared lib includes a .note.gnu.build-id header +APP_LDFLAGS := -Wl,--build-id + +NDK_TOOLCHAIN_VERSION := 4.8 diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk new file mode 100644 index 00000000..3361c433 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk @@ -0,0 +1,30 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + assert.cpp \ + log.cpp \ + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.. $(LOCAL_PATH)/include + +LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" +LOCAL_CFLAGS += -Wall -Werror +# include/utils/threads.h has unused parameters +LOCAL_CFLAGS += -Wno-unused-parameter +ifeq ($(TOOLCHAIN_PERMISSIVE),true) + LOCAL_CFLAGS += -Wno-error=unused-but-set-variable +endif +LOCAL_CFLAGS += -DHAVE_POSIX_CLOCKS + +CXX11_FLAGS := -std=c++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) + +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_LDLIBS := -llog -ldl -landroid +LOCAL_EXPORT_LDLIBS := -llog + +LOCAL_MODULE := libfb + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h new file mode 100644 index 00000000..1e402a3f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.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 +#include +#include +#include +#include + +namespace facebook { + +class Countable : public noncopyable, public nonmovable { +public: + // RefPtr expects refcount to start at 0 + Countable() : m_refcount(0) {} + virtual ~Countable() + { + FBASSERT(m_refcount == 0); + } + +private: + void ref() { + ++m_refcount; + } + + void unref() { + if (0 == --m_refcount) { + delete this; + } + } + + bool hasOnlyOneRef() const { + return m_refcount == 1; + } + + template friend class RefPtr; + std::atomic m_refcount; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h new file mode 100644 index 00000000..36f7737f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h @@ -0,0 +1,50 @@ +/* + * 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 +#include +#include + +namespace facebook { + +#define FROM_HERE facebook::ProgramLocation(__FUNCTION__, __FILE__, __LINE__) + +class ProgramLocation { +public: + ProgramLocation() : m_functionName("Unspecified"), m_fileName("Unspecified"), m_lineNumber(0) {} + + ProgramLocation(const char* functionName, const char* fileName, int line) : + m_functionName(functionName), + m_fileName(fileName), + m_lineNumber(line) + {} + + const char* functionName() const { return m_functionName; } + const char* fileName() const { return m_fileName; } + int lineNumber() const { return m_lineNumber; } + + std::string asFormattedString() const { + std::stringstream str; + str << "Function " << m_functionName << " in file " << m_fileName << ":" << m_lineNumber; + return str.str(); + } + + bool operator==(const ProgramLocation& other) const { + // Assumes that the strings are static + return (m_functionName == other.m_functionName) && (m_fileName == other.m_fileName) && m_lineNumber == other.m_lineNumber; + } + +private: + const char* m_functionName; + const char* m_fileName; + int m_lineNumber; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h new file mode 100644 index 00000000..d21fe697 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h @@ -0,0 +1,274 @@ +/* + * 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 +#include + +namespace facebook { + +// Reference counting smart pointer. This is designed to work with the +// Countable class or other implementations in the future. It is designed in a +// way to be both efficient and difficult to misuse. Typical usage is very +// simple once you learn the patterns (and the compiler will help!): +// +// By default, the internal pointer is null. +// RefPtr ref; +// +// Object creation requires explicit construction: +// RefPtr ref = createNew(...); +// +// Or if the constructor is not public: +// RefPtr ref = adoptRef(new Foo(...)); +// +// But you can implicitly create from nullptr: +// RefPtr maybeRef = cond ? ref : nullptr; +// +// Move/Copy Construction/Assignment are straightforward: +// RefPtr ref2 = ref; +// ref = std::move(ref2); +// +// Destruction automatically drops the RefPtr's reference as expected. +// +// Upcasting is implicit but downcasting requires an explicit cast: +// struct Bar : public Foo {}; +// RefPtr barRef = static_cast>(ref); +// ref = barRef; +// +template +class RefPtr { +public: + constexpr RefPtr() : + m_ptr(nullptr) + {} + + // Allow implicit construction from a pointer only from nullptr + constexpr RefPtr(std::nullptr_t ptr) : + m_ptr(nullptr) + {} + + RefPtr(const RefPtr& ref) : + m_ptr(ref.m_ptr) + { + refIfNecessary(m_ptr); + } + + // Only allow implicit upcasts. A downcast will result in a compile error + // unless you use static_cast (which will end up invoking the explicit + // operator below). + template + RefPtr(const RefPtr& ref, typename std::enable_if::value, U>::type* = nullptr) : + m_ptr(ref.get()) + { + refIfNecessary(m_ptr); + } + + RefPtr(RefPtr&& ref) : + m_ptr(nullptr) + { + *this = std::move(ref); + } + + // Only allow implicit upcasts. A downcast will result in a compile error + // unless you use static_cast (which will end up invoking the explicit + // operator below). + template + RefPtr(RefPtr&& ref, typename std::enable_if::value, U>::type* = nullptr) : + m_ptr(nullptr) + { + *this = std::move(ref); + } + + ~RefPtr() { + unrefIfNecessary(m_ptr); + m_ptr = nullptr; + } + + RefPtr& operator=(const RefPtr& ref) { + if (m_ptr != ref.m_ptr) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + refIfNecessary(m_ptr); + } + return *this; + } + + // The STL assumes rvalue references are unique and for simplicity's sake, we + // make the same assumption here, that &ref != this. + RefPtr& operator=(RefPtr&& ref) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + ref.m_ptr = nullptr; + return *this; + } + + template + RefPtr& operator=(RefPtr&& ref) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + ref.m_ptr = nullptr; + return *this; + } + + void reset() { + unrefIfNecessary(m_ptr); + m_ptr = nullptr; + } + + T* get() const { + return m_ptr; + } + + T* operator->() const { + return m_ptr; + } + + T& operator*() const { + return *m_ptr; + } + + template + explicit operator RefPtr () const; + + explicit operator bool() const { + return m_ptr ? true : false; + } + + bool isTheLastRef() const { + FBASSERT(m_ptr); + return m_ptr->hasOnlyOneRef(); + } + + // Creates a strong reference from a raw pointer, assuming that is already + // referenced from some other RefPtr. This should be used sparingly. + static inline RefPtr assumeAlreadyReffed(T* ptr) { + return RefPtr(ptr, ConstructionMode::External); + } + + // Creates a strong reference from a raw pointer, assuming that it points to a + // freshly-created object. See the documentation for RefPtr for usage. + static inline RefPtr adoptRef(T* ptr) { + return RefPtr(ptr, ConstructionMode::Adopted); + } + +private: + enum class ConstructionMode { + Adopted, + External + }; + + RefPtr(T* ptr, ConstructionMode mode) : + m_ptr(ptr) + { + FBASSERTMSGF(ptr, "Got null pointer in %s construction mode", mode == ConstructionMode::Adopted ? "adopted" : "external"); + ptr->ref(); + if (mode == ConstructionMode::Adopted) { + FBASSERT(ptr->hasOnlyOneRef()); + } + } + + static inline void refIfNecessary(T* ptr) { + if (ptr) { + ptr->ref(); + } + } + static inline void unrefIfNecessary(T* ptr) { + if (ptr) { + ptr->unref(); + } + } + + template friend class RefPtr; + + T* m_ptr; +}; + +// Creates a strong reference from a raw pointer, assuming that is already +// referenced from some other RefPtr and that it is non-null. This should be +// used sparingly. +template +static inline RefPtr assumeAlreadyReffed(T* ptr) { + return RefPtr::assumeAlreadyReffed(ptr); +} + +// As above, but tolerant of nullptr. +template +static inline RefPtr assumeAlreadyReffedOrNull(T* ptr) { + return ptr ? RefPtr::assumeAlreadyReffed(ptr) : nullptr; +} + +// Creates a strong reference from a raw pointer, assuming that it points to a +// freshly-created object. See the documentation for RefPtr for usage. +template +static inline RefPtr adoptRef(T* ptr) { + return RefPtr::adoptRef(ptr); +} + +template +static inline RefPtr createNew(Args&&... arguments) { + return RefPtr::adoptRef(new T(std::forward(arguments)...)); +} + +template template +RefPtr::operator RefPtr() const { + static_assert(std::is_base_of::value, "Invalid static cast"); + return assumeAlreadyReffedOrNull(static_cast(m_ptr)); +} + +template +inline bool operator==(const RefPtr& a, const RefPtr& b) { + return a.get() == b.get(); +} + +template +inline bool operator!=(const RefPtr& a, const RefPtr& b) { + return a.get() != b.get(); +} + +template +inline bool operator==(const RefPtr& ref, U* ptr) { + return ref.get() == ptr; +} + +template +inline bool operator!=(const RefPtr& ref, U* ptr) { + return ref.get() != ptr; +} + +template +inline bool operator==(U* ptr, const RefPtr& ref) { + return ref.get() == ptr; +} + +template +inline bool operator!=(U* ptr, const RefPtr& ref) { + return ref.get() != ptr; +} + +template +inline bool operator==(const RefPtr& ref, std::nullptr_t ptr) { + return ref.get() == ptr; +} + +template +inline bool operator!=(const RefPtr& ref, std::nullptr_t ptr) { + return ref.get() != ptr; +} + +template +inline bool operator==(std::nullptr_t ptr, const RefPtr& ref) { + return ref.get() == ptr; +} + +template +inline bool operator!=(std::nullptr_t ptr, const RefPtr& ref) { + return ref.get() != ptr; +} + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h new file mode 100644 index 00000000..6d943972 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h @@ -0,0 +1,40 @@ +/* + * 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 +#include + +namespace facebook { + +// Class that lets you declare a global but does not add a static constructor +// to the binary. Eventually I'd like to have this auto-initialize in a +// multithreaded environment but for now it's easiest just to use manual +// initialization. +template +class StaticInitialized { +public: + constexpr StaticInitialized() : + m_instance(nullptr) + {} + + template + void initialize(Args&&... arguments) { + FBASSERT(!m_instance); + m_instance = new T(std::forward(arguments)...); + } + + T* operator->() const { + return m_instance; + } +private: + T* m_instance; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h new file mode 100644 index 00000000..d86a2f0d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h @@ -0,0 +1,118 @@ +/* + * 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 +#include + +#include + +namespace facebook { + +/////////////////////////////////////////////////////////////////////////////// + +/** + * A thread-local object is a "global" object within a thread. This is useful + * for writing apartment-threaded code, where nothing is actullay shared + * between different threads (hence no locking) but those variables are not + * on stack in local scope. To use it, just do something like this, + * + * ThreadLocal static_object; + * static_object->data_ = ...; + * static_object->doSomething(); + * + * ThreadLocal static_number; + * int value = *static_number; + * + * So, syntax-wise it's similar to pointers. T can be primitive types, and if + * it's a class, there has to be a default constructor. + */ +template +class ThreadLocal { +public: + /** + * Constructor that has to be called from a thread-neutral place. + */ + ThreadLocal() : + m_key(0), + m_cleanup(OnThreadExit) { + initialize(); + } + + /** + * As above but with a custom cleanup function + */ + typedef void (*CleanupFunction)(void* obj); + explicit ThreadLocal(CleanupFunction cleanup) : + m_key(0), + m_cleanup(cleanup) { + FBASSERT(cleanup); + initialize(); + } + + /** + * Access object's member or method through this operator overload. + */ + T *operator->() const { + return get(); + } + + T &operator*() const { + return *get(); + } + + T *get() const { + return (T*)pthread_getspecific(m_key); + } + + T* release() { + T* obj = get(); + pthread_setspecific(m_key, NULL); + return obj; + } + + void reset(T* other = NULL) { + T* old = (T*)pthread_getspecific(m_key); + if (old != other) { + FBASSERT(m_cleanup); + m_cleanup(old); + pthread_setspecific(m_key, other); + } + } + +private: + void initialize() { + int ret = pthread_key_create(&m_key, m_cleanup); + if (ret != 0) { + const char *msg = "(unknown error)"; + switch (ret) { + case EAGAIN: + msg = "PTHREAD_KEYS_MAX (1024) is exceeded"; + break; + case ENOMEM: + msg = "Out-of-memory"; + break; + } + (void) msg; + FBASSERTMSGF(0, "pthread_key_create failed: %d %s", ret, msg); + } + } + + static void OnThreadExit(void *obj) { + if (NULL != obj) { + delete (T*)obj; + } + } + + pthread_key_t m_key; + CleanupFunction m_cleanup; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp new file mode 100644 index 00000000..db9a4315 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include + +#include +#include + +namespace facebook { + +#define ASSERT_BUF_SIZE 4096 +static char sAssertBuf[ASSERT_BUF_SIZE]; +static AssertHandler gAssertHandler; + +void assertInternal(const char* formatstr ...) { + va_list va_args; + va_start(va_args, formatstr); + vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args); + va_end(va_args); + if (gAssertHandler != NULL) { + gAssertHandler(sAssertBuf); + } + FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf); + // crash at this specific address so that we can find our crashes easier + *(int*)0xdeadb00c = 0; + // let the compiler know we won't reach the end of the function + __builtin_unreachable(); +} + +void setAssertHandler(AssertHandler assertHandler) { + gAssertHandler = assertHandler; +} + +} // namespace facebook diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h new file mode 100644 index 00000000..648ab2cd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef FBASSERT_H +#define FBASSERT_H + +namespace facebook { +#define ENABLE_FBASSERT 1 + +#if ENABLE_FBASSERT +#define FBASSERTMSGF(expr, msg, ...) !(expr) ? facebook::assertInternal("Assert (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) : (void) 0 +#else +#define FBASSERTMSGF(expr, msg, ...) +#endif // ENABLE_FBASSERT + +#define FBASSERT(expr) FBASSERTMSGF(expr, "%s", #expr) + +#define FBCRASH(msg, ...) facebook::assertInternal("Fatal error (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) +#define FBUNREACHABLE() facebook::assertInternal("This code should be unreachable (%s:%d)", __FILE__, __LINE__) + +void assertInternal(const char* formatstr, ...) __attribute__((noreturn)); + +// This allows storing the assert message before the current process terminates due to a crash +typedef void (*AssertHandler)(const char* message); +void setAssertHandler(AssertHandler assertHandler); + +} // namespace facebook +#endif // FBASSERT_H diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h new file mode 100644 index 00000000..4e08b558 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2005 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. + */ + +/* + * FB Wrapper for logging functions. + * + * The android logging API uses the macro "LOG()" for its logic, which means + * that it conflicts with random other places that use LOG for their own + * purposes and doesn't work right half the places you include it + * + * FBLOG uses exactly the same semantics (FBLOGD for debug etc) but because of + * the FB prefix it's strictly better. FBLOGV also gets stripped out based on + * whether NDEBUG is set, but can be overridden by FBLOG_NDEBUG + * + * Most of the rest is a copy of with minor changes. + */ + +// +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use LOG in a signal handler. +// +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ANDROID +#include +#else + // These declarations are needed for our internal use even on non-Android builds. + // (they are borrowed from ) + + /* + * Android log priority values, in ascending priority order. + */ + typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ + } android_LogPriority; + + /* + * Send a simple string to the log. + */ + int __android_log_write(int prio, const char *tag, const char *text); + + /* + * Send a formatted string to the log, used like printf(fmt,...) + */ + int __android_log_print(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) +#endif + ; + +#endif + +// --------------------------------------------------------------------- + +/* + * Normally we strip FBLOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define FBLOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef FBLOG_NDEBUG +#ifdef NDEBUG +#define FBLOG_NDEBUG 1 +#else +#define FBLOG_NDEBUG 0 +#endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ +#ifndef FBLOGV +#if FBLOG_NDEBUG +#define FBLOGV(...) ((void)0) +#else +#define FBLOGV(...) ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef FBLOGV_IF +#if FBLOG_NDEBUG +#define FBLOGV_IF(cond, ...) ((void)0) +#else +#define FBLOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ +#ifndef FBLOGD +#define FBLOGD(...) ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGD_IF +#define FBLOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info log message using the current LOG_TAG. + */ +#ifndef FBLOGI +#define FBLOGI(...) ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGI_IF +#define FBLOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ +#ifndef FBLOGW +#define FBLOGW(...) ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGW_IF +#define FBLOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error log message using the current LOG_TAG. + */ +#ifndef FBLOGE +#define FBLOGE(...) ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGE_IF +#define FBLOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +// --------------------------------------------------------------------- + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ +#ifndef IF_FBLOGV +#if FBLOG_NDEBUG +#define IF_FBLOGV() if (false) +#else +#define IF_FBLOGV() IF_FBLOG(LOG_VERBOSE, LOG_TAG) +#endif +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ +#ifndef IF_FBLOGD +#define IF_FBLOGD() IF_FBLOG(LOG_DEBUG, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ +#ifndef IF_FBLOGI +#define IF_FBLOGI() IF_FBLOG(LOG_INFO, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ +#ifndef IF_FBLOGW +#define IF_FBLOGW() IF_FBLOG(LOG_WARN, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ +#ifndef IF_FBLOGE +#define IF_FBLOGE() IF_FBLOG(LOG_ERROR, LOG_TAG) +#endif + + +// --------------------------------------------------------------------- + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#define FBLOG_ALWAYS_FATAL_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)fb_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) + +#define FBLOG_ALWAYS_FATAL(...) \ + ( ((void)fb_printAssert(NULL, LOG_TAG, __VA_ARGS__)) ) + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if FBLOG_NDEBUG + +#define FBLOG_FATAL_IF(cond, ...) ((void)0) +#define FBLOG_FATAL(...) ((void)0) + +#else + +#define FBLOG_FATAL_IF(cond, ...) FBLOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) +#define FBLOG_FATAL(...) FBLOG_ALWAYS_FATAL(__VA_ARGS__) + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#define FBLOG_ASSERT(cond, ...) FBLOG_FATAL_IF(!(cond), __VA_ARGS__) +//#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * FBLOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef FBLOG +#define FBLOG(priority, tag, ...) \ + FBLOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +#ifndef FBLOG_BY_DELIMS +#define FBLOG_BY_DELIMS(priority, tag, delims, msg, ...) \ + logPrintByDelims(ANDROID_##priority, tag, delims, msg, ##__VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef FBLOG_PRI +#define FBLOG_PRI(priority, tag, ...) \ + fb_printLog(priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef FBLOG_PRI_VA +#define FBLOG_PRI_VA(priority, tag, fmt, args) \ + fb_vprintLog(priority, NULL, tag, fmt, args) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_FBLOG +#define IF_FBLOG(priority, tag) \ + if (fb_testLog(ANDROID_##priority, tag)) +#endif + +typedef void (*LogHandler)(int priority, const char* tag, const char* message); +void setLogHandler(LogHandler logHandler); + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ +int fb_printLog(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) +#endif +; + +#define fb_vprintLog(prio, cond, tag, fmt...) \ + __android_log_vprint(prio, tag, fmt) + +#define fb_printAssert(cond, tag, fmt...) \ + __android_log_assert(cond, tag, fmt) + +#define fb_writeLog(prio, tag, text) \ + __android_log_write(prio, tag, text) + +#define fb_bWriteLog(tag, payload, len) \ + __android_log_bwrite(tag, payload, len) +#define fb_btWriteLog(tag, type, payload, len) \ + __android_log_btwrite(tag, type, payload, len) + +#define fb_testLog(prio, tag) (1) + +/* + * FB extensions + */ +void logPrintByDelims(int priority, const char* tag, const char* delims, + const char* msg, ...); + + +#ifdef __cplusplus +} +#endif + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp new file mode 100644 index 00000000..b58b7ac9 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include + +#define LOG_BUFFER_SIZE 4096 +static LogHandler gLogHandler; + +void setLogHandler(LogHandler logHandler) { + gLogHandler = logHandler; +} + +int fb_printLog(int prio, const char *tag, const char *fmt, ...) { + char logBuffer[LOG_BUFFER_SIZE]; + + va_list va_args; + va_start(va_args, fmt); + int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args); + va_end(va_args); + if (gLogHandler != NULL) { + gLogHandler(prio, tag, logBuffer); + } + __android_log_write(prio, tag, logBuffer); + return result; +} + +void logPrintByDelims(int priority, const char* tag, const char* delims, + const char* msg, ...) +{ + va_list ap; + char buf[32768]; + char* context; + char* tok; + + va_start(ap, msg); + vsnprintf(buf, sizeof(buf), msg, ap); + va_end(ap); + + tok = strtok_r(buf, delims, &context); + + if (!tok) { + return; + } + + do { + __android_log_write(priority, tag, tok); + } while ((tok = strtok_r(NULL, delims, &context))); +} + +#ifndef ANDROID + +// Implementations of the basic android logging functions for non-android platforms. + +static char logTagChar(int prio) { + switch (prio) { + default: + case ANDROID_LOG_UNKNOWN: + case ANDROID_LOG_DEFAULT: + case ANDROID_LOG_SILENT: + return ' '; + case ANDROID_LOG_VERBOSE: + return 'V'; + case ANDROID_LOG_DEBUG: + return 'D'; + case ANDROID_LOG_INFO: + return 'I'; + case ANDROID_LOG_WARN: + return 'W'; + case ANDROID_LOG_ERROR: + return 'E'; + case ANDROID_LOG_FATAL: + return 'F'; + } +} + +int __android_log_write(int prio, const char *tag, const char *text) { + return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text); +} + +int __android_log_print(int prio, const char *tag, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + + int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag); + res += vfprintf(stderr, "%s\n", ap); + + va_end(ap); + return res; +} + +#endif diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h new file mode 100644 index 00000000..7212cc4d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h @@ -0,0 +1,21 @@ +/* + * 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 + +namespace facebook { + +struct noncopyable { + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; +protected: + noncopyable() = default; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h new file mode 100644 index 00000000..37f006a4 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h @@ -0,0 +1,21 @@ +/* + * 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 + +namespace facebook { + +struct nonmovable { + nonmovable(nonmovable&&) = delete; + nonmovable& operator=(nonmovable&&) = delete; +protected: + nonmovable() = default; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h new file mode 100644 index 00000000..0ed1c5fd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +/** @file ALog.h + * + * Very simple android only logging. Define LOG_TAG to enable the macros. + */ + +#pragma once + +#ifdef __ANDROID__ + +#include + +namespace facebook { +namespace alog { + +template +inline void log(int level, const char* tag, const char* msg, ARGS... args) noexcept { + __android_log_print(level, tag, msg, args...); +} + +template +inline void log(int level, const char* tag, const char* msg) noexcept { + __android_log_write(level, tag, msg); +} + +template +inline void logv(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_VERBOSE, tag, msg, args...); +} + +template +inline void logd(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_DEBUG, tag, msg, args...); +} + +template +inline void logi(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_INFO, tag, msg, args...); +} + +template +inline void logw(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_WARN, tag, msg, args...); +} + +template +inline void loge(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_ERROR, tag, msg, args...); +} + +template +inline void logf(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_FATAL, tag, msg, args...); +} + + +#ifdef LOG_TAG +# define ALOGV(...) ::facebook::alog::logv(LOG_TAG, __VA_ARGS__) +# define ALOGD(...) ::facebook::alog::logd(LOG_TAG, __VA_ARGS__) +# define ALOGI(...) ::facebook::alog::logi(LOG_TAG, __VA_ARGS__) +# define ALOGW(...) ::facebook::alog::logw(LOG_TAG, __VA_ARGS__) +# define ALOGE(...) ::facebook::alog::loge(LOG_TAG, __VA_ARGS__) +# define ALOGF(...) ::facebook::alog::logf(LOG_TAG, __VA_ARGS__) +#endif + +}} + +#else +# define ALOGV(...) ((void)0) +# define ALOGD(...) ((void)0) +# define ALOGI(...) ((void)0) +# define ALOGW(...) ((void)0) +# define ALOGE(...) ((void)0) +# define ALOGF(...) ((void)0) +#endif diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk new file mode 100644 index 00000000..e77eaf1a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk @@ -0,0 +1,35 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Countable.cpp \ + Environment.cpp \ + fbjni.cpp \ + jni_helpers.cpp \ + LocalString.cpp \ + OnLoad.cpp \ + WeakReference.cpp \ + fbjni/Exceptions.cpp \ + fbjni/Hybrid.cpp \ + fbjni/References.cpp + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.. + +LOCAL_CFLAGS := -DLOG_TAG=\"fbjni\" -fexceptions -frtti +LOCAL_CFLAGS += -Wall -Werror + +CXX11_FLAGS := -std=gnu++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) + +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_LDLIBS := -landroid + +LOCAL_SHARED_LIBRARIES := libfb + +LOCAL_MODULE := libfbjni + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,fb) diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp new file mode 100644 index 00000000..6ff7efe9 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +static jfieldID gCountableNativePtr; + +static RefPtr* rawCountableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return reinterpret_cast*>(env->GetLongField(obj, gCountableNativePtr)); +} + +const RefPtr& countableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return *rawCountableFromJava(env, obj); +} + +void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable) { + int oldValue = env->GetLongField(obj, gCountableNativePtr); + FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue); + + FBASSERT(countable); + uintptr_t fieldValue = (uintptr_t) new RefPtr(std::move(countable)); + env->SetLongField(obj, gCountableNativePtr, fieldValue); +} + +/** + * NB: THREAD SAFETY (this comment also exists at Countable.java) + * + * This method deletes the corresponding native object on whatever thread the method is called + * on. In the common case when this is called by Countable#finalize(), this will be called on the + * system finalizer thread. If you manually call dispose on the Java object, the native object + * will be deleted synchronously on that thread. + */ +void dispose(JNIEnv* env, jobject obj) { + // Grab the pointer + RefPtr* countable = rawCountableFromJava(env, obj); + if (!countable) { + // That was easy. + return; + } + + // Clear out the old value to avoid double-frees + env->SetLongField(obj, gCountableNativePtr, 0); + + delete countable; +} + +void CountableOnLoad(JNIEnv* env) { + jclass countable = env->FindClass("com/facebook/jni/Countable"); + gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J"); + registerNatives(env, countable, { + { "dispose", "()V", (void*) dispose }, + }); +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h new file mode 100644 index 00000000..69460d8a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h @@ -0,0 +1,33 @@ +/* + * 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 +#include +#include + +namespace facebook { +namespace jni { + +const RefPtr& countableFromJava(JNIEnv* env, jobject obj); + +template RefPtr extractRefPtr(JNIEnv* env, jobject obj) { + return static_cast>(countableFromJava(env, obj)); +} + +template RefPtr extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) { + return obj ? extractRefPtr(env, obj) : nullptr; +} + +void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable); + +void CountableOnLoad(JNIEnv* env); + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile new file mode 100644 index 00000000..8b4df6a7 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile @@ -0,0 +1,18 @@ +PROJECT_NAME = "Facebook JNI" +PROJECT_BRIEF = "Helper library to provide safe and convenient access to JNI with very low overhead" +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +RECURSIVE = YES +EXCLUDE = tests Asserts.h Countable.h GlobalReference.h LocalReference.h LocalString.h Registration.h WeakReference.h jni_helpers.h Environment.h +EXCLUDE_PATTERNS = *-inl.h *.cpp +GENERATE_HTML = YES +GENERATE_LATEX = NO +ENABLE_PREPROCESSING = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_SCOPE_NAMES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_UNDOC_CLASSES = YES +SHOW_INCLUDE_FILES = NO +PREDEFINED = LOG_TAG=fbjni +EXAMPLE_PATH = samples +#ENABLED_SECTIONS = INTERNAL diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp new file mode 100644 index 00000000..02b88ce7 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +static StaticInitialized> g_env; +static JavaVM* g_vm = nullptr; + +/* static */ +JNIEnv* Environment::current() { + JNIEnv* env = g_env->get(); + if ((env == nullptr) && (g_vm != nullptr)) { + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { + FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); + env = nullptr; + } else { + g_env->reset(env); + } + } + return env; +} + +/* static */ +void Environment::detachCurrentThread() { + auto env = g_env->get(); + if (env) { + FBASSERT(g_vm); + g_vm->DetachCurrentThread(); + g_env->reset(); + } +} + +struct EnvironmentInitializer { + EnvironmentInitializer(JavaVM* vm) { + FBASSERT(!g_vm); + FBASSERT(vm); + g_vm = vm; + g_env.initialize([] (void*) {}); + } +}; + +/* static */ +void Environment::initialize(JavaVM* vm) { + static EnvironmentInitializer init(vm); +} + +/* static */ +JNIEnv* Environment::ensureCurrentThreadIsAttached() { + auto env = g_env->get(); + if (!env) { + FBASSERT(g_vm); + g_vm->AttachCurrentThread(&env, nullptr); + g_env->reset(env); + } + return env; +} + +ThreadScope::ThreadScope() + : attachedWithThisScope_(false) { + JNIEnv* env = nullptr; + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) { + return; + } + env = facebook::jni::Environment::ensureCurrentThreadIsAttached(); + FBASSERT(env); + attachedWithThisScope_ = true; +} + +ThreadScope::~ThreadScope() { + if (attachedWithThisScope_) { + Environment::detachCurrentThread(); + } +} + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h new file mode 100644 index 00000000..4dc6966a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h @@ -0,0 +1,61 @@ +/* + * 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 +#include + +namespace facebook { +namespace jni { + +// Keeps a thread-local reference to the current thread's JNIEnv. +struct Environment { + // May be null if this thread isn't attached to the JVM + static JNIEnv* current(); + static void initialize(JavaVM* vm); + static JNIEnv* ensureCurrentThreadIsAttached(); + 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. + * + * Potential concerns: + * - 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: + * 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 + * 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) + */ +class ThreadScope { + public: + ThreadScope(); + ThreadScope(ThreadScope&) = delete; + ThreadScope(ThreadScope&&) = default; + ThreadScope& operator=(ThreadScope&) = delete; + ThreadScope& operator=(ThreadScope&&) = delete; + ~ThreadScope(); + + private: + bool attachedWithThisScope_; +}; + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h new file mode 100644 index 00000000..55f537ab --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h @@ -0,0 +1,91 @@ +/* + * 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 +#include + +#include + +#include + +namespace facebook { namespace jni { + +template +class GlobalReference { + static_assert(std::is_convertible::value, + "GlobalReference instantiated with type that is not " + "convertible to jobject"); + + public: + explicit GlobalReference(T globalReference) : + reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) { + } + + ~GlobalReference() { + reset(); + } + + GlobalReference() : + reference_(nullptr) { + } + + // enable move constructor and assignment + GlobalReference(GlobalReference&& rhs) : + reference_(std::move(rhs.reference_)) { + rhs.reference_ = nullptr; + } + + GlobalReference& operator=(GlobalReference&& rhs) { + if (this != &rhs) { + reset(); + reference_ = std::move(rhs.reference_); + rhs.reference_ = nullptr; + } + return *this; + } + + GlobalReference(const GlobalReference& rhs) : + reference_{} { + reset(rhs.get()); + } + + GlobalReference& operator=(const GlobalReference& rhs) { + if (this == &rhs) { + return *this; + } + reset(rhs.get()); + return *this; + } + + explicit operator bool() const { + return (reference_ != nullptr); + } + + T get() const { + return reinterpret_cast(reference_); + } + + void reset(T globalReference = nullptr) { + if (reference_) { + Environment::current()->DeleteGlobalRef(reference_); + } + if (globalReference) { + reference_ = Environment::current()->NewGlobalRef(globalReference); + } else { + reference_ = nullptr; + } + } + + private: + jobject reference_; +}; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h new file mode 100644 index 00000000..b5d9c54a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h @@ -0,0 +1,37 @@ +/* + * 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 +#include + +#include + +#include + +namespace facebook { +namespace jni { + +template +struct LocalReferenceDeleter { + static_assert(std::is_convertible::value, + "LocalReferenceDeleter instantiated with type that is not convertible to jobject"); + void operator()(T localReference) { + if (localReference != nullptr) { + Environment::current()->DeleteLocalRef(localReference); + } + } + }; + +template +using LocalReference = + std::unique_ptr::type, LocalReferenceDeleter>; + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp new file mode 100644 index 00000000..5fc8d0c8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +namespace { + +inline void encode3ByteUTF8(char32_t code, uint8_t* out) { + FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits"); + + out[0] = 0xE0 | (code >> 12); + out[1] = 0x80 | ((code >> 6) & 0x3F); + out[2] = 0x80 | (code & 0x3F); +} + +inline char32_t decode3ByteUTF8(const uint8_t* in) { + return (((in[0] & 0x0f) << 12) | + ((in[1] & 0x3f) << 6) | + ( in[2] & 0x3f)); +} + +inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) { + FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits"); + + out[offset] = (char) (0xF0 | (code >> 18)); + out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F)); + out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F)); + out[offset + 3] = (char) (0x80 | (code & 0x3F)); +} + +template +inline bool isFourByteUTF8Encoding(const T* utf8) { + return ((*utf8 & 0xF8) == 0xF0); +} + +} + +namespace detail { + +size_t modifiedLength(const std::string& str) { + // Scan for supplementary characters + size_t j = 0; + for (size_t i = 0; i < str.size(); ) { + if (str[i] == 0) { + i += 1; + j += 2; + } else if (i + 4 > str.size() || + !isFourByteUTF8Encoding(&(str[i]))) { + // See the code in utf8ToModifiedUTF8 for what's happening here. + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + return j; +} + +// returns modified utf8 length; *length is set to strlen(str) +size_t modifiedLength(const uint8_t* str, size_t* length) { + // NUL-terminated: Scan for length and supplementary characters + size_t i = 0; + size_t j = 0; + while (str[i] != 0) { + if (str[i + 1] == 0 || + str[i + 2] == 0 || + str[i + 3] == 0 || + !isFourByteUTF8Encoding(&(str[i]))) { + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + *length = i; + return j; +} + +void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen) +{ + size_t j = 0; + for (size_t i = 0; i < len; ) { + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + if (utf8[i] == 0) { + FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short"); + modified[j] = 0xc0; + modified[j + 1] = 0x80; + i += 1; + j += 2; + continue; + } + + if (i + 4 > len || + !isFourByteUTF8Encoding(utf8 + i)) { + // If the input is too short for this to be a four-byte + // encoding, or it isn't one for real, just copy it on through. + modified[j] = utf8[i]; + i++; + j++; + continue; + } + + // Convert 4 bytes of input to 2 * 3 bytes of output + char32_t code = (((utf8[i] & 0x07) << 18) | + ((utf8[i + 1] & 0x3f) << 12) | + ((utf8[i + 2] & 0x3f) << 6) | + ( utf8[i + 3] & 0x3f)); + char32_t first; + char32_t second; + + if (code > 0x10ffff) { + // These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit + // limit on that representation. Encode two replacement characters, so the expected output + // length lines up. + const char32_t kUnicodeReplacementChar = 0xfffd; + first = kUnicodeReplacementChar; + second = kUnicodeReplacementChar; + } else { + // split into surrogate pair + first = ((code - 0x010000) >> 10) | 0xd800; + second = ((code - 0x010000) & 0x3ff) | 0xdc00; + } + + // encode each as a 3 byte surrogate value + FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short"); + encode3ByteUTF8(first, modified + j); + encode3ByteUTF8(second, modified + j + 3); + i += 4; + j += 6; + } + + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + modified[j++] = '\0'; +} + +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) { + // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient + std::string utf8(len, 0); + size_t j = 0; + for (size_t i = 0; i < len; ) { + // surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx + // encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx + + if (len >= i + 6 && + modified[i] == 0xed && + (modified[i + 1] & 0xf0) == 0xa0 && + modified[i + 3] == 0xed && + (modified[i + 4] & 0xf0) == 0xb0) { + // Valid surrogate pair + char32_t pair1 = decode3ByteUTF8(modified + i); + char32_t pair2 = decode3ByteUTF8(modified + i + 3); + char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) | + ( pair2 & 0x3ff)); + encode4ByteUTF8(ch, utf8, j); + i += 6; + j += 4; + continue; + } else if (len >= i + 2 && + modified[i] == 0xc0 && + modified[i + 1] == 0x80) { + utf8[j] = 0; + i += 2; + j += 1; + continue; + } + + // copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid + // encoding of some sort, but garbage in garbage out is ok. + + utf8[j] = (char) modified[i]; + i++; + j++; + } + + utf8.resize(j); + + return utf8; +} + +} + +LocalString::LocalString(const std::string& str) +{ + size_t modlen = detail::modifiedLength(str); + if (modlen == str.size()) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str.data()); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str.data()), str.size(), + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::LocalString(const char* str) +{ + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(str), &len); + if (modlen == len) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str), len, + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::~LocalString() { + Environment::current()->DeleteLocalRef(m_string); +} + +std::string fromJString(JNIEnv* env, jstring str) { + const char* modified = env->GetStringUTFChars(str, NULL); + jsize length = env->GetStringUTFLength(str); + std::string s = detail::modifiedUTF8ToUTF8(reinterpret_cast(modified), length); + env->ReleaseStringUTFChars(str, modified); + return s; +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h new file mode 100644 index 00000000..a85efa48 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h @@ -0,0 +1,61 @@ +/* + * 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 +#include + +namespace facebook { +namespace jni { + +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); + +} + +// JNI represents strings encoded with modified version of UTF-8. The difference between UTF-8 and +// Modified UTF-8 is that the latter support only 1-byte, 2-byte, and 3-byte formats. Supplementary +// character (4 bytes in unicode) needs to be represented in the form of surrogate pairs. To create +// a Modified UTF-8 surrogate pair that Dalvik would understand we take 4-byte unicode character, +// encode it with UTF-16 which gives us two 2 byte chars (surrogate pair) and then we encode each +// pair as UTF-8. This result in 2 x 3 byte characters. To convert modified UTF-8 to standard +// UTF-8, this mus tbe reversed. +// +// The second difference is that Modified UTF-8 is encoding NUL byte in 2-byte format. +// +// In order to avoid complex error handling, only a minimum of validity checking is done to avoid +// crashing. If the input is invalid, the output may be invalid as well. +// +// Relevant links: +// - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html +// - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8 + +class LocalString { +public: + // Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string + // contains unicode supplementary characters. + explicit LocalString(const std::string& str); + explicit LocalString(const char* str); + jstring string() const { + return m_string; + } + ~LocalString(); +private: + jstring m_string; +}; + +// The string from JNI is converted to standard UTF-8 if the string contains supplementary +// characters. +std::string fromJString(JNIEnv* env, jstring str); + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp new file mode 100644 index 00000000..a4c40409 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include +#include + +using namespace facebook::jni; + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return facebook::jni::initialize(vm, [] { + CountableOnLoad(Environment::current()); + HybridDataOnLoad(); + }); +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h new file mode 100644 index 00000000..243a9478 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include + +namespace facebook { +namespace jni { + +static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list methods) { + auto result = env->RegisterNatives(cls, methods.begin(), methods.size()); + FBASSERT(result == 0); +} + +static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list list) { + registerNatives(env, env->FindClass(cls), list); +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp new file mode 100644 index 00000000..d87ea33c --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include + +namespace facebook { +namespace jni { + +WeakReference::WeakReference(jobject strongRef) : + m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef)) +{ +} + +WeakReference::~WeakReference() { + auto env = Environment::current(); + FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread"); + env->DeleteWeakGlobalRef(m_weakReference); +} + +ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef)) +{ +} + +ResolvedWeakReference::ResolvedWeakReference(const RefPtr& weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef())) +{ +} + +ResolvedWeakReference::~ResolvedWeakReference() { + if (m_strongReference) + Environment::current()->DeleteLocalRef(m_strongReference); +} + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h new file mode 100644 index 00000000..2723155f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h @@ -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. + */ + +#pragma once +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +class WeakReference : public Countable { +public: + typedef RefPtr Ptr; + WeakReference(jobject strongRef); + ~WeakReference(); + jweak weakRef() { + return m_weakReference; + } + +private: + jweak m_weakReference; +}; + +// This class is intended to take a weak reference and turn it into a strong +// local reference. Consequently, it should only be allocated on the stack. +class ResolvedWeakReference : public noncopyable { +public: + ResolvedWeakReference(jobject weakRef); + ResolvedWeakReference(const RefPtr& weakRef); + ~ResolvedWeakReference(); + + operator jobject () { + return m_strongReference; + } + + explicit operator bool () { + return m_strongReference != nullptr; + } + +private: + jobject m_strongReference; +}; + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp new file mode 100644 index 00000000..a083371b --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "fbjni.h" + +#include +#include +#include + +namespace facebook { +namespace jni { + +template +static void log(Args... args) { +// TODO (7623232) Migrate to glog +#ifdef __ANDROID__ + facebook::alog::loge("fbjni", args...); +#endif +} + +jint initialize(JavaVM* vm, void(*init_fn)()) noexcept { + static std::once_flag init_flag; + static auto failed = false; + + std::call_once(init_flag, [vm] { + try { + Environment::initialize(vm); + internal::initExceptionHelpers(); + } catch (std::exception& ex) { + log("Failed to initialize fbjni: %s", ex.what()); + failed = true; + } catch (...) { + log("Failed to initialize fbjni"); + failed = true; + } + }); + + if (failed) { + return JNI_ERR; + } + + try { + init_fn(); + } catch (...) { + translatePendingCppExceptionToJavaException(); + // So Java will handle the translated exception, fall through and + // return a good version number. + } + return JNI_VERSION_1_6; +} + +alias_ref findClassStatic(const char* name) { + const auto env = internal::getEnv(); + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + auto leaking_ref = (jclass)env->NewGlobalRef(cls); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref); + return wrap_alias(leaking_ref); +} + +local_ref findClassLocal(const char* name) { + const auto env = internal::getEnv(); + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + return adopt_local(cls); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +std::string JObjectWrapper::toStdString() const { + const auto env = internal::getEnv(); + auto modified = env->GetStringUTFChars(self(), nullptr); + auto length = env->GetStringUTFLength(self()); + auto string = detail::modifiedUTF8ToUTF8(reinterpret_cast(modified), length); + env->ReleaseStringUTFChars(self(), modified); + return string; +} + +local_ref make_jstring(const char* utf8) { + if (!utf8) { + return {}; + } + const auto env = internal::getEnv(); + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(utf8), &len); + jstring result; + if (modlen == len) { + // The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars + // and '\0' that is encoded on 2 bytes. + // + // Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we + // know that if those two strings are of the same length we don't need to do any + // conversion -> no 4-byte chars nor '\0'. + result = env->NewStringUTF(utf8); + } else { + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(utf8), len, + reinterpret_cast(modified.data()), modified.size()); + result = env->NewStringUTF(modified.data()); + } + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(result); +} + + +// PinnedPrimitiveArray /////////////////////////////////////////////////////////////////////////// + +// TODO(T7847300): Allow array to be specified as constant so that JNI_ABORT can be passed +// on release, as opposed to 0, which results in unnecessary copying. +#pragma push_macro("DEFINE_PRIMITIVE_METHODS") +#undef DEFINE_PRIMITIVE_METHODS +#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME) \ +template<> \ +TYPE* PinnedPrimitiveArray::get() { \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr); \ + const auto env = internal::getEnv(); \ + elements_ = env->Get ## NAME ## ArrayElements( \ + static_cast(array_.get()), &isCopy_); \ + size_ = array_->size(); \ + return elements_; \ +} \ +template<> \ +void PinnedPrimitiveArray::release() { \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr); \ + const auto env = internal::getEnv(); \ + env->Release ## NAME ## ArrayElements( \ + static_cast(array_.get()), elements_, 0); \ + elements_ = nullptr; \ + size_ = 0; \ +} + +DEFINE_PRIMITIVE_METHODS(jboolean, Boolean) +DEFINE_PRIMITIVE_METHODS(jbyte, Byte) +DEFINE_PRIMITIVE_METHODS(jchar, Char) +DEFINE_PRIMITIVE_METHODS(jshort, Short) +DEFINE_PRIMITIVE_METHODS(jint, Int) +DEFINE_PRIMITIVE_METHODS(jlong, Long) +DEFINE_PRIMITIVE_METHODS(jfloat, Float) +DEFINE_PRIMITIVE_METHODS(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_METHODS") + + +#define DEFINE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME) \ + \ +local_ref make_ ## TYPE ## _array(jsize size) { \ + auto array = internal::getEnv()->New ## NAME ## Array(size); \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \ + return adopt_local(array); \ +} \ + \ +local_ref JArray ## NAME::newArray(size_t count) { \ + return make_ ## TYPE ## _array(count); \ +} \ + \ +j ## TYPE* JArray ## NAME::getRegion(jsize start, jsize length, j ## TYPE* buf) { \ + internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return buf; \ +} \ + \ +std::unique_ptr JArray ## NAME::getRegion(jsize start, jsize length) { \ + auto buf = std::unique_ptr{new j ## TYPE[length]}; \ + internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf.get()); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return buf; \ +} \ + \ +void JArray ## NAME::setRegion(jsize start, jsize length, const j ## TYPE* buf) { \ + internal::getEnv()->Set ## NAME ## ArrayRegion(self(), start, length, buf); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ +} \ + \ +PinnedPrimitiveArray JArray ## NAME::pin() { \ + return PinnedPrimitiveArray{self()}; \ +} \ + +DEFINE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean) +DEFINE_PRIMITIVE_ARRAY_UTILS(byte, Byte) +DEFINE_PRIMITIVE_ARRAY_UTILS(char, Char) +DEFINE_PRIMITIVE_ARRAY_UTILS(short, Short) +DEFINE_PRIMITIVE_ARRAY_UTILS(int, Int) +DEFINE_PRIMITIVE_ARRAY_UTILS(long, Long) +DEFINE_PRIMITIVE_ARRAY_UTILS(float, Float) +DEFINE_PRIMITIVE_ARRAY_UTILS(double, Double) + + +// Internal debug ///////////////////////////////////////////////////////////////////////////////// + +namespace internal { +ReferenceStats g_reference_stats; + +void facebook::jni::internal::ReferenceStats::reset() noexcept { + locals_deleted = globals_deleted = weaks_deleted = 0; +} +} + +}} + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h new file mode 100644 index 00000000..7ea816b6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h @@ -0,0 +1,23 @@ +/* + * 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 + +#include "Environment.h" +#include "ALog.h" +#include "fbjni/Common.h" +#include "fbjni/Exceptions.h" +#include "fbjni/ReferenceAllocators.h" +#include "fbjni/References.h" +#include "fbjni/Meta.h" +#include "fbjni/CoreClasses.h" +#include "fbjni/Hybrid.h" +#include "fbjni/Registration.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h new file mode 100644 index 00000000..479f4371 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +/** @file Common.h + * + * Defining the stuff that don't deserve headers of their own... + */ + +#pragma once + +#include + +#include +#include + +/// @cond INTERNAL + +namespace facebook { +namespace jni { + +/** + * This needs to be called at library load time, typically in your JNI_OnLoad method. + * + * The intended use is to return the result of initialize() directly + * from JNI_OnLoad and to do nothing else there. Library specific + * initialization code should go in the function passed to initialize + * (which can be, and probably should be, a C++ lambda). This approach + * provides correct error handling and translation errors during + * initialization into Java exceptions when appropriate. + * + * Failure to call this will cause your code to crash in a remarkably + * unhelpful way (typically a segfault) while trying to handle an exception + * which occurs later. + */ +jint initialize(JavaVM*, void(*)()) noexcept; + +namespace internal { + +/** + * Retrieve a pointer the JNI environment of the current thread. + * + * @pre The current thread must be attached to the VM + */ +inline JNIEnv* getEnv() noexcept { + // TODO(T6594868) Benchmark against raw JNI access + return Environment::current(); +} + +// Define to get extremely verbose logging of references and to enable reference stats +#if defined(__ANDROID__) && defined(FBJNI_DEBUG_REFS) +template +inline void dbglog(Args... args) noexcept { + facebook::alog::logv("fbjni_ref", args...); +} +#else +template +inline void dbglog(Args...) noexcept {} +#endif + +}}} + +/// @endcond diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h new file mode 100644 index 00000000..134acc87 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h @@ -0,0 +1,443 @@ +/* + * 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 +#include + +#include "Common.h" +#include "Exceptions.h" + +namespace facebook { +namespace jni { + +inline bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept { + return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE; +} + + +// jobject ///////////////////////////////////////////////////////////////////////////////////////// + +inline JObjectWrapper::JObjectWrapper(jobject reference) noexcept + : this_{reference} +{} + +inline JObjectWrapper::JObjectWrapper(const JObjectWrapper& other) noexcept + : this_{other.this_} { + internal::dbglog("wrapper copy from this=%p ref=%p other=%p", this, other.this_, &other); +} + +inline local_ref JObjectWrapper::getClass() const noexcept { + return adopt_local(internal::getEnv()->GetObjectClass(self())); +} + +inline bool JObjectWrapper::isInstanceOf(alias_ref cls) const noexcept { + return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE; +} + +template +inline T JObjectWrapper::getFieldValue(JField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JObjectWrapper::getFieldValue(JField field) noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JObjectWrapper::setFieldValue(JField field, T value) noexcept { + field.set(self(), value); +} + +inline std::string JObjectWrapper::toString() const { + static auto method = findClassLocal("java/lang/Object")->getMethod("toString"); + + return method(self())->toStdString(); +} + +inline void JObjectWrapper::set(jobject reference) noexcept { + this_ = reference; +} + +inline jobject JObjectWrapper::get() const noexcept { + return this_; +} + +inline jobject JObjectWrapper::self() const noexcept { + return this_; +} + +inline void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept { + using std::swap; + swap(a.this_, b.this_); +} + + +// jclass ////////////////////////////////////////////////////////////////////////////////////////// + +namespace detail { + +// This is not a real type. It is used so people won't accidentally +// use a void* to initialize a NativeMethod. +struct NativeMethodWrapper; + +}; + +struct NativeMethod { + const char* name; + std::string descriptor; + detail::NativeMethodWrapper* wrapper; +}; + +inline local_ref JObjectWrapper::getSuperclass() const noexcept { + return adopt_local(internal::getEnv()->GetSuperclass(self())); +} + +inline void JObjectWrapper::registerNatives(std::initializer_list methods) { + const auto env = internal::getEnv(); + + JNINativeMethod jnimethods[methods.size()]; + size_t i = 0; + for (auto it = methods.begin(); it < methods.end(); ++it, ++i) { + jnimethods[i].name = it->name; + jnimethods[i].signature = it->descriptor.c_str(); + jnimethods[i].fnPtr = reinterpret_cast(it->wrapper); + } + + auto result = env->RegisterNatives(self(), jnimethods, methods.size()); + FACEBOOK_JNI_THROW_EXCEPTION_IF(result != JNI_OK); +} + +inline bool JObjectWrapper::isAssignableFrom(alias_ref other) const noexcept { + const auto env = internal::getEnv(); + const auto result = env->IsAssignableFrom(self(), other.get()); + return result; +} + +template +inline JConstructor JObjectWrapper::getConstructor() const { + return getConstructor(jmethod_traits::constructor_descriptor().c_str()); +} + +template +inline JConstructor JObjectWrapper::getConstructor(const char* descriptor) const { + constexpr auto constructor_method_name = ""; + return getMethod(constructor_method_name, descriptor); +} + +template +inline JMethod JObjectWrapper::getMethod(const char* name) const { + return getMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JMethod JObjectWrapper::getMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JMethod{method}; +} + +template +inline JStaticMethod JObjectWrapper::getStaticMethod(const char* name) const { + return getStaticMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JStaticMethod JObjectWrapper::getStaticMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetStaticMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JStaticMethod{method}; +} + +template +inline JNonvirtualMethod JObjectWrapper::getNonvirtualMethod(const char* name) const { + return getNonvirtualMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JNonvirtualMethod JObjectWrapper::getNonvirtualMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JNonvirtualMethod{method}; +} + +template +inline JField(), T>> +JObjectWrapper::getField(const char* name) const { + return getField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JField(), T>> JObjectWrapper::getField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JField{field}; +} + +template +inline JStaticField(), T>> JObjectWrapper::getStaticField( + const char* name) const { + return getStaticField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JStaticField(), T>> JObjectWrapper::getStaticField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetStaticFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JStaticField{field}; +} + +template +inline T JObjectWrapper::getStaticFieldValue(JStaticField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JObjectWrapper::getStaticFieldValue(JStaticField field) noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JObjectWrapper::setStaticFieldValue(JStaticField field, T value) noexcept { + field.set(self(), value); +} + +template +inline local_ref JObjectWrapper::newObject( + JConstructor constructor, + Args... args) const { + const auto env = internal::getEnv(); + auto object = env->NewObject(self(), constructor.getId(), args...); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!object); + return adopt_local(static_cast(object)); +} + +inline jclass JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + +inline void registerNatives(const char* name, std::initializer_list methods) { + findClassLocal(name)->registerNatives(methods); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +inline local_ref make_jstring(const std::string& modifiedUtf8) { + return make_jstring(modifiedUtf8.c_str()); +} + +inline jstring JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// jthrowable ////////////////////////////////////////////////////////////////////////////////////// + +inline jthrowable JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// jtypeArray ////////////////////////////////////////////////////////////////////////////////////// +template +inline ElementProxy::ElementProxy( + JObjectWrapper<_jtypeArray*>* target, + size_t idx) + : target_{target}, idx_{idx} {} + +template +inline ElementProxy& ElementProxy::operator=(const T& o) { + target_->setElement(idx_, o); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref&& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(const ElementProxy& o) { + auto src = o.target_->getElement(o.idx_); + target_->setElement(idx_, src.get()); + return *this; +} + +template +inline ElementProxy::ElementProxy::operator const local_ref () const { + return target_->getElement(idx_); +} + +template +inline ElementProxy::ElementProxy::operator local_ref () { + return target_->getElement(idx_); +} + +template +local_ref> JObjectWrapper>::newArray(size_t size) { + static auto elementClass = findClassStatic(jtype_traits::base_name().c_str()); + const auto env = internal::getEnv(); + auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray); + return adopt_local(static_cast>(rawArray)); +} + +template +inline void JObjectWrapper>::setElement(size_t idx, const T& value) { + const auto env = internal::getEnv(); + env->SetObjectArrayElement(static_cast(self()), idx, value); +} + +template +inline local_ref JObjectWrapper>::getElement(size_t idx) { + const auto env = internal::getEnv(); + auto rawElement = env->GetObjectArrayElement(static_cast(self()), idx); + return adopt_local(static_cast(rawElement)); +} + +template +inline size_t JObjectWrapper>::size() { + const auto env = internal::getEnv(); + return env->GetArrayLength(static_cast(self())); +} + +template +inline ElementProxy JObjectWrapper>::operator[](size_t index) { + return ElementProxy(this, index); +} + +template +inline jtypeArray JObjectWrapper>::self() const noexcept { + return static_cast>(this_); +} + + +// jarray ///////////////////////////////////////////////////////////////////////////////////////// + +inline size_t JObjectWrapper::size() const noexcept { + const auto env = internal::getEnv(); + return env->GetArrayLength(self()); +} + +inline jarray JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// PinnedPrimitiveArray /////////////////////////////////////////////////////////////////////////// + +template +inline PinnedPrimitiveArray::PinnedPrimitiveArray(alias_ref array) noexcept + : array_{array} { + get(); +} + +template +PinnedPrimitiveArray::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) noexcept { + array_ = std::move(o.array_); + elements_ = o.elements_; + isCopy_ = o.isCopy_; + size_ = o.size_; + o.elements_ = nullptr; + o.isCopy_ = false; + o.size_ = 0; +} + +template +PinnedPrimitiveArray& +PinnedPrimitiveArray::operator=(PinnedPrimitiveArray&& o) noexcept { + array_ = std::move(o.array_); + elements_ = o.elements_; + isCopy_ = o.isCopy_; + size_ = o.size_; + o.elements_ = nullptr; + o.isCopy_ = false; + o.size_ = 0; + return *this; +} + +template +inline T& PinnedPrimitiveArray::operator[](size_t index) { + FACEBOOK_JNI_THROW_EXCEPTION_IF(elements_ == nullptr); + return elements_[index]; +} + +template +inline bool PinnedPrimitiveArray::isCopy() const noexcept { + return isCopy_ == JNI_TRUE; +} + +template +inline size_t PinnedPrimitiveArray::size() const noexcept { + return size_; +} + +template +inline PinnedPrimitiveArray::~PinnedPrimitiveArray() noexcept { + if (elements_) { + release(); + } +} + +#pragma push_macro("DECLARE_PRIMITIVE_METHODS") +#undef DECLARE_PRIMITIVE_METHODS +#define DECLARE_PRIMITIVE_METHODS(TYPE, NAME) \ +template<> TYPE* PinnedPrimitiveArray::get(); \ +template<> void PinnedPrimitiveArray::release(); \ + +DECLARE_PRIMITIVE_METHODS(jboolean, Boolean) +DECLARE_PRIMITIVE_METHODS(jbyte, Byte) +DECLARE_PRIMITIVE_METHODS(jchar, Char) +DECLARE_PRIMITIVE_METHODS(jshort, Short) +DECLARE_PRIMITIVE_METHODS(jint, Int) +DECLARE_PRIMITIVE_METHODS(jlong, Long) +DECLARE_PRIMITIVE_METHODS(jfloat, Float) +DECLARE_PRIMITIVE_METHODS(jdouble, Double) +#pragma pop_macro("DECLARE_PRIMITIVE_METHODS") + + +template +inline alias_ref JavaClass::javaClassStatic() { + static auto cls = findClassStatic( + std::string(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2).c_str()); + return cls; +} + +template +inline local_ref JavaClass::javaClassLocal() { + std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2); + return findClassLocal(className.c_str()); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h new file mode 100644 index 00000000..457b79b5 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h @@ -0,0 +1,510 @@ +/* + * 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 + +/** @file CoreClasses.h + * + * In CoreClasses.h wrappers for the core classes (jobject, jclass, and jstring) is defined + * to provide access to corresponding JNI functions + some conveniance. + */ + +#include "Meta.h" +#include "References.h" + +#include + +#include + +namespace facebook { +namespace jni { + +/// Lookup a class by name. Note this functions returns an alias_ref that +/// points to a leaked global reference. This is appropriate for classes +/// that are never unloaded (which is any class in an Android app and most +/// Java programs). +/// +/// The most common use case for this is storing the result +/// in a "static auto" variable, or a static global. +/// +/// @return Returns a leaked global reference to the class +alias_ref findClassStatic(const char* name); + +/// Lookup a class by name. Note this functions returns a local reference, +/// which means that it must not be stored in a static variable. +/// +/// The most common use case for this is one-time initialization +/// (like caching method ids). +/// +/// @return Returns a global reference to the class +local_ref findClassLocal(const char* name); + +/// Check to see if two references refer to the same object. Comparison with nullptr +/// returns true if and only if compared to another nullptr. A weak reference that +/// refers to a reclaimed object count as nullptr. +bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept; + + +/// Wrapper to provide functionality to jobject references +template<> +class JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Object;"; + + static constexpr const char* get_instantiated_java_descriptor() { return nullptr; } + + /// Wrap an existing JNI reference + JObjectWrapper(jobject reference = nullptr) noexcept; + + // Copy constructor + JObjectWrapper(const JObjectWrapper& other) noexcept; + + /// Get a @ref local_ref of the object's class + local_ref getClass() const noexcept; + + /// Checks if the object is an instance of a class + bool isInstanceOf(alias_ref cls) const noexcept; + + /// Get the primitive value of a field + template + T getFieldValue(JField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + template + local_ref getFieldValue(JField field) noexcept; + + /// Set the value of field. Any Java type is accepted, including the primitive types + /// and raw reference types. + template + void setFieldValue(JField field, T value) noexcept; + + /// Convenience method to create a std::string representing the object + std::string toString() const; + + protected: + jobject this_; + + private: + template + friend class base_owned_ref; + + template + friend class alias_ref; + + friend void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept; + + void set(jobject reference) noexcept; + jobject get() const noexcept; + jobject self() const noexcept; +}; + +using JObject = JObjectWrapper; + +void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept; + + +/// Wrapper to provide functionality to jclass references +struct NativeMethod; + +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;"; + + using JObjectWrapper::JObjectWrapper; + + /// Get a @local_ref to the super class of this class + local_ref getSuperclass() const noexcept; + + /// Register native methods for the class. Usage looks like this: + /// + /// classRef->registerNatives({ + /// makeNativeMethod("nativeMethodWithAutomaticDescriptor", + /// methodWithAutomaticDescriptor), + /// makeNativeMethod("nativeMethodWithExplicitDescriptor", + /// "(Lcom/facebook/example/MyClass;)V", + /// methodWithExplicitDescriptor), + /// }); + /// + /// By default, C++ exceptions raised will be converted to Java exceptions. + /// To avoid this and get the "standard" JNI behavior of a crash when a C++ + /// exception is crashing out of the JNI method, declare the method noexcept. + void registerNatives(std::initializer_list methods); + + /// Check to see if the class is assignable from another class + /// @pre cls != nullptr + bool isAssignableFrom(alias_ref cls) const noexcept; + + /// Convenience method to lookup the constructor with descriptor as specified by the + /// type arguments + template + JConstructor getConstructor() const; + + /// Convenience method to lookup the constructor with specified descriptor + template + JConstructor getConstructor(const char* descriptor) const; + + /// Look up the method with given name and descriptor as specified with the type arguments + template + JMethod getMethod(const char* name) const; + + /// Look up the method with given name and descriptor + template + JMethod getMethod(const char* name, const char* descriptor) const; + + /// Lookup the field with the given name and deduced descriptor + template + JField(), T>> getField(const char* name) const; + + /// Lookup the field with the given name and descriptor + template + JField(), T>> getField(const char* name, const char* descriptor) const; + + /// Lookup the static field with the given name and deduced descriptor + template + JStaticField(), T>> getStaticField(const char* name) const; + + /// Lookup the static field with the given name and descriptor + template + JStaticField(), T>> getStaticField( + const char* name, + const char* descriptor) const; + + /// Get the primitive value of a static field + template + T getStaticFieldValue(JStaticField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + template + local_ref getStaticFieldValue(JStaticField field) noexcept; + + /// Set the value of field. Any Java type is accepted, including the primitive types + /// and raw reference types. + template + void setStaticFieldValue(JStaticField field, T value) noexcept; + + /// Allocates a new object and invokes the specified constructor + template + local_ref newObject(JConstructor constructor, Args... args) const; + + /// Look up the static method with given name and descriptor as specified with the type arguments + template + JStaticMethod getStaticMethod(const char* name) const; + + /// Look up the static method with given name and descriptor + template + JStaticMethod getStaticMethod(const char* name, const char* descriptor) const; + + /// Look up the non virtual method with given name and descriptor as specified with the + /// type arguments + template + JNonvirtualMethod getNonvirtualMethod(const char* name) const; + + /// Look up the non virtual method with given name and descriptor + template + JNonvirtualMethod getNonvirtualMethod(const char* name, const char* descriptor) const; + + private: + jclass self() const noexcept; +}; + +using JClass = JObjectWrapper; + +// Convenience method to register methods on a class without holding +// onto the class object. +void registerNatives(const char* name, std::initializer_list methods); + +/// Wrapper to provide functionality to jstring references +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/String;"; + + using JObjectWrapper::JObjectWrapper; + + /// Convenience method to convert a jstring object to a std::string + std::string toStdString() const; + + private: + jstring self() const noexcept; +}; + +/// Convenience functions to convert a std::string or const char* into a @ref local_ref to a +/// jstring +local_ref make_jstring(const char* modifiedUtf8); +local_ref make_jstring(const std::string& modifiedUtf8); + +using JString = JObjectWrapper; + +/// Wrapper to provide functionality to jthrowable references +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; + + using JObjectWrapper::JObjectWrapper; + + private: + jthrowable self() const noexcept; +}; + + +/// @cond INTERNAL +template class _jtypeArray : public _jobjectArray {}; +// @endcond +/// Wrapper to provide functionality for arrays of j-types +template using jtypeArray = _jtypeArray*; + +template +class ElementProxy { + private: + JObjectWrapper<_jtypeArray*>* target_; + size_t idx_; + + public: + ElementProxy(JObjectWrapper<_jtypeArray*>* target, size_t idx); + + ElementProxy& operator=(const T& o); + + ElementProxy& operator=(alias_ref& o); + + ElementProxy& operator=(alias_ref&& o); + + ElementProxy& operator=(const ElementProxy& o); + + operator const local_ref () const; + + operator local_ref (); + }; + +template +class JObjectWrapper> : public JObjectWrapper { + public: + static constexpr const char* kJavaDescriptor = nullptr; + static std::string get_instantiated_java_descriptor() { + return "[" + jtype_traits::descriptor(); + }; + + using JObjectWrapper::JObjectWrapper; + + /// Allocate a new array from Java heap, for passing as a JNI parameter or return value. + /// NOTE: if using as a return value, you want to call release() instead of get() on the + /// smart pointer. + static local_ref> newArray(size_t count); + + /// Assign an object to the array. + /// Typically you will use the shorthand (*ref)[idx]=value; + void setElement(size_t idx, const T& value); + + /// Read an object from the array. + /// Typically you will use the shorthand + /// T value = (*ref)[idx]; + /// If you use auto, you'll get an ElementProxy, which may need to be cast. + local_ref getElement(size_t idx); + + /// Get the size of the array. + size_t size(); + + /// EXPERIMENTAL SUBSCRIPT SUPPORT + /// This implementation of [] returns a proxy object which then has a bunch of specializations + /// (adopt_local free function, operator= and casting overloads on the ElementProxy) that can + /// make code look like it is dealing with a T rather than an obvious proxy. In particular, the + /// proxy in this iteration does not read a value and therefore does not create a LocalRef + /// until one of these other operators is used. There are certainly holes that you may find + /// by using idioms that haven't been tried yet. Consider yourself warned. On the other hand, + /// it does make for some idiomatic assignment code; see TestBuildStringArray in fbjni_tests + /// for some examples. + ElementProxy operator[](size_t idx); + + private: + jtypeArray self() const noexcept; +}; + +template +using JArrayClass = JObjectWrapper>; + +template +local_ref> adopt_local_array(jobjectArray ref) { + return adopt_local(static_cast>(ref)); +} + +template +local_ref adopt_local(ElementProxy elementProxy) { + return static_cast>(elementProxy); +} + +/// Wrapper to provide functionality to jarray references. +/// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with +/// the elements of the array. +template<> +class JObjectWrapper : public JObjectWrapper { + public: + static constexpr const char* kJavaDescriptor = nullptr; + + using JObjectWrapper::JObjectWrapper; + size_t size() const noexcept; + + private: + jarray self() const noexcept; +}; + +using JArray = JObjectWrapper; + +template +class PinnedPrimitiveArray; + +#pragma push_macro("DECLARE_PRIMITIVE_ARRAY_UTILS") +#undef DECLARE_PRIMITIVE_ARRAY_UTILS +#define DECLARE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME, DESC) \ + \ +local_ref make_ ## TYPE ## _array(jsize size); \ + \ +template<> class JObjectWrapper : public JArray { \ + public: \ + static constexpr const char* kJavaDescriptor = "[" # DESC; \ + \ + using JArray::JArray; \ + \ + static local_ref newArray(size_t count); \ + \ + j ## TYPE* getRegion(jsize start, jsize length, j ## TYPE* buf); \ + std::unique_ptr getRegion(jsize start, jsize length); \ + void setRegion(jsize start, jsize length, const j ## TYPE* buf); \ + PinnedPrimitiveArray pin(); \ + \ + private: \ + j ## TYPE ## Array self() const noexcept { \ + return static_cast(this_); \ + } \ +}; \ + \ +using JArray ## NAME = JObjectWrapper \ + + +DECLARE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean, "Z"); +DECLARE_PRIMITIVE_ARRAY_UTILS(byte, Byte, "B"); +DECLARE_PRIMITIVE_ARRAY_UTILS(char, Char, "C"); +DECLARE_PRIMITIVE_ARRAY_UTILS(short, Short, "S"); +DECLARE_PRIMITIVE_ARRAY_UTILS(int, Int, "I"); +DECLARE_PRIMITIVE_ARRAY_UTILS(long, Long, "J"); +DECLARE_PRIMITIVE_ARRAY_UTILS(float, Float, "F"); +DECLARE_PRIMITIVE_ARRAY_UTILS(double, Double, "D"); + +#pragma pop_macro("DECLARE_PRIMITIVE_ARRAY_UTILS") + + +/// RAII class for pinned primitive arrays +/// This currently only supports read/write access to existing java arrays. You can't create a +/// primitive array this way yet. This class also pins the entire array into memory during the +/// lifetime of the PinnedPrimitiveArray. If you need to unpin the array manually, call the +/// release() function. During a long-running block of code, you should unpin the array as soon +/// as you're done with it, to avoid holding up the Java garbage collector. +template +class PinnedPrimitiveArray { + public: + static_assert(is_jni_primitive::value, + "PinnedPrimitiveArray requires primitive jni type."); + + PinnedPrimitiveArray(PinnedPrimitiveArray&&) noexcept; + PinnedPrimitiveArray(const PinnedPrimitiveArray&) = delete; + ~PinnedPrimitiveArray() noexcept; + + PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&) noexcept; + PinnedPrimitiveArray& operator=(const PinnedPrimitiveArray&) = delete; + + T* get(); + void release(); + + const T& operator[](size_t index) const; + T& operator[](size_t index); + bool isCopy() const noexcept; + size_t size() const noexcept; + + private: + alias_ref array_; + T* elements_; + jboolean isCopy_; + size_t size_; + + PinnedPrimitiveArray(alias_ref) noexcept; + + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; +}; + + +namespace detail { + +class BaseJavaClass { +public: + typedef _jobject _javaobject; + typedef _javaobject* javaobject; +}; + +} + +// Together, these classes allow convenient use of any class with the fbjni +// helpers. To use: +// +// struct MyClass : public JavaClass { +// constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;"; +// }; +// +// alias_ref myClass = foo(); + +template +class JavaClass { +public: + // JNI pattern for jobject assignable pointer + struct _javaobject : public Base::_javaobject { + typedef T javaClass; + }; + typedef _javaobject* javaobject; + + static alias_ref javaClassStatic(); + static local_ref javaClassLocal(); +}; + +template +class JObjectWrapper::value && + std::is_class::type::javaClass>::value + >::type> + : public JObjectWrapper { +public: + static constexpr const char* kJavaDescriptor = + std::remove_pointer::type::javaClass::kJavaDescriptor; + + using JObjectWrapper::JObjectWrapper; + + template + JObjectWrapper(const JObjectWrapper& w) + : JObjectWrapper(w) { + static_assert(std::is_convertible::value, + "U must be convertible to T"); + } +}; + +}} + +#include "CoreClasses-inl.h" +// This is here because code in Meta-inl.h uses alias_ref, which +// requires JObjectWrapper to be concrete before it can work. +#include "Meta-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp new file mode 100644 index 00000000..20925ae8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "Exceptions.h" +#include "CoreClasses.h" +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +// CommonJniExceptions ///////////////////////////////////////////////////////////////////////////// + +class CommonJniExceptions { + public: + static void init(); + + static jclass getThrowableClass() { + return throwableClass_; + } + + static jclass getUnknownCppExceptionClass() { + return unknownCppExceptionClass_; + } + + 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; + + +// 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); + +void CommonJniExceptions::init() { + JNIEnv* env = internal::getEnv(); + FBASSERTMSGF(env, "Could not get JNI Environment"); + + // Throwable class + jclass localThrowableClass = env->FindClass("java/lang/Throwable"); + FBASSERT(localThrowableClass); + throwableClass_ = static_cast(env->NewGlobalRef(localThrowableClass)); + FBASSERT(throwableClass_); + env->DeleteLocalRef(localThrowableClass); + + // 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); + + // UnknownCppException object + jthrowable localUnknownCppExceptionObject = static_cast(env->NewObject( + unknownCppExceptionClass_, + unknownCppExceptionConstructorMID)); + FBASSERT(localUnknownCppExceptionObject); + unknownCppExceptionObject_ = static_cast(env->NewGlobalRef( + localUnknownCppExceptionObject)); + FBASSERT(unknownCppExceptionObject_); + env->DeleteLocalRef(localUnknownCppExceptionObject); + + // RuntimeException object + jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException"); + FBASSERT(localRuntimeExceptionClass); + + 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_); + + env->DeleteLocalRef(localRuntimeExceptionClass); + env->DeleteLocalRef(localRuntimeExceptionObject); +} + + +// 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!"); +} + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw Java exceptions + +namespace { + +void setJavaExceptionAndAbortOnFailure(jthrowable throwable) noexcept { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + if (throwable) { + env->Throw(throwable); + } + 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); + 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 +void throwPendingJniExceptionAsCppException() { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + if (env->ExceptionCheck() == JNI_FALSE) { + return; + } + + jthrowable throwable = 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(); + if (env->ExceptionCheck() == JNI_TRUE) { + throwPendingJniExceptionAsCppException(); + return; + } + + throw JniException(); +} + +void throwNewJavaException(jthrowable throwable) { + throw JniException(throwable); +} + +void throwNewJavaException(const char* throwableName, const char* msg) { + // If anything of the fbjni calls fail, an exception of a suitable + // form will be thrown, which is what we want. + auto throwableClass = findClassLocal(throwableName); + auto throwable = throwableClass->newObject( + throwableClass->getConstructor(), + make_jstring(msg).release()); + throwNewJavaException(throwable.get()); +} + +// Translate C++ to Java Exception + +void translatePendingCppExceptionToJavaException() noexcept { + assertIfExceptionsNotInitialized(); + try { + try { + throw; + } catch(const JniException& ex) { + ex.setJavaException(); + } catch(const std::ios_base::failure& ex) { + setNewJavaException("java/io/IOException", ex.what()); + } catch(const std::bad_alloc& ex) { + setNewJavaException("java/lang/OutOfMemoryError", ex.what()); + } catch(const std::out_of_range& ex) { + setNewJavaException("java/lang/ArrayIndexOutOfBoundsException", ex.what()); + } catch(const std::system_error& ex) { + setCppSystemErrorExceptionInJava(ex); + } catch(const std::runtime_error& ex) { + setNewJavaException("java/lang/RuntimeException", ex.what()); + } catch(const std::exception& ex) { + setNewJavaException("com/facebook/jni/CppException", ex.what()); + } catch(const char* msg) { + setNewJavaException(CommonJniExceptions::getUnknownCppExceptionClass(), msg); + } catch(...) { + setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getUnknownCppExceptionObject()); + } + } catch(...) { + // This block aborts the program, if something bad happens when handling exceptions, thus + // keeping this function noexcept. + std::abort(); + } +} + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; + +JniException::JniException() : JniException(CommonJniExceptions::getRuntimeExceptionObject()) { } + +JniException::JniException(jthrowable throwable) : isMessageExtracted_(false) { + assertIfExceptionsNotInitialized(); + throwableGlobalRef_ = static_cast(internal::getEnv()->NewGlobalRef(throwable)); + if (!throwableGlobalRef_) { + throw std::bad_alloc(); + } +} + +JniException::JniException(JniException &&rhs) + : throwableGlobalRef_(std::move(rhs.throwableGlobalRef_)), + 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; + } +} + +JniException::~JniException() noexcept { + if (throwableGlobalRef_) { + internal::getEnv()->DeleteGlobalRef(throwableGlobalRef_); + } +} + +jthrowable JniException::getThrowable() const noexcept { + return throwableGlobalRef_; +} + +// 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; + } + + try { + what_ = std::string(chars); + } catch(...) { + what_ = kExceptionMessageFailure_; + } + + env->ReleaseStringUTFChars(messageJString, chars); +} + +const char* JniException::what() const noexcept { + if (!isMessageExtracted_) { + populateWhat(); + } + return what_.c_str(); +} + +void JniException::setJavaException() const noexcept { + setJavaExceptionAndAbortOnFailure(throwableGlobalRef_); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h new file mode 100644 index 00000000..9ba9367f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h @@ -0,0 +1,130 @@ +/* + * 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. + */ + +/** + * @file Exceptions.h + * + * After invoking a JNI function that can throw a Java exception, the macro + * @ref FACEBOOK_JNI_THROW_PENDING_EXCEPTION() or @ref FACEBOOK_JNI_THROW_EXCEPTION_IF() + * should be invoked. + * + * IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! + * To use these methods you MUST call initExceptionHelpers() when your library is loaded. + */ + +#pragma once + +#include +#include +#include + +#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) + +namespace facebook { +namespace jni { + +namespace internal { + void initExceptionHelpers(); +} + +/** + * Before using any of the state initialized above, call this. It + * will assert if initialization has not yet occurred. + */ +void assertIfExceptionsNotInitialized(); + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +/** + * This class wraps a Java exception into a C++ exception; if the exception is routed back + * to the Java side, it can be unwrapped and just look like a pure Java interaction. The class + * is resilient to errors while creating the exception, falling back to some pre-allocated + * exceptions if a new one cannot be allocated or populated. + * + * Note: the what() method of this class is not thread-safe (t6900503). + */ +class JniException : public std::exception { + public: + JniException(); + + explicit JniException(jthrowable throwable); + + JniException(JniException &&rhs); + + JniException(const JniException &other); + + ~JniException() noexcept; + + jthrowable getThrowable() const noexcept; + + virtual const char* what() const noexcept; + + void setJavaException() const noexcept; + + private: + jthrowable throwableGlobalRef_; + mutable std::string what_; + mutable bool isMessageExtracted_; + const static std::string kExceptionMessageFailure_; + + void populateWhat() const noexcept; +}; + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw C++ exceptions + +void throwPendingJniExceptionAsCppException(); + +void throwCppExceptionIf(bool condition); + +static const int kMaxExceptionMessageBufferSize = 512; + +[[noreturn]] void throwNewJavaException(jthrowable); + +[[noreturn]] 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 +// translatePendingCppExceptionToJavaException is called at the +// topmost level of the native stack, the wrapped Java exception is +// 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); + snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...); + throwNewJavaException(throwableName, msg); +} + +// 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; + +// For convenience, some exception names in java.lang are available here. + +const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException"; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp new file mode 100644 index 00000000..ebcb778d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "Hybrid.h" + +#include "Exceptions.h" +#include "Registration.h" + +namespace facebook { +namespace jni { + +namespace detail { + +void setNativePointer(alias_ref hybridData, + std::unique_ptr new_value) { + static auto pointerField = hybridData->getClass()->getField("mNativePointer"); + auto* old_value = reinterpret_cast(hybridData->getFieldValue(pointerField)); + if (new_value) { + // Modify should only ever be called once with a non-null + // new_value. If this happens again it's a programmer error, so + // blow up. + FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice"); + } else if (old_value == 0) { + return; + } + // delete on a null pointer is defined to be a noop. + delete old_value; + // This releases ownership from the unique_ptr, and passes the pointer, and + // ownership of it, to HybridData which is managed by the java GC. The + // finalizer on hybridData calls resetNative which will delete the object, if + // reseetNative has not already been called. + hybridData->setFieldValue(pointerField, reinterpret_cast(new_value.release())); +} + +BaseHybridClass* getNativePointer(alias_ref hybridData) { + static auto pointerField = hybridData->getClass()->getField("mNativePointer"); + auto* value = reinterpret_cast(hybridData->getFieldValue(pointerField)); + if (!value) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return value; +} + +local_ref getHybridData(alias_ref jthis, + JField field) { + auto hybridData = jthis->getFieldValue(field); + if (!hybridData) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return hybridData; +} + +} + +namespace { + +void resetNative(alias_ref jthis) { + detail::setNativePointer(jthis, nullptr); +} + +} + +void HybridDataOnLoad() { + registerNatives("com/facebook/jni/HybridData", { + makeNativeMethod("resetNative", resetNative), + }); +} + +}} + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h new file mode 100644 index 00000000..8b62ccde --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h @@ -0,0 +1,277 @@ +/* + * 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 +#include +#include +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +namespace detail { + +class BaseHybridClass : public BaseJavaClass { +public: + virtual ~BaseHybridClass() {} +}; + +struct HybridData : public JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;"; +}; + +void setNativePointer(alias_ref hybridData, + std::unique_ptr new_value); +BaseHybridClass* getNativePointer(alias_ref hybridData); +local_ref getHybridData(alias_ref jthis, + JField field); + +// Normally, pass through types unmolested. +template +struct Convert { + typedef T jniType; + static jniType fromJni(jniType t) { + return t; + } + static jniType toJniRet(jniType t) { + return t; + } + static jniType toCall(jniType t) { + return t; + } +}; + +// This is needed for return conversion +template <> +struct Convert { + typedef void jniType; +}; + +// convert to std::string from jstring +template <> +struct Convert { + typedef jstring jniType; + static std::string fromJni(jniType t) { + return wrap_alias(t)->toStdString(); + } + static jniType toJniRet(const std::string& t) { + return make_jstring(t).release(); + } + static local_ref toCall(const std::string& t) { + return make_jstring(t); + } +}; + +// convert return from const char* +template <> +struct Convert { + typedef jstring jniType; + // no automatic synthesis of const char*. (It can't be freed.) + static jniType toJniRet(const char* t) { + return make_jstring(t).release(); + } + static local_ref toCall(const char* t) { + return make_jstring(t); + } +}; + +// jboolean is an unsigned char, not a bool. Allow it to work either way. +template<> +struct Convert { + typedef jboolean jniType; + static bool fromJni(jniType t) { + return t; + } + static jniType toJniRet(bool t) { + return t; + } + static jniType toCall(bool t) { + return t; + } +}; + +// convert to alias_ref from T +template +struct Convert> { + typedef T jniType; + static alias_ref fromJni(jniType t) { + return wrap_alias(t); + } + static jniType toJniRet(alias_ref t) { + return t.get(); + } + static jniType toCall(alias_ref t) { + return t.get(); + } +}; + +// convert return from local_ref +template +struct Convert> { + typedef T jniType; + // No automatic synthesis of local_ref + static jniType toJniRet(local_ref t) { + return t.release(); + } + static jniType toCall(local_ref t) { + return t.get(); + } +}; + +// convert return from global_ref +template +struct Convert> { + typedef T jniType; + // No automatic synthesis of global_ref + static jniType toJniRet(global_ref t) { + return t.get(); + } + static jniType toCall(global_ref t) { + return t.get(); + } +}; + +// In order to avoid potentially filling the jni locals table, +// temporary objects (right now, this is just jstrings) need to be +// released. This is done by returning a holder which autoconverts to +// jstring. This is only relevant when the jniType is passed down, as +// in newObjectJavaArgs. + +template +inline T callToJni(T&& t) { + return t; +} + +inline jstring callToJni(local_ref&& sref) { + return sref.get(); +} + +struct jstring_holder { + local_ref s_; + jstring_holder(const char* s) : s_(make_jstring(s)) {} + operator jstring() { return s_.get(); } +}; + +template +struct HybridRoot {}; + +template +struct HybridRoot::value>::type> + : public BaseHybridClass {}; + +} + +template +class HybridClass : public Base + , public detail::HybridRoot + , public JavaClass { +public: + typedef detail::HybridData::javaobject jhybriddata; + typedef typename JavaClass::javaobject jhybridobject; + + using JavaClass::javaClassStatic; + using JavaClass::javaClassLocal; + using JavaClass::javaobject; + typedef typename JavaClass::_javaobject _javaobject; + +protected: + typedef HybridClass HybridBase; + + // This ensures that a C++ hybrid part cannot be created on its own + // by default. If a hybrid wants to enable this, it can provide its + // own public ctor, or change the accessibility of this to public. + using Base::Base; + + static void registerHybrid(std::initializer_list methods) { + javaClassStatic()->registerNatives(methods); + } + + static local_ref makeHybridData(std::unique_ptr cxxPart) { + static auto dataCtor = detail::HybridData::javaClassStatic()->getConstructor(); + auto hybridData = detail::HybridData::javaClassStatic()->newObject(dataCtor); + detail::setNativePointer(hybridData, std::move(cxxPart)); + return hybridData; + } + + template + static local_ref makeCxxInstance(Args&&... args) { + return makeHybridData(std::unique_ptr(new T(std::forward(args)...))); + } + +public: + // Factory method for creating a hybrid object where the arguments + // are used to initialize the C++ part directly without passing them + // through java. This method requires the Java part to have a ctor + // which takes a HybridData, and for the C++ part to have a ctor + // compatible with the arguments passed here. For safety, the ctor + // can be private, and the hybrid declared a friend of its base, so + // the hybrid can only be created from here. + // + // Exception behavior: This can throw an exception if creating the + // C++ object fails, or any JNI methods throw. + template + static local_ref newObjectCxxArgs(Args&&... args) { + auto hybridData = makeCxxInstance(std::forward(args)...); + static auto ctor = javaClassStatic()->template getConstructor(); + return javaClassStatic()->newObject(ctor, hybridData.get()); + } + + // Factory method for creating a hybrid object where the arguments + // are passed to the java ctor. + template + static local_ref newObjectJavaArgs(Args&&... args) { + static auto ctor = + javaClassStatic()->template getConstructor< + jhybridobject(typename detail::Convert::type>::jniType...)>(); + // This can't use the same impl as Convert::toJniRet because that + // function sometimes creates and then releases local_refs, which + // could potentially cause the locals table to fill. Instead, we + // use two calls, one which can return a local_ref if needed, and + // a second which extracts its value. The lifetime of the + // local_ref is the expression, after which it is destroyed and + // the local_ref is cleaned up. + auto lref = + javaClassStatic()->newObject( + ctor, detail::callToJni( + detail::Convert::type>::toCall(args))...); + return lref; + } + + // If a hybrid class throws an exception which derives from + // std::exception, it will be passed to mapException on the hybrid + // class, or nearest ancestor. This allows boilerplate exception + // translation code (for example, calling throwNewJavaException on a + // particular java class) to be hoisted to a common function. If + // mapException returns, then the std::exception will be translated + // to Java. + static void mapException(const std::exception& ex) {} +}; + +// Given a *_ref object which refers to a hybrid class, this will reach inside +// of it, find the mHybridData, extract the C++ instance pointer, cast it to +// the appropriate type, and return it. +template +inline typename std::remove_pointer::type::javaClass* cthis(T jthis) { + static auto dataField = + jthis->getClass()->template getField("mHybridData"); + // I'd like to use dynamic_cast here, but -fno-rtti is the default. + auto* value = static_cast::type::javaClass*>( + detail::getNativePointer(detail::getHybridData(jthis, dataField))); + // This would require some serious programmer error. + FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field"); + return value; +} + +void HybridDataOnLoad(); + +} +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h new file mode 100644 index 00000000..d51157b1 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h @@ -0,0 +1,352 @@ +/* + * 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 + +#include "Common.h" +#include "Exceptions.h" + +namespace facebook { +namespace jni { + +// JMethod ///////////////////////////////////////////////////////////////////////////////////////// + +inline JMethodBase::JMethodBase(jmethodID method_id) noexcept + : method_id_{method_id} +{} + +inline JMethodBase::operator bool() const noexcept { + return method_id_ != nullptr; +} + +inline jmethodID JMethodBase::getId() const noexcept { + return method_id_; +} + +template +inline void JMethod::operator()(alias_ref self, Args... args) { + const auto env = internal::getEnv(); + env->CallVoidMethod(self.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_CALL") +#undef DEFINE_PRIMITIVE_CALL +#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD) \ +template \ +inline TYPE JMethod::operator()(alias_ref self, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->Call ## METHOD ## Method(self.get(), getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_CALL(jchar, Char) +DEFINE_PRIMITIVE_CALL(jshort, Short) +DEFINE_PRIMITIVE_CALL(jint, Int) +DEFINE_PRIMITIVE_CALL(jlong, Long) +DEFINE_PRIMITIVE_CALL(jfloat, Float) +DEFINE_PRIMITIVE_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_CALL") + +template +inline local_ref JMethod::operator()(alias_ref self, Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallObjectMethod(self.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + +template +inline void JStaticMethod::operator()(alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + env->CallStaticVoidMethod(cls.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_CALL") +#undef DEFINE_PRIMITIVE_STATIC_CALL +#define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD) \ +template \ +inline TYPE JStaticMethod::operator()(alias_ref cls, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->CallStatic ## METHOD ## Method(cls.get(), getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_STATIC_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_STATIC_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_STATIC_CALL(jchar, Char) +DEFINE_PRIMITIVE_STATIC_CALL(jshort, Short) +DEFINE_PRIMITIVE_STATIC_CALL(jint, Int) +DEFINE_PRIMITIVE_STATIC_CALL(jlong, Long) +DEFINE_PRIMITIVE_STATIC_CALL(jfloat, Float) +DEFINE_PRIMITIVE_STATIC_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_CALL") + +template +inline local_ref JStaticMethod::operator()(alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallStaticObjectMethod(cls.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + + +template +inline void +JNonvirtualMethod::operator()(alias_ref self, jclass cls, Args... args) { + const auto env = internal::getEnv(); + env->CallNonvirtualVoidMethod(self.get(), cls, getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_CALL +#define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD) \ +template \ +inline TYPE \ +JNonvirtualMethod::operator()(alias_ref self, jclass cls, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->CallNonvirtual ## METHOD ## Method(self.get(), cls, getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jchar, Char) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jshort, Short) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jint, Int) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jlong, Long) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jfloat, Float) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") + +template +inline local_ref JNonvirtualMethod::operator()( + alias_ref self, + jclass cls, + Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallNonvirtualObjectMethod(self.get(), cls, getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + + +// jtype_traits //////////////////////////////////////////////////////////////////////////////////// + +/// The generic way to associate a descriptor to a type is to look it up in the +/// corresponding @ref JObjectWrapper specialization. This makes it easy to add +/// support for your user defined type. +template +struct jtype_traits { + // The jni type signature (described at + // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html). + static std::string descriptor() { + static const auto descriptor = JObjectWrapper::kJavaDescriptor != nullptr ? + std::string{JObjectWrapper::kJavaDescriptor} : + JObjectWrapper::get_instantiated_java_descriptor(); + return descriptor; + } + + // The signature used for class lookups. See + // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName(). + static std::string base_name() { + if (JObjectWrapper::kJavaDescriptor != nullptr) { + std::string base_name = JObjectWrapper::kJavaDescriptor; + return base_name.substr(1, base_name.size() - 2); + } + return JObjectWrapper::get_instantiated_java_descriptor(); + } +}; + +#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") +#undef DEFINE_FIELD_AND_ARRAY_TRAIT + +#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC) \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{#DSC}; } \ + static std::string base_name() { return descriptor(); } \ +}; \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{"[" #DSC}; } \ + static std::string base_name() { return descriptor(); } \ +}; + +// There is no voidArray, handle that without the macro. +template<> +struct jtype_traits { + static std::string descriptor() { return std::string{"V"}; }; +}; + +DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z) +DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte, B) +DEFINE_FIELD_AND_ARRAY_TRAIT(jchar, C) +DEFINE_FIELD_AND_ARRAY_TRAIT(jshort, S) +DEFINE_FIELD_AND_ARRAY_TRAIT(jint, I) +DEFINE_FIELD_AND_ARRAY_TRAIT(jlong, J) +DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat, F) +DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble, D) + +#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") + + +// JField /////////////////////////////////////////////////////////////////////////////////////// + +template +inline JField::JField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_FIELD_PRIMITIVE_GET_SET +#define DEFINE_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JField::get(jobject object) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->Get ## METHOD ## Field(object, field_id_); \ +} \ + \ +template<> \ +inline void JField::set(jobject object, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->Set ## METHOD ## Field(object, field_id_, value); \ +} + +DEFINE_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") + +template +inline T JField::get(jobject object) const noexcept { + return static_cast(internal::getEnv()->GetObjectField(object, field_id_)); +} + +template +inline void JField::set(jobject object, T value) noexcept { + internal::getEnv()->SetObjectField(object, field_id_, static_cast(value)); +} + +// JStaticField ///////////////////////////////////////////////////////////////////////////////// + +template +inline JStaticField::JStaticField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JStaticField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JStaticField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET +#define DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JStaticField::get(jclass jcls) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->GetStatic ## METHOD ## Field(jcls, field_id_); \ +} \ + \ +template<> \ +inline void JStaticField::set(jclass jcls, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->SetStatic ## METHOD ## Field(jcls, field_id_, value); \ +} + +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") + +template +inline T JStaticField::get(jclass jcls) const noexcept { + const auto env = internal::getEnv(); + return static_cast(env->GetStaticObjectField(jcls, field_id_)); +} + +template +inline void JStaticField::set(jclass jcls, T value) noexcept { + internal::getEnv()->SetStaticObjectField(jcls, field_id_, value); +} + + +// jmethod_traits ////////////////////////////////////////////////////////////////////////////////// + +// TODO(T6608405) Adapt this to implement a register natives method that requires no descriptor +namespace internal { + +template +inline std::string JavaDescriptor() { + return jtype_traits::descriptor(); +} + +template +inline std::string JavaDescriptor() { + return JavaDescriptor() + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "(" + JavaDescriptor() + ")" + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "()" + JavaDescriptor(); +} + +} // internal + +template +inline std::string jmethod_traits::descriptor() { + return internal::JMethodDescriptor(); +} + +template +inline std::string jmethod_traits::constructor_descriptor() { + return internal::JMethodDescriptor(); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h new file mode 100644 index 00000000..de1bde0d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h @@ -0,0 +1,302 @@ +/* + * 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. + */ + +/** @file meta.h + * + * Provides wrappers for meta data such as methods and fields. + */ + +#pragma once + +#include +#include + +#include + +#include "References.h" + +namespace facebook { +namespace jni { + +/// Wrapper of a jmethodID. Provides a common base for JMethod specializations +class JMethodBase { + public: + /// Verify that the method is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jmethodID getId() const noexcept; + + protected: + /// Create a wrapper of a method id + explicit JMethodBase(jmethodID method_id = nullptr) noexcept; + + private: + jmethodID method_id_; +}; + + +/// Representation of a jmethodID +template +class JMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_METHOD_CLASS + +// Defining JMethod specializations based on return value +#define DEFINE_PRIMITIVE_METHOD_CLASS(TYPE) \ +template \ +class JMethod : public JMethodBase { \ + public: \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "TYPE must be primitive or void"); \ + \ + using JMethodBase::JMethodBase; \ + JMethod() noexcept {}; \ + JMethod(const JMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_METHOD_CLASS(void); +DEFINE_PRIMITIVE_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_METHOD_CLASS") +/// @endcond + + +/// JMethod specialization for references that wraps the return value in a @ref local_ref +template +class JMethod : public JMethodBase { + public: + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + using JMethodBase::JMethodBase; + JMethod() noexcept {}; + JMethod(const JMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref self, Args... args); + + friend class JObjectWrapper; +}; + + +/// Convenience type representing constructors +template +using JConstructor = JMethod; + +/// Representation of a jStaticMethodID +template +class JStaticMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_STATIC_METHOD_CLASS + +// Defining JStaticMethod specializations based on return value +#define DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(TYPE) \ +template \ +class JStaticMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JStaticMethod() noexcept {}; \ + JStaticMethod(const JStaticMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref cls, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(void); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") +/// @endcond + + +/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref +template +class JStaticMethod : public JMethodBase { + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + public: + using JMethodBase::JMethodBase; + JStaticMethod() noexcept {}; + JStaticMethod(const JStaticMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref cls, Args... args); + + friend class JObjectWrapper; +}; + +/// Representation of a jNonvirtualMethodID +template +class JNonvirtualMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS + +// Defining JNonvirtualMethod specializations based on return value +#define DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(TYPE) \ +template \ +class JNonvirtualMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JNonvirtualMethod() noexcept {}; \ + JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, jclass cls, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(void); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") +/// @endcond + + +/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref +template +class JNonvirtualMethod : public JMethodBase { + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + public: + using JMethodBase::JMethodBase; + JNonvirtualMethod() noexcept {}; + JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref self, jclass cls, Args... args); + + friend class JObjectWrapper; +}; + + +/** + * JField represents typed fields and simplifies their access. Note that object types return + * raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jobject object) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jobject object, T value) noexcept; + + friend class JObjectWrapper; +}; + + +/** + * JStaticField represents typed fields and simplifies their access. Note that object types + * return raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JStaticField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JStaticField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jclass jcls) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jclass jcls, T value) noexcept; + + friend class JObjectWrapper; + +}; + + +/// Type traits for Java types (currently providing Java type descriptors) +template +struct jtype_traits; + + +/// Type traits for Java methods (currently providing Java type descriptors) +template +struct jmethod_traits; + +/// Template magic to provide @ref jmethod_traits +template +struct jmethod_traits { + static std::string descriptor(); + static std::string constructor_descriptor(); +}; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h new file mode 100644 index 00000000..d60c9002 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h @@ -0,0 +1,123 @@ +/* + * 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 +#include +#include + +#include "Exceptions.h" +#include "References.h" + +namespace facebook { +namespace jni { + +/// @cond INTERNAL +namespace internal { + +// Statistics mostly provided for test (only updated if FBJNI_DEBUG_REFS is defined) +struct ReferenceStats { + std::atomic_uint locals_deleted, globals_deleted, weaks_deleted; + + void reset() noexcept; +}; + +extern ReferenceStats g_reference_stats; +} +/// @endcond + + +// LocalReferenceAllocator ///////////////////////////////////////////////////////////////////////// + +inline jobject LocalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Local new: %p", original); + auto ref = internal::getEnv()->NewLocalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void LocalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Local release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.locals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteLocalRef(reference); + } +} + +inline bool LocalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNILocalRefType; +} + + +// GlobalReferenceAllocator //////////////////////////////////////////////////////////////////////// + +inline jobject GlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Global new: %p", original); + auto ref = internal::getEnv()->NewGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void GlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.globals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteGlobalRef(reference); + } +} + +inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIGlobalRefType; +} + + +// WeakGlobalReferenceAllocator //////////////////////////////////////////////////////////////////// + +inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Weak global new: %p", original); + auto ref = internal::getEnv()->NewWeakGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void WeakGlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Weak Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.weaks_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteWeakGlobalRef(reference); + } +} + +inline bool WeakGlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIWeakGlobalRefType; +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h new file mode 100644 index 00000000..ee328e07 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +/** + * @file ReferenceAllocators.h + * + * Reference allocators are used to create and delete various classes of JNI references (local, + * global, and weak global). + */ + +#pragma once + +#include "Common.h" + +namespace facebook { namespace jni { + +/// Allocator that handles local references +class LocalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles global references +class GlobalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles weak global references +class WeakGlobalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// @cond INTERNAL +namespace internal { + +/** + * @return true iff env->GetObjectRefType is expected to work properly. + */ +bool doesGetObjectRefTypeWork(); + +} +/// @endcond + +}} + +#include "ReferenceAllocators-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h new file mode 100644 index 00000000..bae3d5d6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h @@ -0,0 +1,387 @@ +/* + * 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 +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +template +inline enable_if_t(), local_ref> adopt_local(T ref) noexcept { + return local_ref{ref}; +} + +template +inline enable_if_t(), global_ref> adopt_global(T ref) noexcept { + return global_ref{ref}; +} + +template +inline enable_if_t(), weak_ref> adopt_weak_global(T ref) noexcept { + return weak_ref{ref}; +} + + +template +inline enable_if_t(), alias_ref> wrap_alias(T ref) noexcept { + return alias_ref(ref); +} + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + + +template +inline enable_if_t(), T> getPlainJniReference(T ref) { + return ref; +} + +template +inline T getPlainJniReference(alias_ref ref) { + return ref.get(); +} + +template +inline T getPlainJniReference(const base_owned_ref& ref) { + return ref.getPlainJniReference(); +} + + +namespace internal { + +template +enable_if_t(), plain_jni_reference_t> make_ref(const T& reference) { + auto old_reference = getPlainJniReference(reference); + if (!old_reference) { + return nullptr; + } + + auto ref = Alloc{}.newReference(old_reference); + if (!ref) { + // Note that we end up here if we pass a weak ref that refers to a collected object. + // Thus, it's hard to come up with a reason why this function should be used with + // weak references. + throw std::bad_alloc{}; + } + + return static_cast>(ref); +} + +} + +template +enable_if_t(), local_ref>> +make_local(const T& ref) { + return adopt_local(internal::make_ref(ref)); +} + +template +enable_if_t(), global_ref>> +make_global(const T& ref) { + return adopt_global(internal::make_ref(ref)); +} + +template +enable_if_t(), weak_ref>> +make_weak(const T& ref) { + return adopt_weak_global(internal::make_ref(ref)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b) { + return isSameObject(getPlainJniReference(a), getPlainJniReference(b)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b) { + return !(a == b); +} + + +// base_owned_ref /////////////////////////////////////////////////////////////////////// + +template +inline constexpr base_owned_ref::base_owned_ref() noexcept + : object_{nullptr} +{} + +template +inline constexpr base_owned_ref::base_owned_ref( + std::nullptr_t t) noexcept + : object_{nullptr} +{} + +template +inline base_owned_ref::base_owned_ref( + const base_owned_ref& other) + : object_{Alloc{}.newReference(other.getPlainJniReference())} +{} + +template +template +inline base_owned_ref::base_owned_ref(const base_owned_ref& other) + : object_{Alloc{}.newReference(other.getPlainJniReference())} +{} + +template +inline facebook::jni::base_owned_ref::base_owned_ref( + T reference) noexcept + : object_{reference} { + assert(Alloc{}.verifyReference(reference)); + internal::dbglog("New wrapped ref=%p this=%p", getPlainJniReference(), this); +} + +template +inline base_owned_ref::base_owned_ref( + base_owned_ref&& other) noexcept + : object_{other.object_} { + internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other); + internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this); + // JObjectWrapper is a simple type and does not support move semantics so we explicitly + // clear other + other.object_.set(nullptr); +} + +template +template +base_owned_ref::base_owned_ref(base_owned_ref&& other) noexcept + : object_{other.object_} { + internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other); + internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this); + // JObjectWrapper is a simple type and does not support move semantics so we explicitly + // clear other + other.object_.set(nullptr); +} + +template +inline base_owned_ref::~base_owned_ref() noexcept { + reset(); + internal::dbglog("Ref destruct ref=%p this=%p", getPlainJniReference(), this); +} + +template +inline T base_owned_ref::release() noexcept { + auto value = getPlainJniReference(); + internal::dbglog("Ref release ref=%p this=%p", value, this); + object_.set(nullptr); + return value; +} + +template +inline void base_owned_ref::reset() noexcept { + reset(nullptr); +} + +template +inline void base_owned_ref::reset(T reference) noexcept { + if (getPlainJniReference()) { + assert(Alloc{}.verifyReference(reference)); + Alloc{}.deleteReference(getPlainJniReference()); + } + object_.set(reference); +} + +template +inline T base_owned_ref::getPlainJniReference() const noexcept { + return static_cast(object_.get()); +} + + +// weak_ref /////////////////////////////////////////////////////////////////////// + +template +inline weak_ref& weak_ref::operator=( + const weak_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline weak_ref& weak_ref::operator=( + weak_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + getPlainJniReference(), this, other.getPlainJniReference(), &other); + reset(other.release()); + return *this; +} + +template +local_ref weak_ref::lockLocal() { + return adopt_local(static_cast(LocalReferenceAllocator{}.newReference(getPlainJniReference()))); +} + +template +global_ref weak_ref::lockGlobal() { + return adopt_global(static_cast(GlobalReferenceAllocator{}.newReference(getPlainJniReference()))); +} + +template +inline void swap( + weak_ref& a, + weak_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.getPlainJniReference(), &a, b.getPlainJniReference(), &b); + using std::swap; + swap(a.object_, b.object_); +} + + +// basic_strong_ref //////////////////////////////////////////////////////////////////////////// + +template +inline basic_strong_ref& basic_strong_ref::operator=( + const basic_strong_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline basic_strong_ref& basic_strong_ref::operator=( + basic_strong_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + getPlainJniReference(), this, other.getPlainJniReference(), &other); + reset(other.release()); + return *this; +} + +template +inline alias_ref basic_strong_ref::releaseAlias() noexcept { + return wrap_alias(release()); +} + +template +inline basic_strong_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline T basic_strong_ref::get() const noexcept { + return getPlainJniReference(); +} + +template +inline JObjectWrapper* basic_strong_ref::operator->() noexcept { + return &object_; +} + +template +inline const JObjectWrapper* basic_strong_ref::operator->() const noexcept { + return &object_; +} + +template +inline JObjectWrapper& basic_strong_ref::operator*() noexcept { + return object_; +} + +template +inline const JObjectWrapper& basic_strong_ref::operator*() const noexcept { + return object_; +} + +template +inline void swap( + basic_strong_ref& a, + basic_strong_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.getPlainJniReference(), &a, b.getPlainJniReference(), &b); + using std::swap; + swap(a.object_, b.object_); +} + + +// alias_ref ////////////////////////////////////////////////////////////////////////////// + +template +inline constexpr alias_ref::alias_ref() noexcept + : object_{nullptr} +{} + +template +inline constexpr alias_ref::alias_ref(std::nullptr_t) noexcept + : object_{nullptr} +{} + +template +inline alias_ref::alias_ref(const alias_ref& other) noexcept + : object_{other.object_} +{} + + +template +inline alias_ref::alias_ref(T ref) noexcept + : object_{ref} { + assert( + LocalReferenceAllocator{}.verifyReference(ref) || + GlobalReferenceAllocator{}.verifyReference(ref)); +} + +template +template +inline alias_ref::alias_ref(alias_ref other) noexcept + : object_{other.get()} +{} + +template +template +inline alias_ref::alias_ref(const basic_strong_ref& other) noexcept + : object_{other.get()} +{} + +template +inline alias_ref& alias_ref::operator=(alias_ref other) noexcept { + swap(*this, other); + return *this; +} + +template +inline alias_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline T facebook::jni::alias_ref::get() const noexcept { + return static_cast(object_.get()); +} + +template +inline JObjectWrapper* alias_ref::operator->() noexcept { + return &object_; +} + +template +inline const JObjectWrapper* alias_ref::operator->() const noexcept { + return &object_; +} + +template +inline JObjectWrapper& alias_ref::operator*() noexcept { + return object_; +} + +template +inline const JObjectWrapper& alias_ref::operator*() const noexcept { + return object_; +} + +template +inline void swap(alias_ref& a, alias_ref& b) noexcept { + using std::swap; + swap(a.object_, b.object_); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp new file mode 100644 index 00000000..0ee4c9e8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "References.h" + +namespace facebook { +namespace jni { + +JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity) + : env_(env) { + hasFrame_ = false; + auto pushResult = env->PushLocalFrame(capacity); + FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0); + hasFrame_ = true; +} + +JniLocalScope::~JniLocalScope() { + if (hasFrame_) { + env_->PopLocalFrame(nullptr); + } +} + +namespace internal { + +// Default implementation always returns true. +// Platform-specific sources can override this. +bool doesGetObjectRefTypeWork() __attribute__ ((weak)); +bool doesGetObjectRefTypeWork() { + return true; +} + +} + +} +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h new file mode 100644 index 00000000..575f2cc6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h @@ -0,0 +1,515 @@ +/* + * 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. + */ + + +/** @file References.h + * + * Functionality similar to smart pointers, but for references into the VM. Four main reference + * types are provided: local_ref, global_ref, weak_ref, and alias_ref. All are generic + * templates that and refer to objects in the jobject hierarchy. The type of the referred objects + * are specified using the template parameter. All reference types except alias_ref own their + * underlying reference, just as a std smart pointer owns the underlying raw pointer. In the context + * of std smart pointers, these references behave like unique_ptr, and have basically the same + * interface. Thus, when the reference is destructed, the plain JNI reference, i.e. the underlying + * JNI reference (like the parameters passed directly to JNI functions), is released. The alias + * references provides no ownership and is a simple wrapper for plain JNI references. + * + * All but the weak references provides access to the underlying object using dereferencing, and a + * get() method. It is also possible to convert these references to booleans to test for nullity. + * To access the underlying object of a weak reference, the reference must either be released, or + * the weak reference can be used to create a local or global reference. + * + * An owning reference is created either by moving the reference from an existing owned reference, + * by copying an existing owned reference (which creates a new underlying reference), by using the + * default constructor which initialize the reference to nullptr, or by using a helper function. The + * helper function exist in two flavors: make_XXX or adopt_XXX. + * + * Adopting takes a plain JNI reference and wrap it in an owned reference. It takes ownership of the + * plain JNI reference so be sure that no one else owns the reference when you adopt it, and make + * sure that you know what kind of reference it is. + * + * New owned references can be created from existing plain JNI references, alias references, local + * references, and global references (i.e. non-weak references) using the make_local, make_global, + * and make_weak functions. + * + * Alias references can be implicitly initialized using global, local and plain JNI references using + * the wrap_alias function. Here, we don't assume ownership of the passed-in reference, but rather + * create a separate reference that we do own, leaving the passed-in reference to its fate. + * + * Similar rules apply for assignment. An owned reference can be copy or move assigned using a smart + * reference of the same type. In the case of copy assignment a new reference is created. Alias + * reference can also be assigned new values, but since they are simple wrappers of plain JNI + * references there is no move semantics involved. + * + * Alias references are special in that they do not own the object and can therefore safely be + * converted to and from its corresponding plain JNI reference. They are useful as parameters of + * functions that do not affect the lifetime of a reference. Usage can be compared with using plain + * JNI pointers as parameters where a function does not take ownership of the underlying object. + * + * The local, global, and alias references makes it possible to access methods in the underlying + * objects. A core set of classes are implemented in CoreClasses.h, and user defined wrappers are + * supported (see example below). The wrappers also supports inheritance so a wrapper can inherit + * from another wrapper to gain access to its functionality. As an example the jstring wrapper + * inherits from the jobject wrapper, so does the jclass wrapper. That means that you can for + * example call the toString() method using the jclass wrapper, or any other class that inherits + * from the jobject wrapper. + * + * Note that the wrappers are parameterized on the static type of your (jobject) pointer, thus if + * you have a jobject that refers to a Java String you will need to cast it to jstring to get the + * jstring wrapper. This also mean that if you make a down cast that is invalid there will be no one + * stopping you and the wrappers currently does not detect this which can cause crashes. Thus, cast + * wisely. + * + * @include WrapperSample.cpp + */ + +#pragma once + +#include +#include +#include + +#include + +#include "ReferenceAllocators.h" +#include "TypeTraits.h" + +namespace facebook { +namespace jni { + +/** + * The JObjectWrapper is specialized to provide functionality for various Java classes, some + * specializations are provided, and it is easy to add your own. See example + * @sample WrapperSample.cpp + */ +template +class JObjectWrapper; + + +template +class base_owned_ref; + +template +class basic_strong_ref; + +template +class weak_ref; + +template +class alias_ref; + + +/// A smart unique reference owning a local JNI reference +template +using local_ref = basic_strong_ref; + +/// A smart unique reference owning a global JNI reference +template +using global_ref = basic_strong_ref; + + +/// Convenience function to wrap an existing local reference +template +enable_if_t(), local_ref> adopt_local(T ref) noexcept; + +/// Convenience function to wrap an existing global reference +template +enable_if_t(), global_ref> adopt_global(T ref) noexcept; + +/// Convenience function to wrap an existing weak reference +template +enable_if_t(), weak_ref> adopt_weak_global(T ref) noexcept; + + +/** + * Create a new local reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned local reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), local_ref>> +make_local(const T& r); + +/** + * Create a new global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned global reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), global_ref>> +make_global(const T& r); + +/** + * Create a new weak global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned weak global reference (referring to null if the input does) + * @throws std::bad_alloc if the returned reference is null + */ +template +enable_if_t(), weak_ref>> +make_weak(const T& r); + + +/// Swaps two owning references of the same type +template +void swap(weak_ref& a, weak_ref& b) noexcept; + +/// Swaps two owning references of the same type +template +void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; + +/** + * Retrieve the plain reference from a plain reference. + */ +template +enable_if_t(), T> getPlainJniReference(T ref); + +/** + * Retrieve the plain reference from an alias reference. + */ +template +T getPlainJniReference(alias_ref ref); + +/** + * Retrieve the plain JNI reference from any reference owned reference. + */ +template +T getPlainJniReference(const base_owned_ref& ref); + +/** + * Compare two references to see if they refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b); + +/** + * Compare two references to see if they don't refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b); + + +template +class base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + /** + * Release the ownership and set the reference to null. Thus no deleter is invoked. + * @return Returns the reference + */ + T release() noexcept; + + /** + * Reset the reference to refer to nullptr. + */ + void reset() noexcept; + + protected: + + JObjectWrapper object_; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit base_owned_ref(T reference) noexcept; + + /// Create a null reference + constexpr base_owned_ref() noexcept; + + /// Create a null reference + constexpr explicit base_owned_ref(std::nullptr_t) noexcept; + + /// Copy constructor (note creates a new reference) + base_owned_ref(const base_owned_ref& other); + template + base_owned_ref(const base_owned_ref& other); + + /// Transfers ownership of an underlying reference from one unique reference to another + base_owned_ref(base_owned_ref&& other) noexcept; + template + base_owned_ref(base_owned_ref&& other) noexcept; + + /// The delete the underlying reference if applicable + ~base_owned_ref() noexcept; + + + /// Assignment operator (note creates a new reference) + base_owned_ref& operator=(const base_owned_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + base_owned_ref& operator=(base_owned_ref&& rhs) noexcept; + + + T getPlainJniReference() const noexcept; + + void reset(T reference) noexcept; + + + friend T jni::getPlainJniReference<>(const base_owned_ref& ref); + + template + friend class base_owned_ref; +}; + + +/** + * A smart reference that owns its underlying JNI reference. The class provides basic + * functionality to handle a reference but gives no access to it unless the reference is + * released, thus no longer owned. The API is stolen with pride from unique_ptr and the + * semantics should be basically the same. This class should not be used directly, instead use + * @ref weak_ref + */ +template +class weak_ref : public base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + using Allocator = WeakGlobalReferenceAllocator; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + + /// Create a null reference + constexpr weak_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + constexpr explicit weak_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + weak_ref(const weak_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + weak_ref(weak_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + + /// Assignment operator (note creates a new reference) + weak_ref& operator=(const weak_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + weak_ref& operator=(weak_ref&& rhs) noexcept; + + + // Creates an owned local reference to the referred object or to null if the object is reclaimed + local_ref lockLocal(); + + // Creates an owned global reference to the referred object or to null if the object is reclaimed + global_ref lockGlobal(); + + private: + + using base_owned_ref::getPlainJniReference; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit weak_ref(T reference) noexcept + : base_owned_ref{reference} {} + + + template friend class weak_ref; + friend weak_ref(), T>> + adopt_weak_global(T ref) noexcept; + friend void swap(weak_ref& a, weak_ref& b) noexcept; +}; + + +/** + * A class representing owned strong references to Java objects. This class + * should not be used directly, instead use @ref local_ref, or @ref global_ref. + */ +template +class basic_strong_ref : public base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + using Allocator = Alloc; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + using base_owned_ref::release; + using base_owned_ref::reset; + + /// Create a null reference + constexpr basic_strong_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + constexpr explicit basic_strong_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + basic_strong_ref(const basic_strong_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + basic_strong_ref(basic_strong_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + /// Assignment operator (note creates a new reference) + basic_strong_ref& operator=(const basic_strong_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + basic_strong_ref& operator=(basic_strong_ref&& rhs) noexcept; + + + /// Release the ownership of the reference and return the wrapped reference in an alias + alias_ref releaseAlias() noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Get the plain JNI reference + T get() const noexcept; + + /// Access the functionality provided by the object wrappers + JObjectWrapper* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const JObjectWrapper* operator->() const noexcept; + + /// Provide a reference to the underlying wrapper (be sure that it is non-null before invoking) + JObjectWrapper& operator*() noexcept; + + /// Provide a const reference to the underlying wrapper (be sure that it is non-null + /// before invoking) + const JObjectWrapper& operator*() const noexcept; + + private: + + using base_owned_ref::object_; + using base_owned_ref::getPlainJniReference; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit basic_strong_ref(T reference) noexcept + : base_owned_ref{reference} {} + + + friend enable_if_t(), local_ref> adopt_local(T ref) noexcept; + friend enable_if_t(), global_ref> adopt_global(T ref) noexcept; + friend void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; +}; + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + +/// Swaps to alias referencec of the same type +template +void swap(alias_ref& a, alias_ref& b) noexcept; + +/** + * A non-owning variant of the smart references (a dumb reference). These references still provide + * access to the functionality of the @ref JObjectWrapper specializations including exception + * handling and ease of use. Use this representation when you don't want to claim ownership of the + * underlying reference (compare to using raw pointers instead of smart pointers.) For symmetry use + * @ref alias_ref instead of this class. + */ +template +class alias_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + + + /// Create a null reference + constexpr alias_ref() noexcept; + + /// Create a null reference + constexpr alias_ref(std::nullptr_t) noexcept; + + /// Copy constructor + alias_ref(const alias_ref& other) noexcept; + + /// Wrap an existing plain JNI reference + alias_ref(T ref) noexcept; + + /// Wrap an existing smart reference of any type convertible to T + template(), T>> + alias_ref(alias_ref other) noexcept; + + /// Wrap an existing alias reference of a type convertible to T + template(), T>> + alias_ref(const basic_strong_ref& other) noexcept; + + + /// Assignment operator + alias_ref& operator=(alias_ref other) noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Converts back to a plain JNI reference + T get() const noexcept; + + /// Access the functionality provided by the object wrappers + JObjectWrapper* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const JObjectWrapper* operator->() const noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + JObjectWrapper& operator*() noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + const JObjectWrapper& operator*() const noexcept; + + private: + JObjectWrapper object_; + + friend void swap(alias_ref& a, alias_ref& b) noexcept; +}; + + +/** + * RAII object to create a local JNI frame, using PushLocalFrame/PopLocalFrame. + * + * This is useful when you have a call which is initiated from C++-land, and therefore + * doesn't automatically get a local JNI frame managed for you by the JNI framework. + */ +class JniLocalScope { +public: + JniLocalScope(JNIEnv* p_env, jint capacity); + ~JniLocalScope(); + +private: + JNIEnv* env_; + bool hasFrame_; +}; + +}} + +#include "References-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h new file mode 100644 index 00000000..29414d19 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h @@ -0,0 +1,193 @@ +/* + * 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 "Exceptions.h" +#include "Hybrid.h" + +namespace facebook { +namespace jni { + +namespace detail { + +// convert to HybridClass* from jhybridobject +template +struct Convert< + T, typename std::enable_if< + std::is_base_of::type>::value>::type> { + typedef typename std::remove_pointer::type::jhybridobject jniType; + static T fromJni(jniType t) { + if (t == nullptr) { + return nullptr; + } + return facebook::jni::cthis(wrap_alias(t)); + } + // There is no automatic return conversion for objects. +}; + +// registration wrapper for legacy JNI-style functions + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(JNIEnv*, C, Args... args)) { + struct funcWrapper { + static void call(JNIEnv* env, jobject obj, Args... args) { + // Note that if func was declared noexcept, then both gcc and clang are smart + // enough to elide the try/catch. + try { + (*func)(env, static_cast(obj), args...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) { + struct funcWrapper { + static R call(JNIEnv* env, jobject obj, Args... args) { + try { + return (*func)(env, static_cast(obj), args...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return R{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +// registration wrappers for functions, with autoconversion of arguments. + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(alias_ref, Args... args)) { + struct funcWrapper { + static void call(JNIEnv*, jobject obj, + typename Convert::type>::jniType... args) { + try { + (*func)(static_cast(obj), Convert::type>::fromJni(args)...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref, Args... args)) { + struct funcWrapper { + typedef typename Convert::type>::jniType jniRet; + + static jniRet call(JNIEnv*, jobject obj, + typename Convert::type>::jniType... args) { + try { + return Convert::type>::toJniRet( + (*func)(static_cast(obj), Convert::type>::fromJni(args)...)); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return jniRet{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +// registration wrappers for non-static methods, with autoconvertion of arguments. + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)) { + struct funcWrapper { + static void call(JNIEnv* env, jobject obj, + typename Convert::type>::jniType... args) { + try { + try { + auto aref = wrap_alias(static_cast(obj)); + // This is usually a noop, but if the hybrid object is a + // base class of other classes which register JNI methods, + // this will get the right type for the registered method. + auto cobj = static_cast(facebook::jni::cthis(aref)); + (cobj->*method)(Convert::type>::fromJni(args)...); + } catch (const std::exception& ex) { + C::mapException(ex); + throw; + } + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) { + struct funcWrapper { + typedef typename Convert::type>::jniType jniRet; + + static jniRet call(JNIEnv* env, jobject obj, + typename Convert::type>::jniType... args) { + try { + try { + auto aref = wrap_alias(static_cast(obj)); + // This is usually a noop, but if the hybrid object is a + // base class of other classes which register JNI methods, + // this will get the right type for the registered method. + auto cobj = static_cast(facebook::jni::cthis(aref)); + return Convert::type>::toJniRet( + (cobj->*method)(Convert::type>::fromJni(args)...)); + } catch (const std::exception& ex) { + C::mapException(ex); + throw; + } + } catch (...) { + translatePendingCppExceptionToJavaException(); + return jniRet{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) { + return jmethod_traits::descriptor(); +} + +template +inline std::string makeDescriptor(R (*)(alias_ref, Args... args)) { + typedef typename Convert::type>::jniType jniRet; + return jmethod_traits::type>::jniType...)> + ::descriptor(); +} + +template +inline std::string makeDescriptor(R (C::*)(Args... args)) { + typedef typename Convert::type>::jniType jniRet; + return jmethod_traits::type>::jniType...)> + ::descriptor(); +} + +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h new file mode 100644 index 00000000..e690f96b --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h @@ -0,0 +1,87 @@ +/* + * 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 +#include "References.h" + +namespace facebook { +namespace jni { + +namespace detail { + +// This uses the real JNI function as a non-type template parameter to +// cause a (static member) function to exist with the same signature, +// but with try/catch exception translation. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(JNIEnv*, jobject, Args... args)); + +// Same as above, but for non-void return types. +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(JNIEnv*, jobject, Args... args)); + +// Automatically wrap object argument, and don't take env explicitly. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(alias_ref, Args... args)); + +// Automatically wrap object argument, and don't take env explicitly, +// non-void return type. +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(alias_ref, Args... args)); + +// Extract C++ instance from object, and invoke given method on it. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)); + +// Extract C++ instance from object, and invoke given method on it, +// non-void return type +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(JNIEnv*, C, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(alias_ref, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (C::*method0)(Args... args)); + +} + +// We have to use macros here, because the func needs to be used +// as both a decltype expression argument and as a non-type template +// parameter, since C++ provides no way for translateException +// to deduce the type of its non-type template parameter. +// The empty string in the macros below ensures that name +// is always a string literal (because that syntax is only +// valid when name is a string literal). +#define makeNativeMethod2(name, func) \ + { name "", ::facebook::jni::detail::makeDescriptor(&func), \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +#define makeNativeMethod3(name, desc, func) \ + { name "", desc, \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +// Variadic template hacks to get macros with different numbers of +// arguments. Usage instructions are in CoreClasses.h. +#define makeNativeMethodN(a, b, c, count, ...) makeNativeMethod ## count +#define makeNativeMethod(...) makeNativeMethodN(__VA_ARGS__, 3, 2)(__VA_ARGS__) + +}} + +#include "Registration-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h new file mode 100644 index 00000000..b4bdd15e --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h @@ -0,0 +1,149 @@ +/* + * 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 + +namespace facebook { +namespace jni { + +/// Generic std::enable_if helper +template +using enable_if_t = typename std::enable_if::type; + +/// Generic std::is_convertible helper +template +constexpr bool IsConvertible() { + return std::is_convertible::value; +} + +template class TT, typename T> +struct is_instantiation_of : std::false_type {}; + +template class TT, typename... Ts> +struct is_instantiation_of> : std::true_type {}; + +template class TT, typename... Ts> +constexpr bool IsInstantiationOf() { + return is_instantiation_of::value; +} + +/// Metafunction to determine whether a type is a JNI reference or not +template +struct is_plain_jni_reference : + std::integral_constant::value && + std::is_base_of< + typename std::remove_pointer::type, + typename std::remove_pointer::type>::value> {}; + +/// Helper to simplify use of is_plain_jni_reference +template +constexpr bool IsPlainJniReference() { + return is_plain_jni_reference::value; +} + +/// Metafunction to determine whether a type is a primitive JNI type or not +template +struct is_jni_primitive : + std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> {}; + +/// Helper to simplify use of is_jni_primitive +template +constexpr bool IsJniPrimitive() { + return is_jni_primitive::value; +} + +/// Metafunction to determine if a type is a scalar (primitive or reference) JNI type +template +struct is_jni_scalar : + std::integral_constant::value || + is_jni_primitive::value> {}; + +/// Helper to simplify use of is_jni_scalar +template +constexpr bool IsJniScalar() { + return is_jni_scalar::value; +} + +// Metafunction to determine if a type is a JNI type +template +struct is_jni_type : + std::integral_constant::value || + std::is_void::value> {}; + +/// Helper to simplify use of is_jni_type +template +constexpr bool IsJniType() { + return is_jni_type::value; +} + +template +class weak_global_ref; + +template +class basic_strong_ref; + +template +class alias_ref; + +template +struct is_non_weak_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsNonWeakReference() { + return is_non_weak_reference::value; +} + +template +struct is_any_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsAnyReference() { + return is_any_reference::value; +} + +template +struct reference_traits { + static_assert(IsPlainJniReference(), "Need a plain JNI reference"); + using plain_jni_reference_t = T; +}; + +template