用ASM做精准测试(Java)

precise-testing-ASM

项目:precise-testing-ASM

基于 Java ASM 字节码框架的方法调用链分析工具,能够从 .class 文件或 JAR 包中递归构建方法调用链的 DAG 结构。

项目简介

项目:precise-testing-ASM

precise-testing-ASM 是一个轻量级的字节码分析工具,专注于方法调用链的追踪与可视化。它能够:

  • 从编译后的 .class 文件或 JAR 包中解析字节码
  • 递归构建方法调用关系的 DAG(有向无环图)结构
  • 支持简单的多态解析、Lambda 表达式追踪、循环调用检测等复杂场景
  • 自动过滤 JDK 自带类,只关注业务代码的调用链路

技术栈

  • Java
  • ASM 依赖
  • 构建工具 Maven

快速上手

完全可以参考 src/main/java/Main.java 中的示例代码,快速上手使用

1. 引入依赖

java 复制代码
import org.example.CallChainAnalyzer;
import org.example.node.DagNode;
import org.example.traverser.DagTraverser;

2. 分析 JAR 包中的方法调用链

java 复制代码
// 创建分析器,传入 JAR 包路径和解压目录
CallChainAnalyzer analyzer = new CallChainAnalyzer(
    "path/to/application.jar",
    "extracted/classes"
);

// 分析指定方法的调用链
DagNode root = analyzer.analyze(
    "com.example.StudentListServlet",  // 类名
    "handle",                           // 方法名
    Arrays.asList(                      // 参数类型列表
        "javax.servlet.http.HttpServletRequest",
        "javax.servlet.http.HttpServletResponse"
    )
);

// 打印调用链树形结构
DagTraverser traverser = new DagTraverser();
traverser.printTree(root);

3. 分析目录中的 .class 文件

java 复制代码
// 创建分析器,传入包含 .class 文件的目录路径
CallChainAnalyzer analyzer = new CallChainAnalyzer("path/to/classes");

// 分析方法调用链
DagNode root = analyzer.analyze(
    "com.example.MyClass",
    "myMethod",
    Arrays.asList("java.lang.String", "int")
);

// 打印调用链
DagTraverser traverser = new DagTraverser();
traverser.printTree(root);

当然 DagTraverser 中也有其他方法,方便你去遍历方法调用链

4. 输出示例

分析 StudentListServlet#handle 方法后的树形结构:

复制代码
StudentListServlet#handle(HttpServletRequest, HttpServletResponse)
├── StudentDao#query(String, String, Integer, String, int, int)
│   ├── StudentDao#lambda$query$0(String, Student)
│   │   └── Student#getId()
│   ├── StudentDao#lambda$query$1(String, Student)
│   │   └── Student#getName()
│   └── PageResult#PageResult(List, int, int, long)
├── ApiResponse#success(Object)
│   └── ApiResponse#ApiResponse(int, String, Object)
├── StudentListServlet#writeJson(HttpServletResponse, Object)
│   └── Gson#toJson(Object)
├── ApiResponse#error(int, String)
│   └── ApiResponse#ApiResponse(int, String, Object)
└── StudentListServlet#writeJson(HttpServletResponse, Object)
    └── Gson#toJson(Object)

详细使用指南

CallChainAnalyzer(入口类)

CallChainAnalyzer 是整个工具的入口,负责初始化字节码解析环境和执行分析。

构造方式

方式一:从目录加载

java 复制代码
CallChainAnalyzer analyzer = new CallChainAnalyzer(String bytecodePath);
  • bytecodePath: 包含 .class 文件的目录路径
  • 适用于已解压的项目编译输出目录

方式二:从 JAR 包加载

java 复制代码
CallChainAnalyzer analyzer = new CallChainAnalyzer(String jarPath, String extractDir);
  • jarPath: JAR 包文件路径
  • extractDir: 解压目标目录,工具会自动将 JAR 内容解压到此目录
  • 适用于分析打包后的应用程序
初始化方法
java 复制代码
analyzer.init();
  • 扫描指定路径下的所有 .class 文件
  • 构建类的继承关系图谱
  • 注意:首次调用 analyze() 时会自动调用 init(),也可手动提前调用
