-Wcatch-incomplete-type-extensions

I'm wondering why this is a warning and not an error:

struct A;

void foo();

#include <iostream>

int main()
{
    try
    {
        foo();
    }
    catch (A&)
    {
        std::cout << "Caught an A\n";
    }
    catch (...)
    {
        std::cout << "Caught ...\n";
    }
}

struct A {};

void foo()
{
    throw A();
}

test.cpp:13:14: warning: ISO C++ forbids catching a reference to incomplete type 'A' [-Wcatch-incomplete-type-extensions]
    catch (A&)
             ^
test.cpp:1:8: note: forward declaration of 'A'
struct A;
       ^
1 warning generated.

g++-4.2 just says:

test.cpp: In function ‘int main()’:
test.cpp:13: error: invalid use of incomplete type ‘struct A’
test.cpp:1: error: forward declaration of ‘struct A’

15.3 [except.handle]/p1 says:

... The exception-declaration shall not denote an incomplete type, ...

There is no mention of "no diagnostic is required" in that paragraph. Furthermore 1.4 [intro.compliance]/p2/b2 says:

  • If a program contains a violation of any diagnosable rule or <...> a conforming implementation shall issue at least one diagnostic message.

And 1.4 [intro.compliance]/p1 says:

  • The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”

Strictly speaking clang looks conforming to me unless the -Wcatch-incomplete-type-extensions warning has been turned off. But the spirit of the rule seems to me that this should be an error not a warning.

The C++ runtime is responsible for dealing with the catch clause and matching the thrown object to the type in the catch handler. The gcc library simply crashes on Apple's OS Lion:

Bus error: 10

