diff --git a/core/src/main/java/de/robv/android/xposed/XposedBridge.java b/core/src/main/java/de/robv/android/xposed/XposedBridge.java index 60ae322a..176d8239 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/core/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -82,6 +82,20 @@ public final class XposedBridge { public static volatile ClassLoader dummyClassLoader = null; + private static final ClassCastException castException = new ClassCastException("Return value's type from hook callback does not match the hooked method"); + + private static final Method getCause; + + static { + Method tmp; + try { + tmp = InvocationTargetException.class.getMethod("getCause"); + } catch (Throwable e) { + tmp = null; + } + getCause = tmp; + } + public static void initXResources() { if (dummyClassLoader != null) { return; @@ -381,32 +395,21 @@ public final class XposedBridge { } public static class AdditionalHookInfo { - private static final ClassCastException castException = new ClassCastException("Return value's type from hook callback does not match the hooked method"); - private static final Method getCause; - private final Executable method; - private final Object[][] callbacks; - private final Class returnType; - private final boolean isStatic; + private final Object params; - static { - Method tmp; - try { - tmp = InvocationTargetException.class.getMethod("getCause"); - } catch (Throwable e) { - tmp = null; - } - getCause = tmp; - } - - private AdditionalHookInfo(Executable method, Object[][] callbacks) { - this.method = method; - isStatic = Modifier.isStatic(method.getModifiers()); + private AdditionalHookInfo(Executable method) { + var isStatic = Modifier.isStatic(method.getModifiers()); + Object returnType; if (method instanceof Method) { returnType = ((Method) method).getReturnType(); } else { returnType = null; } - this.callbacks = callbacks; + params = new Object[] { + method, + returnType, + isStatic, + }; } // This method is quite critical. We should try not to use system methods to avoid @@ -414,6 +417,12 @@ public final class XposedBridge { public Object callback(Object[] args) throws Throwable { XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam(); + var array = ((Object[]) params); + + var method = (Executable) array[0]; + var returnType = (Class) array[1]; + var isStatic = (Boolean) array[2]; + param.method = method; if (isStatic) { @@ -428,9 +437,8 @@ public final class XposedBridge { } } - Object[] callbacksSnapshot = callbacks[0]; - final int callbacksLength = callbacksSnapshot.length; - if (callbacksLength == 0) { + Object[] callbacksSnapshot = HookBridge.callbackSnapshot(method); + if (callbacksSnapshot == null || callbacksSnapshot.length == 0) { try { return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args); } catch (InvocationTargetException ite) { @@ -457,7 +465,7 @@ public final class XposedBridge { beforeIdx++; break; } - } while (++beforeIdx < callbacksLength); + } while (++beforeIdx < callbacksSnapshot.length); // call original method if not requested otherwise if (!param.returnEarly) { diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java index 8097f91c..c48c7471 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java @@ -18,4 +18,6 @@ public class HookBridge { @FastNative public static native boolean setTrusted(Object cookie); + + public static native Object[] callbackSnapshot(Executable method); } diff --git a/core/src/main/jni/src/jni/hook_bridge.cpp b/core/src/main/jni/src/jni/hook_bridge.cpp index dc436d99..bc3ba130 100644 --- a/core/src/main/jni/src/jni/hook_bridge.cpp +++ b/core/src/main/jni/src/jni/hook_bridge.cpp @@ -30,8 +30,7 @@ namespace { struct HookItem { jobject backup {nullptr}; - jobjectArray callbacks {nullptr}; - std::multiset> priorities {}; + std::multimap> callbacks {}; }; std::shared_mutex hooked_lock; @@ -71,40 +70,19 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod, if (!hook_item) { std::unique_lock lk(hooked_lock); hook_item = &hooked_methods[target]; - if (!hook_item->callbacks) { - newHook = true; - hook_item->callbacks = (jobjectArray) env->NewGlobalRef( - env->NewObjectArray(1, env->FindClass("[Ljava/lang/Object;"), - env->NewObjectArray(0, JNI_FindClass(env, "java/lang/Object"), nullptr))); - } + newHook = true; } if (newHook) { - auto init = env->GetMethodID(hooker, "", "(Ljava/lang/reflect/Executable;[[Ljava/lang/Object;)V"); + auto init = env->GetMethodID(hooker, "", "(Ljava/lang/reflect/Executable;)V"); auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback", "([Ljava/lang/Object;)Ljava/lang/Object;"), false); - auto hooker_object = env->NewObject(hooker, init, hookMethod, hook_item->callbacks); + auto hooker_object = env->NewObject(hooker, init, hookMethod); hook_item->backup = lsplant::Hook(env, hookMethod, hooker_object, callback_method); env->DeleteLocalRef(hooker_object); } - env->MonitorEnter(hook_item->callbacks); - auto insert_point = hook_item->priorities.emplace(priority); - auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0); - auto new_array = env->NewObjectArray(static_cast(hook_item->priorities.size()), env->FindClass("java/lang/Object"), nullptr); - for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) { - if (current == insert_point) { - env->SetObjectArrayElement(new_array, i, callback); - passed = true; - } else { - auto element = env->GetObjectArrayElement(old_array, i - passed); - env->SetObjectArrayElement(new_array, i, element); - env->DeleteLocalRef(element); - } - } - env->SetObjectArrayElement(hook_item->callbacks, 0, new_array); - env->DeleteLocalRef(old_array); - env->DeleteLocalRef(new_array); - env->MonitorExit(hook_item->callbacks); + JNIMonitor monitor(env, hook_item->backup); + hook_item->callbacks.emplace(std::make_pair(priority, env->NewGlobalRef(callback))); return hook_item->backup ? JNI_TRUE : JNI_FALSE; } @@ -118,33 +96,14 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jo } } if (!hook_item) return JNI_FALSE; - JNIMonitor monitor(env, hook_item->callbacks); - auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0); - if (hook_item->priorities.empty()) return JNI_FALSE; - auto new_array = env->NewObjectArray(static_cast(hook_item->priorities.size() - 1), env->FindClass("java/lang/Object"), nullptr); - auto to_remove = hook_item->priorities.end(); - for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) { - auto element = env->GetObjectArrayElement(old_array, i); - if (env->IsSameObject(element, callback)) { - to_remove = current; - passed = true; - } else { - if (i - passed >= hook_item->priorities.size() - 1) { - return JNI_FALSE; - } - env->SetObjectArrayElement(new_array, i - passed, element); + JNIMonitor monitor(env, hook_item->backup); + for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) { + if (env->IsSameObject(i->second, callback)) { + hook_item->callbacks.erase(i); + return JNI_TRUE; } - env->DeleteLocalRef(element); } - bool removed = false; - if (to_remove != hook_item->priorities.end()) { - hook_item->priorities.erase(to_remove); - env->SetObjectArrayElement(hook_item->callbacks, 0, new_array); - removed = true; - } - env->DeleteLocalRef(old_array); - env->DeleteLocalRef(new_array); - return removed ? JNI_TRUE : JNI_FALSE; + return JNI_FALSE; } LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, deoptimizeMethod, jobject hookMethod, @@ -177,6 +136,24 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, setTrusted, jobject cookie) { return lsplant::MakeDexFileTrusted(env, cookie); } +LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jobject method) { + auto target = env->FromReflectedMethod(method); + HookItem *hook_item = nullptr; + { + std::shared_lock lk(hooked_lock); + if (auto found = hooked_methods.find(target); found != hooked_methods.end()) { + hook_item = &found->second; + } + } + if (!hook_item) return nullptr; + JNIMonitor monitor(env, hook_item->backup); + auto res = env->NewObjectArray((jsize) hook_item->callbacks.size(), env->FindClass("java/lang/Object"), nullptr); + for (jsize i = 0; auto callback: hook_item->callbacks) { + env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback.second)); + } + return res; +} + static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"), @@ -184,10 +161,11 @@ static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"), LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"), + LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"), }; void RegisterHookBridge(JNIEnv *env) { - auto method = env->FindClass("java/lang/reflect/Method"); + jclass method = env->FindClass("java/lang/reflect/Method"); invoke = env->GetMethodID( method, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");