diff --git a/examples/ReactExample/android/app/build.gradle b/examples/ReactExample/android/app/build.gradle index 51daba44..18f845a3 100644 --- a/examples/ReactExample/android/app/build.gradle +++ b/examples/ReactExample/android/app/build.gradle @@ -73,6 +73,6 @@ android { dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.facebook.react:react-native:0.18.0-patched' + compile 'com.facebook.react:react-native:0.18.0' compile project(':realm') } diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 8fba13f8..0ae44443 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -285,5 +285,5 @@ afterEvaluate { project -> dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'org.nanohttpd:nanohttpd:2.2.0' - compile 'com.facebook.react:react-native:0.18.0-patched' + compile 'com.facebook.react:react-native:0.18.0' } diff --git a/react-native/android/publish_android_template b/react-native/android/publish_android_template index fd855c05..ec5e9c81 100644 --- a/react-native/android/publish_android_template +++ b/react-native/android/publish_android_template @@ -29,5 +29,5 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'org.nanohttpd:nanohttpd:2.2.0' - compile 'com.facebook.react:react-native:0.18.0-patched' + compile 'com.facebook.react:react-native:0.18.0' } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactAndroid.java b/react-native/android/src/main/java/io/realm/react/RealmReactAndroid.java index 6f51abb0..cd96a6f8 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactAndroid.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactAndroid.java @@ -21,19 +21,23 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule { private static final int DEFAULT_PORT = 8082; private AndroidWebServer webServer; - private long rpcServerPtr; - - private String filesDirPath; private Handler handler = new Handler(Looper.getMainLooper()); + static { + SoLoader.loadLibrary("realmreact"); + } + public RealmReactAndroid(ReactApplicationContext reactContext) { super(reactContext); + + String fileDir; try { - filesDirPath = getReactApplicationContext().getFilesDir().getCanonicalPath(); + fileDir = reactContext.getFilesDir().getCanonicalPath(); } catch (IOException e) { throw new IllegalStateException(e); } - SoLoader.loadLibrary("realmreact"); + + setDefaultRealmFileDirectory(fileDir); } @Override @@ -43,31 +47,18 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule { @Override public Map getConstants() { - long contexts = injectRealmJsContext(filesDirPath); - - if (contexts == -1) { - Log.e("RealmReactAndroid", "Error during initializing Realm context"); - throw new IllegalStateException("Error during initializing Realm context"); - } - - Log.i("RealmReactAndroid", "Initialized " + contexts + " contexts"); - - if (contexts == 0) {// we're running in Chrome debug mode - startWebServer(); - } + // FIXME: Only start web server when in Chrome debug mode! + startWebServer(); return Collections.EMPTY_MAP; } @Override public void onCatalystInstanceDestroy() { - if (webServer != null) { - Log.i("RealmReactAndroid", "Stopping the debugging Webserver"); - webServer.stop(); - } + stopWebServer(); } private void startWebServer() { - rpcServerPtr = setupChromeDebugModeRealmJsContext(); + setupChromeDebugModeRealmJsContext(); webServer = new AndroidWebServer(DEFAULT_PORT); try { webServer.start(); @@ -77,7 +68,13 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule { } } - // WebServer + private void stopWebServer() { + if (webServer != null) { + Log.i("RealmReactAndroid", "Stopping the webserver"); + webServer.stop(); + } + } + class AndroidWebServer extends NanoHTTPD { public AndroidWebServer(int port) { super(port); @@ -105,7 +102,7 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule { handler.post(new Runnable() { @Override public void run() { - jsonResponse[0] = processChromeDebugCommand(rpcServerPtr, cmdUri, json); + jsonResponse[0] = processChromeDebugCommand(cmdUri, json); latch.countDown(); } }); @@ -122,11 +119,11 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule { } // fileDir: path of the internal storage of the application - private native long injectRealmJsContext(String fileDir); + private native void setDefaultRealmFileDirectory(String fileDir); // responsible for creating the rpcServer that will accept the chrome Websocket command private native long setupChromeDebugModeRealmJsContext(); // this receives one command from Chrome debug then return the processing we should post back - private native String processChromeDebugCommand(long rpcServerPointer, String cmd, String args); + private native String processChromeDebugCommand(String cmd, String args); } diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 253b5da6..2ee3a48f 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -25,6 +25,7 @@ LOCAL_SRC_FILES := \ src/rpc.cpp \ src/android/platform.cpp \ src/android/io_realm_react_RealmReactAndroid.cpp \ + src/android/jsc_override.cpp \ src/object-store/index_set.cpp \ src/object-store/list.cpp \ src/object-store/object_schema.cpp \ diff --git a/src/android/io_realm_react_RealmReactAndroid.cpp b/src/android/io_realm_react_RealmReactAndroid.cpp index bdede139..c5f454e2 100644 --- a/src/android/io_realm_react_RealmReactAndroid.cpp +++ b/src/android/io_realm_react_RealmReactAndroid.cpp @@ -3,88 +3,42 @@ */ #include -#include -#include -#include - -#include "io_realm_react_RealmReactAndroid.h" -#include "js_init.h" -#include "rpc.hpp" -#include "platform.hpp" -#include "shared_realm.hpp" -#include #include -namespace facebook { - namespace react { - class JSCExecutor; - } -} +#include "io_realm_react_RealmReactAndroid.h" +#include "rpc.hpp" +#include "platform.hpp" -/* - * Class: io_realm_react_RealmReactAndroid - * Method: injectRealmJsContext - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsContext +static realm_js::RPCServer *s_rpc_server; + +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactAndroid_setDefaultRealmFileDirectory (JNIEnv *env, jclass, jstring fileDir) - { - __android_log_print(ANDROID_LOG_DEBUG, "JSRealm", "Java_io_realm_react_RealmReactAndroid_injectRealmJsContext"); - void* handle = dlopen ("libreactnativejni.so", RTLD_LAZY); - if (!handle) { - __android_log_print(ANDROID_LOG_ERROR, "JSRealm", "Cannot open libreactnativejni.so"); - return -1; - } +{ + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setDefaultRealmFileDirectory"); - // Getting the internal storage path for the application - const char* strFileDir = env->GetStringUTFChars(fileDir , NULL); + // Setting the internal storage path for the application + const char* strFileDir = env->GetStringUTFChars(fileDir, NULL); realm::set_default_realm_file_directory(strFileDir); env->ReleaseStringUTFChars(fileDir , strFileDir); - // load the symbol - typedef std::unordered_map (*get_jsc_context_t)(); + __android_log_print(ANDROID_LOG_DEBUG, "JSRealm", "Absolute path: %s", realm::default_realm_file_directory().c_str()); +} - get_jsc_context_t get_jsc_context = (get_jsc_context_t) dlsym(handle, "get_jsc_context"); - - if (get_jsc_context != NULL) { - // clearing previous instances - realm::Realm::s_global_cache.clear(); - std::unordered_map s_globalContextRefToJSCExecutor = get_jsc_context(); - for (auto pair : s_globalContextRefToJSCExecutor) { - RJSInitializeInContext(pair.first); - } - return s_globalContextRefToJSCExecutor.size(); - } else { - __android_log_print(ANDROID_LOG_ERROR, "JSRealm", "Cannot find symbol get_jsc_context"); - return -1; - } - } - -/* - * Class: io_realm_react_RealmReactAndroid - * Method: setupChromeDebugModeRealmJsContext - */ - static realm_js::RPCServer *s_rpc_server; - JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugModeRealmJsContext +JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugModeRealmJsContext (JNIEnv *, jclass) - { - __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Java_com_reacttests_RealmReactAndroid_setupChromeDebugModeRealmJsContext"); +{ + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setupChromeDebugModeRealmJsContext"); if (s_rpc_server) { - delete s_rpc_server; + delete s_rpc_server; } s_rpc_server = new realm_js::RPCServer(); return (jlong)s_rpc_server; - } +} -/* - * Class: io_realm_react_RealmReactAndroid - * Method: processChromeDebugCommand - */ - - JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand - (JNIEnv *env, jclass, jlong rpc_server_ptr, jstring chrome_cmd, jstring chrome_args) - { - __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand"); +JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand + (JNIEnv *env, jclass, jstring chrome_cmd, jstring chrome_args) +{ + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "processChromeDebugCommand"); const char* cmd = env->GetStringUTFChars(chrome_cmd, NULL); const char* args = env->GetStringUTFChars(chrome_args, NULL); realm_js::json json = realm_js::json::parse(args); @@ -92,4 +46,4 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsConte env->ReleaseStringUTFChars(chrome_cmd, cmd); env->ReleaseStringUTFChars(chrome_args, args); return env->NewStringUTF(response.dump().c_str()); - } +} diff --git a/src/android/io_realm_react_RealmReactAndroid.h b/src/android/io_realm_react_RealmReactAndroid.h index 33f11ef7..4c5276e5 100644 --- a/src/android/io_realm_react_RealmReactAndroid.h +++ b/src/android/io_realm_react_RealmReactAndroid.h @@ -7,12 +7,12 @@ #ifdef __cplusplus extern "C" { #endif + /* - * Class: io_realm_react_RealmReactAndroid - * Method: injectRealmJsContext - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsContext +* Class: io_realm_react_RealmReactAndroid +* Method: setDefaultRealmFileDirectory +*/ +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactAndroid_setDefaultRealmFileDirectory (JNIEnv *, jclass, jstring); /* @@ -24,11 +24,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugMo /* * Class: io_realm_react_RealmReactAndroid - * Method: processsetupChromeDebugCommand + * Method: processChromeDebugCommand */ JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand - (JNIEnv *, jclass, jlong, jstring, jstring); - + (JNIEnv *, jclass, jstring, jstring); #ifdef __cplusplus } diff --git a/src/android/jsc_override.cpp b/src/android/jsc_override.cpp new file mode 100644 index 00000000..1d5f90c0 --- /dev/null +++ b/src/android/jsc_override.cpp @@ -0,0 +1,94 @@ +/* Copyright 2016 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + +#include +#include +#include +#include +#include +#include + +#include "js_init.h" +#include "shared_realm.hpp" + +#if __arm__ +#define HOOK_SIZE 8 +#else +#define HOOK_SIZE 5 +#endif + +static void swap_function() __attribute__((constructor)); + +static JSGlobalContextRef create_context(JSContextGroupRef group, JSClassRef global_class) +{ + static std::mutex s_mutex; + std::lock_guard lock(s_mutex); + + // Replace JSGlobalContextCreateInGroup with its original implementation and call it. + swap_function(); + JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(group, global_class); + + // Reinstall the hook. + swap_function(); + + // Clear cache from previous instances. + realm::Realm::s_global_cache.clear(); + + RJSInitializeInContext(ctx); + return ctx; +} + +static void swap_function() +{ + static int8_t s_orig_code[HOOK_SIZE]; + static bool s_swapped = false; + + int8_t *orig_func = (int8_t*)&JSGlobalContextCreateInGroup; + int8_t *new_func = (int8_t*)&create_context; + +#if __arm__ + bool orig_thumb = (uintptr_t)orig_func % 4 != 0; + if (orig_thumb) { + orig_func--; + } +#endif + + size_t page_size = sysconf(_SC_PAGESIZE); + uintptr_t page_start = (uintptr_t)orig_func & ~(page_size - 1); + uintptr_t code_end = (uintptr_t)orig_func + HOOK_SIZE; + + // Make this memory region writable. + mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_WRITE | PROT_EXEC); + + if (s_swapped) { + // Copy original code back into place. + memcpy(orig_func, s_orig_code, HOOK_SIZE); + } else { + // Store the original code before replacing it. + memcpy(s_orig_code, orig_func, HOOK_SIZE); + +#if __arm__ + if (orig_thumb) { + // LDR PC, [PC, #0]; BX PC; + memcpy(orig_func, "\x00\x4f\x38\x47", 4); + memcpy(orig_func + 4, &new_func, 4); + } else { + // LDR PC, [PC, #0]; + memcpy(orig_func, "\x00\xf0\x9f\xe5", 4); + memcpy(orig_func + 4, &new_func, 4); + } +#else + int32_t jmp_offset = (int64_t)new_func - (int64_t)orig_func - HOOK_SIZE; + + // Change original function to jump to our new one. + *orig_func = 0xE9; // JMP + *(int32_t*)(orig_func + 1) = jmp_offset; +#endif + } + + s_swapped = !s_swapped; + + // Return this region to no longer being writable. + mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_EXEC); +} diff --git a/tests/react-test-app/android/app/build.gradle b/tests/react-test-app/android/app/build.gradle index 94982d4e..ea383ba9 100644 --- a/tests/react-test-app/android/app/build.gradle +++ b/tests/react-test-app/android/app/build.gradle @@ -74,7 +74,7 @@ android { dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.facebook.react:react-native:0.18.0-patched' + compile 'com.facebook.react:react-native:0.18.0' compile project(':realm') compile project(':react-native-fs') }