[Checkers] Question about the flow algorithm

Adam Warski adam at warski.org
Wed Mar 25 13:17:31 EDT 2009


Hello,

to reply to the first post, checking the code:

  String s = null;
  try {
    methodThrowingAnException();
    s = "m";  // assignment never happens
  } finally {
    // what's the type of 's' here?
    s.toString(); // throw NullPointerException;
  }

with the current algorithm works - the NPE is detected, because  
finally has an annotation set which is a conjunction of the  
annotations before, after and in the points where a method throwing  
exceptions is invoked. This has the downside that the set of  
annotations after the finally block is the same one - which can be  
quite restricted, because of the possible exceptions. What I'm trying  
to achieve is something less restrictive, as if all catches are not- 
alive, then the annotation set after finally shouldn't be additionally  
restricted at all.

> Now that I have read your code more thoroughly, I think the proposed  
> algorithm has one bug.  It assumes that catches never throw an  
> exception implicitly.  Here is an example:
>
>  String s = null;
>  try {
>    methodThrowingException();
>    s = "m";
>    // annosAfterNormal = {NonNull={s}}
>    // annosBeforeCatch = {NonNull={}}
>  } catch (Exception e) {
>    methodThrowingException();
>    s = "m"; // <-- basically dead-code
>    // annosAfterCatch = {NonNull={s}}, alive == true
>  } catch(Error e) {
>    s = "m";
>    return;
>    // annosAfterCatch = {NonNull={s}}, alive == false;
>  } finally {
>    // what's the type of 's' here?
>    s.toString();
>  }
>
> annosAfterNormal and annosAfterCatch for all catches has 's' as  
> NonNull.  However, 's' is null because s is never assigned.

As for your example - you're right, but that's because I didn't  
rewrite my notes exactly enough ;)
The finally block should be evaluated twice with annotations sets  
which are a conjunction of:
(1) all alive-catch after-annotations, and before-annotations
(2) all non-alive catches after-annotations, and before-annotations

(then the (1)+finally is used as the starting point for evaluating the  
rest of the code).

The important part is that you take a conjunction of the before- and  
after-catch annotations. The before-catch annotations are again a  
conjunction of begin-of-try annotations, end-of-try annotations and  
exception-throwing-statements annotations. This again can be relaxed  
by only taking a conjunction of exception-throwing annotations and end- 
of-try annos.

There is another problem, however. If you have:

String s = "s"
try {
    methodThrowingException();
} catch (Exception e) {
    s = null;
    methodThrowingException();
    s = "s";
} finally {
    s.toString();
}

the NPE will be missed by both current and my implementation. The  
reason is that while in a catch, the annotations aren't &&-ed with the  
annotations for finally (only with the annos for the surrounding try,  
if any). So the solution would be to introduce an additional in-catch  
annotations (similar to tryBits), which would be &&-ed on any  
exception-throwing statements.

>> Then we come to evaluating the finally block; it is evaluated twice,
>> with initial annotation sets:
>> (1) conjunction of all annosAfterCatch where catchAlive == true and
>> the "annosAfterNormal" set
>> (2) conjunction of all annosAfterCatch where catchAlive == false
>>
>> The annotation set after the whole try-catch-construct should be the
>> annotation set after evaluating finally (1).
>
> I assume that your algorithm here assumes the presence of catches  
> for all possible throwables.  Otherwise, we would need another  
> evaluation with annosBeforeCatch as initial set for the case where  
> an exception is not caught.

Yes, you are right.

Also, here's another problem.
Imagine you have two nested try-s. If the exception is not caught in  
the first try, then it could get caught in the second. So the  
annotations in the second try's catches and finally block should  
really cover the fact that the exception can be thrown at the point of  
execution inside the first try. So when invoking a method, not only  
the top annos from tryBits stack should be &&-ed with the current  
annos (like it is in the current algorithm), but possibly more. The  
conservative solution is to &&-ed them with the whole stack.

Adam





More information about the checkers mailing list