[cfe-dev] [LLVMdev] [LLVMDev] Object in a try-catch block not being destroyed even after an exception

Richard Smith richard at metafoo.co.uk
Tue Jun 19 15:27:33 PDT 2012


On Tue, Jun 19, 2012 at 12:19 PM, Joshua Cranmer <pidgeot18 at gmail.com> wrote:
> On 6/19/2012 7:14 AM, SHIVA PRASAD wrote:
>
> Dear LLVM Members,
>
>         The below source code from gcc test suite replicates the problem
> which i am facing.  When it is built with clang, it throws an
> abort(highlighted).
> The following are my observations -
>
> 1. The object(tmp) is not getting destroyed immediately after the exception
> is thrown.  In case of Clang, the object is getting destroyed at a later
> point of time.
> 2. In case of gcc, the scope of the object created inside the try-catch
> block is limited to the block.  Once the exception is thrown, the
> object(tmp) is destroyed.
> 3. The assembly files for both clang and gcc were generated and compared,
> wherein the sequence of operations look similar.
> 4. I also tried the MS-Visual Studio compiler, which gives a result similar
> to gcc.
>
>
> This looks specific to clang, not to LLVM, so moving to cfe-dev.
>
> [Source code below not elided for anyone on cfe-dev who isn't on llvmdev; I
> have more comments trailing, so scroll down].
>
> As per my understanding of the C++ standard, the object has to be destroyed
> as soon as the exception is thrown.
> Please let me know which is the correct behavior and suggest me the changes
> required.
>
>
> Thanks & Regards,
> Shivaprasad
> shivahms at gmail.com
>
> /******************************************************************Source
> Code
> Start*******************************************************************/
> extern "C" void abort ();
>
> int thrown;
>
> int as;
> struct a {
>   a () { ++as; }
>   ~a () { --as; if (thrown++ == 0) throw 42; }
> };
>
> int f (a const&) { return 1; }
> int f (a const&, a const&) { return 1; }
>
> int bs;
> int as_sav;
> struct b {
>   b (...) { ++bs; }
>   ~b ()   { --bs; as_sav = as; }
> };
>
> bool p;
> void g()
> {
>   if (p) throw 42;
> }
>
> int main () {
>   thrown = 0;
>   try {
>     b tmp(f (a(), a()));
>
>     g();
>   }
>   catch (...) {}
>
>   // We throw when the first a is destroyed, which should destroy b before
>   // the other a.
>   if (as_sav != 1)
>     abort ();
>
>   thrown = 0;
>   try {
>     b tmp(f (a()));
>
>     g();
>   }
>   catch (...) {}
>
>   if (bs != 0)
>     abort ();
> }
> /******************************************************************Source
> Code End*******************************************************************/
>
>
> What clang is generating can be approximated by the following pseudocode:
> atmp1, atmp2, btmp = alloca space for temporaries
> call a::a on atmp1
> try {
>   call a::a on atmp2
>   try {
>     call f(atmp1, atmp2)
>     call b::b on btmp
>   } catch {
>   } finally {
>     call a::~a on atmp2
>   }
> } catch {
> } finally {
>   call a::~a on atmp1
> }
> // NOW b is "constructed"
>
> Looking at the output IR, the best interpretation I can give is that Clang
> is cleaning up the temporary arguments before calling the new variable
> constructed. The question, of course, is whether or not this is legal per
> the C++ spec. C++11 section 12.2, par 3 states that
>
> Temporary objects are destroyed as the last step
> in evaluating the full-expression (1.9) that (lexically) contains the point
> where they were created.
>
>
> Section 1.9, par 10 has an example which states that the full expression
> associated with the call to the constructor in this case is indeed the
> actual constructor call.
>
> Section 15.2, par 1 states:
>
> As control passes from a throw-expression to a handler, destructors are
> invoked for all automatic objects
> constructed since the try block was entered. The automatic objects are
> destroyed in the reverse order of the
> completion of their construction.
>
>
> I'm not 100% what the correct interpretation is in this case. It comes down
> to whether b should be considered constructed before or after the full
> expression of the declarator is completed.

b is constructed, since its constructor has finished.

However, the problem seems to be a bit more subtle. When we get to the
destructors at the end of the full-expression, the two 'a' temporaries
and 'tmp' have been constructed. In normal execution, we would then
destroy the 'a' temporaries in reverse construction order, and then
destroy the 'tmp' object at the end of its scope.

Now, GCC seems to believe that after the first '~a' call throws, we
should destroy 'tmp' and then destroy the 'a' temporary. Perhaps it's
trying to enforce reverse construction order ('tmp' was constructed
after the 'a' temporaries), per 15.2/1. However, that paragraph does
not directly apply, since temporaries don't have automatic storage
duration (see core issue 365). Clang believes that, after the first
'~a' throws, we destroy the second 'a' then destroy 'tmp'.

The relevant paragraph for the destruction of temporaries is 12.2/3:
"Temporary objects are destroyed as the last step in evaluating the
full-expression (1.9) that (lexically) contains the point where they
were created. This is true even if that evaluation ends in throwing an
exception. The value computations and side effects of destroying a
temporary object are associated only with the full-expression, not
with any specific subexpression."

So the question is, how are the "As control passes from a
throw-expression to a handler" from 15.2/1 and the "last step in
evaluating the full-expression" in 12.2/3 ordered? GCC acts as if
'control passes' before 'the last step in evaluating the
full-expression'. Clang acts as if 'the last step in evaluating the
full-expression' happens before 'control passes'. I'm not sure who is
right. Does GCC's revision control history for the test file provide
us with any clues?




More information about the cfe-dev mailing list