[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