- 使用 Core&Tree Api 读取 ArrayList
- 使用 Core&Tree Api 生成 Main 类
- 使用 Core&Tree Api 输出方法的入参和出参
- 使用 Core&Tree Api 删除方法里面的日志输出语句
- 使用 Core&Tree Api 输出特定方法耗时
- 使用 Core&Tree Api 统一线程重命名
- 使用 Core&Tree Api 给特定方法加上 try-catch 块
- 使用 Core&Tree Api 替换方法调用,为系统类或第三方库代码兜底
- 使用 Core&Tree Api 进行序列化检查
- 使用 Core&Tree Api 检查是否有调用不存在的方法
掌握最基础的 class 表现形式。
使用 ASM Api 生成以下 Main 类:
public class Main {
private static final String TAG = "Main";
public static void main(String[] args) {
System.out.println("Hello World.");
}
}
修改前的 Java 文件:
public class PrintMethodParams {
public String print(String name, int age) {
return name + ": " + age;
}
}
目标生成的类文件,且需要验证反射该 class 文件的正确性:
public class PrintMethodParamsCoreClass {
public PrintMethodParamsCoreClass() {
}
public String print(String var1, int var2) {
System.out.println(var1);
System.out.println(var2);
String var10000 = var1 + ": " + var2;
System.out.println(var10000);
return var10000;
}
}
修改前的 Java 文件:
public class DeleteLogInvoke {
public String print(String name, int age) {
System.out.println(name);
String result = name + ": " + age;
System.out.println(result);
System.out.println("Delete current line.");
System.out.println("name = " + name + ", age = " + age);
System.out.printf("name: %s%n", name);
System.out.println(String.format("age: %d", age));
return result;
}
}
目标生成的类文件,且需要验证反射该 class 文件的正确性:
public class DeleteLogInvokeCoreClass {
public DeleteLogInvokeCoreClass() {
}
public String print(String var1, int var2) {
String var3 = var1 + ": " + var2;
return var3;
}
}
修改前的 Java 文件:
public class MeasureMethodTime {
@MeasureTime
public void measure() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
目标生成的类文件,且需要验证反射该 class 的正确性:
public class MeasureMethodTimeCoreClass {
public MeasureMethodTimeCoreClass() {
}
@MeasureTime
public void measure() {
long var1 = System.currentTimeMillis();
try {
Thread.sleep(2000L);
} catch (InterruptedException var8) {
RuntimeException var10000 = new RuntimeException(var8);
long var4 = System.currentTimeMillis();
System.out.println(var4 - var1);
throw var10000;
}
long var6 = System.currentTimeMillis();
System.out.println(var6 - var1);
}
}
修改前的 Java 文件:
public class ThreadReName {
public static void main(String[] args) {
// 不带线程名称
new Thread(new InternalRunnable()).start();
// 带线程名称
Thread thread0 = new Thread(new InternalRunnable(), "thread0");
System.out.println("thread0: " + thread0.getName());
thread0.start();
Thread thread1 = new Thread(new InternalRunnable());
// 设置线程名字
thread1.setName("thread1");
System.out.println("thread1: " + thread1.getName());
thread1.start();
}
}
目标生成的类文件,且需要验证反射该 class 的正确性:
public class ThreadReNameCoreClass {
public ThreadReNameCoreClass() {
}
public static void main(String[] var0) {
(new ShadowThread(new InternalRunnable(), "sample/ThreadReNameCoreClass#main-Thread-0")).start();
ShadowThread var1 = new ShadowThread(new InternalRunnable(), "thread0", "sample/ThreadReNameCoreClass#main-Thread-1");
System.out.println("thread0: " + var1.getName());
var1.start();
ShadowThread var2 = new ShadowThread(new InternalRunnable(), "sample/ThreadReNameCoreClass#main-Thread-2");
var2.setName(ShadowThread.makeThreadName("thread1", "sample/ThreadReNameCoreClass#main-Thread-3"));
System.out.println("thread1: " + var2.getName());
var2.start();
}
}
修改前的 Java 文件:
public class CatchMethodInvoke {
public int calc() {
return 1 / 0;
}
}
目标生成的类文件,且需要验证反射该 class 的正确性:
public class CatchMethodInvokeCoreClass {
public CatchMethodInvokeCoreClass() {
}
public int calc() {
try {
return 1 / 0;
} catch (Exception var2) {
var2.printStackTrace();
return 0;
}
}
}
这个任务的思路来源于:Booster 修复系统 Bug - Android 7.1 Toast 崩溃
其实延续上个任务的思路,为特定方法添加 try-catch 也能解决,但是 try-catch 过于粗暴,替换方法调用的方式灵活性更好些(其实在任务六里也有用到该思路)。
修改前的 Java 文件:
public class ReplaceMethodInvoke {
public static void main(String[] args) {
// throw NPE
new Toast().show();
}
}
目标生成的类文件,且需要验证反射该 class 的正确性:
public class ReplaceMethodInvokeCoreClass {
public ReplaceMethodInvokeCoreClass() {
}
public static void main(String[] var0) {
ShadowToast.show(new Toast(), "sample/ReplaceMethodInvokeCoreClass");
}
}
这个任务的思路来源于:ByteX - 序列化检查
对于 Java 的序列化,有以下几条规则需要遵守(也就是 IDEA Inspections 里的几条规则):
- 实现了 Serializable 的类未提供 serialVersionUID 字段
- 实现了 Serializable 的类包含非 transient、static 的字段,这些字段并未实现 Serializable 接口
- 未实现 Serializable 接口的类,包含 transient、serialVersionUID 字段
- 实现了 Serializable 的非静态内部类,它的外层类并未实现 Serializable 接口
针对以下代码:
public class SerializationCheck implements Serializable {
private ItemBean1 itemBean1;
private ItemBean2 itemBean2;
private transient ItemBean3 itemBean3;
private String name;
private int age;
static class ItemBean1 {
}
static class ItemBean2 implements Serializable {
}
static class ItemBean3 {
}
}
检查输出:
Attention: Non-serializable field 'itemBean1' in a Serializable class [sample/SerializationCheckCoreClass]
Attention: This [sample/SerializationCheckCoreClass] class is serializable, but does not define a 'serialVersionUID' field.
这个任务的思路来源于:ByteX - 非法引用检查
不过示例里写的相对简单很多,怎么模拟引用找不到但编译不会报错的情况呢?我们可以借助于依赖方式:
compileOnly(project(":library1"))
runtimeOnly(fileTree("libs") { include("*.jar") })
针对以下代码:
fun main() {
// throw IllegalAccessError:
// class task_11.ReferCheckKt tried to access private method LibraryClass.testPrivateMethod()V
LibraryClass().testPrivateMethod()
// throw NoSuchMethodError: LibraryClass.testModifyParamsMethod()V
LibraryClass().testModifyParamsMethod()
// NoSuchMethodError: LibraryClass.testModifyReturnTypeMethod()V
LibraryClass().testModifyReturnTypeMethod()
}
检查输出:
在 sample/ReferCheckTreeClass$testPrivateMethod 方法里监测到非法调用:
IllegalAccessError: 试图调用 private final LibraryClass.testPrivateMethod()V 方法.
在 sample/ReferCheckTreeClass$testModifyParamsMethod 方法里监测到非法调用:
NoSuchMethodError: 找不到 LibraryClass.testModifyParamsMethod()V 方法.
在 sample/ReferCheckTreeClass$testModifyReturnTypeMethod 方法里监测到非法调用:
NoSuchMethodError: 找不到 LibraryClass.testModifyReturnTypeMethod()V 方法.