Is there any rationale for making this a warning instead of a hard error? This question is pertinent to my work in our libc++abi project (http://libcxxabi.llvm.org/). I.e. should I spend extra run time cycles to handle this situation more gracefully (e.g. call terminate())? Or should I be assured that the compiler will make sure this can't happen?

Howard

See http://llvm.org/bugs/show_bug.cgi?id=6527 . Granted, I'm not
really sure that fix was appropriate.

-Eli

Thanks Eli. That is the context I was looking for. This looks more like a template-2-phase-lookup issue to me (where clang is doing a better job than gcc during template parsing).

In order to match the thrown object to a catch handler the personality function needs to read the std::type_info* out of the lsda table at the end of a procedure fragment. So the question becomes: what does the compiler encode for a std::type_info* for an incomplete class type? I haven't found a way to entice the compiler to reveal that except in the case of clang and in the case of the incomplete type in the catch clause. For example typeid(A) enforces a complete type A.

If clang created a special type_info-derived class to relegate incomplete types to, I could detect them and call terminate. But as it is, I don't know how to detect type_info's to incomplete types. The Itanium ABI (http://sourcery.mentor.com/public/cxx-abi/abi.html#rtti) specifies how to detect the type_info's for fundamental types, arrays, functions, enums, classes (which presumably includes unions), pointers, and member pointers. But not incomplete types.

Quote (from http://sourcery.mentor.com/public/cxx-abi/abi.html#rtti):

  • The possible derived types are:

    • abi::__fundamental_type_info
    • abi::__array_type_info
    • abi::__function_type_info
    • abi::__enum_type_info
    • abi::__class_type_info
    • abi::__si_class_type_info
    • abi::__vmi_class_type_info
    • abi::__pbase_type_info
    • abi::__pointer_type_info
    • abi::__pointer_to_member_type_info

My recommendation at this point is to turn this back into a hard error. But I'm still early in the learning curve on this issue.

Howard

Thanks Eli. That is the context I was looking for. This looks more like a template-2-phase-lookup issue to me (where clang is doing a better job than gcc during template parsing).

The weasel wording about incomplete types within templates is actually separate, but tt's that general category of problem, yeah.

In order to match the thrown object to a catch handler the personality function needs to read the std::type_info* out of the lsda table at the end of a procedure fragment. So the question becomes: what does the compiler encode for a std::type_info* for an incomplete class type? I haven't found a way to entice the compiler to reveal that except in the case of clang and in the case of the incomplete type in the catch clause. For example typeid(A) enforces a complete type A.

The Itanium ABI tells us to generate a weak (in both the "dynamically replaceable" and "coalesced by the linker" senses) object of type __class_type_info. It has to be linker-coalesced because there's no way for translation units to agree on who should emit it; it has to be dynamically replaceable so that RTTI emitted by translation units with the class definition available will replace it. Of course, there's no guarantee that the "defining" translation units will emit the RTTI at all, and even if they do, I'm not sure that static+dynamic linkers necessarily support all the crazy combinations of replacement that can happen here.

I am also willing to believe that Clang have bugs relating to that replacement.

John.

Thanks for your comments John.

I've just stumbled across a troubling piece of code. I know why it is happening. But I'm not sure exactly what we should do about it.

Here's the code:

#include <cassert>

struct A;

void foo();

int main()
{
    try
    {
        foo();
    }
    catch (A& a)
    {
    }
    catch (...)
    {
    }
}

struct B {virtual ~B() {}};

struct A : B {};

void foo()
{
    A a;
    B* bp = &a;
    assert(dynamic_cast<A*>(bp) != 0);
}

This asserts. But if you comment out the catch (A&) frame:

// catch (A& a)
// {
// }

It runs correctly.

The issue: When clang sees the catch(A&) (and A is incomplete at this point) it lays down the wrong kind of type_info: a __class_type_info. And that never gets corrected. So when it comes time to do the dynamic_cast, later when A is complete, the dynamic_cast malfunctions because the type_info for A should be a __si_class_type_info. Without the correct type_info, the dynamic_cast malfunctions.

I'm willing to file a bug on this, but I wanted to get guidance here first.

g++-4.2 refuses to compile the code that malfunctions, and thus never gets into this situation.

Thanks,
Howard

Thanks Eli. That is the context I was looking for. This looks more like a template-2-phase-lookup issue to me (where clang is doing a better job than gcc during template parsing).

The weasel wording about incomplete types within templates is actually separate, but tt's that general category of problem, yeah.

In order to match the thrown object to a catch handler the personality function needs to read the std::type_info* out of the lsda table at the end of a procedure fragment. So the question becomes: what does the compiler encode for a std::type_info* for an incomplete class type? I haven't found a way to entice the compiler to reveal that except in the case of clang and in the case of the incomplete type in the catch clause. For example typeid(A) enforces a complete type A.

The Itanium ABI tells us to generate a weak (in both the "dynamically replaceable" and "coalesced by the linker" senses) object of type __class_type_info. It has to be linker-coalesced because there's no way for translation units to agree on who should emit it; it has to be dynamically replaceable so that RTTI emitted by translation units with the class definition available will replace it. Of course, there's no guarantee that the "defining" translation units will emit the RTTI at all, and even if they do, I'm not sure that static+dynamic linkers necessarily support all the crazy combinations of replacement that can happen here.

I am also willing to believe that Clang have bugs relating to that replacement.

John.

Thanks for your comments John.

I've just stumbled across a troubling piece of code. I know why it is happening. But I'm not sure exactly what we should do about it.

Here's the code:

#include <cassert>

struct A;

void foo();

int main()
{
   try
   {
       foo();
   }
   catch (A& a)
   {
   }
   catch (...)
   {
   }
}

struct B {virtual ~B() {}};

struct A : B {};

void foo()
{
   A a;
   B* bp = &a;
   assert(dynamic_cast<A*>(bp) != 0);
}

This asserts. But if you comment out the catch (A&) frame:

// catch (A& a)
// {
// }

It runs correctly.

The issue: When clang sees the catch(A&) (and A is incomplete at this point) it lays down the wrong kind of type_info: a __class_type_info. And that never gets corrected. So when it comes time to do the dynamic_cast, later when A is complete, the dynamic_cast malfunctions because the type_info for A should be a __si_class_type_info. Without the correct type_info, the dynamic_cast malfunctions.

I'm willing to file a bug on this, but I wanted to get guidance here first.

That is absolutely a bug that we should fix.

g++-4.2 refuses to compile the code that malfunctions, and thus never gets into this situation.

Because it catches a reference to incomplete type? Rejecting this is not allowed.

John.

I'm willing to file a bug on this, but I wanted to get guidance here first.

That is absolutely a bug that we should fix.

http://llvm.org/bugs/show_bug.cgi?id=11803

g++-4.2 refuses to compile the code that malfunctions, and thus never gets into this situation.

Because it catches a reference to incomplete type? Rejecting this is not allowed.

The compiler is required to issue a diagnostic because the code is ill-formed (clang does issue a warning):

15.3 [except.handle]/p1:

The exception-declaration in a handler describes the type(s) of exceptions that can cause that handler to be entered. The exception-declaration shall not denote an incomplete type, an abstract class type, or an rvalue reference type. The exception-declaration shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*.

Howard

Okay. I don't know off-hand why we accept this, then, particularly if GCC does not.

John.

It is because of http://llvm.org/bugs/show_bug.cgi?id=6527

I tried to collect all this information in the bug report (http://llvm.org/bugs/show_bug.cgi?id=11803) so it is there in one place. And at this point I'm not sure what the best solution is. But I'm pretty sure we're in an anywhere-but-here situation.

Howard

Agreed. I've made the warning into an error in Clang r148838.

  - Doug

Thanks for looking into this Doug.

Howard