What is Jimple? Jimple is the intermediate representation IR of Soot, and thus SootUp.
Soot's intention is to provide a simplified way to analyze JVM bytecode. JVM bytecode is stack-based, which makes it difficult for program analysis.
Java source code, on the other hand, is also not quite suitable for program analysis, due to its nested structures.
Therefore, Jimple aims to bring the best of both worlds, a non-stack-based and flat (non-nested) representation.
For this purpose Jimple was designed as a representation of JVM bytecode which is human readable.
Info
To learn more about jimple, refer to the thesis by Raja Vallee-Rai.
It might help to visualize how the Jimple version of a Java code looks like. Have a look at the following example on the HelloWorld class.
Jimple mimics the JVMs class file structure.
Therefore it is object oriented.
A Single Class (or Interface) per file.
Three-Address-Code which means there are no nested expressions.
(nested expressions can be modeled via Locals that store intermediate calculation results.)
Class (or Interface)
A class consists of Fields and Methods.
It is referenced by its ClassType.
1 2 3 4 5 6 7 8 910
public classtarget.exercise1.DemoClassextendsjava.lang.Object
{
publicvoid<init>()
{
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
specialinvokethis.<java.lang.Object: void<init>()>();
return;
}
}
123
packagetarget.exercise1;publicclassDemoClass{}
1 2 3 4 5 6 7 8 9101112131415161718
// class version 52.0 (52)
// access flags 0x21
public class target/exercise1/DemoClass {
// compiled from: DemoClass.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltarget/exercise1/DemoClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
Field
A Field is a piece of memory which can store a value that is accessible according to its visibility modifier.
It is referenced by its FieldSignature.
1 2 3 4 5 6 7 8 91011121314
public classtarget.exercise1.DemoClassextendsjava.lang.Object
{
publicvoid<init>()
{
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
specialinvokethis.<java.lang.Object: void<init>()>();
this.<target.exercise1.DemoClass: doublepi> = 3.14;
return;
}
}
/*
"this.<target.exercise1.DemoClass: double pi>" is JInstanceFieldRef
*/
// class version 52.0 (52)
// access flags 0x21
public class target/exercise1/DemoClass {
// compiled from: DemoClass.java
// access flags 0x12
private final D pi = 3.14
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 4 L1
ALOAD 0
LDC 3.14
PUTFIELD target/exercise1/DemoClass.pi : D
RETURN
L2
LOCALVARIABLE this Ltarget/exercise1/DemoClass; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
Method and the Body
The interesting part is a method. A method is a "piece of code" that can be executed.
It is referenced by its MethodSignature and contains a StmtGraph that models the sequence of single instructions/statements (Stmts).
Signatures are required for identifying or referencing things across a method, such as Classes, Interfaces, Methods or Fields.
Locals, on the other hand, do not need signatures, since they are referenced within method boundaries.
public classtarget.exercise1.DemoClassextendsjava.lang.Object
{
publicvoid<init>()
{
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
specialinvokethis.<java.lang.Object: void<init>()>();
return;
}
publicstaticvoidsampleMethod()
{
inti;
i = 0;
label1:
ifi>=5goto label3;
ifi!=3goto label2;
goto label3;
label2:
i = i+1;
goto label1;
label3:
return;
}
}
/*
Here for statements "goto label3;" and "goto label1;",
we have two instances of JGotoStmt :
"goto[?=return]" and "goto[?=(branch)]".
*/
public classtarget.exercise1.DemoClassextendsjava.lang.Object
{
publicvoid<init>()
{
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
specialinvokethis.<java.lang.Object: void<init>()>();
return;
}
publicvoidswitchExample(int)
{
intx;
java.io.PrintStream$stack2, $stack3, $stack4;
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
x := @parameter0: int;
lookupswitch(x)
{
case 1: goto label1;
case 2: goto label2;
default: goto label3;
};
label1:
$stack3 = <java.lang.System: java.io.PrintStreamout>;
virtualinvoke$stack3.<java.io.PrintStream:
voidprintln(java.lang.String)>("Input 1");
goto label4;
label2:
$stack2 = <java.lang.System: java.io.PrintStreamout>;
virtualinvoke$stack2.<java.io.PrintStream:
voidprintln(java.lang.String)>("Input 2");
goto label4;
label3:
$stack4 = <java.lang.System: java.io.PrintStreamout>;
virtualinvoke$stack4.<java.io.PrintStream:
voidprintln(java.lang.String)>("Input more than 2");
label4:
return;
}
}
/*
Here for below statement:
lookupswitch(x)
{
case 1: goto label1;
case 2: goto label2;
default: goto label3;
};
we have an instance of JLookupSwitchStmt :
lookupswitch(x)
{
case 1: goto $stack3
= <java.lang.System: java.io.PrintStream out>;
case 2: goto $stack2
= <java.lang.System: java.io.PrintStream out>;
default: goto $stack4
= <java.lang.System: java.io.PrintStream out>;
}
*/
1 2 3 4 5 6 7 8 91011121314151617181920
packagetarget.exercise1;publicclassDemoClass{publicvoidswitchExample(intx){switch(x){case1:System.out.println("Input 1");break;case2:System.out.println("Input 2");break;default:System.out.println("Input more than 2");break;}}}
public classtarget.exercise1.DemoClassextendsjava.lang.Object
{
publicvoid<init>()
{
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
specialinvokethis.<java.lang.Object: void<init>()>();
return;
}
publicvoiddivideExample(int, int)
{
inty, x, $stack6;
java.lang.StringBuilder$stack3, $stack5, $stack7;
java.io.PrintStream$stack4;
java.lang.String$stack8;
java.lang.RuntimeException$stack9;
target.exercise1.DemoClassthis;
this := @this:target.exercise1.DemoClass;
x := @parameter0: int;
y := @parameter1: int;
ify!=0goto label1;
$stack9 = newjava.lang.RuntimeException;
specialinvoke$stack9.<java.lang.RuntimeException:
void<init>(java.lang.String)>("Divide by zero error");
throw$stack9;
label1:
$stack4 = <java.lang.System: java.io.PrintStreamout>;
$stack3 = newjava.lang.StringBuilder;
specialinvoke$stack3.<java.lang.StringBuilder: void<init>()>();
$stack5 = virtualinvoke$stack3.<java.lang.StringBuilder:
java.lang.StringBuilderappend(java.lang.String)>("Divide result : ");
$stack6 = x/y;
$stack7 = virtualinvoke$stack5.<java.lang.StringBuilder:
java.lang.StringBuilderappend(int)>($stack6);
$stack8 = virtualinvoke$stack7.<java.lang.StringBuilder:
java.lang.StringtoString()>();
virtualinvoke$stack4.<java.io.PrintStream:
voidprintln(java.lang.String)>($stack8);
return;
}
}
/*
"throw $stack9" is JThrowStmt.
*/
1 2 3 4 5 6 7 8 910
packagetarget.exercise1;publicclassDemoClass{publicvoiddivideExample(intx,inty){if(y==0){thrownewRuntimeException("Divide by zero error");}System.out.println("Divide result : "+x/y);}}
assigns a Value from the right hand-side to the left hand-side.
Left hand-side of an assignment can be a Local referencing a variable (i.e. a Local) or a FieldRef referencing a Field.
Right hand-side of an assignment can be an expression (Expr), a Local, a FieldRef or a Constant.
A Local is a variable and its scope is inside its method i.e. no referencing from outside a method.
Values can be assigned to Locals via JIdentityStmt or JAssignStmt.
// class version 52.0 (52)
// access flags 0x21
public class target/exercise1/DemoClass {
// compiled from: DemoClass.java
// access flags 0x2
private I global
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltarget/exercise1/DemoClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public compute()V
L0
LINENUMBER 9 L0
ICONST_1
ISTORE 1
L1
LINENUMBER 10 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L2
LINENUMBER 11 L2
ALOAD 0
GETFIELD target/exercise1/DemoClass.global : I
ISTORE 1
L3
LINENUMBER 12 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L4
LINENUMBER 14 L4
RETURN
L5
LOCALVARIABLE this Ltarget/exercise1/DemoClass; L0 L5 0
LOCALVARIABLE local I L1 L5 1
MAXSTACK = 2
MAXLOCALS = 2
}
Constant
represents a value itself. don't confuse it with a variable/Local which has a immutable (i.e. final) attribute.
There exists a constant entity for every Type - that way all value types can have a representation.
Expr
An expression is a language construct that returns a value. E.g. a binary operation such as addition.
Ref
JArrayRef
1
$arr[1]
referencing a position inside an array.
JFieldRef (JStaticFieldRef & JInstanceFieldRef)
123
<SomePackage.ExampleClass: fieldname>
// or
$r1.<SomePackage.ExampleClass: fieldname>
referencing a Field via its FieldSignature and if necessary (i.e. with JInstanceFieldRef) the corresponding Local instance that points to the object instance.
IdentityRef
The IdentityRef makes those implicit special value assignments explicit.
JThisRef
1
@this: package.fruit.Banana
represents the this pointer of the current class.
JCaughtExceptionRef
1
@caughtexception
represents the value of the thrown exception (caught by this exceptionhandler).
JParameterRef
12
$i0 := @parameter0
$i1 := @parameter1
represents a parameter of a method, identified by its index.