分析方法
java 复制代码
DagNode root = analyzer.analyze(String className, String methodName, List<String> paramTypes);
  • className: 完整类名(如 com.example.MyClass
  • methodName: 方法名
  • paramTypes: 参数类型列表,使用完整类名(如 java.lang.String
  • 返回值:调用链的根节点 DagNode

DagNode(调用链节点)

每个 DagNode 代表调用链中的一个方法节点,包含以下信息:

字段 类型 说明
declClass String 声明类,多态场景下变量的声明类型
implClass String 实现类,运行时实际执行的类型
funcName String 方法名
funcParams List<String> 参数类型列表
funcReturnType String 返回类型
isloop boolean 是否为循环调用节点
children List<DagNode> 子节点列表,当前方法调用的其他方法
parents List<DagNode> 父节点列表,调用当前方法的节点

DagTraverser(遍历器)

DagTraverser 提供了多种遍历和查询调用链的方式。

打印树形结构
java 复制代码
DagTraverser traverser = new DagTraverser();
traverser.printTree(root);

从根节点向下遍历,以树形格式打印整个调用链。

查找根节点
java 复制代码
Map<DagNode, List<DagNode>> roots = traverser.findRoots(nodeList);

从任意节点列表向上追溯,找到每个节点的根节点。

先序遍历
java 复制代码
List<DagNode> nodes = traverser.preorderTraversal(root);

按先序遍历收集所有节点,顺序为:根节点 -> 左子树 -> 右子树。

后序遍历
java 复制代码
List<DagNode> nodes = traverser.postorderTraversal(root);

按后序遍历收集所有节点,顺序为:左子树 -> 右子树 -> 根节点。

MethodSignature(方法签名)

用于唯一标识一个方法的签名对象,包含类名、方法名和参数类型列表。

项目结构

复制代码
src/main/java/org/example/
├── CallChainAnalyzer.java    # 入口类,提供分析器的构造和初始化
├── Main.java                 # Demo 示例,展示工具的基本用法
├── builder/
│   └── CallChainBuilder.java # 调用链构建器,核心递归逻辑实现
├── graph/
│   └── InheritanceGraph.java # 继承关系图谱,支持多态解析
├── model/
│   └── MethodSignature.java  # 方法签名模型,唯一标识方法
├── node/
│   └── DagNode.java          # DAG 节点,表示调用链中的方法节点
├── parser/
│   └── BytecodeParser.java   # 字节码解析器,读取 .class 文件
├── traverser/
│   └── DagTraverser.java     # DAG 遍历器,提供多种遍历方式
└── util/
    └── TypeUtils.java        # 类型描述符工具类,处理字节码类型转换

支持的复杂场景

本工具能够处理以下字节码分析中的复杂场景:

1. 多态调用

接口或父类引用指向子类实例时,工具能够推断实际的实现类。

java 复制代码
InterfaceA a = new ImplB();
a.doSomething();  // 工具会解析出实际调用的是 ImplB#doSomething

2. Lambda 表达式

Java 8+ 的 Lambda 表达式编译为 invokedynamic 指令,工具会解析 LambdaMetafactory 获取实际的实现方法。

java 复制代码
list.stream()
    .filter(item -> item.isValid())  // 解析为 lambda$filter$0 方法
    .collect(Collectors.toList());

3. 循环调用检测

自动检测方法间的递归或相互调用,标记循环节点,避免无限递归分析。

复制代码
A -> B -> A (循环)
工具会在第二个 A 节点标记 isloop=true,停止继续递归

4. 节点共享

同一方法被多处调用时,复用同一个 DagNode 实例,构建 DAG 而非树结构,节省内存并保持调用关系的准确性。

5. 构造方法调用

正确解析 <init> 特殊方法,追踪对象创建过程中的构造函数调用链。

6. 静态代码块过滤

自动过滤 <clinit> 静态初始化块,避免追踪类加载时的初始化逻辑,只关注业务方法调用。

7. JDK 类过滤

自动跳过 java.javax.jdk. 等 JDK 自带类的调用,只分析业务代码的调用链路,减少噪音。

8. 泛型擦除处理

字节码中泛型信息已被擦除,工具能够正确处理不含泛型的类型描述符,确保类型匹配的准确性。

运行 Demo

项目自带一个示例 JAR 包,位于 src/main/resources/test/java-web-demo-1.0-SNAPSHOT.jar

运行 Main.java 即可看到完整的分析效果:

bash 复制代码
mvn compile exec:java -Dexec.mainClass="org.example.Main"

或直接在 IDE 中运行 Main 类。

总结

precise-testing-ASM 提供了一种从字节码层面分析方法调用链的方案,能够处理多态、Lambda、循环调用等复杂场景。它适用于:

  • 理解复杂项目的代码调用关系
  • 代码重构前的影响范围分析
  • 测试覆盖率分析时确定需要覆盖的调用路径
  • 技术文档生成时的方法依赖梳理

工具设计简洁,API 易用,只需几行代码即可完成调用链的分析和可视化。

项目:precise-testing-ASM

相关推荐
@杰克成2 小时前
Java学习26
java·学习·idea
伏加特遇上西柚2 小时前
Loki+Alloy+Grafana日志采集部署
java·linux·服务器·spring boot·grafana·prometheus
阿丘Akiu2 小时前
Linux部署我的世界服务器
java
折哥的程序人生 · 物流技术专研3 小时前
《Java面试85题图解版(二)》进阶深化中篇:Spring核心 + 数据库进阶
java·后端·spring·面试
SamDeepThinking3 小时前
写代码不考虑前后兼容,迟早要还的
java·后端·程序员
亿牛云爬虫专家3 小时前
深度解析:数据采集场景下的 Java 代理技术实战
java·开发语言·数据采集·动态ip·动态代理·代理配置·连接池复用
小小仙。3 小时前
IT自学第四十二天
java·开发语言
java1234_小锋3 小时前
说一下Spring的事务传播行为?
java·数据库·spring
庞轩px3 小时前
第四篇:SpringBoot自动配置——约定大于配置的底层原理
java·spring boot·后端·spring·自动配置·注解开发