Skip to content

abcnull/precise-testing-ASM

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

precise-testing-ASM

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

项目简介

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

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

技术栈

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

快速上手

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

1. 引入依赖

import org.example.CallChainAnalyzer;
import org.example.node.DagNode;
import org.example.traverser.DagTraverser;

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

// 创建分析器,传入 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 文件

// 创建分析器,传入包含 .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 是整个工具的入口,负责初始化字节码解析环境和执行分析。

构造方式

方式一:从目录加载

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

方式二:从 JAR 包加载

CallChainAnalyzer analyzer = new CallChainAnalyzer(String jarPath, String extractDir);
  • jarPath: JAR 包文件路径
  • extractDir: 解压目标目录,工具会自动将 JAR 内容解压到此目录
  • 适用于分析打包后的应用程序

初始化方法

analyzer.init();
  • 扫描指定路径下的所有 .class 文件
  • 构建类的继承关系图谱
  • 注意:首次调用 analyze() 时会自动调用 init(),也可手动提前调用

分析方法

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 提供了多种遍历和查询调用链的方式。

打印树形结构

DagTraverser traverser = new DagTraverser();
traverser.printTree(root);

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

查找根节点

Map<DagNode, List<DagNode>> roots = traverser.findRoots(nodeList);

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

先序遍历

List<DagNode> nodes = traverser.preorderTraversal(root);

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

后序遍历

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. 多态调用

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

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

2. Lambda 表达式

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

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 即可看到完整的分析效果:

mvn compile exec:java -Dexec.mainClass="org.example.Main"

或直接在 IDE 中运行 Main 类。

总结

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

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

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

LICENSE

MIT LICENSE

About

使用 java ASM 进行方法调用链分析。Perform method call chain analysis using Java ASM.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages