I'm trying to write a try-catch block in soot
.
Let's decompile
public class A {
public static void main(String[] args) {
try{
String a = args[9999];
}catch(Throwable t){
t.printStackTrace();
}
System.out.println("Hello World");
}
}
I will add the full decompilation to the end of the question. The important parts are
0: aload_0
1: sipush 9999
4: aaload
5: astore_1
6: goto 14
9: astore_1
10: aload_1
11: invokevirtual #3 // Method java/lang/Throwable.printStackTrace:()V
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #5 // String Hello World
19: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: return
and
Exception table:
from to target type
0 6 9 Class java/lang/Throwable
So what this implies is that if any of the instructions 0-6 throw an exception, control will be re-routed to 9
and execution will continue as if normal, the astore_1
there being the storing of the reference to the exception object in a local variable.
However, I'm trying to generate a try-catch block in soot
and I found one example that actually does something like this: CatchWrapInstrumenter.java
According to documentation, newTrap
Constructs a new JTrap for the given exception on the given Stmt range with the given Stmt handler.
So when we see
b.getTraps().add(Jimple.v().newTrap(thrwCls, sFirstNonId, sGotoLast, sCatch));
We can guess that this essentially is saying "if there's an exception of type thrwCls
thrown in any of the stmts between sFirstNonId
, sGotoLast
, jump to the handler at sCatch
.
So far, this matches expectations.
Now, what bothers me, is that
Stmt sCatch = Jimple.v().newIdentityStmt(lException1, Jimple.v().newCaughtExceptionRef());
is an identity stmt.
Because identity stmts
are statements which define locals to be pre-loaded (upon method entry) with special values such as parameters or the this value.
For example, l0 := @this: A defines local l0 to be the this of the method.
This identification is necessary because the local variables are not numbered (normally the this variable is the variable in local variable slot 0 at the bytecode level.)
(page 31 [numbered 23] in the soot paper)
So -- taking again class A
as an example -- if I were to generate (simplified)
Sting a;Throwable t;PrintStream out;
a = arg[9990] //stmt1
goto stmt5 //stmt2
t := thrownException; //stmt3
t.printStackTrace(); //stmt4
out = System.out; //stmt5
out.println("Hello World") //stmt6
with the intention to register
newTrap(exception=java.lang.Throwable, beginStmt=stmt1,endStmt=stmt2,handlerStmt=stmt3)
, as I would have based on the decompiled A.class
, this would actually be illegal because t := thrownException
is supposed to be "pre-loaded (upon method entry)".
Yet at the same time, this is exactly what we are to do if the example on page 86 [numbered 78] of the soot paper is to be trusted.
This concept of "pre-loading" something that does not yet exists at the time it does, doesn't make terribly much sense to me and makes me think I could be wrong on the control flow, though I do not see how.
Could somebody please explain what is actually happening, here?
Full decompiled code of the small try-catch example:
public class A
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#28 // java/lang/Object."<init>":()V
#2 = Class #29 // java/lang/Throwable
#3 = Methodref #2.#30 // java/lang/Throwable.printStackTrace:()V
#4 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #33 // Hello World
#6 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #36 // A
#8 = Class #37 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 LA;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 a
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 t
#21 = Utf8 Ljava/lang/Throwable;
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 StackMapTable
#25 = Class #29 // java/lang/Throwable
#26 = Utf8 SourceFile
#27 = Utf8 A.java
#28 = NameAndType #9:#10 // "<init>":()V
#29 = Utf8 java/lang/Throwable
#30 = NameAndType #38:#10 // printStackTrace:()V
#31 = Class #39 // java/lang/System
#32 = NameAndType #40:#41 // out:Ljava/io/PrintStream;
#33 = Utf8 Hello World
#34 = Class #42 // java/io/PrintStream
#35 = NameAndType #43:#44 // println:(Ljava/lang/String;)V
#36 = Utf8 A
#37 = Utf8 java/lang/Object
#38 = Utf8 printStackTrace
#39 = Utf8 java/lang/System
#40 = Utf8 out
#41 = Utf8 Ljava/io/PrintStream;
#42 = Utf8 java/io/PrintStream
#43 = Utf8 println
#44 = Utf8 (Ljava/lang/String;)V
{
public A();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LA;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: sipush 9999
4: aaload
5: astore_1
6: goto 14
9: astore_1
10: aload_1
11: invokevirtual #3 // Method java/lang/Throwable.printStackTrace:()V
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: ldc #5 // String Hello World
19: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: return
Exception table:
from to target type
0 6 9 Class java/lang/Throwable
LineNumberTable:
line 4: 0
line 7: 6
line 5: 9
line 6: 10
line 8: 14
line 9: 22
LocalVariableTable:
Start Length Slot Name Signature
6 0 1 a Ljava/lang/String;
10 4 1 t Ljava/lang/Throwable;
0 23 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 4 /* same */
}
SourceFile: "A.java"
I think you have a misconception of the notion of IdentityStmts. What different IdentityStmts have in common is that they assign values that "magically fall from the sky". At method entry those are parameters and "this", and in exception handlers those are the thrown exceptions. In other words, it's totally fine and expected to have an IdentityStmt with an exception reference inside a method's body at the place where the handler begins. The only assumption that Soot has is that if a statement is a target of a Trap, i.e., the start of the handler, then the first statement in that handler should be an IdentityStmt with an appropriate exception reference.
I hope this answers your question.