1. 引言

本文将介绍 SootUp 库。SootUp 是一个用于对 JVM 代码进行静态分析的库,支持原始源代码或编译后的 JVM 字节码。它是 Soot 库的全面重构版本,旨在提供更好的模块化、可测试性、可维护性和易用性。

2. 依赖项

使用 SootUp 前,需要将最新版本(当前为 1.3.0)添加到构建文件中:

<dependency>
    <groupId>org.soot-oss</groupId>
    <artifactId>sootup.core</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>org.soot-oss</groupId>
    <artifactId>sootup.java.core</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>org.soot-oss</groupId>
    <artifactId>sootup.java.sourcecode</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>org.soot-oss</groupId>
    <artifactId>sootup.java.bytecode</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>org.soot-oss</groupId>
    <artifactId>sootup.jimple.parser</artifactId>
    <version>1.3.0</version>
</dependency>

各依赖项的作用:

  • sootup.core:核心库
  • sootup.java.core:Java 核心模块
  • sootup.java.sourcecode:Java 源码分析模块
  • sootup.java.bytecode:字节码分析模块
  • sootup.jimple.parser:Jimple 解析模块

⚠️ 目前没有可用的 BOM 依赖,需要单独管理版本。

3. 什么是 Jimple?

SootUp 支持多种代码格式分析:

  • Java 源代码
  • 编译字节码
  • JVM 内部类

所有输入都会转换为 Jimple 中间表示。Jimple 的设计目标是:

  1. 简化分析:将字节码的栈式操作转换为变量式操作
  2. 扁平化结构:将源码的嵌套结构转换为扁平结构
  3. 可读可写:提供人类可读的中间表示

示例对比:

// 原始 Java 代码
public void demoMethod() {
    System.out.println("Inside method.");
}
// 对应的 Jimple 表示
public void demoMethod() {
    java.io.PrintStream $stack1;
    target.exercise1.DemoClass this;

    this := @this: target.exercise1.DemoClass;
    $stack1 = <java.lang.System: java.io.PrintStream out>;

    virtualinvoke $stack1.<java.io.PrintStream: void println(java.lang.String)>("Inside method.");
    return;
}

Jimple 虽然更冗长,但提供了更易分析的统一结构。分析时我们将操作 SootClassSootFieldSootMethod 等类型。

4. 分析代码

分析代码需要两个步骤:

  1. 创建 AnalysisInputLocation
  2. 构建 JavaView

输入源类型

  • JVM 内部类

    AnalysisInputLocation inputLocation = new JrtFileSystemAnalysisInputLocation();
    
  • 源文件分析

    // 单文件
    AnalysisInputLocation inputLocation = new OTFCompileAnalysisInputLocation(
      Path.of("src/test/java/com/baeldung/sootup/AnalyzeUnitTest.java"));
    
    // 多文件
    AnalysisInputLocation inputLocation = new OTFCompileAnalysisInputLocation(List.of(...));
    
    // 内存字符串
    String javaContents = Files.readString(Path.of("..."));
    AnalysisInputLocation inputLocation = new OTFCompileAnalysisInputLocation("Test.java", javaContents);
    
  • 字节码分析

    AnalysisInputLocation inputLocation = new JavaClassPathAnalysisInputLocation("target/classes");
    

创建视图

JavaView view = new JavaView(inputLocation);

5. 访问类

获取类对象

// 获取标识符工厂
IdentifierFactory identifierFactory = view.getIdentifierFactory();

// 创建类签名
ClassType javaClass = identifierFactory.getClassType("com.baeldung.sootup.ClassUnitTest");

// 获取类(安全方式)
Optional<JavaSootClass> sootClass = view.getClass(javaClass);

// 获取类(抛异常方式)
SootClass sootClass = view.getClassOrThrow(javaClass);

类信息检查

assertTrue(classUnitTest.isPublic());
assertTrue(classUnitTest.isConcrete());
assertFalse(classUnitTest.isFinal());
assertFalse(classUnitTest.isEnum());

类关系导航

// 超类
Optional<? extends ClassType> superclass = sootClass.getSuperclass();

// 接口
Set<? extends ClassType> interfaces = sootClass.getInterfaces();

✅ 返回 ClassType 而非 SootClass,因为类定义可能不在当前视图中。

6. 访问字段和方法

批量获取

Set<? extends SootField> fields = sootClass.getFields();
Set<? extends SootMethod> methods = sootClass.getMethods();

精准获取字段

Optional<? extends SootField> field = sootClass.getField("aField");

精准获取方法

// 无参方法
Optional<? extends SootMethod> method = sootClass.getMethod("someMethod", List.of());

// 带参方法
Optional<? extends SootMethod> method = sootClass.getMethod("anotherMethod",
  List.of(identifierFactory.getClassType("java.lang.String")));

获取重载方法

Set<? extends SootMethod> methods = sootClass.getMethodsByName("someMethod");

成员信息检查

assertTrue(sootMethod.isPrivate());
assertFalse(sootMethod.isStatic());

7. 分析方法体

获取方法体

Body methodBody = sootMethod.getBody();

7.1. 访问局部变量

Set<Local> methodLocals = methodBody.getLocals();

⚠️ 局部变量来自 Jimple 表示,可能包含:

  • 原始变量(如方法参数)
  • 临时变量(如 $stack3
  • 重命名变量(如 I1 代替 name

示例分析:

private void someMethod(String name) {
    var capitals = name.toUpperCase();
    System.out.println("Hello, " + capitals);
}

实际局部变量:

  1. this
  2. I1(参数 name
  3. I2(变量 capitals
  4. $stack3System.out
  5. $stack4(字符串拼接结果)

7.2. 访问方法语句图

StmtGraph<?> stmtGraph = methodBody.getStmtGraph();
List<Stmt> stmts = stmtGraph.getStmts();

语句图示例(对应上述方法): sootup callgraph

语句类型解析:

  1. JIdentityStmt:参数赋值(thisI1
  2. JAssignStmt:变量赋值(3次)
    • I1.toUpperCase()I2
    • System.out$stack3
    • "Hello, " + I2$stack4
  3. JInvokeStmt:方法调用($stack3.println()
  4. JReturnVoidStmt:返回语句

✅ 即使简单方法也会生成完整语句图,包含所有操作细节。

8. 总结

SootUp 提供了强大的 JVM 代码静态分析能力:

  • 支持多种输入格式(源码/字节码)
  • 统一的 Jimple 中间表示
  • 完整的类/方法/字段分析
  • 详细的语句级分析

下次需要分析 Java 代码时,不妨试试这个工具!


原始标题:Introduction to SootUp | Baeldung