[cfe-dev] Try-Throw-Catch handling in CFG

Jim Goodnow II Jim at TheGoodnows.net
Thu Dec 1 13:34:40 PST 2011


On 11/30/2011 5:24 PM, Ted Kremenek wrote:
> On Nov 30, 2011, at 2:52 PM, Jim Goodnow II wrote:
>
>> So, if I have a simple program like:
>>
>> void foo()
>> {
>>    try { throw( 5 ); }
>>    catch( int x ) {}
>>    catch( ... ) {}
>> }
>>
>> This generates a CFG that looks like: (Excuse the ASCII graphics .)
>>
>>        throw
>>             |
>>             v
>>            try
>>   |                    |
>>   v                    v
>> catch(int)     catch(...)
>>
>> where these are each blocks. How does the try block know the type of the
>> throw argument? Should that be stored in the state on exit from the
>> throw block or is there a better mechanism for passing that information?
> Hi Jim,
>
> I'm not certain I understand your question.  What are you trying to accomplish?  The CFG certainly doesn't retain any type information; that's up to the AST.  If you are talking about the static analyzer, it's up to the static analyzer's value flow analysis to track the thrown value, including its type, so that the path analysis matches with the right 'catch' block.
>
> Ted
>
Hi Ted,

In the AST, the body of the TRY and any CATCHes are sub-expressions of 
the TRY statement. In the CFG, this is rearranged such that the body is 
independent of the TRY block and only linked to the TRY block if there 
is a THROW expression. Three approaches to connecting the value of the 
THROW expressions with the TRYs come to mind:

1) One approach would be to walk up the AST from the THROW expression 
looking for a TRY statement and use the statement pointer as the key for 
storing the value of the THROW expression. When the TRY block is 
entered, it would check the state for a THROW value using it's statement 
address as the key, remove it from the state and transfer to the 
appropriate CATCH block after binding the value to the CATCH block argument.

The only caveat would be with nested TRY statements and a THROW 
occurring within an inner CATCH. In this case while walking back up the 
AST, if a CATCH is encountered, the parent TRY gets skipped and walking 
continues until another TRY is encountered.

2 ) Another approach would be to somehow tag the THROW expressions with 
the appropriate TRY statement while building the CFG. Maybe add 
something to the context that points to the enclosing TRY. This would 
more closely simulate what generated code does as it usually keeps a 
stack of the nested TRY execution states.

3) Which brings me to the third approach which is to change the way the 
CFG is built to actually have a TRY start block precede the body. This 
would push it's context onto a TRY stack. There would have to be a TRY 
end block as well to pop the context from the TRY stack. (This could 
also be used for the ObjC FINALLY). There would be a generic CATCH block 
that would transfer to the appropriate CATCH block based on the value of 
the THROW. This would pop the TRY stack as well. THROW expressions would 
use the topmost value on the TRY stack to save the value/type of their 
expression in the state to be used by the generic CATCH block. This 
would even more closely simulate actual behavior and work with IPA which 
could use the TRY stack to connect to the proper CATCH statement.

A minor alternative to number 3 would be to have TRY end block have the 
CATCH blocks as successors as well as the code after the CATCH blocks 
(call it continuation code ) as a successor. If no THROW expression is 
stored in the state, control is passed to the continuation code. If a 
THROW expression is found, control is passed to the appropriate CATCH 
block after binding the value to the argument of the CATCH block.


I'd be interested in hearing what you or anyone else thinks. Thanks!

  - jim




More information about the cfe-dev mailing list