Skip to content

Commit fc718e7

Browse files
committed
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.
1 parent cdc536f commit fc718e7

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,27 @@ public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, i
189189
return LSPosedBridge.doHook(origin, priority, hooker);
190190
}
191191

192+
@Override
193+
@NonNull
194+
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker) {
195+
return hookClassInitializer(origin, PRIORITY_DEFAULT, hooker);
196+
}
197+
198+
@Override
199+
@NonNull
200+
@SuppressWarnings({"unchecked", "rawtypes"})
201+
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
202+
Method staticInitializer = HookBridge.getStaticInitializer(origin);
203+
204+
// The class might not have a static initializer block
205+
if (staticInitializer == null) {
206+
throw new IllegalArgumentException("Class " + origin.getName() + " has no static initializer");
207+
}
208+
209+
// Use the existing doHook logic. It will return a MethodUnhooker<Method>.
210+
return (MethodUnhooker) LSPosedBridge.doHook(staticInitializer, priority, hooker);
211+
}
212+
192213
private static boolean doDeoptimize(@NonNull Executable method) {
193214
if (Modifier.isAbstract(method.getModifiers())) {
194215
throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + method);

core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lsposed.lspd.nativebridge;
22

33
import java.lang.reflect.Executable;
4+
import java.lang.reflect.Method;
45
import java.lang.reflect.InvocationTargetException;
56

67
import dalvik.annotation.optimization.FastNative;
@@ -25,4 +26,12 @@ public class HookBridge {
2526
public static native boolean setTrusted(Object cookie);
2627

2728
public static native Object[][] callbackSnapshot(Class<?> hooker_callback, Executable method);
29+
30+
/**
31+
* Retrieves the static initializer (<clinit>) of a class as a Method object.
32+
* Standard Java reflection cannot access this.
33+
* @param clazz The class to inspect.
34+
* @return A Method object for the static initializer, or null if it doesn't exist.
35+
*/
36+
public static native Method getStaticInitializer(Class<?> clazz);
2837
}

core/src/main/jni/src/jni/hook_bridge.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,21 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac
322322
return res;
323323
}
324324

325+
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass target_class) {
326+
// <clinit> is the internal name for a static initializer.
327+
// Its signature is always ()V (no arguments, void return).
328+
jmethodID mid = env->GetStaticMethodID(target_class, "<clinit>", "()V");
329+
if (!mid) {
330+
// If GetStaticMethodID fails, it throws an exception.
331+
// We clear it and return null to let the Java side handle it gracefully.
332+
env->ExceptionClear();
333+
return nullptr;
334+
}
335+
// Convert the method ID to a java.lang.reflect.Method object.
336+
// The last parameter must be JNI_TRUE because it's a static method.
337+
return env->ToReflectedMethod(target_class, mid, JNI_TRUE);
338+
}
339+
325340
static JNINativeMethod gMethods[] = {
326341
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
327342
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
@@ -332,6 +347,7 @@ static JNINativeMethod gMethods[] = {
332347
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
333348
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"),
334349
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"),
350+
LSP_NATIVE_METHOD(HookBridge, getStaticInitializer, "(Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
335351
};
336352

337353
void RegisterHookBridge(JNIEnv *env) {

0 commit comments

Comments
 (0)