Skip to content

The combination of -fno-rtti -fexceptions is very brittle #66117

@ldionne

Description

@ldionne

The combination of -fno-rtti and -fexceptions is not supported very well, and we've seen several issues related to that internally at Apple. This bug report captures an analysis of this issue I did years ago to try to make it visible to the larger community.

Current behaviour

When both these options are specified, we don't generate RTTI for types, except when a type is thrown, at which point we generate some minimal RTTI in the TU where it is thrown (I am not sure whether the "minimal RTTI is exactly the same as "normal RTTI"). Also note that we don't generate RTTI even in the place where a key function exists, and similarly we don't assume that other TUs have a definition of the RTTI when a key function exists.

Problem with the current approach

Let's say some TU a.cpp built with -fno-rtti -fexceptions calls a function (defined in some other TU) that can throw a type E, and tries to catch E. Then, let's say that function is defined in some other TU b.cpp built with -frtti -fexceptions throws a type E. Let's also assume that another TU c.cpp built with -frtti -fexceptions defines the RTTI for E (through a key function, for example). The problem here is that in a.cpp, we'll generate minimal RTTI for E, and in b.cpp we'll use the normal RTTI assumed to be in c.cpp (because there's a key function). Since the two RTTIs don't get de-duplicated, we get a type identity mismatch in a.cpp and b.cpp, and the exception isn't caught.

Here's a minimal working example:

#/usr/bin/env bash

cat <<EOF > a.cpp
struct E { virtual ~E(); };
extern void f();
int main() {
    try {
        f();
    } catch (E const&) { // tries catching E with RTTI from a.o

    }
}
EOF

cat <<EOF > b.cpp
struct E { virtual ~E(); };
extern void f() { throw E{}; } // throws E with RTTI from c.o
EOF

cat <<EOF > c.cpp
struct E { virtual ~E(); };
E::~E() { } // key function
EOF

clang++ a.cpp -fno-rtti -fexceptions -std=c++11 -c -o a.o
clang++ b.cpp -frtti -fexceptions -std=c++11 -c -o b.o
clang++ c.cpp -frtti -fexceptions -std=c++11 -c -o c.o
clang++ b.o c.o -shared -o b.dylib
clang++ a.o b.dylib -o a.exe
nm a.o b.o c.o a.exe b.dylib | c++filt
./a.exe

To map this example to reality, imagine that E is something like std::exception (or a derived class), that b.dylib is libc++.dylib, and that a.exe is a user program built with -fno-rtti -fexceptions. It becomes clear why people are having problems with the feature.

I once discussed this issue with @dexonsmith and we had discussed this potential solution:

  • Add an attribute generate_rtti (name TBD)
  • When you’d normally generate RTTI for a type and currently suppress the RTTI generation based on whether -fno-rtti is present, instead check whether the type has the attribute and still generate full RTTI if it has it.
  • When you try to throw a type with -fno-rtti -fexceptions, you get:
    • a warning if the type doesn’t have the attribute, and minimal RTTI gets generated (like today, for backwards compatibility)
    • the normal behaviour (like when -fno-rtti is not specified) if the type has the attribute. In particular, this means that no RTTI is generated if we can tell it’s somewhere else (e.g. when there’s a key function), and full RTTI (that the linker can dedupe) is generated when we can’t tell for sure.

rdar://58055046

Metadata

Metadata

Assignees

No one assigned

    Labels

    c++clangClang issues not falling into any other category

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions