-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Description
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