├── .gitignore ├── README.md ├── build.gradle.kts ├── files ├── CatchMethodInvokeCoreClass.class ├── CatchMethodInvokeTreeClass.class ├── DeleteLogInvokeCoreClass.class ├── DeleteLogInvokeTreeClass.class ├── MainCoreClass.class ├── MainTreeClass.class ├── MeasureMethodTimeCoreClass.class ├── MeasureMethodTimeTreeClass.class ├── PrintMethodParamsCoreClass.class ├── PrintMethodParamsTreeClass.class ├── ReferCheckCoreClass.class ├── ReferCheckTreeClass.class ├── ReplaceMethodInvokeCoreClass.class ├── ReplaceMethodInvokeTreeClass.class ├── SerializationCheckCoreClass.class ├── SerializationCheckTreeClass.class ├── ThreadReNameCoreClass.class └── ThreadReNameTreeClass.class ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library1 ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── LibraryClass.kt ├── libs └── library1-1.0-SNAPSHOT.jar ├── settings.gradle.kts └── src └── main └── kotlin ├── Main.kt ├── common ├── ByteArrayExt.kt ├── MethodNodeExt.kt └── OpcodesExt.kt ├── task_1 ├── ReadArrayListCoreApi.kt └── ReadArrayListTreeApi.kt ├── task_10 ├── ReferCheck.kt ├── ReferCheckCoreApi.kt └── ReferCheckTreeApi.kt ├── task_2 ├── GenerateMainClassCoreApi.kt ├── GenerateMainClassTreeApi.kt └── Main.java ├── task_3 ├── PrintMethodParams.java ├── PrintMethodParamsCoreApi.kt └── PrintMethodParamsTreeApi.kt ├── task_4 ├── DeleteLogInvoke.java ├── DeleteLogInvokeCoreApi.kt └── DeleteLogInvokeTreeApi.kt ├── task_5 ├── MeasureMethodTime.java ├── MeasureMethodTimeCoreApi.kt ├── MeasureMethodTimeTreeApi.kt └── MeasureTime.java ├── task_6 ├── InternalRunnable.java ├── ShadowThread.java ├── ThreadReName.java ├── ThreadReNameCoreApi.kt └── ThreadReNameTreeApi.kt ├── task_7 ├── CatchMethodInvoke.java ├── CatchMethodInvokeCoreApi.kt └── CatchMethodInvokeTreeApi.kt ├── task_8 ├── ReplaceMethodInvoke.java ├── ReplaceMethodInvokeCoreApi.kt ├── ReplaceMethodInvokeTreeApi.kt ├── ShadowToast.java └── Toast.java └── task_9 ├── SerializationCheck.java ├── SerializationCheckCoreApi.kt └── SerializationCheckTreeApi.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | *.iws 10 | *.iml 11 | *.ipr 12 | out/ 13 | !**/src/main/**/out/ 14 | !**/src/test/**/out/ 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | bin/ 25 | !**/src/main/**/bin/ 26 | !**/src/test/**/bin/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS ### 39 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | ASM-Task 3 | --- 4 | 5 | #### 目录 6 | 7 | 1. 使用 Core&Tree Api 读取 ArrayList 8 | 2. 使用 Core&Tree Api 生成 Main 类 9 | 3. 使用 Core&Tree Api 输出方法的入参和出参 10 | 4. 使用 Core&Tree Api 删除方法里面的日志输出语句 11 | 4. 使用 Core&Tree Api 输出特定方法耗时 12 | 4. 使用 Core&Tree Api 统一线程重命名 13 | 4. 使用 Core&Tree Api 给特定方法加上 try-catch 块 14 | 4. 使用 Core&Tree Api 替换方法调用,为系统类或第三方库代码兜底 15 | 4. 使用 Core&Tree Api 进行序列化检查 16 | 4. 使用 Core&Tree Api 检查是否有调用不存在的方法 17 | 18 | #### 任务一:读取 ArrayList 类 19 | 20 | 掌握最基础的 class 表现形式。 21 | 22 | #### 任务二:生成 Main 类 23 | 24 | 使用 ASM Api 生成以下 Main 类: 25 | 26 | ```java 27 | public class Main { 28 | 29 | private static final String TAG = "Main"; 30 | 31 | public static void main(String[] args) { 32 | System.out.println("Hello World."); 33 | } 34 | } 35 | ``` 36 | 37 | #### 任务三:输出方法的入参和出参 38 | 39 | 修改前的 Java 文件: 40 | 41 | ```java 42 | public class PrintMethodParams { 43 | public String print(String name, int age) { 44 | return name + ": " + age; 45 | } 46 | } 47 | ``` 48 | 49 | 目标生成的类文件,且需要验证反射该 class 文件的正确性: 50 | 51 | ```java 52 | public class PrintMethodParamsCoreClass { 53 | public PrintMethodParamsCoreClass() { 54 | } 55 | 56 | public String print(String var1, int var2) { 57 | System.out.println(var1); 58 | System.out.println(var2); 59 | String var10000 = var1 + ": " + var2; 60 | System.out.println(var10000); 61 | return var10000; 62 | } 63 | } 64 | ``` 65 | 66 | #### 任务四:删除方法里面的日志输出语句 67 | 68 | 修改前的 Java 文件: 69 | 70 | ```java 71 | public class DeleteLogInvoke { 72 | 73 | public String print(String name, int age) { 74 | System.out.println(name); 75 | String result = name + ": " + age; 76 | System.out.println(result); 77 | System.out.println("Delete current line."); 78 | System.out.println("name = " + name + ", age = " + age); 79 | System.out.printf("name: %s%n", name); 80 | System.out.println(String.format("age: %d", age)); 81 | return result; 82 | } 83 | } 84 | ``` 85 | 86 | 目标生成的类文件,且需要验证反射该 class 文件的正确性: 87 | 88 | ```java 89 | public class DeleteLogInvokeCoreClass { 90 | public DeleteLogInvokeCoreClass() { 91 | } 92 | 93 | public String print(String var1, int var2) { 94 | String var3 = var1 + ": " + var2; 95 | return var3; 96 | } 97 | } 98 | ``` 99 | 100 | #### 任务五:输出特定方法的方法耗时 101 | 102 | 修改前的 Java 文件: 103 | 104 | ```java 105 | public class MeasureMethodTime { 106 | 107 | @MeasureTime 108 | public void measure() { 109 | try { 110 | Thread.sleep(2000); 111 | } catch (InterruptedException e) { 112 | throw new RuntimeException(e); 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | 目标生成的类文件,且需要验证反射该 class 的正确性: 119 | 120 | ```java 121 | public class MeasureMethodTimeCoreClass { 122 | public MeasureMethodTimeCoreClass() { 123 | } 124 | 125 | @MeasureTime 126 | public void measure() { 127 | long var1 = System.currentTimeMillis(); 128 | 129 | try { 130 | Thread.sleep(2000L); 131 | } catch (InterruptedException var8) { 132 | RuntimeException var10000 = new RuntimeException(var8); 133 | long var4 = System.currentTimeMillis(); 134 | System.out.println(var4 - var1); 135 | throw var10000; 136 | } 137 | 138 | long var6 = System.currentTimeMillis(); 139 | System.out.println(var6 - var1); 140 | } 141 | } 142 | ``` 143 | 144 | #### 任务六:线程重命名 145 | 146 | 修改前的 Java 文件: 147 | 148 | ```java 149 | public class ThreadReName { 150 | 151 | public static void main(String[] args) { 152 | // 不带线程名称 153 | new Thread(new InternalRunnable()).start(); 154 | 155 | // 带线程名称 156 | Thread thread0 = new Thread(new InternalRunnable(), "thread0"); 157 | System.out.println("thread0: " + thread0.getName()); 158 | thread0.start(); 159 | 160 | Thread thread1 = new Thread(new InternalRunnable()); 161 | // 设置线程名字 162 | thread1.setName("thread1"); 163 | System.out.println("thread1: " + thread1.getName()); 164 | thread1.start(); 165 | } 166 | 167 | } 168 | ``` 169 | 170 | 目标生成的类文件,且需要验证反射该 class 的正确性: 171 | 172 | ```java 173 | public class ThreadReNameCoreClass { 174 | public ThreadReNameCoreClass() { 175 | } 176 | 177 | public static void main(String[] var0) { 178 | (new ShadowThread(new InternalRunnable(), "sample/ThreadReNameCoreClass#main-Thread-0")).start(); 179 | ShadowThread var1 = new ShadowThread(new InternalRunnable(), "thread0", "sample/ThreadReNameCoreClass#main-Thread-1"); 180 | System.out.println("thread0: " + var1.getName()); 181 | var1.start(); 182 | ShadowThread var2 = new ShadowThread(new InternalRunnable(), "sample/ThreadReNameCoreClass#main-Thread-2"); 183 | var2.setName(ShadowThread.makeThreadName("thread1", "sample/ThreadReNameCoreClass#main-Thread-3")); 184 | System.out.println("thread1: " + var2.getName()); 185 | var2.start(); 186 | } 187 | } 188 | ``` 189 | 190 | #### 任务七:给特定方法加上 try-catch 块 191 | 192 | 修改前的 Java 文件: 193 | 194 | ```java 195 | public class CatchMethodInvoke { 196 | 197 | public int calc() { 198 | return 1 / 0; 199 | } 200 | } 201 | ``` 202 | 203 | 目标生成的类文件,且需要验证反射该 class 的正确性: 204 | 205 | ```java 206 | public class CatchMethodInvokeCoreClass { 207 | public CatchMethodInvokeCoreClass() { 208 | } 209 | 210 | public int calc() { 211 | try { 212 | return 1 / 0; 213 | } catch (Exception var2) { 214 | var2.printStackTrace(); 215 | return 0; 216 | } 217 | } 218 | } 219 | ``` 220 | 221 | #### 任务八:使用 Core&Tree Api 替换方法调用,为系统类或第三方库代码兜底 222 | 223 | 这个任务的思路来源于:[Booster 修复系统 Bug - Android 7.1 Toast 崩溃](https://booster.johnsonlee.io/zh/guide/bugfixing/toast-crash-on-android-25.html#%E6%A0%B9%E6%9C%AC%E5%8E%9F%E5%9B%A0) 224 | 225 | 其实延续上个任务的思路,为特定方法添加 try-catch 也能解决,但是 try-catch 过于粗暴,替换方法调用的方式灵活性更好些(其实在任务六里也有用到该思路)。 226 | 227 | 修改前的 Java 文件: 228 | 229 | ```java 230 | public class ReplaceMethodInvoke { 231 | public static void main(String[] args) { 232 | // throw NPE 233 | new Toast().show(); 234 | } 235 | } 236 | ``` 237 | 238 | 目标生成的类文件,且需要验证反射该 class 的正确性: 239 | 240 | ```java 241 | public class ReplaceMethodInvokeCoreClass { 242 | public ReplaceMethodInvokeCoreClass() { 243 | } 244 | 245 | public static void main(String[] var0) { 246 | ShadowToast.show(new Toast(), "sample/ReplaceMethodInvokeCoreClass"); 247 | } 248 | } 249 | ``` 250 | 251 | #### 任务九:序列化检查 252 | 253 | 这个任务的思路来源于:[ByteX - 序列化检查](https://github.com/bytedance/ByteX/blob/master/serialization-check-plugin/README-zh.md) 254 | 255 | 对于 Java 的序列化,有以下几条规则需要遵守(也就是 IDEA Inspections 里的几条规则): 256 | 257 | 1. 实现了 Serializable 的类未提供 serialVersionUID 字段 258 | 2. 实现了 Serializable 的类包含非 transient、static 的字段,这些字段并未实现 Serializable 接口 259 | 3. 未实现 Serializable 接口的类,包含 transient、serialVersionUID 字段 260 | 4. 实现了 Serializable 的非静态内部类,它的外层类并未实现 Serializable 接口 261 | 262 | 针对以下代码: 263 | 264 | ```java 265 | public class SerializationCheck implements Serializable { 266 | 267 | private ItemBean1 itemBean1; 268 | private ItemBean2 itemBean2; 269 | private transient ItemBean3 itemBean3; 270 | private String name; 271 | private int age; 272 | 273 | static class ItemBean1 { 274 | } 275 | 276 | static class ItemBean2 implements Serializable { 277 | } 278 | 279 | static class ItemBean3 { 280 | } 281 | } 282 | ``` 283 | 284 | 检查输出: 285 | 286 | ```java 287 | Attention: Non-serializable field 'itemBean1' in a Serializable class [sample/SerializationCheckCoreClass] 288 | Attention: This [sample/SerializationCheckCoreClass] class is serializable, but does not define a 'serialVersionUID' field. 289 | ``` 290 | 291 | #### 任务十:检查是否有调用不存在的方法 292 | 293 | 这个任务的思路来源于:[ByteX - 非法引用检查](https://github.com/bytedance/ByteX/blob/master/refer-check-plugin/README-zh.md) 294 | 295 | 不过示例里写的相对简单很多,怎么模拟引用找不到但编译不会报错的情况呢?我们可以借助于依赖方式: 296 | 297 | ```groovy 298 | compileOnly(project(":library1")) 299 | runtimeOnly(fileTree("libs") { include("*.jar") }) 300 | ``` 301 | 302 | 针对以下代码: 303 | 304 | ```java 305 | fun main() { 306 | // throw IllegalAccessError: 307 | // class task_11.ReferCheckKt tried to access private method LibraryClass.testPrivateMethod()V 308 | LibraryClass().testPrivateMethod() 309 | // throw NoSuchMethodError: LibraryClass.testModifyParamsMethod()V 310 | LibraryClass().testModifyParamsMethod() 311 | // NoSuchMethodError: LibraryClass.testModifyReturnTypeMethod()V 312 | LibraryClass().testModifyReturnTypeMethod() 313 | } 314 | ``` 315 | 316 | 检查输出: 317 | 318 | ``` 319 | 在 sample/ReferCheckTreeClass$testPrivateMethod 方法里监测到非法调用: 320 | IllegalAccessError: 试图调用 private final LibraryClass.testPrivateMethod()V 方法. 321 | 322 | 在 sample/ReferCheckTreeClass$testModifyParamsMethod 方法里监测到非法调用: 323 | NoSuchMethodError: 找不到 LibraryClass.testModifyParamsMethod()V 方法. 324 | 325 | 在 sample/ReferCheckTreeClass$testModifyReturnTypeMethod 方法里监测到非法调用: 326 | NoSuchMethodError: 找不到 LibraryClass.testModifyReturnTypeMethod()V 方法. 327 | ``` 328 | 329 | #### 任务十一: 330 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") version "1.7.10" 5 | application 6 | } 7 | 8 | sourceSets { 9 | main { 10 | java { 11 | srcDir("src/main") 12 | } 13 | } 14 | } 15 | 16 | group = "org.example" 17 | version = "1.0-SNAPSHOT" 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | testImplementation(kotlin("test")) 25 | 26 | // ASM 27 | implementation("org.ow2.asm:asm:9.4") 28 | implementation("org.ow2.asm:asm-commons:9.4") 29 | implementation("org.ow2.asm:asm-util:9.4") 30 | implementation("org.ow2.asm:asm-analysis:9.4") 31 | implementation("org.ow2.asm:asm-tree:9.4") 32 | compileOnly(project(":library1")) 33 | runtimeOnly(fileTree("libs") { include("*.jar") }) 34 | } 35 | 36 | tasks.test { 37 | useJUnitPlatform() 38 | } 39 | 40 | tasks.withType { 41 | kotlinOptions.jvmTarget = "11" 42 | } 43 | 44 | application { 45 | mainClass.set("MainKt") 46 | } -------------------------------------------------------------------------------- /files/CatchMethodInvokeCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/CatchMethodInvokeCoreClass.class -------------------------------------------------------------------------------- /files/CatchMethodInvokeTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/CatchMethodInvokeTreeClass.class -------------------------------------------------------------------------------- /files/DeleteLogInvokeCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/DeleteLogInvokeCoreClass.class -------------------------------------------------------------------------------- /files/DeleteLogInvokeTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/DeleteLogInvokeTreeClass.class -------------------------------------------------------------------------------- /files/MainCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/MainCoreClass.class -------------------------------------------------------------------------------- /files/MainTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/MainTreeClass.class -------------------------------------------------------------------------------- /files/MeasureMethodTimeCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/MeasureMethodTimeCoreClass.class -------------------------------------------------------------------------------- /files/MeasureMethodTimeTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/MeasureMethodTimeTreeClass.class -------------------------------------------------------------------------------- /files/PrintMethodParamsCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/PrintMethodParamsCoreClass.class -------------------------------------------------------------------------------- /files/PrintMethodParamsTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/PrintMethodParamsTreeClass.class -------------------------------------------------------------------------------- /files/ReferCheckCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ReferCheckCoreClass.class -------------------------------------------------------------------------------- /files/ReferCheckTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ReferCheckTreeClass.class -------------------------------------------------------------------------------- /files/ReplaceMethodInvokeCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ReplaceMethodInvokeCoreClass.class -------------------------------------------------------------------------------- /files/ReplaceMethodInvokeTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ReplaceMethodInvokeTreeClass.class -------------------------------------------------------------------------------- /files/SerializationCheckCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/SerializationCheckCoreClass.class -------------------------------------------------------------------------------- /files/SerializationCheckTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/SerializationCheckTreeClass.class -------------------------------------------------------------------------------- /files/ThreadReNameCoreClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ThreadReNameCoreClass.class -------------------------------------------------------------------------------- /files/ThreadReNameTreeClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/files/ThreadReNameTreeClass.class -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /library1/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.7.10" 3 | } 4 | 5 | sourceSets { 6 | main { 7 | java { 8 | srcDir("src/main") 9 | } 10 | } 11 | } 12 | 13 | group = "org.example" 14 | version = "1.0-SNAPSHOT" 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | testImplementation(kotlin("test")) 22 | } 23 | 24 | tasks.test { 25 | useJUnitPlatform() 26 | } 27 | 28 | tasks.withType { 29 | kotlinOptions.jvmTarget = "11" 30 | } 31 | 32 | tasks.getByName("test") { 33 | useJUnitPlatform() 34 | } -------------------------------------------------------------------------------- /library1/src/main/kotlin/LibraryClass.kt: -------------------------------------------------------------------------------- 1 | class LibraryClass { 2 | 3 | /** 4 | * libs 里的该类,该方法是 private 的 5 | */ 6 | fun testPrivateMethod() { 7 | println("LibraryClass#testPrivateMethod") 8 | } 9 | 10 | /** 11 | * libs 里的该类,该方法多了一个 int 参数 12 | */ 13 | fun testModifyParamsMethod() { 14 | println("LibraryClass#testModifyParamsMethod") 15 | } 16 | 17 | /** 18 | * libs 里的该类,该方法有了返回值 19 | */ 20 | fun testModifyReturnTypeMethod() { 21 | println("LibraryClass#testModifyReturnTypeMethod") 22 | } 23 | } -------------------------------------------------------------------------------- /libs/library1-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Omooo/ASM-Task/3b023acc6f5725e803f50c1dbfbfddd7c7633030/libs/library1-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "ASM-Study" 3 | include("library1") 4 | -------------------------------------------------------------------------------- /src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | fun main(args: Array) { 2 | println("Hello World!") 3 | 4 | // Try adding program arguments via Run/Debug configuration. 5 | // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. 6 | println("Program arguments: ${args.joinToString()}") 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/common/ByteArrayExt.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | 4 | import java.io.FileOutputStream 5 | 6 | internal fun ByteArray.toFile(fileName: String) { 7 | FileOutputStream(fileName).apply { 8 | write(this@toFile) 9 | flush() 10 | close() 11 | } 12 | } 13 | 14 | internal fun ByteArray.loadClass(fileName: String): Class<*>? { 15 | return Class.forName(fileName, true, Loader(this)) 16 | } 17 | 18 | internal class Loader(private val bytes: ByteArray) : ClassLoader() { 19 | 20 | override fun findClass(name: String?): Class<*> { 21 | return defineClass(name, bytes, 0, bytes.size) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/common/MethodNodeExt.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import org.objectweb.asm.Opcodes 4 | import org.objectweb.asm.tree.LabelNode 5 | import org.objectweb.asm.tree.LineNumberNode 6 | import org.objectweb.asm.tree.MethodNode 7 | 8 | /** 9 | * 校验是否是空方法体 10 | * 11 | * @return true: 是 12 | */ 13 | internal fun MethodNode.isEmpty(): Boolean { 14 | val instructionList = instructions.iterator().asSequence().filterNot { 15 | it is LabelNode || it is LineNumberNode 16 | }.toList() 17 | return instructionList.size == 1 && instructionList[0].opcode == Opcodes.RETURN 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/common/OpcodesExt.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import org.objectweb.asm.Opcodes 4 | import org.objectweb.asm.Type 5 | import java.lang.reflect.Modifier 6 | 7 | internal fun Int.isMethodReturn(): Boolean { 8 | return (this >= Opcodes.IRETURN && this <= Opcodes.RETURN) 9 | || this == Opcodes.ATHROW 10 | } 11 | 12 | internal fun Type.const0Opcode(): Int { 13 | return when (sort) { 14 | Type.BOOLEAN, 15 | Type.BYTE, 16 | Type.CHAR, 17 | Type.INT -> Opcodes.ICONST_0 18 | 19 | Type.FLOAT -> Opcodes.FCONST_0 20 | Type.DOUBLE -> Opcodes.DCONST_0 21 | Type.LONG -> Opcodes.LCONST_0 22 | Type.OBJECT -> Opcodes.ACONST_NULL 23 | else -> throw IllegalStateException("Type is illegal.") 24 | } 25 | } 26 | 27 | /** 28 | * 是否是可达的 29 | * 30 | * @param access 方法或字段的访问权限 31 | * @return true: 可达 32 | */ 33 | internal fun Int.accessible(access: Int): Boolean { 34 | // 非静态调用静态方法 35 | if (Modifier.isStatic(access) && this != Opcodes.INVOKESTATIC) { 36 | return false 37 | } 38 | // 非私有调用私有方法 39 | if (Modifier.isPrivate(access) && this != Opcodes.INVOKESPECIAL) { 40 | return false 41 | } 42 | // todo 完善其他 case 43 | return true 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_1/ReadArrayListCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_1 2 | 3 | import org.objectweb.asm.* 4 | import java.lang.reflect.Modifier 5 | import java.util.ArrayList 6 | 7 | fun main() { 8 | readArrayListByCoreApi() 9 | } 10 | 11 | /** 12 | * 使用 Core Api 读取 ArrayList 类 13 | */ 14 | private fun readArrayListByCoreApi() { 15 | val classReader = ClassReader(ArrayList::class.java.canonicalName) 16 | val classWrite = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 17 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWrite) { 18 | override fun visit( 19 | version: Int, 20 | access: Int, 21 | name: String?, 22 | signature: String?, 23 | superName: String?, 24 | interfaces: Array? 25 | ) { 26 | println("$version ${Modifier.toString(access)} $name $signature $superName ${interfaces?.joinToString(", ")}") 27 | super.visit(version, access, name, signature, superName, interfaces) 28 | } 29 | 30 | override fun visitField( 31 | access: Int, 32 | name: String?, 33 | descriptor: String?, 34 | signature: String?, 35 | value: Any? 36 | ): FieldVisitor { 37 | println("${Modifier.toString(access)} $name $descriptor $signature $value") 38 | return super.visitField(access, name, descriptor, signature, value) 39 | } 40 | 41 | override fun visitMethod( 42 | access: Int, 43 | name: String?, 44 | descriptor: String?, 45 | signature: String?, 46 | exceptions: Array? 47 | ): MethodVisitor { 48 | println("${Modifier.toString(access)} $name $descriptor $signature ${exceptions?.joinToString(", ")}") 49 | return super.visitMethod(access, name, descriptor, signature, exceptions) 50 | } 51 | 52 | override fun visitEnd() { 53 | println("visitEnd") 54 | super.visitEnd() 55 | } 56 | 57 | }, ClassReader.SKIP_CODE) 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/task_1/ReadArrayListTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_1 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.Opcodes 5 | import org.objectweb.asm.tree.ClassNode 6 | import java.lang.reflect.Modifier 7 | import java.util.ArrayList 8 | 9 | fun main() { 10 | readArrayListByTreeApi() 11 | } 12 | 13 | /** 14 | * 使用 Tree Api 读取 ArrayList 类 15 | */ 16 | private fun readArrayListByTreeApi() { 17 | val classReader = ClassReader(ArrayList::class.java.canonicalName) 18 | val classNode = ClassNode(Opcodes.ASM9) 19 | classReader.accept(classNode, ClassReader.SKIP_CODE) 20 | classNode.apply { 21 | println("name: $name\n") 22 | fields.forEach { 23 | println("field: ${it.name} ${Modifier.toString(it.access)} ${it.desc} ${it.value}") 24 | } 25 | println() 26 | methods.forEach { 27 | println("method: ${it.name} ${Modifier.toString(it.access)} ${it.desc}") 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_10/ReferCheck.kt: -------------------------------------------------------------------------------- 1 | package task_10 2 | 3 | import LibraryClass 4 | 5 | internal class ReferCheck { 6 | 7 | fun testPrivateMethod() { 8 | LibraryClass().testPrivateMethod() 9 | } 10 | 11 | fun testModifyParamsMethod() { 12 | LibraryClass().testModifyParamsMethod() 13 | } 14 | 15 | fun testModifyReturnTypeMethod() { 16 | LibraryClass().testModifyReturnTypeMethod() 17 | } 18 | } 19 | 20 | fun main() { 21 | // throw IllegalAccessError: 22 | // class task_11.ReferCheckKt tried to access private method LibraryClass.testPrivateMethod()V 23 | LibraryClass().testPrivateMethod() 24 | // throw NoSuchMethodError: LibraryClass.testModifyParamsMethod()V 25 | LibraryClass().testModifyParamsMethod() 26 | // NoSuchMethodError: LibraryClass.testModifyReturnTypeMethod()V 27 | LibraryClass().testModifyReturnTypeMethod() 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_10/ReferCheckCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_10 2 | 3 | import LibraryClass 4 | import common.accessible 5 | import common.loadClass 6 | import common.toFile 7 | import org.objectweb.asm.* 8 | import org.objectweb.asm.tree.ClassNode 9 | import java.lang.reflect.Modifier 10 | 11 | fun main() { 12 | referCheckByCoreApi() 13 | } 14 | 15 | 16 | /** 17 | * 使用 Core Api 检查是否调用了不存在的方法或字段 18 | */ 19 | private fun referCheckByCoreApi() { 20 | val classReader = ClassReader(ReferCheck::class.java.canonicalName) 21 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 22 | val classMap = readLibrary() 23 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 24 | 25 | val className = "sample/ReferCheckCoreClass" 26 | 27 | override fun visit( 28 | version: Int, 29 | access: Int, 30 | name: String?, 31 | signature: String?, 32 | superName: String?, 33 | interfaces: Array? 34 | ) { 35 | super.visit(version, access, className, signature, superName, interfaces) 36 | } 37 | 38 | override fun visitMethod( 39 | access: Int, 40 | name: String, 41 | descriptor: String?, 42 | signature: String?, 43 | exceptions: Array? 44 | ): MethodVisitor { 45 | val visitMethod = super.visitMethod(access, name, descriptor, signature, exceptions) 46 | return InternalMethodVisitor(visitMethod, className, name, classMap) 47 | } 48 | }, ClassReader.SKIP_DEBUG) 49 | 50 | classWriter.toByteArray().apply { 51 | toFile("files/ReferCheckCoreClass.class") 52 | loadClass("sample.ReferCheckCoreClass")?.apply { 53 | // 抛异常 54 | getMethod("testPrivateMethod").invoke(getConstructor().newInstance()) 55 | getMethod("testModifyParamsMethod").invoke(getConstructor().newInstance()) 56 | getMethod("testModifyReturnTypeMethod").invoke(getConstructor().newInstance()) 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * 读取依赖类生成 Map 63 | * 64 | * @return Map, key: 类名;value: [ClassNode],选择 [ClassNode] 是因为很好表示 65 | */ 66 | private fun readLibrary(): Map { 67 | val classReader = ClassReader(LibraryClass::class.java.canonicalName) 68 | val classNode = ClassNode(Opcodes.ASM9) 69 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 70 | return mapOf(classNode.name to classNode) 71 | } 72 | 73 | private class InternalMethodVisitor( 74 | methodVisitor: MethodVisitor, 75 | val className: String, 76 | val methodName: String, 77 | val classMap: Map 78 | ) : MethodVisitor(Opcodes.ASM9, methodVisitor) { 79 | override fun visitMethodInsn( 80 | opcode: Int, 81 | owner: String?, 82 | name: String?, 83 | descriptor: String?, 84 | isInterface: Boolean 85 | ) { 86 | if (classMap.containsKey(owner)) { 87 | classMap[owner]?.methods?.find { 88 | it.name == name && descriptor == it.desc 89 | }?.let { 90 | if (!opcode.accessible(it.access)) { 91 | println( 92 | """ 93 | 在 $className${'$'}$methodName 方法里监测到非法调用: 94 | IllegalAccessError: 试图调用 ${Modifier.toString(it.access)} $owner.$name$descriptor 方法. 95 | 96 | """.trimIndent() 97 | ) 98 | } 99 | } ?: run { 100 | println( 101 | """ 102 | 在 $className${'$'}$methodName 方法里监测到非法调用: 103 | NoSuchMethodError: 找不到 $owner.$name$descriptor 方法. 104 | 105 | """.trimIndent() 106 | ) 107 | } 108 | } else { 109 | println("classMap 未包含 $owner 类.") 110 | } 111 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 112 | } 113 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_10/ReferCheckTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_10 2 | 3 | import LibraryClass 4 | import common.accessible 5 | import common.loadClass 6 | import common.toFile 7 | import org.objectweb.asm.ClassReader 8 | import org.objectweb.asm.ClassWriter 9 | import org.objectweb.asm.Opcodes 10 | import org.objectweb.asm.tree.ClassNode 11 | import org.objectweb.asm.tree.MethodInsnNode 12 | import java.lang.reflect.Modifier 13 | 14 | fun main() { 15 | referCheckByTreeApi() 16 | } 17 | 18 | /** 19 | * 使用 Core Api 检查是否调用了不存在的方法或字段 20 | */ 21 | private fun referCheckByTreeApi() { 22 | val classReader = ClassReader(ReferCheck::class.qualifiedName) 23 | val classNode = ClassNode(Opcodes.ASM9) 24 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 25 | classNode.name = "sample/ReferCheckTreeClass" 26 | 27 | val classMap = readLibrary() 28 | classNode.methods.forEach { methodNode -> 29 | methodNode.instructions.filterIsInstance().forEach { methodInsnNode -> 30 | if (classMap.containsKey(methodInsnNode.owner)) { 31 | classMap[methodInsnNode.owner]?.methods?.find { 32 | it.name == methodInsnNode.name && methodInsnNode.desc == it.desc 33 | }?.let { 34 | if (!methodInsnNode.opcode.accessible(it.access)) { 35 | println( 36 | """ 37 | 在 ${classNode.name}$${methodNode.name} 方法里监测到非法调用: 38 | IllegalAccessError: 试图调用 ${Modifier.toString(it.access)} ${methodInsnNode.owner}.${methodInsnNode.name}${methodInsnNode.desc} 方法. 39 | 40 | """.trimIndent() 41 | ) 42 | } 43 | } ?: run { 44 | println( 45 | """ 46 | 在 ${classNode.name}${'$'}${methodNode.name} 方法里监测到非法调用: 47 | NoSuchMethodError: 找不到 ${methodInsnNode.owner}.${methodInsnNode.name}${methodInsnNode.desc} 方法. 48 | 49 | """.trimIndent() 50 | ) 51 | } 52 | } else { 53 | println("classMap 未包含 ${methodInsnNode.owner} 类.") 54 | } 55 | } 56 | } 57 | 58 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 59 | classNode.accept(classWriter) 60 | classWriter.toByteArray()?.apply { 61 | toFile("files/ReferCheckTreeClass.class") 62 | loadClass("sample.ReferCheckTreeClass")?.apply { 63 | // 抛异常 64 | getMethod("testPrivateMethod").invoke(getConstructor().newInstance()) 65 | getMethod("testModifyParamsMethod").invoke(getConstructor().newInstance()) 66 | getMethod("testModifyReturnTypeMethod").invoke(getConstructor().newInstance()) 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * 读取依赖类生成 Map 73 | * 74 | * @return Map, key: 类名;value: [ClassNode],选择 [ClassNode] 是因为很好表示 75 | */ 76 | private fun readLibrary(): Map { 77 | val classReader = ClassReader(LibraryClass::class.java.canonicalName) 78 | val classNode = ClassNode(Opcodes.ASM9) 79 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 80 | return mapOf(classNode.name to classNode) 81 | } 82 | -------------------------------------------------------------------------------- /src/main/kotlin/task_2/GenerateMainClassCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_2 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassWriter 6 | import org.objectweb.asm.Opcodes 7 | import org.objectweb.asm.Type 8 | 9 | fun main() { 10 | generateMainClassByCoreApi() 11 | } 12 | 13 | /** 14 | * 使用 Core Api 生成 [Main] 类 15 | */ 16 | private fun generateMainClassByCoreApi() { 17 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 18 | classWriter.visit(Opcodes.V11, Opcodes.ACC_PUBLIC, "sample/MainCoreClass", null, "java/lang/Object", null) 19 | // 生成 [private static final String TAG = "Main";] 20 | classWriter.visitField( 21 | Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, 22 | "TAG", 23 | Type.getDescriptor(String::class.java), 24 | null, 25 | "Main" 26 | ) 27 | // 生成构造方法 28 | classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null).apply { 29 | visitVarInsn(Opcodes.ALOAD, 0) 30 | visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false) 31 | visitInsn(Opcodes.RETURN) 32 | visitMaxs(1, 1) 33 | visitEnd() 34 | } 35 | // 生成 main 方法 [public static void main(String[] args) {}] 36 | classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null) 37 | .apply { 38 | visitCode() 39 | visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") 40 | visitLdcInsn("Hello World.") 41 | visitMethodInsn( 42 | Opcodes.INVOKEVIRTUAL, 43 | "java/io/PrintStream", 44 | "println", 45 | "(${Type.getDescriptor(String::class.java)})V", 46 | false 47 | ) 48 | visitInsn(Opcodes.RETURN) 49 | visitMaxs(1, 1) 50 | visitEnd() 51 | } 52 | classWriter.visitEnd() 53 | // 写入 class 文件 54 | classWriter.toByteArray().apply { 55 | toFile("files/MainCoreClass.class") 56 | loadClass("sample.MainCoreClass")?.apply { 57 | declaredFields.forEach { 58 | it.isAccessible = true 59 | println("field name: ${it.name}, value: ${it.get(null)}") 60 | } 61 | getMethod("main", Array::class.java).invoke(null, null) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_2/GenerateMainClassTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_2 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassWriter 6 | import org.objectweb.asm.Opcodes 7 | import org.objectweb.asm.Type 8 | import org.objectweb.asm.tree.* 9 | 10 | fun main() { 11 | generateMainClassByTreeApi() 12 | } 13 | 14 | /** 15 | * 使用 Tree Api 生成 [Main] 类 16 | */ 17 | private fun generateMainClassByTreeApi() { 18 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 19 | val classNode = ClassNode(Opcodes.ASM9) 20 | classNode.apply { 21 | version = Opcodes.V11 22 | name = "sample/MainTreeClass" 23 | access = Opcodes.ACC_PUBLIC 24 | superName = "java/lang/Object" 25 | 26 | // 生成 [private static final String TAG = "Main";] 27 | fields.add( 28 | FieldNode( 29 | Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, 30 | "TAG", Type.getDescriptor(String::class.java), null, "Main" 31 | ) 32 | ) 33 | // 生成构造方法 34 | methods.add(MethodNode( 35 | Opcodes.ACC_PUBLIC, "", "()V", null, null 36 | ).apply { 37 | instructions.add(InsnList().apply { 38 | add(VarInsnNode(Opcodes.ALOAD, 0)) 39 | add(MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false)) 40 | add(InsnNode(Opcodes.RETURN)) 41 | }) 42 | maxLocals = 1 43 | maxStack = 1 44 | }) 45 | // 生成 main 方法 [public static void main(String[] args) {}] 46 | methods.add(MethodNode( 47 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null 48 | ).apply { 49 | instructions.apply { 50 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 51 | add(LdcInsnNode("Hello World.")) 52 | add( 53 | MethodInsnNode( 54 | Opcodes.INVOKEVIRTUAL, 55 | "java/io/PrintStream", 56 | "println", 57 | "(Ljava/lang/String;)V", 58 | false 59 | ) 60 | ) 61 | add(InsnNode(Opcodes.RETURN)) 62 | } 63 | }) 64 | } 65 | classNode.accept(classWriter) 66 | classWriter.toByteArray().apply { 67 | toFile("files/MainTreeClass.class") 68 | loadClass("sample.MainTreeClass")?.apply { 69 | declaredFields.forEach { 70 | it.isAccessible = true 71 | println("field name: ${it.name}, value: ${it.get(null)}") 72 | } 73 | getMethod("main", Array::class.java).invoke(null, null) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_2/Main.java: -------------------------------------------------------------------------------- 1 | package task_2; 2 | 3 | public class Main { 4 | 5 | private static final String TAG = "Main"; 6 | 7 | public static void main(String[] args) { 8 | System.out.println("Hello World."); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/task_3/PrintMethodParams.java: -------------------------------------------------------------------------------- 1 | package task_3; 2 | 3 | public class PrintMethodParams { 4 | public String print(String name, int age) { 5 | return name + ": " + age; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/task_3/PrintMethodParamsCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_3 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.* 6 | import java.lang.reflect.Modifier 7 | 8 | fun main() { 9 | printMethodParamsByCoreApi() 10 | } 11 | 12 | /** 13 | * 使用 Core Api 打印出 [PrintMethodParams] 方法的入参和出参 14 | */ 15 | private fun printMethodParamsByCoreApi() { 16 | val classReader = ClassReader(PrintMethodParams::class.java.canonicalName) 17 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 18 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 19 | override fun visit( 20 | version: Int, 21 | access: Int, 22 | name: String?, 23 | signature: String?, 24 | superName: String?, 25 | interfaces: Array? 26 | ) { 27 | // 更改类名和包名 28 | super.visit(version, access, "sample/PrintMethodParamsCoreClass", signature, superName, interfaces) 29 | } 30 | 31 | override fun visitMethod( 32 | access: Int, 33 | name: String?, 34 | descriptor: String, 35 | signature: String?, 36 | exceptions: Array? 37 | ): MethodVisitor { 38 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 39 | // 不是构造方法、抽象方法、Native 方法 40 | if (name != "" && !Modifier.isAbstract(access) && !Modifier.isNative(access)) { 41 | return InternalMethodVisitor(methodVisitor, access, descriptor) 42 | } 43 | return methodVisitor 44 | } 45 | }, ClassReader.SKIP_DEBUG) 46 | classWriter.toByteArray().apply { 47 | toFile("files/PrintMethodParamsCoreClass.class") 48 | loadClass("sample.PrintMethodParamsCoreClass")?.apply { 49 | val method = getMethod("print", String::class.java, Int::class.java) 50 | method.invoke(getConstructor().newInstance(), "Omooo", 25) 51 | } 52 | } 53 | } 54 | 55 | class InternalMethodVisitor( 56 | methodVisitor: MethodVisitor, 57 | private val methodAccess: Int, 58 | private val methodDesc: String 59 | ) : 60 | MethodVisitor(Opcodes.ASM9, methodVisitor) { 61 | 62 | override fun visitCode() { 63 | // 局部变量表的索引,非 static 方法,第 0 个是 this 指针 64 | var slotIndex = if (Modifier.isStatic(methodAccess)) 0 else 1 65 | val methodType = Type.getMethodType(methodDesc) 66 | methodType.argumentTypes.forEach { type -> 67 | when (type.sort) { 68 | Type.INT -> { 69 | mv.visitVarInsn(Opcodes.ILOAD, slotIndex) 70 | printInt() 71 | } 72 | 73 | Type.getType(String::class.java).sort -> { 74 | mv.visitVarInsn(Opcodes.ALOAD, slotIndex) 75 | printString() 76 | } 77 | // 其他类型大差不差就不写了 78 | } 79 | // type.size 就是该局部变量所占的槽位大小,对于 Long、Double 占两个槽位,其他类型占一个槽位 80 | slotIndex += type.size 81 | } 82 | super.visitCode() 83 | } 84 | 85 | override fun visitInsn(opcode: Int) { 86 | when (opcode) { 87 | Opcodes.ARETURN -> { 88 | mv.visitInsn(Opcodes.DUP) 89 | printObject() 90 | } 91 | 92 | Opcodes.IRETURN -> { 93 | mv.visitInsn(Opcodes.DUP) 94 | printInt() 95 | } 96 | // 其他类型大差不差就不写了 97 | } 98 | super.visitInsn(opcode) 99 | } 100 | 101 | private fun printInt() { 102 | mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") 103 | mv.visitInsn(Opcodes.SWAP) 104 | mv.visitMethodInsn( 105 | Opcodes.INVOKEVIRTUAL, 106 | "java/io/PrintStream", 107 | "println", 108 | "(I)V", 109 | false 110 | ) 111 | } 112 | 113 | private fun printString() { 114 | mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") 115 | mv.visitInsn(Opcodes.SWAP) 116 | mv.visitMethodInsn( 117 | Opcodes.INVOKEVIRTUAL, 118 | "java/io/PrintStream", 119 | "println", 120 | "(Ljava/lang/String;)V", 121 | false 122 | ) 123 | } 124 | 125 | private fun printObject() { 126 | mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") 127 | mv.visitInsn(Opcodes.SWAP) 128 | mv.visitMethodInsn( 129 | Opcodes.INVOKEVIRTUAL, 130 | "java/io/PrintStream", 131 | "println", 132 | "(${Type.getDescriptor(Object::class.java)})V", 133 | false 134 | ) 135 | } 136 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_3/PrintMethodParamsTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_3 2 | 3 | import common.isMethodReturn 4 | import common.loadClass 5 | import common.toFile 6 | import org.objectweb.asm.ClassReader 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.Opcodes 9 | import org.objectweb.asm.Type 10 | import org.objectweb.asm.tree.* 11 | import java.lang.reflect.Modifier 12 | 13 | fun main() { 14 | printMethodParamsByTreeApi() 15 | } 16 | 17 | private fun printMethodParamsByTreeApi() { 18 | val classReader = ClassReader(PrintMethodParams::class.java.canonicalName) 19 | val classNode = ClassNode(Opcodes.ASM9) 20 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 21 | classNode.name = "sample/PrintMethodParamsTreeClass" 22 | classNode.methods.filter { 23 | it.name != "" && !Modifier.isAbstract(it.access) && !Modifier.isNative(it.access) 24 | }.forEach { methodNode -> 25 | val methodType = Type.getType(methodNode.desc) 26 | var slotIndex = if (Modifier.isStatic(methodNode.access)) 0 else 1 27 | // 给方法入参添加输出 28 | methodType.argumentTypes.forEach { 29 | when (it.sort) { 30 | Type.INT -> { 31 | printInt(methodNode, slotIndex) 32 | } 33 | 34 | Type.getType(String::class.java).sort -> { 35 | printString(methodNode, slotIndex) 36 | } 37 | // 其他类型大差不差就不写了 38 | } 39 | slotIndex += it.size 40 | } 41 | // 给方法出参添加输出 42 | methodNode.instructions.filter { 43 | it.opcode.isMethodReturn() 44 | }.forEach { 45 | when (it.opcode) { 46 | Opcodes.IRETURN -> { 47 | printInt(it, methodNode) 48 | } 49 | 50 | Opcodes.ARETURN -> { 51 | printObject(it, methodNode) 52 | } 53 | } 54 | } 55 | } 56 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 57 | classNode.accept(classWriter) 58 | classWriter.toByteArray().apply { 59 | toFile("files/PrintMethodParamsTreeClass.class") 60 | loadClass("sample.PrintMethodParamsTreeClass")?.apply { 61 | val method = getMethod("print", String::class.java, Int::class.java) 62 | method.invoke(getConstructor().newInstance(), "Omooo", 25) 63 | } 64 | } 65 | 66 | } 67 | 68 | /** 69 | * 输出出参,类型 [Int] 70 | * 71 | * @param nextInsn 下一个节点 72 | * @param methodNode [MethodNode] 73 | */ 74 | private fun printInt(nextInsn: AbstractInsnNode, methodNode: MethodNode) { 75 | methodNode.instructions.insertBefore(nextInsn, InsnList().apply { 76 | add(InsnNode(Opcodes.DUP)) 77 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 78 | add(InsnNode(Opcodes.SWAP)) 79 | add( 80 | MethodInsnNode( 81 | Opcodes.INVOKEVIRTUAL, 82 | "java/io/PrintStream", 83 | "println", 84 | "(I)V", 85 | false 86 | ) 87 | ) 88 | }) 89 | } 90 | 91 | /** 92 | * 输出出参,引用类型 93 | * 94 | * @param nextInsn 下一个节点 95 | * @param methodNode [MethodNode] 96 | */ 97 | private fun printObject(nextInsn: AbstractInsnNode, methodNode: MethodNode) { 98 | methodNode.instructions.insertBefore(nextInsn, InsnList().apply { 99 | add(InsnNode(Opcodes.DUP)) 100 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 101 | add(InsnNode(Opcodes.SWAP)) 102 | add( 103 | MethodInsnNode( 104 | Opcodes.INVOKEVIRTUAL, 105 | "java/io/PrintStream", 106 | "println", 107 | "(Ljava/lang/Object;)V", 108 | false 109 | ) 110 | ) 111 | }) 112 | } 113 | 114 | /** 115 | * 输出入参,类型 [Int] 116 | * 117 | * @param methodNode [MethodNode] 118 | * @param slotIndex 局部变量表索引 119 | */ 120 | private fun printInt(methodNode: MethodNode, slotIndex: Int) { 121 | methodNode.instructions.insert(InsnList().apply { 122 | add(VarInsnNode(Opcodes.ILOAD, slotIndex)) 123 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 124 | add(InsnNode(Opcodes.SWAP)) 125 | add( 126 | MethodInsnNode( 127 | Opcodes.INVOKEVIRTUAL, 128 | "java/io/PrintStream", 129 | "println", 130 | "(I)V", 131 | false 132 | ) 133 | ) 134 | }) 135 | } 136 | 137 | /** 138 | * 输出出参,类型 [String] 139 | * 140 | * @param methodNode [MethodNode] 141 | * @param slotIndex 局部变量表索引 142 | */ 143 | private fun printString(methodNode: MethodNode, slotIndex: Int) { 144 | methodNode.instructions.insert(InsnList().apply { 145 | add(VarInsnNode(Opcodes.ALOAD, slotIndex)) 146 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 147 | add(InsnNode(Opcodes.SWAP)) 148 | add( 149 | MethodInsnNode( 150 | Opcodes.INVOKEVIRTUAL, 151 | "java/io/PrintStream", 152 | "println", 153 | "(${Type.getDescriptor(Object::class.java)})V", 154 | false 155 | ) 156 | ) 157 | }) 158 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_4/DeleteLogInvoke.java: -------------------------------------------------------------------------------- 1 | package task_4; 2 | 3 | /** 4 | * 删除方法里的 sout 语句 5 | */ 6 | public class DeleteLogInvoke { 7 | 8 | public String print(String name, int age) { 9 | System.out.println(name); 10 | String result = name + ": " + age; 11 | System.out.println(result); 12 | System.out.println("Delete current line."); 13 | System.out.println("name = " + name + ", age = " + age); 14 | System.out.printf("name: %s%n", name); 15 | System.out.println(String.format("age: %d", age)); 16 | return result; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/task_4/DeleteLogInvokeCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_4 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.* 6 | 7 | fun main() { 8 | deleteLogInvokeByCoreApi() 9 | } 10 | 11 | /** 12 | * 使用 Core Api 删除 sout 日志语句 13 | * 14 | * 思路: 删除 GETSTATIC out 和 INVOKEVIRTUAL print 之间的指令 15 | */ 16 | private fun deleteLogInvokeByCoreApi() { 17 | val classReader = ClassReader(DeleteLogInvoke::class.java.canonicalName) 18 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 19 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 20 | 21 | override fun visit( 22 | version: Int, 23 | access: Int, 24 | name: String?, 25 | signature: String?, 26 | superName: String?, 27 | interfaces: Array? 28 | ) { 29 | super.visit(version, access, "sample/DeleteLogInvokeCoreClass", signature, superName, interfaces) 30 | } 31 | 32 | override fun visitMethod( 33 | access: Int, 34 | name: String?, 35 | descriptor: String?, 36 | signature: String?, 37 | exceptions: Array? 38 | ): MethodVisitor { 39 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 40 | return DeleteMethodVisitor(methodVisitor) 41 | } 42 | }, ClassReader.SKIP_DEBUG) 43 | 44 | classWriter.toByteArray().apply { 45 | toFile("files/DeleteLogInvokeCoreClass.class") 46 | loadClass("sample.DeleteLogInvokeCoreClass")?.apply { 47 | val printMethod = getMethod("print", String::class.java, Int::class.java) 48 | printMethod.invoke(getConstructor().newInstance(), "Omooo", 25) 49 | } 50 | } 51 | 52 | } 53 | 54 | private class DeleteMethodVisitor(methodVisitor: MethodVisitor) : MethodVisitor(Opcodes.ASM9, methodVisitor) { 55 | /** 删除期间 */ 56 | var deleteDuration = false 57 | 58 | /** 删除 pop 指令 */ 59 | var deletePopInsn = false 60 | override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) { 61 | if (opcode == Opcodes.GETSTATIC && owner == "java/lang/System" && name == "out" && descriptor == "Ljava/io/PrintStream;") { 62 | deleteDuration = true 63 | return 64 | } 65 | deleteDuration = false 66 | super.visitFieldInsn(opcode, owner, name, descriptor) 67 | } 68 | 69 | override fun visitLdcInsn(value: Any?) { 70 | if (deleteDuration) { 71 | return 72 | } 73 | super.visitLdcInsn(value) 74 | } 75 | 76 | override fun visitInsn(opcode: Int) { 77 | if (deleteDuration) { 78 | return 79 | } 80 | if (deletePopInsn) { 81 | deletePopInsn = false 82 | return 83 | } 84 | super.visitInsn(opcode) 85 | } 86 | 87 | override fun visitVarInsn(opcode: Int, varIndex: Int) { 88 | if (deleteDuration) { 89 | return 90 | } 91 | super.visitVarInsn(opcode, varIndex) 92 | } 93 | 94 | override fun visitTypeInsn(opcode: Int, type: String?) { 95 | if (deleteDuration) { 96 | return 97 | } 98 | super.visitTypeInsn(opcode, type) 99 | } 100 | 101 | override fun visitInvokeDynamicInsn( 102 | name: String?, 103 | descriptor: String?, 104 | bootstrapMethodHandle: Handle?, 105 | vararg bootstrapMethodArguments: Any? 106 | ) { 107 | if (deleteDuration) { 108 | return 109 | } 110 | super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, *bootstrapMethodArguments) 111 | } 112 | 113 | override fun visitMethodInsn( 114 | opcode: Int, 115 | owner: String?, 116 | name: String, 117 | descriptor: String?, 118 | isInterface: Boolean 119 | ) { 120 | if (opcode == Opcodes.INVOKEVIRTUAL 121 | && owner == "java/io/PrintStream" 122 | && name.startsWith("print") 123 | ) { 124 | deletePopInsn = Type.getMethodType(descriptor).returnType != Type.VOID_TYPE 125 | deleteDuration = false 126 | return 127 | } 128 | if (deleteDuration) { 129 | return 130 | } 131 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/kotlin/task_4/DeleteLogInvokeTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_4 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.ClassWriter 7 | import org.objectweb.asm.Opcodes 8 | import org.objectweb.asm.Type 9 | import org.objectweb.asm.tree.AbstractInsnNode 10 | import org.objectweb.asm.tree.ClassNode 11 | import org.objectweb.asm.tree.FieldInsnNode 12 | import org.objectweb.asm.tree.MethodInsnNode 13 | 14 | fun main() { 15 | deleteLogInvokeByTreeApi() 16 | } 17 | 18 | /** 19 | * 使用 Tree Api 删除 sout 日志语句 20 | * 21 | * 思路: 删除 GETSTATIC out 和 INVOKEVIRTUAL print 之间的指令 22 | */ 23 | private fun deleteLogInvokeByTreeApi() { 24 | val classReader = ClassReader(DeleteLogInvoke::class.qualifiedName) 25 | val classNode = ClassNode(Opcodes.ASM9) 26 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 27 | 28 | classNode.name = "sample/DeleteLogInvokeTreeClass" 29 | classNode.methods.forEach { methodNode -> 30 | val deleteNodes = mutableListOf() 31 | methodNode.instructions.forEachIndexed { index, node -> 32 | if (node is MethodInsnNode && node.opcode == Opcodes.INVOKEVIRTUAL 33 | && node.name.startsWith("print") 34 | && node.owner == "java/io/PrintStream" 35 | ) { 36 | // 往前找到 getStatic 37 | var startIndex = index - 1 38 | while (!methodNode.instructions[startIndex].let { 39 | it is FieldInsnNode && it.opcode == Opcodes.GETSTATIC 40 | && it.name == "out" && it.desc == "Ljava/io/PrintStream;" 41 | && it.owner == "java/lang/System" 42 | }) { 43 | startIndex-- 44 | } 45 | var endIndex = index 46 | if (Type.getMethodType(node.desc).returnType != Type.VOID_TYPE 47 | && node.next != null && node.next.opcode == Opcodes.POP 48 | ) { 49 | // 如何方法的返回值不是 Void,还要删除后面的 POP 指令 50 | endIndex++ 51 | } 52 | (startIndex..endIndex).forEach { 53 | deleteNodes.add(methodNode.instructions[it]) 54 | } 55 | } 56 | } 57 | 58 | deleteNodes.forEach { 59 | methodNode.instructions.remove(it) 60 | } 61 | 62 | } 63 | 64 | val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) 65 | classNode.accept(classWriter) 66 | classWriter.toByteArray().apply { 67 | toFile("files/DeleteLogInvokeTreeClass.class") 68 | loadClass("sample.DeleteLogInvokeTreeClass")?.apply { 69 | val printMethod = getMethod("print", String::class.java, Int::class.java) 70 | printMethod.invoke(getConstructor().newInstance(), "Omooo", 25) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/kotlin/task_5/MeasureMethodTime.java: -------------------------------------------------------------------------------- 1 | package task_5; 2 | 3 | public class MeasureMethodTime { 4 | 5 | @MeasureTime 6 | public void measure() { 7 | try { 8 | Thread.sleep(2000); 9 | } catch (InterruptedException e) { 10 | throw new RuntimeException(e); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/task_5/MeasureMethodTimeCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_5 2 | 3 | import common.isMethodReturn 4 | import common.loadClass 5 | import common.toFile 6 | import org.objectweb.asm.* 7 | import org.objectweb.asm.commons.LocalVariablesSorter 8 | 9 | fun main() { 10 | measureMethodTimeByCoreApi() 11 | } 12 | 13 | /** 14 | * 使用 Core Api 测量被 @MeasureTime 标记的方法的耗时 15 | */ 16 | private fun measureMethodTimeByCoreApi() { 17 | val classReader = ClassReader(MeasureMethodTime::class.java.canonicalName) 18 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 19 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 20 | 21 | override fun visit( 22 | version: Int, 23 | access: Int, 24 | name: String?, 25 | signature: String?, 26 | superName: String?, 27 | interfaces: Array? 28 | ) { 29 | super.visit(version, access, "sample/MeasureMethodTimeCoreClass", signature, superName, interfaces) 30 | } 31 | 32 | override fun visitMethod( 33 | access: Int, 34 | name: String, 35 | descriptor: String, 36 | signature: String?, 37 | exceptions: Array? 38 | ): MethodVisitor { 39 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 40 | return InternalMethodVisitor(access, descriptor, methodVisitor) 41 | } 42 | }, ClassReader.EXPAND_FRAMES) 43 | 44 | classWriter.toByteArray().apply { 45 | toFile("files/MeasureMethodTimeCoreClass.class") 46 | loadClass("sample.MeasureMethodTimeCoreClass")?.apply { 47 | getMethod("measure").invoke(getConstructor().newInstance()) 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * MethodVisitor 54 | * 55 | * 这里使用 LocalVariablesSorter 是为了方便生成局部变量获取其索引 56 | */ 57 | private class InternalMethodVisitor(methodAccess: Int, methodDesc: String, methodVisitor: MethodVisitor) : 58 | LocalVariablesSorter(Opcodes.ASM9, methodAccess, methodDesc, methodVisitor) { 59 | 60 | /** 标记需要测量的方法 */ 61 | var trackMethodFlag = false 62 | 63 | /** 起始时间索引 */ 64 | var startTimeIndex = 0 65 | 66 | /** 终止时间索引 */ 67 | var endTimeIndex = 0 68 | 69 | override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { 70 | if (Type.getDescriptor(MeasureTime::class.java) == descriptor) { 71 | trackMethodFlag = true 72 | } 73 | return super.visitAnnotation(descriptor, visible) 74 | } 75 | 76 | override fun visitCode() { 77 | if (trackMethodFlag) { 78 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false) 79 | startTimeIndex = newLocal(Type.LONG_TYPE) 80 | mv.visitVarInsn(Opcodes.LSTORE, startTimeIndex) 81 | } 82 | super.visitCode() 83 | } 84 | 85 | override fun visitInsn(opcode: Int) { 86 | if (trackMethodFlag && opcode.isMethodReturn()) { 87 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false) 88 | endTimeIndex = newLocal(Type.LONG_TYPE) 89 | mv.visitVarInsn(Opcodes.LSTORE, endTimeIndex) 90 | mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") 91 | mv.visitVarInsn(Opcodes.LLOAD, endTimeIndex) 92 | mv.visitVarInsn(Opcodes.LLOAD, startTimeIndex) 93 | mv.visitInsn(Opcodes.LSUB) 94 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false) 95 | } 96 | super.visitInsn(opcode) 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_5/MeasureMethodTimeTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_5 2 | 3 | import common.isMethodReturn 4 | import common.loadClass 5 | import common.toFile 6 | import org.objectweb.asm.ClassReader 7 | import org.objectweb.asm.ClassWriter 8 | import org.objectweb.asm.Opcodes 9 | import org.objectweb.asm.Type 10 | import org.objectweb.asm.tree.ClassNode 11 | import org.objectweb.asm.tree.FieldInsnNode 12 | import org.objectweb.asm.tree.InsnList 13 | import org.objectweb.asm.tree.InsnNode 14 | import org.objectweb.asm.tree.MethodInsnNode 15 | import org.objectweb.asm.tree.VarInsnNode 16 | 17 | fun main() { 18 | measureMethodTimeByTreeApi() 19 | } 20 | 21 | /** 22 | * 使用 Tree Api 测量被 @MeasureTime 标记的方法的耗时 23 | */ 24 | private fun measureMethodTimeByTreeApi() { 25 | val classReader = ClassReader(MeasureMethodTime::class.java.canonicalName) 26 | val classNode = ClassNode(Opcodes.ASM9) 27 | classReader.accept(classNode, ClassReader.SKIP_FRAMES) 28 | 29 | classNode.name = "sample/MeasureMethodTimeTreeClass" 30 | classNode.methods.forEach { methodNode -> 31 | if (methodNode.invisibleAnnotations?.map { it.desc } 32 | ?.contains(Type.getDescriptor(MeasureTime::class.java)) == true) { 33 | val localVariablesSize = methodNode.localVariables.size 34 | // 在方法的第一个指令之前插入 35 | val firstInsnNode = methodNode.instructions.first 36 | methodNode.instructions.insertBefore(firstInsnNode, InsnList().apply { 37 | add(MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")) 38 | add(VarInsnNode(Opcodes.LSTORE, localVariablesSize + 1)) 39 | }) 40 | 41 | // 在方法 return 指令之前插入 42 | methodNode.instructions.filter { 43 | it.opcode.isMethodReturn() 44 | }.forEach { 45 | methodNode.instructions.insertBefore(it, InsnList().apply { 46 | add(MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")) 47 | // 注意,Long 是占两个局部变量槽位的,所以这里要较之前 +3,而不是 +2 48 | add(VarInsnNode(Opcodes.LSTORE, localVariablesSize + 3)) 49 | add(FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")) 50 | add(VarInsnNode(Opcodes.LLOAD, localVariablesSize + 3)) 51 | add(VarInsnNode(Opcodes.LLOAD, localVariablesSize + 1)) 52 | add(InsnNode(Opcodes.LSUB)) 53 | add(MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V")) 54 | }) 55 | } 56 | } 57 | } 58 | 59 | val classWrite = ClassWriter(ClassWriter.COMPUTE_FRAMES) 60 | classNode.accept(classWrite) 61 | classWrite.toByteArray().apply { 62 | toFile("files/MeasureMethodTimeTreeClass.class") 63 | loadClass("sample.MeasureMethodTimeTreeClass")?.apply { 64 | getMethod("measure").invoke(getConstructor().newInstance()) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/task_5/MeasureTime.java: -------------------------------------------------------------------------------- 1 | package task_5; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.CLASS) 10 | public @interface MeasureTime { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/task_6/InternalRunnable.java: -------------------------------------------------------------------------------- 1 | package task_6; 2 | 3 | public class InternalRunnable implements Runnable { 4 | @Override 5 | public void run() { 6 | System.out.println("InternalRunnable#run"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/task_6/ShadowThread.java: -------------------------------------------------------------------------------- 1 | package task_6; 2 | 3 | /** 4 | * copy from Booster 5 | * ... 6 | */ 7 | public class ShadowThread extends Thread { 8 | /** 9 | * {@code U+200B}: Zero-Width Space 10 | */ 11 | static final String MARK = "\u200B"; 12 | 13 | public static Thread newThread(final String prefix) { 14 | return new Thread(prefix); 15 | } 16 | 17 | public static Thread newThread(final Runnable target, final String prefix) { 18 | return new Thread(target, prefix); 19 | } 20 | 21 | public static Thread newThread(final ThreadGroup group, final Runnable target, final String prefix) { 22 | return new Thread(group, target, prefix); 23 | } 24 | 25 | public static Thread newThread(final String name, final String prefix) { 26 | return new Thread(makeThreadName(name, prefix)); 27 | } 28 | 29 | public static Thread newThread(final ThreadGroup group, final String name, final String prefix) { 30 | return new Thread(group, makeThreadName(name, prefix)); 31 | } 32 | 33 | public static Thread newThread(final Runnable target, final String name, final String prefix) { 34 | return new Thread(target, makeThreadName(name, prefix)); 35 | } 36 | 37 | public static Thread newThread(final ThreadGroup group, final Runnable target, final String name, final String prefix) { 38 | return new Thread(group, target, makeThreadName(name, prefix)); 39 | } 40 | 41 | public static Thread newThread(final ThreadGroup group, final Runnable target, final String name, final long stackSize, final String prefix) { 42 | return new Thread(group, target, makeThreadName(name, prefix), stackSize); 43 | } 44 | 45 | public static Thread setThreadName(final Thread t, final String prefix) { 46 | t.setName(makeThreadName(t.getName(), prefix)); 47 | return t; 48 | } 49 | 50 | public static String makeThreadName(final String name) { 51 | return name == null ? "" : name.startsWith(MARK) ? name : (MARK + name); 52 | } 53 | 54 | public static String makeThreadName(final String name, final String prefix) { 55 | return name == null ? prefix : (name.startsWith(MARK) ? name : (prefix + "#" + name)); 56 | } 57 | 58 | /** 59 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 60 | * 61 | * @param prefix the new name 62 | */ 63 | public ShadowThread(final String prefix) { 64 | super(makeThreadName(prefix)); 65 | } 66 | 67 | /** 68 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 69 | * 70 | * @param target the object whose {@code run} method is invoked when this thread is started. 71 | * If {@code null}, this thread's run method is invoked. 72 | * @param prefix the new name 73 | */ 74 | public ShadowThread(final Runnable target, final String prefix) { 75 | super(target, makeThreadName(prefix)); 76 | } 77 | 78 | /** 79 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 80 | * 81 | * @param group the thread group 82 | * @param target the object whose {@code run} method is invoked when this thread is started. 83 | * If {@code null}, this thread's run method is invoked. 84 | * @param prefix the new name 85 | */ 86 | public ShadowThread(final ThreadGroup group, final Runnable target, final String prefix) { 87 | super(group, target, makeThreadName(prefix)); 88 | } 89 | 90 | /** 91 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 92 | * 93 | * @param name the original name 94 | * @param prefix the prefix of new name 95 | */ 96 | public ShadowThread(final String name, final String prefix) { 97 | super(makeThreadName(name, prefix)); 98 | } 99 | 100 | /** 101 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 102 | * 103 | * @param group the thread group 104 | * @param name the original name 105 | * @param prefix the prefix of new name 106 | */ 107 | public ShadowThread(final ThreadGroup group, final String name, final String prefix) { 108 | super(group, makeThreadName(name, prefix)); 109 | } 110 | 111 | /** 112 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 113 | * 114 | * @param target the object whose {@code run} method is invoked when this thread is started. 115 | * If {@code null}, this thread's run method is invoked. 116 | * @param name the original name 117 | * @param prefix the prefix of new name 118 | */ 119 | public ShadowThread(final Runnable target, final String name, final String prefix) { 120 | super(target, makeThreadName(name, prefix)); 121 | } 122 | 123 | /** 124 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 125 | * 126 | * @param group the thread group 127 | * @param target the object whose {@code run} method is invoked when this thread is started. 128 | * If {@code null}, this thread's run method is invoked. 129 | * @param name the original name 130 | * @param prefix the prefix of new name 131 | */ 132 | public ShadowThread(final ThreadGroup group, final Runnable target, final String name, final String prefix) { 133 | super(group, target, makeThreadName(name, prefix)); 134 | } 135 | 136 | /** 137 | * Initialize {@code Thread} with new name, this constructor is used by {@code ThreadTransformer} for renaming 138 | * 139 | * @param group the thread group 140 | * @param target the object whose {@code run} method is invoked when this thread is started. 141 | * If {@code null}, this thread's run method is invoked. 142 | * @param name the original name 143 | * @param stackSize the desired stack size for the new thread, or zero to indicate that this parameter is to be ignored. 144 | * @param prefix the prefix of new name 145 | */ 146 | public ShadowThread(final ThreadGroup group, final Runnable target, final String name, final long stackSize, final String prefix) { 147 | super(group, target, makeThreadName(name, prefix), stackSize); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/kotlin/task_6/ThreadReName.java: -------------------------------------------------------------------------------- 1 | package task_6; 2 | 3 | public class ThreadReName { 4 | 5 | public static void main(String[] args) { 6 | // 不带线程名称 7 | new Thread(new InternalRunnable()).start(); 8 | 9 | // 带线程名称 10 | Thread thread0 = new Thread(new InternalRunnable(), "thread0"); 11 | System.out.println("thread0: " + thread0.getName()); 12 | thread0.start(); 13 | 14 | Thread thread1 = new Thread(new InternalRunnable()); 15 | // 设置线程名字 16 | thread1.setName("thread1"); 17 | System.out.println("thread1: " + thread1.getName()); 18 | thread1.start(); 19 | 20 | // 以下情况反射时会抛 InvocationTargetException 异常,原因是内外部类的类加载器不一致 21 | // 一般不需要考虑该问题,因为 ThreadReNameCoreClass 是使用自定义的类加载器加载的 22 | // new Thread(() -> { 23 | // 24 | // }).start(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/task_6/ThreadReNameCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_6 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.* 6 | 7 | fun main() { 8 | threadReNameByCoreApi() 9 | } 10 | 11 | /** 12 | * 使用 Core Api 线程重命名 13 | */ 14 | private fun threadReNameByCoreApi() { 15 | val classReader = ClassReader(ThreadReName::class.java.canonicalName) 16 | val classWrite = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 17 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWrite) { 18 | 19 | private val className = "sample/ThreadReNameCoreClass" 20 | 21 | override fun visit( 22 | version: Int, 23 | access: Int, 24 | name: String?, 25 | signature: String?, 26 | superName: String?, 27 | interfaces: Array? 28 | ) { 29 | super.visit(version, access, className, signature, superName, interfaces) 30 | } 31 | 32 | override fun visitMethod( 33 | access: Int, 34 | name: String?, 35 | descriptor: String?, 36 | signature: String?, 37 | exceptions: Array? 38 | ): MethodVisitor { 39 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 40 | val threadNamePrefix = "$className#${name}-Thread-" 41 | return InternalMethodVisitor(threadNamePrefix, methodVisitor) 42 | } 43 | }, ClassReader.SKIP_DEBUG) 44 | 45 | classWrite.toByteArray().apply { 46 | toFile("files/ThreadReNameCoreClass.class") 47 | loadClass("sample.ThreadReNameCoreClass")?.apply { 48 | getMethod("main", Array::class.java).invoke(null, null) 49 | } 50 | } 51 | } 52 | 53 | private class InternalMethodVisitor(private val threadNamePrefix: String, methodVisitor: MethodVisitor) : 54 | MethodVisitor(Opcodes.ASM9, methodVisitor) { 55 | 56 | var threadNameIndex = 0 57 | 58 | override fun visitTypeInsn(opcode: Int, type: String?) { 59 | if (opcode == Opcodes.NEW && type == THREAD) { 60 | super.visitTypeInsn(opcode, SHADOW_THREAD) 61 | return 62 | } 63 | super.visitTypeInsn(opcode, type) 64 | } 65 | 66 | override fun visitMethodInsn( 67 | opcode: Int, 68 | owner: String?, 69 | name: String?, 70 | descriptor: String, 71 | isInterface: Boolean 72 | ) { 73 | if (owner == THREAD && name == "") { 74 | // 调用 ShadowThread 对应的参数列表最后多一个 String 参数的构造方法 75 | val rp = descriptor.lastIndexOf(')') 76 | val newDesc = "${descriptor.substring(0, rp)}Ljava/lang/String;${descriptor.substring(rp)}" 77 | visitLdcInsn("$threadNamePrefix${threadNameIndex++}") 78 | super.visitMethodInsn(opcode, SHADOW_THREAD, name, newDesc, isInterface) 79 | return 80 | } 81 | 82 | // 由于 Thread#setName 没法重写,所以直接搞一个静态方法调用 83 | if (opcode == Opcodes.INVOKEVIRTUAL && owner == THREAD && name == "setName" && descriptor == "(Ljava/lang/String;)V") { 84 | visitLdcInsn("$threadNamePrefix${threadNameIndex++}") 85 | super.visitMethodInsn( 86 | Opcodes.INVOKESTATIC, 87 | SHADOW_THREAD, 88 | "makeThreadName", 89 | "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", 90 | false 91 | ) 92 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 93 | return 94 | } 95 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 96 | } 97 | } 98 | 99 | private const val THREAD = "java/lang/Thread" 100 | 101 | private const val SHADOW_THREAD = "task_6/ShadowThread" 102 | -------------------------------------------------------------------------------- /src/main/kotlin/task_6/ThreadReNameTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_6 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.ClassWriter 7 | import org.objectweb.asm.Opcodes 8 | import org.objectweb.asm.tree.ClassNode 9 | import org.objectweb.asm.tree.LdcInsnNode 10 | import org.objectweb.asm.tree.MethodInsnNode 11 | import org.objectweb.asm.tree.TypeInsnNode 12 | 13 | fun main() { 14 | threadReNameByTreeApi() 15 | } 16 | 17 | /** 18 | * 使用 Tree Api 线程重命名 19 | */ 20 | fun threadReNameByTreeApi() { 21 | val classReader = ClassReader(ThreadReName::class.java.canonicalName) 22 | val classNode = ClassNode(Opcodes.ASM9) 23 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 24 | classNode.name = "sample/ThreadReNameTreeClass" 25 | 26 | var threadNameIndex = 0 27 | classNode.methods.forEach { methodNode -> 28 | val threadNamePrefix = "${classNode.name}#${methodNode.name}-Thread-" 29 | methodNode.instructions.forEach { 30 | if (it is TypeInsnNode && it.opcode == Opcodes.NEW && it.desc == THREAD) { 31 | // 替换类型 32 | it.desc = SHADOW_THREAD 33 | } 34 | if (it is MethodInsnNode && it.owner == THREAD && it.name == "") { 35 | // 调用 ShadowThread 对应的参数列表最后多一个 String 参数的构造方法 36 | val rp = it.desc.lastIndexOf(')') 37 | val newDesc = "${it.desc.substring(0, rp)}Ljava/lang/String;${it.desc.substring(rp)}" 38 | it.owner = SHADOW_THREAD 39 | it.desc = newDesc 40 | methodNode.instructions.insertBefore(it, LdcInsnNode("$threadNamePrefix${threadNameIndex++}")) 41 | } 42 | 43 | if (it is MethodInsnNode && it.opcode == Opcodes.INVOKEVIRTUAL && it.owner == THREAD 44 | && it.name == "setName" && it.desc == "(Ljava/lang/String;)V" 45 | ) { 46 | // 由于 Thread#setName 没法重写,所以直接搞一个静态方法调用 47 | methodNode.instructions.insertBefore(it, LdcInsnNode("$threadNamePrefix${threadNameIndex++}")) 48 | methodNode.instructions.insertBefore( 49 | it, MethodInsnNode( 50 | Opcodes.INVOKESTATIC, 51 | SHADOW_THREAD, 52 | "makeThreadName", 53 | "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", 54 | false 55 | ) 56 | ) 57 | } 58 | } 59 | } 60 | 61 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 62 | classNode.accept(classWriter) 63 | classWriter.toByteArray().apply { 64 | toFile("files/ThreadReNameTreeClass.class") 65 | loadClass("sample.ThreadReNameTreeClass")?.apply { 66 | getMethod("main", Array::class.java).invoke(null, null) 67 | } 68 | } 69 | } 70 | 71 | private const val THREAD = "java/lang/Thread" 72 | 73 | private const val SHADOW_THREAD = "task_6/ShadowThread" 74 | -------------------------------------------------------------------------------- /src/main/kotlin/task_7/CatchMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package task_7; 2 | 3 | public class CatchMethodInvoke { 4 | 5 | public int calc() { 6 | return 1 / 0; 7 | } 8 | 9 | // 以下用例都是可以通过的. 10 | 11 | // public int catchCalc() { 12 | // try { 13 | // return 1 / 0; 14 | // } catch (Exception e) { 15 | // e.printStackTrace(); 16 | // return 0; 17 | // } 18 | // } 19 | // 20 | // public String throwException() { 21 | // throw new RuntimeException(""); 22 | // } 23 | // 24 | // public void show() { 25 | // 26 | // } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/task_7/CatchMethodInvokeCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_7 2 | 3 | import common.const0Opcode 4 | import common.loadClass 5 | import common.toFile 6 | import org.objectweb.asm.* 7 | import org.objectweb.asm.commons.AdviceAdapter 8 | import java.lang.reflect.Modifier 9 | 10 | 11 | fun main() { 12 | catchMethodInvokeByCoreApi() 13 | } 14 | 15 | /** 16 | * 使用 Core Api try-catch 特定方法 17 | */ 18 | fun catchMethodInvokeByCoreApi() { 19 | val classReader = ClassReader(CatchMethodInvoke::class.java.canonicalName) 20 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 21 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 22 | 23 | override fun visit( 24 | version: Int, 25 | access: Int, 26 | name: String?, 27 | signature: String?, 28 | superName: String?, 29 | interfaces: Array? 30 | ) { 31 | super.visit(version, access, "sample/CatchMethodInvokeCoreClass", signature, superName, interfaces) 32 | } 33 | 34 | override fun visitMethod( 35 | access: Int, 36 | name: String, 37 | descriptor: String, 38 | signature: String?, 39 | exceptions: Array? 40 | ): MethodVisitor { 41 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 42 | if (name == "" || Modifier.isNative(access) || Modifier.isAbstract(access)) { 43 | return methodVisitor 44 | } 45 | return InternalMethodVisitor(methodVisitor, access, name, descriptor) 46 | } 47 | }, ClassReader.EXPAND_FRAMES) 48 | 49 | classWriter.toByteArray().apply { 50 | toFile("files/CatchMethodInvokeCoreClass.class") 51 | loadClass("sample.CatchMethodInvokeCoreClass")?.apply { 52 | val returnValue = getMethod("calc").invoke(getConstructor().newInstance()) 53 | println(returnValue) 54 | } 55 | } 56 | } 57 | 58 | private class InternalMethodVisitor( 59 | methodVisitor: MethodVisitor, 60 | methodAccess: Int, 61 | methodName: String, 62 | methodDesc: String, 63 | ) : 64 | AdviceAdapter(Opcodes.ASM9, methodVisitor, methodAccess, methodName, methodDesc) { 65 | 66 | val fromLabel = Label() 67 | val toLabel = Label() 68 | val targetLabel = Label() 69 | 70 | override fun onMethodEnter() { 71 | super.onMethodEnter() 72 | mv.visitTryCatchBlock(fromLabel, toLabel, targetLabel, "java/lang/Exception") 73 | mv.visitLabel(fromLabel) 74 | } 75 | 76 | override fun visitMaxs(maxStack: Int, maxLocals: Int) { 77 | mv.visitLabel(toLabel) 78 | mv.visitLabel(targetLabel) 79 | val varIndex = newLocal(Type.getType("Ljava/lang/Exception;")) 80 | mv.visitVarInsn(Opcodes.ASTORE, varIndex) 81 | mv.visitVarInsn(Opcodes.ALOAD, varIndex) 82 | mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false) 83 | 84 | Type.getMethodType(methodDesc).returnType.apply { 85 | if (sort == Type.VOID) { 86 | mv.visitInsn(Opcodes.RETURN) 87 | } else { 88 | // 异常时返回默认值 89 | mv.visitInsn(this.const0Opcode()) 90 | mv.visitInsn(this.getOpcode(IRETURN)) 91 | } 92 | } 93 | super.visitMaxs(maxStack, maxLocals) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/kotlin/task_7/CatchMethodInvokeTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_7 2 | 3 | import common.* 4 | import common.isEmpty 5 | import common.loadClass 6 | import common.toFile 7 | import org.objectweb.asm.* 8 | import org.objectweb.asm.tree.* 9 | 10 | fun main() { 11 | catchMethodInvokeByTreeApi() 12 | } 13 | 14 | // 参考自: https://github.com/s1rius/pudge/blob/d6c110f023/willfix-core/src/main/java/wtf/s1/willfix/core/visitors/HasReturnMethodTransformer.kt 15 | /** 16 | * 使用 Tree Api try-catch 特定方法 17 | */ 18 | fun catchMethodInvokeByTreeApi() { 19 | val classReader = ClassReader(CatchMethodInvoke::class.java.canonicalName) 20 | val classNode = ClassNode(Opcodes.ASM9) 21 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 22 | classNode.name = "sample/CatchMethodInvokeTreeClass" 23 | 24 | classNode.methods.filter { 25 | it.name != "" && !it.isEmpty() 26 | }.forEach { methodNode -> 27 | 28 | val fromLabel = LabelNode() 29 | val toLabel = LabelNode() 30 | val targetLabel = LabelNode() 31 | 32 | val returnType = Type.getReturnType(methodNode.desc) 33 | val nextLocalIndex = methodNode.maxLocals 34 | 35 | methodNode.tryCatchBlocks.add(TryCatchBlockNode(fromLabel, toLabel, targetLabel, "java/lang/Exception")) 36 | // 插入 try 块 37 | methodNode.instructions.insertBefore(methodNode.instructions.first, InsnList().apply { 38 | add(InsnNode(returnType.const0Opcode())) 39 | add(VarInsnNode(returnType.getOpcode(Opcodes.ISTORE), nextLocalIndex)) 40 | add(fromLabel) 41 | }) 42 | 43 | // 插入 catch 块 44 | methodNode.instructions.add(InsnList().apply { 45 | add(toLabel) 46 | add(targetLabel) 47 | add(VarInsnNode(Opcodes.ASTORE, nextLocalIndex + 1)) 48 | add(VarInsnNode(Opcodes.ALOAD, nextLocalIndex + 1)) 49 | add(MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V")) 50 | add(VarInsnNode(returnType.getOpcode(Opcodes.ILOAD), nextLocalIndex)) 51 | add(InsnNode(returnType.getOpcode(Opcodes.IRETURN))) 52 | }) 53 | 54 | methodNode.instructions.filter { 55 | it.opcode in Opcodes.IRETURN until Opcodes.ARETURN 56 | }.forEach { 57 | methodNode.instructions.insertBefore(it, InsnList().apply { 58 | add(VarInsnNode(returnType.getOpcode(Opcodes.ISTORE), nextLocalIndex)) 59 | add(VarInsnNode(returnType.getOpcode(Opcodes.ILOAD), nextLocalIndex)) 60 | }) 61 | } 62 | } 63 | 64 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 65 | classNode.accept(classWriter) 66 | classWriter.toByteArray().apply { 67 | toFile("files/CatchMethodInvokeTreeClass.class") 68 | loadClass("sample.CatchMethodInvokeTreeClass")?.apply { 69 | val returnValue = getMethod("calc").invoke(getConstructor().newInstance()) 70 | println(returnValue) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/kotlin/task_8/ReplaceMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package task_8; 2 | 3 | public class ReplaceMethodInvoke { 4 | public static void main(String[] args) { 5 | // throw NPE 6 | new Toast().show(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_8/ReplaceMethodInvokeCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_8 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.* 6 | 7 | fun main() { 8 | replaceMethodInvokeByCoreApi() 9 | } 10 | 11 | /** 12 | * 使用 Core Api 替换方法调用 13 | */ 14 | private fun replaceMethodInvokeByCoreApi() { 15 | val classReader = ClassReader(ReplaceMethodInvoke::class.java.canonicalName) 16 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 17 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 18 | 19 | private val className = "sample/ReplaceMethodInvokeCoreClass" 20 | 21 | override fun visit( 22 | version: Int, 23 | access: Int, 24 | name: String?, 25 | signature: String?, 26 | superName: String?, 27 | interfaces: Array? 28 | ) { 29 | super.visit(version, access, className, signature, superName, interfaces) 30 | } 31 | 32 | override fun visitMethod( 33 | access: Int, 34 | name: String?, 35 | descriptor: String?, 36 | signature: String?, 37 | exceptions: Array? 38 | ): MethodVisitor { 39 | 40 | val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 41 | return InternalMethodVisitor(className, methodVisitor) 42 | } 43 | }, ClassReader.SKIP_DEBUG) 44 | 45 | classWriter.toByteArray().apply { 46 | toFile("files/ReplaceMethodInvokeCoreClass.class") 47 | loadClass("sample.ReplaceMethodInvokeCoreClass")?.apply { 48 | val mainMethod = getMethod("main", Array::class.java) 49 | mainMethod.invoke(getConstructor().newInstance(), null) 50 | } 51 | } 52 | } 53 | 54 | private class InternalMethodVisitor(val className: String, methodVisitor: MethodVisitor) : 55 | MethodVisitor(Opcodes.ASM9, methodVisitor) { 56 | override fun visitMethodInsn( 57 | opcode: Int, 58 | owner: String?, 59 | name: String?, 60 | descriptor: String?, 61 | isInterface: Boolean 62 | ) { 63 | if (opcode == Opcodes.INVOKEVIRTUAL && owner == TOAST 64 | && name == "show" && descriptor == "()V" 65 | ) { 66 | mv.visitLdcInsn(className) 67 | mv.visitMethodInsn( 68 | Opcodes.INVOKESTATIC, 69 | SHADOW_TOAST, 70 | name, 71 | "(L$TOAST;Ljava/lang/String;)V", 72 | isInterface 73 | ) 74 | return 75 | } 76 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 77 | } 78 | } 79 | 80 | private const val TOAST = "task_8/Toast" 81 | private const val SHADOW_TOAST = "task_8/ShadowToast" 82 | -------------------------------------------------------------------------------- /src/main/kotlin/task_8/ReplaceMethodInvokeTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_8 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.ClassWriter 7 | import org.objectweb.asm.Opcodes 8 | import org.objectweb.asm.tree.ClassNode 9 | import org.objectweb.asm.tree.LdcInsnNode 10 | import org.objectweb.asm.tree.MethodInsnNode 11 | 12 | fun main() { 13 | replaceMethodInvokeByTreeApi() 14 | } 15 | 16 | /** 17 | * 使用 Tree Api 替换方法调用 18 | */ 19 | fun replaceMethodInvokeByTreeApi() { 20 | val classReader = ClassReader(ReplaceMethodInvoke::class.java.canonicalName) 21 | val classNode = ClassNode(Opcodes.ASM9) 22 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 23 | classNode.name = "sample/ReplaceMethodInvokeTreeClass" 24 | 25 | classNode.methods.asIterable().forEach { methodNode -> 26 | methodNode.instructions.filterIsInstance().filter { 27 | it.opcode == Opcodes.INVOKEVIRTUAL && it.owner == TOAST 28 | && it.name == "show" && it.desc == "()V" 29 | }.forEach { 30 | methodNode.instructions.insertBefore(it, LdcInsnNode(classNode.name)) 31 | it.owner = SHADOW_TOAST 32 | it.desc = "(L$TOAST;Ljava/lang/String;)V" 33 | it.opcode = Opcodes.INVOKESTATIC 34 | } 35 | } 36 | 37 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 38 | classNode.accept(classWriter) 39 | classWriter.toByteArray().apply { 40 | toFile("files/ReplaceMethodInvokeTreeClass.class") 41 | loadClass("sample.ReplaceMethodInvokeTreeClass")?.apply { 42 | val mainMethod = getMethod("main", Array::class.java) 43 | mainMethod.invoke(getConstructor().newInstance(), null) 44 | } 45 | } 46 | } 47 | 48 | private const val TOAST = "task_8/Toast" 49 | private const val SHADOW_TOAST = "task_8/ShadowToast" 50 | -------------------------------------------------------------------------------- /src/main/kotlin/task_8/ShadowToast.java: -------------------------------------------------------------------------------- 1 | package task_8; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class ShadowToast { 6 | 7 | public static void show(Toast toast, String className) { 8 | warp(toast, className).show(); 9 | } 10 | 11 | private static Toast warp(Toast toast, String className) { 12 | Field msgField; 13 | try { 14 | msgField = toast.getClass().getDeclaredField("msg"); 15 | msgField.setAccessible(true); 16 | // 如果该变量为 null,则赋值 17 | if (msgField.get(toast) == null) { 18 | msgField.set(toast, className); 19 | } 20 | } catch (Exception e) { 21 | // ignore 22 | } 23 | return toast; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/task_8/Toast.java: -------------------------------------------------------------------------------- 1 | package task_8; 2 | 3 | public class Toast { 4 | private String msg = null; 5 | 6 | public void show() { 7 | System.out.println("Toast: " + msg + ", msg.length: " + msg.length()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/task_9/SerializationCheck.java: -------------------------------------------------------------------------------- 1 | package task_9; 2 | 3 | import java.io.*; 4 | 5 | public class SerializationCheck implements Serializable { 6 | 7 | public static void main(String[] args) { 8 | // Caused by: java.io.NotSerializableException: task_9.SerializationCheck$ItemBean1 9 | try { 10 | SerializationCheck check = new SerializationCheck(); 11 | check.itemBean1 = new ItemBean1(); 12 | check.itemBean2 = new ItemBean2(); 13 | check.itemBean3 = new ItemBean3(); 14 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("output.txt")); 15 | objectOutputStream.writeObject(check); 16 | objectOutputStream.close(); 17 | 18 | } catch (IOException e) { 19 | throw new RuntimeException(e); 20 | } 21 | } 22 | 23 | private ItemBean1 itemBean1; 24 | private ItemBean2 itemBean2; 25 | private transient ItemBean3 itemBean3; 26 | private String name; 27 | private int age; 28 | 29 | static class ItemBean1 { 30 | } 31 | 32 | static class ItemBean2 implements Serializable { 33 | } 34 | 35 | static class ItemBean3 { 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/kotlin/task_9/SerializationCheckCoreApi.kt: -------------------------------------------------------------------------------- 1 | package task_9 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.* 6 | import java.io.Serializable 7 | import java.lang.reflect.Modifier 8 | 9 | fun main() { 10 | serializationCheckByCoreApi() 11 | } 12 | 13 | /** 14 | * 使用 Core Api 进行序列化检查 15 | */ 16 | private fun serializationCheckByCoreApi() { 17 | val classReader = ClassReader(SerializationCheck::class.java.canonicalName) 18 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 19 | classReader.accept(object : ClassVisitor(Opcodes.ASM9, classWriter) { 20 | 21 | var isSerializable = false 22 | var isStaticClass = false 23 | var hasSerialVersionUID = false 24 | val className = "sample/SerializationCheckCoreClass" 25 | 26 | override fun visit( 27 | version: Int, 28 | access: Int, 29 | name: String?, 30 | signature: String?, 31 | superName: String?, 32 | interfaces: Array? 33 | ) { 34 | isSerializable = interfaces?.contains(Type.getInternalName(Serializable::class.java)) == true 35 | isStaticClass = Modifier.isStatic(access) 36 | super.visit(version, access, className, signature, superName, interfaces) 37 | } 38 | 39 | override fun visitField( 40 | access: Int, 41 | name: String?, 42 | descriptor: String?, 43 | signature: String?, 44 | value: Any? 45 | ): FieldVisitor { 46 | if (!hasSerialVersionUID) { 47 | hasSerialVersionUID = name == "serialVersionUID" && descriptor == "J" && value != null 48 | } 49 | if (!isSerializable && Modifier.isTransient(access)) { 50 | // 未实现 Serializable 接口的类,包含 transient 字段 51 | println("Attention: Non-serializable [$className] class contains transient field '$name'.") 52 | return super.visitField(access, name, descriptor, signature, value) 53 | } 54 | if (!Modifier.isStatic(access) && !Modifier.isTransient(access) 55 | && Type.getType(descriptor).sort == Type.OBJECT 56 | ) { 57 | val isSerializableField = 58 | Class.forName(Type.getType(descriptor).className)?.interfaces?.contains(Serializable::class.java) 59 | if (isSerializableField == false) { 60 | // 实现了 Serializable 的类包含非 transient、static 的字段,这些字段并未实现 Serializable 接口 61 | println("Attention: Non-serializable field '$name' in a Serializable class [$className]") 62 | } 63 | } 64 | return super.visitField(access, name, descriptor, signature, value) 65 | } 66 | 67 | override fun visitEnd() { 68 | // 实现了 Serializable 的类未提供 serialVersionUID 字段 69 | if (isSerializable && !hasSerialVersionUID) { 70 | println("Attention: This [$className] class is serializable, but does not define a 'serialVersionUID' field.") 71 | } 72 | // 未实现 Serializable 接口的类,包含 serialVersionUID 字段 73 | if (!isSerializable && hasSerialVersionUID) { 74 | println("Attention: This [$className] class is non-serializable, but defines a 'serialVersionUID' field.") 75 | } 76 | super.visitEnd() 77 | } 78 | }, ClassReader.SKIP_DEBUG) 79 | 80 | classWriter.toByteArray().apply { 81 | toFile("files/SerializationCheckCoreClass.class") 82 | loadClass("sample.SerializationCheckCoreClass")?.apply { 83 | 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/kotlin/task_9/SerializationCheckTreeApi.kt: -------------------------------------------------------------------------------- 1 | package task_9 2 | 3 | import common.loadClass 4 | import common.toFile 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.ClassWriter 7 | import org.objectweb.asm.Opcodes 8 | import org.objectweb.asm.Type 9 | import org.objectweb.asm.tree.ClassNode 10 | import java.io.Serializable 11 | import java.lang.reflect.Modifier 12 | 13 | fun main() { 14 | serializationCheckByTreeApi() 15 | } 16 | 17 | /** 18 | * 使用 Tree Api 进行序列化检查 19 | */ 20 | fun serializationCheckByTreeApi() { 21 | val classReader = ClassReader(SerializationCheck::class.java.canonicalName) 22 | val classNode = ClassNode(Opcodes.ASM9) 23 | classReader.accept(classNode, ClassReader.SKIP_DEBUG) 24 | classNode.name = "sample/SerializationCheckTreeClass" 25 | 26 | val isSerializable = classNode.interfaces?.contains(Type.getInternalName(Serializable::class.java)) == true 27 | classNode.fields.asIterable().find { 28 | it.name == "serialVersionUID" && it.desc == "J" && it.value != null 29 | }.let { 30 | // 实现了 Serializable 的类未提供 serialVersionUID 字段 31 | if (isSerializable && it == null) { 32 | println("Attention: This [${classNode.name}] class is serializable, but does not define a 'serialVersionUID' field.") 33 | } 34 | // 未实现 Serializable 接口的类,包含 serialVersionUID 字段 35 | if (!isSerializable && it != null) { 36 | println("Attention: This [${classNode.name}] class is non-serializable, but defines a 'serialVersionUID' field.") 37 | } 38 | } 39 | 40 | if (isSerializable) { 41 | classNode.fields.filter { 42 | !Modifier.isStatic(it.access) && !Modifier.isTransient(it.access) 43 | && Type.getType(it.desc).sort == Type.OBJECT 44 | }.filter { 45 | Class.forName(Type.getType(it.desc).className)?.interfaces?.contains(Serializable::class.java) == false 46 | }.forEach { 47 | // 实现了 Serializable 的类包含非 transient、static 的字段,这些字段并未实现 Serializable 接口 48 | println("Attention: Non-serializable field '${it.name}' in a Serializable class [${classNode.name}]") 49 | } 50 | } else { 51 | classNode.fields.filter { 52 | Modifier.isTransient(it.access) 53 | }.forEach { 54 | // 未实现 Serializable 接口的类,包含 transient 字段 55 | println("Attention: Non-serializable [${classNode.name}] class contains transient field '${it.name}'.") 56 | } 57 | } 58 | 59 | if (!isSerializable) { 60 | classNode.innerClasses.filter { 61 | !Modifier.isStatic(it.access) && 62 | Class.forName(it.name.replace("/", "."))?.interfaces?.contains(Serializable::class.java) == true 63 | }.forEach { 64 | // 实现了 Serializable 的非静态内部类,它的外层类并未实现 Serializable 接口 65 | println("Inner class '${it.name}' is serializable while its outer class is not.") 66 | } 67 | } 68 | 69 | val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES) 70 | classNode.accept(classWriter) 71 | classWriter.toByteArray().apply { 72 | toFile("files/SerializationCheckTreeClass.class") 73 | loadClass("sample.SerializationCheckTreeClass")?.apply { 74 | 75 | } 76 | } 77 | } 78 | --------------------------------------------------------------------------------