-
Notifications
You must be signed in to change notification settings - Fork 15k
Description
Bugzilla Link | 34907 |
Version | 5.0 |
OS | Linux |
CC | @apolukhin,@k15tfu,@zygoloid,@rjmccall,@smeenai |
Extended Description
libstdc++ apparently has a convention where the typeinfo name for a class declared in an anonymous namespace begins with an asterisk ('*'), which tells std::type_info::operator== to consider two type_info objects unequal even if their names are equal. Clang is not outputting this asterisk on GNU/Linux. Because it's omitted, if I declare two classes with the same name, in two different anonymous namespaces, the two class types are considered equal according to std::type_info::operator==, and I can cast from one type to another with dynamic_cast. G++ outputs the asterisk, so the types are treated as unequal.
The asterisk is stripped off in GNU's std::type_info::name(), so it's not user visible.
AFAICT, libc++ doesn't have this convention, but for ARM64 iOS, there is a different convention of setting the highest(?) bit of the type_info's __type_name pointer to indicate that string comparison should be performed. (Look for the _LIBCPP_HAS_NONUNIQUE_TYPEINFO and _LIBCPP_NONUNIQUE_RTTI_BIT flags in libc++. I wonder if ARM64 iOS also sets _LIBCXX_DYNAMIC_FALLBACK for libc++abi?)
I'm wondering whether there's a compatibility concern here w.r.t. previous versions of Clang. My first guess is that compatibility with G++/libstdc++/libsupc++ (and correctness) is sufficient to motivate changing Clang. I guess Clang would have to generate different code for -stdlib=libstdc++ and -stdlib=libc++?
Test case:
test.h
#include <typeinfo>
#include <stddef.h>
#include <stdio.h>
struct Base {
virtual ~Base() {}
};
namespace def {
Base *alloc();
const std::type_info &type();
}
test-def.cc
#include "test.h"
namespace {
struct A : Base {};
}
namespace def {
Base *alloc() {
return new A;
}
const std::type_info &type() {
return typeid(A);
}
}
test-run.cc
#include "test.h"
namespace {
struct A : Base {
void func() {
printf("ERROR: run func called, field=%d\n", field);
}
private:
int field = 42;
};
}
__attribute__((noinline))
static A *do_cast(Base *b) {
return dynamic_cast<A*>(b);
}
__attribute__((noinline))
static bool types_eq(const std::type_info &x, const std::type_info &y) {
return x == y;
}
int main() {
printf("def A == run A: %d\n", types_eq(def::type(), typeid(A)));
printf("&def A == &run A: %d\n", &def::type() == &typeid(A));
printf("name of def A: %s\n", def::type().name());
printf("name of run A: %s\n", typeid(A).name());
printf("def A name == run A name: %d\n", def::type().name() == typeid(A).name());
Base *b = def::alloc();
auto *p = do_cast(b);
if (p == nullptr) {
printf("SUCCESS: dynamic_cast returned nullptr\n");
} else {
p->func();
}
#ifdef __GXX_TYPEINFO_EQUALITY_INLINE
printf("__GXX_TYPEINFO_EQUALITY_INLINE = %d\n", __GXX_TYPEINFO_EQUALITY_INLINE);
#endif
#ifdef __GXX_MERGED_TYPEINFO_NAMES
printf("__GXX_MERGED_TYPEINFO_NAMES = %d\n", __GXX_MERGED_TYPEINFO_NAMES);
#endif
}
$ cat /etc/issue
Ubuntu 14.04.5 LTS \n \l
$ uname -m
x86_64
$ g++ test-def.cc test-run.cc -std=c++11 && ./a.out
def A == run A: 0
&def A == &run A: 0
name of def A: N12_GLOBAL__N_11AE
name of run A: N12_GLOBAL__N_11AE
def A name == run A name: 0
SUCCESS: dynamic_cast returned nullptr
__GXX_TYPEINFO_EQUALITY_INLINE = 1
__GXX_MERGED_TYPEINFO_NAMES = 0
$ ~/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04/bin/clang++ test-def.cc test-run.cc -std=c++11 && ./a.out
def A == run A: 1
&def A == &run A: 0
name of def A: N12_GLOBAL__N_11AE
name of run A: N12_GLOBAL__N_11AE
def A name == run A name: 0
ERROR: run func called, field=0
__GXX_TYPEINFO_EQUALITY_INLINE = 1
__GXX_MERGED_TYPEINFO_NAMES = 0
$ g++ test-def.cc -S && cat test-def.s
...
_ZTSN12_GLOBAL__N_11AE:
.string "*N12_GLOBAL__N_11AE"
...
$ ~/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04/bin/clang++ test-def.cc -S && cat test-def.s
...
_ZTSN12_GLOBAL__N_11AE:
.asciz "N12_GLOBAL__N_11AE"
...