WALA
WALA copied to clipboard
Problem with exception handlers in IR
Hello, I've encountered a case where the Exception Handlers in the IR seem to be wrong. The method is runWorker() of java.util.concurrent.ThreadPoolExecutor. The point of interest is the try block with the 3 catch blocks. The source code of the method:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
The generated IR printed by WALA (minus the CFG):
< Primordial, Ljava/util/concurrent/ThreadPoolExecutor, runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V >
Instructions:
BB0
BB1
0 v5 = invokestatic < Primordial, Ljava/lang/Thread, currentThread()Ljava/lang/Thread; > @0 exception:v4(line 1141)
BB2
3 v6 = getfield < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, firstTask, <Primordial,Ljava/lang/Runnable> > v2(line 1142) {2=[w]}
BB3
7 putfield v2.< Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, firstTask, <Primordial,Ljava/lang/Runnable> > = v7:#null(line 1143) {2=[w]}
BB4
9 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, unlock()V > v2 @15 exception:v8(line 1144) {2=[w]}
BB5
BB6
v38 = phi v7:#null,v6
14 conditional branch(ne, to iindex=21) v38,v7:#null(line 1147) {38=[task], 7=[null]}
BB7
16 v11 = invokespecial < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, getTask()Ljava/lang/Runnable; > v1 @26 exception:v10(line 1147) {1=[this]}
BB8
20 conditional branch(eq, to iindex=109) v11,v7:#null(line 1147) {11=[task], 7=[null]}
BB9
v12 = phi v38,v11
22 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, lock()V > v2 @35 exception:v13(line 1148) {2=[w]}
BB10
24 v14 = getfield < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, ctl, <Primordial,Ljava/util/concurrent/atomic/AtomicInteger> > v1(line 1153) {1=[this]}
BB11
25 v16 = invokevirtual < Primordial, Ljava/util/concurrent/atomic/AtomicInteger, get()I > v14 @42 exception:v15(line 1153)
BB12
27 v19 = invokestatic < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, runStateAtLeast(II)Z > v16,v17:#536870912 @47 exception:v18(line 1153)
BB13
29 conditional branch(ne, to iindex=40) v19,v20:#0(line 1153)
BB14
30 v22 = invokestatic < Primordial, Ljava/lang/Thread, interrupted()Z > @53 exception:v21(line 1154)
BB15
32 conditional branch(eq, to iindex=46) v22,v20:#0(line 1154)
BB16
34 v23 = getfield < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, ctl, <Primordial,Ljava/util/concurrent/atomic/AtomicInteger> > v1(line 1154) {1=[this]}
BB17
35 v25 = invokevirtual < Primordial, Ljava/util/concurrent/atomic/AtomicInteger, get()I > v23 @63 exception:v24(line 1155)
BB18
37 v27 = invokestatic < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, runStateAtLeast(II)Z > v25,v17:#536870912 @68 exception:v26(line 1155)
BB19
39 conditional branch(eq, to iindex=46) v27,v20:#0(line 1155)
BB20
41 v29 = invokevirtual < Primordial, Ljava/lang/Thread, isInterrupted()Z > v5 @75 exception:v28(line 1156) {5=[wt]}
BB21
43 conditional branch(ne, to iindex=46) v29,v20:#0(line 1156)
BB22
45 invokevirtual < Primordial, Ljava/lang/Thread, interrupt()V > v5 @82 exception:v30(line 1157) {5=[wt]}
BB23
49 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, beforeExecute(Ljava/lang/Thread;Ljava/lang/Runnable;)V > v1,v5,v12 @88 exception:v31(line 1159) {1=[this], 5=[wt], 12=[task]}
BB24
53 invokeinterface < Primordial, Ljava/lang/Runnable, run()V > v12 @95 exception:v32(line 1162) {12=[task]}
BB25
57 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, afterExecute(Ljava/lang/Runnable;Ljava/lang/Throwable;)V > v1,v12,v7:#null @104 exception:v33(line 1170) {1=[this], 12=[task], 7=[thrown]}
BB26
58 goto (from iindex= 58 to iindex = 84) (line 1171)
BB27<Handler> (<Primordial,Ljava/lang/RuntimeException>)
v41 = getCaughtException
63 throw v41 (line 1164) {41=[thrown, x]}
BB28<Handler> (<Primordial,Ljava/lang/Error>)
v40 = getCaughtException
68 throw v40 (line 1166) {40=[thrown, x]}
BB29<Handler> (<Primordial,Ljava/lang/Throwable>)
No catch instruction. Unreachable?
BB30
BB31
BB32<Handler> (<Primordial,Ljava/lang/Throwable>)
v43 = phi v41,v40,TOP,TOP,TOP
v42 = getCaughtException
81 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, afterExecute(Ljava/lang/Runnable;Ljava/lang/Throwable;)V > v1,v12,v43 @150 exception:v45(line 1170) {1=[this], 12=[task], 43=[thrown]}
BB33
83 throw v42 (line 1170) {42=[null]}
BB34
88 v34 = getfield < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, completedTasks, <Primordial,J> > v2(line 1174) {2=[w]}
BB35
90 v36 = binaryop(add) v34 , v35:#1 (line 1174)
91 putfield v2.< Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, completedTasks, <Primordial,J> > = v36(line 1174) {2=[w]}
BB36
93 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, unlock()V > v2 @169 exception:v37(line 1175) {2=[w]}
BB37
94 goto (from iindex= 94 to iindex = 108) (line 1176)
BB38<Handler> (<Primordial,Ljava/lang/Throwable>)
v46 = getCaughtException
100 v48 = getfield < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, completedTasks, <Primordial,J> > v2(line 1174) {2=[w]}
BB39
102 v49 = binaryop(add) v48 , v35:#1 (line 1174)
103 putfield v2.< Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, completedTasks, <Primordial,J> > = v49(line 1174) {2=[w]}
BB40
105 invokevirtual < Primordial, Ljava/util/concurrent/ThreadPoolExecutor$Worker, unlock()V > v2 @190 exception:v50(line 1175) {2=[w]}
BB41
107 throw v46 (line 1175) {46=[null]}
BB42
108 goto (from iindex= 108 to iindex = 12) (line 1175)
BB43
114 invokespecial < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V > v1,v2,v20:#0 @206 exception:v39(line 1180) {1=[this], 2=[w], 20=[completedAbruptly]}
BB44
115 goto (from iindex= 115 to iindex = 123)(line 1181)
BB45<Handler> (<Primordial,Ljava/lang/Throwable>)
v51 = getCaughtException
120 invokespecial < Primordial, Ljava/util/concurrent/ThreadPoolExecutor, processWorkerExit(Ljava/util/concurrent/ThreadPoolExecutor$Worker;Z)V > v1,v2,v9:#1 @218 exception:v54(line 1180) {1=[this], 2=[w], 9=[completedAbruptly]}
BB46
122 throw v51 (line 1180) {51=[null]}
BB47
123 return (line 1182)
BB48
As you can see the third ExceptionHandlerBasicBlock is empty.
This can cause Throwables that should be caught by this block to not get caught.
Do you perform some kind of analysis that infers that this block is unreachable and should be empty? If so could you explain how this works or if it is indeed an issue.
Thanks in advance,
Sifis.
A quick look at the Java 8 documentation that Runnable.run does not have an explicit throws clause. Hence, I think RuntimeException and Error together cover everything that could be thrown and hence that last catch clause is redundant. WALA might figure this out and delete the last catch.
In Java there are ways to throw a checked exception without declaring it. The 2nd and 4th answers to this question show two possible ways (I don't know of any more): https://stackoverflow.com/questions/4519557/is-there-a-way-to-throw-an-exception-without-adding-the-throws-declaration We want to be able to keep all this information as it is on the bytecode and have our exception analysis infer which Throwables can be thrown by a method, covering corner cases like the ones in the above link. Would it be possible to somehow disable these optimizations WALA does?