WALA icon indicating copy to clipboard operation
WALA copied to clipboard

Problem with exception handlers in IR

Open sifislag opened this issue 6 years ago • 2 comments

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.

sifislag avatar Jun 05 '18 15:06 sifislag

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.

juliandolby avatar Jun 24 '18 12:06 juliandolby

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?

sifislag avatar Jun 25 '18 10:06 sifislag