From 01097812d5dacf56b5630ca15e0da6c22bd68827 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 28 Jun 2018 13:24:10 +0200 Subject: [PATCH] Porting memmove() workaround from Realm Java. (#1901) --- CHANGELOG.md | 2 +- react-native/android/src/main/jni/Android.mk | 8 ++ .../android/src/main/jni/Application.mk | 9 ++ src/android/hack.cpp | 116 ++++++++++++++++++ src/android/hack.hpp | 28 +++++ .../io_realm_react_RealmReactModule.cpp | 4 + 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/android/hack.cpp create mode 100644 src/android/hack.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ca410b00..181e1fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ X.Y.Z Release notes * Improved performance of `Realm.compact()`. ### Bug fixes -* None. +* [RN Android] Ported workaround for crashes in `memmove`/`memcpy` on some old Android devices (#1163 and #1895). ### Internals * Upgraded to Realm Core v5.6.3. diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index a3d727c3..935306e0 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -82,6 +82,14 @@ LOCAL_SRC_FILES += src/object-store/src/sync/impl/sync_metadata.cpp LOCAL_SRC_FILES += src/object-store/src/sync/impl/work_queue.cpp endif +# Workaround for memmove/memcpy bug +ifeq ($(strip $(TARGET_ARCH_ABI)),armeabi-v7a) +LOCAL_SRC_FILES += src/android/hack.cpp +BUILD_WRAP_MEMMOVE = 1 +else +BUILD_WRAP_MEMMOVE = 0 +endif + LOCAL_C_INCLUDES := src LOCAL_C_INCLUDES += src/jsc LOCAL_C_INCLUDES += src/object-store/src diff --git a/react-native/android/src/main/jni/Application.mk b/react-native/android/src/main/jni/Application.mk index d925de04..c950916c 100644 --- a/react-native/android/src/main/jni/Application.mk +++ b/react-native/android/src/main/jni/Application.mk @@ -21,6 +21,15 @@ APP_LDFLAGS += -llog APP_LDFLAGS += -landroid APP_LDFLAGS += -fvisibility=hidden +# Workaround for memmove/memcpy bug +ifeq ($(strip $(BUILD_WRAP_MEMMOVE)),1) +APP_CPPFLAGS += -DREALM_WRAP_MEMMOVE=1 +APP_LDFLAGS += -Wl,--wrap,memmove +APP_LDFLAGS += -Wl,--wrap,memcpy +else +APP_CPPFLAGS += -DREALM_WRAP_MEMMOVE=0 +endif + ifeq ($(strip $(BUILD_TYPE_SYNC)),1) APP_CPPFLAGS += -DREALM_ENABLE_SYNC=1 APP_LDFLAGS += -lz diff --git a/src/android/hack.cpp b/src/android/hack.cpp new file mode 100644 index 00000000..3c51eb18 --- /dev/null +++ b/src/android/hack.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#include "hack.hpp" + +#include +#include + +#include + +#ifndef REALM_WRAP_MEMMOVE +#error "REALM_WRAP_MEMMOVE is not defined!" +#endif + +#if REALM_WRAP_MEMMOVE +extern "C" { +void* __wrap_memmove(void *dest, const void *src, size_t n); +void* __real_memmove(void *dest, const void *src, size_t n); + +void* __wrap_memcpy(void *dest, const void *src, size_t n); +void* __real_memcpy(void *dest, const void *src, size_t n); +} + +using namespace realm::jni_util; + +typedef void* (*MemMoveFunc)(void *dest, const void *src, size_t n); +static MemMoveFunc s_wrap_memmove_ptr = &__real_memmove; +static MemMoveFunc s_wrap_memcpy_ptr = &__real_memcpy; + +static void* hacked_memmove(void* s1, const void* s2, size_t n) +{ + // adapted from https://github.com/dryc/libc11/blob/master/src/string/memmove.c + char* dest = (char*)s1; + const char* src = (const char*)s2; + if (dest <= src) { + while (n--) { + *dest++ = *src++; + } + } + else { + src += n; + dest += n; + while (n--) { + *--dest = *--src; + } + } + return static_cast(s1); +} + +static void* hacked_memcpy(void* s1, const void* s2, size_t n) +{ + // adapted from https://github.com/dryc/libc11/blob/master/src/string/memcpy.c + char* dest = (char*)s1; + const char* src = (const char*)s2; + while (n--) { + *dest++ = *src++; + } + return static_cast(s1); +} + +void* __wrap_memmove(void *dest, const void *src, size_t n) +{ + return (*s_wrap_memmove_ptr)(dest, src, n); +} + +void* __wrap_memcpy(void *dest, const void *src, size_t n) +{ + return (*s_wrap_memcpy_ptr)(dest, src, n); +} + + +// See https://github.com/realm/realm-java/issues/3651#issuecomment-290290228 +// There is a bug in memmove for some Samsung devices which will return "dest-n" instead of dest. +// The bug was originally found by QT, see https://bugreports.qt.io/browse/QTBUG-34984 . +// To work around it, we use linker's wrap feature to use a pure C implementation of memmove if the device has the +// problem. +static void check_memmove() +{ + char* array = strdup("Foobar"); + size_t len = strlen(array); + void* ptr = __real_memmove(array + 1, array, len - 1); + if (ptr != array + 1 || strncmp(array, "FFooba", len) != 0) { + __android_log_print(ANDROID_LOG_DEBUG, "JSRealm", "memmove is broken on this device. Switching to the builtin implementation."); + s_wrap_memmove_ptr = &hacked_memmove; + s_wrap_memcpy_ptr = &hacked_memcpy; + } + free(array); +} +#endif + +namespace realm { +namespace jni_util { + +void hack_init() +{ +#if REALM_WRAP_MEMMOVE + check_memmove(); +#endif +} + +} +} + diff --git a/src/android/hack.hpp b/src/android/hack.hpp new file mode 100644 index 00000000..62ac347e --- /dev/null +++ b/src/android/hack.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Realm Inc. + * + * 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. + */ + +#ifndef REALM_JNI_UTIL_HACK_HPP +#define REALM_JNI_UTIL_HACK_HPP + +namespace realm { +namespace jni_util { + +// Workaround bugs on some devices. +void hack_init(); + +} +} +#endif // REALM_JNI_UTIL_HACK_HPP diff --git a/src/android/io_realm_react_RealmReactModule.cpp b/src/android/io_realm_react_RealmReactModule.cpp index 78c4e730..89bccfc8 100644 --- a/src/android/io_realm_react_RealmReactModule.cpp +++ b/src/android/io_realm_react_RealmReactModule.cpp @@ -24,6 +24,7 @@ #include "rpc.hpp" #include "platform.hpp" #include "jni_utils.hpp" +#include "hack.hpp" using namespace realm::rpc; using namespace realm::jni_util; @@ -39,6 +40,9 @@ namespace realm { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + // Workaround for some known bugs in system calls on specific devices. + hack_init(); + JNIEnv* env; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR;