Implement hookClassInitializer API for static initializers (#535)
Since standard Java Reflection cannot access the `<clinit>` method (class's static initializer), we add a new native function, `HookBridge.getStaticInitializer`, which uses JNI `GetStaticMethodID` to retrieve a `Method` handle for it. The `LSPosedContext` API then passes this handle to the existing `doHook` machinery.
There are two possible exceptions thrown by the API:
- `IllegalArgumentException`: Thrown for user-level errors, such as attempting to hook a class that has no static initializer block (`static { ... }`). This is a predictable failure based on the provided class.
- `HookFailedError`: Thrown when the underlying native hooking engine (`lsplant`) fails to install the hook. This indicates a framework or environment-level issue (e.g., ART incompatibility, method inlining by the JIT/AOT compiler) and is not a fault in the module's logic.
This commit is contained in:
parent
cdc536f10b
commit
fc718e739b
|
|
@ -189,6 +189,27 @@ public class LSPosedContext implements XposedInterface {
|
||||||
return LSPosedBridge.doHook(origin, priority, hooker);
|
return LSPosedBridge.doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker) {
|
||||||
|
return hookClassInitializer(origin, PRIORITY_DEFAULT, hooker);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
||||||
|
Method staticInitializer = HookBridge.getStaticInitializer(origin);
|
||||||
|
|
||||||
|
// The class might not have a static initializer block
|
||||||
|
if (staticInitializer == null) {
|
||||||
|
throw new IllegalArgumentException("Class " + origin.getName() + " has no static initializer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing doHook logic. It will return a MethodUnhooker<Method>.
|
||||||
|
return (MethodUnhooker) LSPosedBridge.doHook(staticInitializer, priority, hooker);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean doDeoptimize(@NonNull Executable method) {
|
private static boolean doDeoptimize(@NonNull Executable method) {
|
||||||
if (Modifier.isAbstract(method.getModifiers())) {
|
if (Modifier.isAbstract(method.getModifiers())) {
|
||||||
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + method);
|
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + method);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.lsposed.lspd.nativebridge;
|
package org.lsposed.lspd.nativebridge;
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
import dalvik.annotation.optimization.FastNative;
|
import dalvik.annotation.optimization.FastNative;
|
||||||
|
|
@ -25,4 +26,12 @@ public class HookBridge {
|
||||||
public static native boolean setTrusted(Object cookie);
|
public static native boolean setTrusted(Object cookie);
|
||||||
|
|
||||||
public static native Object[][] callbackSnapshot(Class<?> hooker_callback, Executable method);
|
public static native Object[][] callbackSnapshot(Class<?> hooker_callback, Executable method);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the static initializer (<clinit>) of a class as a Method object.
|
||||||
|
* Standard Java reflection cannot access this.
|
||||||
|
* @param clazz The class to inspect.
|
||||||
|
* @return A Method object for the static initializer, or null if it doesn't exist.
|
||||||
|
*/
|
||||||
|
public static native Method getStaticInitializer(Class<?> clazz);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,21 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass target_class) {
|
||||||
|
// <clinit> is the internal name for a static initializer.
|
||||||
|
// Its signature is always ()V (no arguments, void return).
|
||||||
|
jmethodID mid = env->GetStaticMethodID(target_class, "<clinit>", "()V");
|
||||||
|
if (!mid) {
|
||||||
|
// If GetStaticMethodID fails, it throws an exception.
|
||||||
|
// We clear it and return null to let the Java side handle it gracefully.
|
||||||
|
env->ExceptionClear();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// Convert the method ID to a java.lang.reflect.Method object.
|
||||||
|
// The last parameter must be JNI_TRUE because it's a static method.
|
||||||
|
return env->ToReflectedMethod(target_class, mid, JNI_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
|
||||||
|
|
@ -332,6 +347,7 @@ static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
|
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, setTrusted, "(Ljava/lang/Object;)Z"),
|
||||||
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"),
|
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"),
|
||||||
|
LSP_NATIVE_METHOD(HookBridge, getStaticInitializer, "(Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
|
||||||
};
|
};
|
||||||
|
|
||||||
void RegisterHookBridge(JNIEnv *env) {
|
void RegisterHookBridge(JNIEnv *env) {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 64e29bd657ef4d2540b34402f5a988778f29e676
|
Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8
|
||||||
Loading…
Reference in New Issue