Skip to content

Commit

Permalink
feat: allocateInstance & invokeConstructor (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine authored Aug 30, 2024
1 parent cfbe892 commit 25a79d4
Show file tree
Hide file tree
Showing 8 changed files with 428 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ from original Xposed and it should work with almost no modificiations.

Additionally, XposedBridge contains these new methods:

- `allocateInstance` - Allocates a class instance without calling any constructors
Can be used as a simpler alternative to `sun.misc.Unsafe#allocateInstance`
- `invokeConstructor` - Invokes a constructor for an existing instance, most useful in conjunction
raw instance allocation.
- `makeClassInheritable` - Makes a final class inheritable, see LSPlant doc for more info
- `deoptimizeMethod` - Deoptimises method to solve inline issues, see LSPlant doc for more info
- `disableProfileSaver` - Disables Android Profile Saver to try to prevent ahead of time compilation
Expand Down
30 changes: 30 additions & 0 deletions core/src/androidTest/java/com/aliucord/hook/Dummy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.aliucord.hook;

public class Dummy {
public boolean initialized;

Object a;
int b;
Integer c;
String d;

public Dummy() {
initialized = true;
}

public Dummy(Object a, int b, Integer c, String d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}

public Dummy(Object... varargs) {}

public static class Dummy2 extends Dummy {
public Dummy2() {
super();
d = "dummy2";
}
}
}
86 changes: 86 additions & 0 deletions core/src/androidTest/java/com/aliucord/hook/UnitTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.os.Build;
Expand Down Expand Up @@ -116,6 +118,90 @@ public void shouldNotHookField() throws Throwable {
XposedBridge.hookMethod(UnitTest.class.getDeclaredField("counter"), XC_MethodReplacement.DO_NOTHING);
}

@Test
public void shouldAllocateInstance() {
var instance = XposedBridge.allocateInstance(Dummy.class);
assertNotNull("failed to alloc", instance);
assertFalse(instance.initialized);
}

@Test
public void shouldInvokeConstructor() throws Throwable {
var instance = XposedBridge.allocateInstance(Dummy.class);
assertFalse("constructor not supposed to be called", instance.initialized);

var success = XposedBridge.invokeConstructor(instance, Dummy.class.getDeclaredConstructor());
assertTrue("invokeConstructor failed", success);
assertTrue("constructor not called", instance.initialized);
}

@Test
public void shouldInvokeSuperConstructor() throws Throwable {
var instance = XposedBridge.allocateInstance(Dummy.Dummy2.class);
assertFalse("constructor not supposed to be called", instance.initialized);

var success = XposedBridge.invokeConstructor(instance, Dummy.class.getDeclaredConstructor());
assertTrue("invokeConstructor failed", success);
assertTrue("supertype ctor not called", instance.initialized);
assertNull("subtype ctor should not be called", instance.d);
}

@Test
public void shouldInvokeSubConstructor() throws Throwable {
var instance = XposedBridge.allocateInstance(Dummy.Dummy2.class);
assertFalse("constructor not supposed to be called", instance.initialized);

var success = XposedBridge.invokeConstructor(instance, Dummy.Dummy2.class.getDeclaredConstructor());
assertTrue("invokeConstructor failed", success);
assertTrue("supertype ctor not called", instance.initialized);
assertEquals("subtype ctor not called", "dummy2", instance.d);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotInvokeVarargsConstructor() throws Throwable {
XposedBridge.invokeConstructor(new Dummy(), Dummy.class.getDeclaredConstructor(Object[].class));
}

@Test(expected = IllegalArgumentException.class)
public void shouldFailInvokeConstructorWrongArgCount() throws Throwable {
XposedBridge.invokeConstructor(
new Dummy(),
Dummy.class.getDeclaredConstructor(),
"balls"
);
}

@Test(expected = IllegalArgumentException.class)
public void shouldFailInvokeConstructorWrongArgs() throws Throwable {
var constructor = Dummy.class.getDeclaredConstructor(Object.class, int.class, Integer.class, String.class);
XposedBridge.invokeConstructor(
new Dummy(),
constructor,
"balls",
Integer.valueOf(1),
"ballsv2",
new Object()
);
}

@Test
public void shouldInvokeArgsConstructor() throws Throwable {
var a = new Object();
var b = 42;
var c = Integer.valueOf(420);
var d = "balls";

var instance = XposedBridge.allocateInstance(Dummy.class);
var constructor = Dummy.class.getDeclaredConstructor(Object.class, int.class, Integer.class, String.class);
var success = XposedBridge.invokeConstructor(instance, constructor, a, b, c, d);
assertTrue("invokeConstructor failed", success);
assertFalse("wrong constructor called", instance.initialized);
assertEquals("a does not match", a, instance.a);
assertEquals("b does not match", b, instance.b);
assertEquals("c does not match", c, instance.c);
assertEquals("d does not match", d, instance.d);
}

@Test
public void shouldDisableProfileSaver() {
assertTrue(XposedBridge.disableProfileSaver());
Expand Down
13 changes: 12 additions & 1 deletion core/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(xz/linux/lib/xz xz/linux/include/linux xz/userspace)

add_library(aliuhook SHARED aliuhook.cpp elf_img.cpp profile_saver.cpp hidden_api.cpp xz/linux/lib/xz/xz_crc32.c xz/linux/lib/xz/xz_crc64.c xz/linux/lib/xz/xz_dec_bcj.c xz/linux/lib/xz/xz_dec_lzma2.c xz/linux/lib/xz/xz_dec_stream.c)
add_library(aliuhook SHARED
aliuhook.cpp
elf_img.cpp
profile_saver.cpp
hidden_api.cpp
invoke_constructor.cpp
xz/linux/lib/xz/xz_crc32.c
xz/linux/lib/xz/xz_crc64.c
xz/linux/lib/xz/xz_dec_bcj.c
xz/linux/lib/xz/xz_dec_lzma2.c
xz/linux/lib/xz/xz_dec_stream.c
)

find_package(lsplant REQUIRED CONFIG)
find_package(dobby REQUIRED CONFIG)
Expand Down
35 changes: 35 additions & 0 deletions core/src/main/cpp/aliuhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cstdlib>
#include <cerrno>
#include "aliuhook.h"
#include "invoke_constructor.h"

int AliuHook::android_version = -1;
pine::ElfImg AliuHook::elf_img; // NOLINT(cert-err58-cpp)
Expand Down Expand Up @@ -110,6 +111,26 @@ Java_de_robv_android_xposed_XposedBridge_disableHiddenApiRestrictions(JNIEnv *en
return disable_hidden_api(env);
}

extern "C"
JNIEXPORT jobject JNICALL
Java_de_robv_android_xposed_XposedBridge_allocateInstance0(JNIEnv *env, jclass, jclass clazz) {
return env->AllocObject(clazz);
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_de_robv_android_xposed_XposedBridge_invokeConstructor0(JNIEnv *env, jclass, jobject instance, jobject constructor, jobjectArray args) {
jmethodID constructorMethodId = env->FromReflectedMethod(constructor);
if (!constructorMethodId) return JNI_FALSE;

if (!args) {
env->CallVoidMethod(instance, constructorMethodId);
return JNI_TRUE;
} else {
return InvokeConstructorWithArgs(env, instance, constructor, args);
}
}

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env;
Expand Down Expand Up @@ -154,5 +175,19 @@ JNI_OnLoad(JavaVM *vm, void *) {

LOGI("lsplant init finished");

res = LoadInvokeConstructorCache(env);
if (!res) {
LOGE("invoke_constructor init failed");
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *) {
JNIEnv *env;
vm->GetEnv((void **) &env, JNI_VERSION_1_1);

UnloadInvokeConstructorCache(env);
}
Loading

0 comments on commit 25a79d4

Please sign in to comment.