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);
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (Modifier.isAbstract(method.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + method);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.lsposed.lspd.nativebridge;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import dalvik.annotation.optimization.FastNative;
|
||||
|
|
@ -25,4 +26,12 @@ public class HookBridge {
|
|||
public static native boolean setTrusted(Object cookie);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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[] = {
|
||||
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"),
|
||||
|
|
@ -332,6 +347,7 @@ static JNINativeMethod gMethods[] = {
|
|||
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/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"),
|
||||
LSP_NATIVE_METHOD(HookBridge, getStaticInitializer, "(Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
|
||||
};
|
||||
|
||||
void RegisterHookBridge(JNIEnv *env) {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 64e29bd657ef4d2540b34402f5a988778f29e676
|
||||
Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8
|
||||
Loading…
Reference in New Issue