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 5ccf429d..aeadc15e 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/core/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -168,7 +168,7 @@ public final class XposedBridge { */ public static void deoptimizeMethod(Member deoptimizedMethod) { if (!(deoptimizedMethod instanceof Executable)) { - throw new IllegalArgumentException("Only methods and constructors can be deoptimized: " + deoptimizedMethod.toString()); + throw new IllegalArgumentException("Only methods and constructors can be deoptimized: " + deoptimizedMethod); } else if (Modifier.isAbstract(deoptimizedMethod.getModifiers())) { throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + deoptimizedMethod); } else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) { @@ -193,16 +193,13 @@ public final class XposedBridge { */ public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { if (!(hookMethod instanceof Executable)) { - throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString()); - } - // No check interface because there may be default methods - /*else if (hookMethod.getDeclaringClass().isInterface()) { - throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString()); - }*/ - else if (Modifier.isAbstract(hookMethod.getModifiers())) { - throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString()); + throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod); + } else if (Modifier.isAbstract(hookMethod.getModifiers())) { + throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod); } else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.class.getClassLoader()) { throw new IllegalArgumentException("Do not allow hooking inner methods"); + } else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) { + throw new IllegalArgumentException("Cannot hook Method.invoke"); } if (callback == null) { @@ -383,26 +380,51 @@ public final class XposedBridge { } public static class AdditionalHookInfo { - final Executable method; - final Object[][] callbacks; + 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; + + 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()); + if (method instanceof Method) { + returnType = ((Method) method).getReturnType(); + } else { + returnType = null; + } this.callbacks = callbacks; } + // This method is quite critical. We should try not to use system methods to avoid + // endless recursive public Object callback(Object[] args) throws Throwable { XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam(); param.method = method; - if (Modifier.isStatic(method.getModifiers())) { + if (isStatic) { param.thisObject = null; param.args = args; } else { param.thisObject = args[0]; param.args = new Object[args.length - 1]; - System.arraycopy(args, 1, param.args, 0, args.length - 1); + //noinspection ManualArrayCopy + for (int i = 0; i < args.length - 1; ++i) { + param.args[i] = args[i + 1]; + } } Object[] callbacksSnapshot = callbacks[0]; @@ -411,7 +433,7 @@ public final class XposedBridge { try { return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args); } catch (InvocationTargetException ite) { - throw ite.getCause(); + throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite); } } @@ -441,7 +463,7 @@ public final class XposedBridge { try { param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args)); } catch (InvocationTargetException e) { - param.setThrowable(e.getCause()); + param.setThrowable((Throwable) HookBridge.invokeOriginalMethod(getCause, e)); } } @@ -469,10 +491,8 @@ public final class XposedBridge { throw param.getThrowable(); else { var result = param.getResult(); - if (method instanceof Method) { - var returnType = ((Method) method).getReturnType(); - if (!returnType.isPrimitive()) - return returnType.cast(result); + if (returnType != null && !returnType.isPrimitive() && !HookBridge.instanceOf(result, returnType)) { + throw castException; } return result; } 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 118e78ed..c174d286 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java @@ -3,11 +3,17 @@ package org.lsposed.lspd.nativebridge; import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; +import dalvik.annotation.optimization.FastNative; + public class HookBridge { public static native boolean hookMethod(Executable hookMethod, Class hooker, int priority, Object callback); public static native boolean unhookMethod(Executable hookMethod, Object callback); public static native boolean deoptimizeMethod(Executable method); - public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; + @FastNative + public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; + + @FastNative + public static native boolean instanceOf(Object obj, Class clazz); } diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java b/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java index 33f6180d..99fa43fc 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java @@ -25,6 +25,8 @@ import android.content.res.XResources; import java.lang.reflect.Constructor; +import dalvik.annotation.optimization.FastNative; + public class ResourcesHook { public static native boolean initXResourcesNative(); @@ -33,5 +35,6 @@ public class ResourcesHook { public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass); + @FastNative public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); } diff --git a/core/src/main/jni/src/jni/hook_bridge.cpp b/core/src/main/jni/src/jni/hook_bridge.cpp index 456c49a4..be07c2b1 100644 --- a/core/src/main/jni/src/jni/hook_bridge.cpp +++ b/core/src/main/jni/src/jni/hook_bridge.cpp @@ -38,7 +38,8 @@ std::shared_mutex hooked_lock; // Rehashing invalidates iterators, changes ordering between elements, and changes which buckets elements appear in, but does not invalidate pointers or references to elements. std::unordered_map hooked_methods; -jmethodID invoke = nullptr; +jobject (*native_invoke)(JNIEnv* env, jobject javaMethod, jobject javaReceiver, + jobjectArray javaArgs); } namespace lspd { @@ -166,7 +167,11 @@ LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMet if (hook_item && hook_item->backup) { to_call = hook_item->backup; } - return env->CallObjectMethod(to_call, invoke, thiz, args); + return native_invoke(env, to_call, thiz, args); +} + +LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, instanceOf, jobject object, jclass expected_class) { + return env->IsInstanceOf(object, expected_class); } static JNINativeMethod gMethods[] = { @@ -174,13 +179,16 @@ static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, deoptimizeMethod, "(Ljava/lang/reflect/Executable;)Z"), 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"), }; void RegisterHookBridge(JNIEnv *env) { auto method = env->FindClass("java/lang/reflect/Method"); - invoke = env->GetMethodID( + auto invoke = env->GetMethodID( method, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + native_invoke = reinterpret_cast( + lsplant::GetNativeFunction(env, env->ToReflectedMethod(method, invoke, false))); env->DeleteLocalRef(method); REGISTER_LSP_NATIVE_METHODS(HookBridge); } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f..41d9927a 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00e33ede..aa991fce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hiddenapi/stubs/src/main/java/dalvik/annotation/optimization/FastNative.java b/hiddenapi/stubs/src/main/java/dalvik/annotation/optimization/FastNative.java new file mode 100644 index 00000000..6c8379dd --- /dev/null +++ b/hiddenapi/stubs/src/main/java/dalvik/annotation/optimization/FastNative.java @@ -0,0 +1,11 @@ +package dalvik.annotation.optimization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface FastNative { +}