Jimple
Jimple IR
like Java, but simple -> Jimple
Jimple是Soot采用的中间表示形式IR,Soot是在IR的基础上进行程序分析的。
JVM字节码是基于栈的语言,不能直观地知道其操作码的意图,而模拟栈的执行相对困难;而Java源代码有着嵌套的结构,也不适合做程序分析。
Jimple IR 作为一种带类型的三地址码(typed 3 Address Code)结合了两者的优点:
- 局部变量,无栈操作,可读性比字节码强 
- this和形参有特殊的变量表示 
- 只有简单语句,平坦结构非嵌套 
三地址码:左边最多一个引用,右边最多两个引用
更多参考:sable-thesis.pdf (washington.edu)
Jimple中签名(Signature)用于全局唯一表示一个类/方法/字段,在Soot中分别由SootClass、SootMethod、SootField表示,对应的签名分别为ClassType、MethodSignature、FieldSignature
Jimple Stmt
Branching Stmts
模拟语句间的条件/无条件跳转,CFG中有多个后继节点

JGotoStmt
无条件跳转到目标语句
label1:
for (int i = 0; i < 5; i++) {
    if (i == 3) {
        break label1;
    }
}{
    int i;
    i = 0;
  label1:
    if i >= 5 goto label3;
    if i != 3 goto label2;
    goto label3; 👈
  label2:
    i = i + 1;
    goto label1; 👈
  label3:
    return;
}第8行和第12行的goto语句都是JGotoStmt的实例
JIfStmt
第一个后继节点为紧跟着的语句(条件为真),第二个后继节点为分支语句(条件为假)
if (x % 2 == 0) {
    return 1;
} else {
    return 0;
}{
    int $stack1, x;
    
    x := @parameter0: int;
    $stack1 = x % 2;
    if $stack1 != 0 goto label1; 👈
    return 1;
  label1:
    return 0;
}method.getBody().getStmts().stream()
    .filter(stmt -> stmt instanceof JIfStmt)
    .forEach(stmt -> System.out.println(
        stmt + "\n" +
        ((JIfStmt) stmt).getCondition() + "\n" +
        ((JIfStmt) stmt).getTargetStmts(method.getBody())
    ));打印得到:
if $stack1 != 0
$stack1 != 0
[return 0]getCondition获取条件表达式
getTargetStmts即为goto后面跟着的标签指向的语句。
JSwitchStmt
switch (x){
    case 1:
        x = 2;
        break;
    case 2:
        x = 4;
        break;
    default:
        x = 6;
        break;
}{
    byte x#1, x#2, x#3;
    int x#0;
    x#0 := @parameter0: int;
    switch(x#0)  👈
    {
        case 1: goto label1;
        case 2: goto label2;
        default: goto label3;
    };
  label1:
    x#1 = 2;
    goto label4;
  label2:
    x#2 = 4;
    goto label4;
  label3:
    x#3 = 6;
  label4:
    return;
}method.getBody().getStmts().stream()
    .filter(stmt -> stmt instanceof JSwitchStmt)
    .forEach(stmt -> System.out.println(
        stmt + "\n" +
        ((JSwitchStmt) stmt).getTargetStmts(method.getBody()) + "\n" +
        ((JSwitchStmt) stmt).getDefaultTarget(method.getBody())+"\n" +
        ((JSwitchStmt) stmt).getKey() + "\n" +
        ((JSwitchStmt) stmt).getValues()
    ));switch(x#0) {     case 1:     case 2:     default:  }
[x#1 = 2, x#2 = 4, x#3 = 6]
Optional[x#3 = 6]
x#0
[1, 2]getValues:case语句匹配的值(List<IntConstant>)
getKey:switch语句要匹配的目标(Immediate)
FallsThrough Stmts
这些语句只有一个后继节点

JInvokeStmt
转换控制流到另一个方法直到方法调用返回。
virtualinvoke、specialinvoke等
JAssignStmt
rhs(right hand-side)赋值给lhs(left hand-side)
lhs可以是变量、字段、数组的引用(LValue)
rhs可以是表达式(Expr)、变量(Local)、引用(Ref)、常量(Constant)(Value)


package org.example;
public class CFG {
    public static int KEY = 666;
    public static void foo(int x) {
        int counter = 3;
        counter = KEY + counter;
    }
}{
    byte counter#0;
    int $stack2, counter#1, x;
    x := @parameter0: int;
    counter#0 = 3;   👈
    $stack2 = <org.example.CFG: int KEY>;  👈
    counter#1 = $stack2 + 3;  👈
    return;
}getLeftOp和getRightOp用于获取lhs和rhs
JIdentityStmt
类似JAssignStmt,但是是处理一些隐式赋值
- JParameterRef将形参赋值给变量,- @parameter0: int表示第一个形参,类型为int
- JCaughtExceptionRef将异常赋值给变量,如- @caughtexception: java.lang.NullpointerException
- JThisRef将- this赋值给变量
JEnterMonitorStmt & JExitMonitorStmt
标记synchronized代码块的入口和出口
package org.example;
public class CFG {
    private int counter = 0;
    public void foo() {
        synchronized (this) {
            counter = counter + 1;
        }
    }
}{
    int $stack3, $stack4;
    java.lang.Throwable $stack5, l2;
    org.example.CFG l1, this;
    this := @this: org.example.CFG;
    l1 = this;
    entermonitor this;  👈
  label1:
    $stack3 = this.<org.example.CFG: int counter>;
    $stack4 = $stack3 + 1;
    this.<org.example.CFG: int counter> = $stack4;
    exitmonitor this;   👈
  label2:
    goto label5;
  label3:
    $stack5 := @caughtexception;
    l2 = $stack5;
    exitmonitor this;   
  label4:
    throw $stack5;
  label5:
    return;
 catch java.lang.Throwable from label1 to label2 with label3;
 catch java.lang.Throwable from label3 to label4 with label3;
}Other Stmts
JReturnStmt & JReturnVoidStmt
结束当前方法的控制流并返回一个值给调用者
JReturnStmt:return xxx;
JReturnVoidStmt:return;
JThrowStmt
如果抛出的异常没有被Trap捕捉,结束当前方法的执行,并跳转到异常处理器。
throw xxx;
Good Tip
很多SootUp的API都会返回Stmt接口,可以利用instanceof来判断它的子类
List<Stmt> stmts = ... ;
for( Stmt stms : stmts ){
    if(stmt instanceof JAssignStmt){
        // found a JAssignStmt
        Value rhsOp = ((JAssignStmt) stmt).getRightOp();
        ...
    }else if(stmt instanceof JInvokeStmt){
        // found a JInvokeStmt
        JInvokeStmt ivkStmt = ((JInvokeStmt) stmt);
        MethodSignature rhsOp = ivkStmt.getInvokeExpr().getMethodSignature();
        ...
    }else ...
}但这会造成大量的if-else语句,总归不太优雅。
因此SootUp提供了另一种处理方式,实现一个AbstractStmtVisitor子类
List<Stmt> stmts = ...;
AbstractStmtVisitor visitor = new AbstractStmtVisitor<Integer>() {
    private int ifStmtsCounter = 0;
    @Override
    public void caseIfStmt(@Nonnull JIfStmt stmt) {
        ifStmtsCounter++;
        setResult(ifStmtCounter);
    }
};
for( Stmt stms : stmts ){
    stmt.accept(visitor);
}
int amountOfIfStmts = visitor.getResult();Jimple Values
Immediate
立即数,包括Local(变量)和Constant(常量)
Local
Local是作用域在当前方法中的变量,可以通过JIdentityStmt或JAssignStmt将Value赋值给Local
Constant
表示实际的值,如一个具体的数字或字符串
常量通常赋值给Local或Ref(即lhs可能的情况)
Expr
表达式是一种能够被计算并返回值的语言结构
- AbstracBinopExpr二元表达式
- AbstracUnopExpr一元表达式
- AbstractInvokeExpr调用表达式

Ref
JArrayRef
数组索引来引用
$arr[1] = 42;
$anotherLocal = arr[99];JFieldRef
对SootField的引用
- JStaticFieldRef:静态字段
- JInstanceFieldRef:实例字段
IdentityRef
用于隐式赋值(JIdentityStmt)的引用
- JThisRef
- JCaughtExceptionRef
- JParameterRef
Good Tip
和Stmts类似,很多SootUp的API返回Value接口,可以利用instanceof判断它的子类
Value op = assignStmt.getRightOp();
if(op instanceof Local){
    // found a Local
    ...
}else if(stmt instanceof Constant){
    // found a Constant
    ...
}else ...也可以实现一个AbstractValueVisitor的子类
Value op = assignStmt.getRightOp() ;
AbstractValueVisitor visitor = new AbstractValueVisitor<Integer>() {
    private int intConstantCounter = 0;
    @Override
    public void caseConstant(@Nonnull Constant c) {
        intConstantCounter++;
        setResult(intConstantCounter);
    }
};
op.accept(visitor);
int amountOfIfStmts = visitor.getResult();如果只需要处理Value的子集,也可以考虑实现ImmediateVisitor、ConstantVisitor、ExprVisitor、RefVisitor
Jimple Type
PrimaryType
- BooleanType
- ByteType
- CharType
- ShortType
- IntType
- LongType
- DoubleType
- FloatType
ReferenceType
- (Java)ClassType
- ArrayType
- NullType
VoidType
- VoidType
Jimple Trap
Trap是用于模拟异常流的机制(try-catch-finally结构)
其定义了捕捉的异常类型、捕捉的范围(from-to)、以及处理异常的代码(handler)
public static void foo(){
    try {
        Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}{
    java.lang.Object $stack3;
    java.lang.Runtime $stack1;
    java.lang.RuntimeException #l1;
    java.lang.Throwable #l0, $stack2, e;
  label1:
    $stack1 = staticinvoke <java.lang.Runtime: java.lang.Runtime getRuntime()>();
    virtualinvoke $stack1.<java.lang.Runtime: java.lang.Process exec(java.lang.String)>("calc");
  label2:
    goto label4;
  label3:
    $stack2 := @caughtexception;
    e = $stack2;
    $stack3 = new java.lang.RuntimeException;
    #l1 = (java.lang.RuntimeException) $stack3;
    specialinvoke #l1.<java.lang.RuntimeException: void <init>(java.lang.Throwable)>($stack2);
    #l0 = (java.lang.Throwable) $stack3;
    throw #l0;
  label4:
    return;
 catch java.io.IOException from label1 to label2 with label3;
}调用Body#getTraps可获取
System.out.println(method.getBody().getTraps());
System.out.println(method.getBody().getStmtGraph().buildTraps());java.io.IOException
from: $stack1 = staticinvoke <java.lang.Runtime: java.lang.Runtime getRuntime()>()
to: goto
handler: $stack2 := @caughtexceptionLast updated
Was this helpful?
