-
Notifications
You must be signed in to change notification settings - Fork 14.8k
Description
If a polymorphic subclass is defined only in a header file, shared libraries built with -flto=thin
give the vtable symbol LOCAL visibility. This means that there may be more than one vtable in a binary that uses this shared library. The dynamic_cast optimizations in llvm 17's libcxxapi (https://reviews.llvm.org/D138005) don't seem to expect that this can happen for final classes, causing dynamic_cast to incorrectly return nullptr.
To repro:
a.h
struct Foo {
virtual ~Foo();
};
struct Bar final : public Foo {
};
Foo* makeBar();
a.cc
#include "a.h"
Foo::~Foo() {}
Foo* makeBar() {
return new Bar();
}
b.cc
#include <cstdio>
#include <cstring>
#include "a.h"
int main(int argc, char **argv) {
Foo* f = makeBar();
Bar* b = dynamic_cast<Bar*>(f); // b will incorrectly be nullptr
void* v;
memcpy(&v, static_cast<void*>(f), sizeof(v));
printf("%p %p %p\n", f, b, v);
delete f;
f = new Bar();
asm volatile ("":"+r"(f));
b = dynamic_cast<Bar*>(f);
memcpy(&v, static_cast<void*>(f), sizeof(v));
printf("%p %p %p\n", f, b, v);
delete f;
return 0;
}
Compile with
clang++-17 -flto=thin -O3 -fPIC -c -o a.o a.cc
clang++-17 -O3 -fPIC -flto=thin -shared -o liba.so a.o
clang++-17 -O3 -o b b.cc -Wl,-rpath,$PWD liba.so
When run, the first dynamic_cast returns nullptr
$ ./b
0x55b5b3dc12b0 (nil) 0x7f555c037dc8
0x55b5b3dc12b0 0x55b5b3dc12b0 0x55b5b337dd80
Changing the final clang invocation to clang++-16 avoids the issue.
readelf shows that Bar's vtable gets LOCAL visibility (in both clang 16 and 17) when -flto=thin
is used, but WEAK in the default case
$ readelf -Wl --syms liba.so | c++filt | grep 'for Bar'
11: 0000000000002005 5 OBJECT WEAK DEFAULT 14 typeinfo name for Bar
14: 0000000000003dd8 24 OBJECT WEAK DEFAULT 19 typeinfo for Bar
35: 0000000000003db8 32 OBJECT LOCAL DEFAULT 19 vtable for Bar
49: 0000000000002005 5 OBJECT WEAK DEFAULT 14 typeinfo name for Bar
57: 0000000000003dd8 24 OBJECT WEAK DEFAULT 19 typeinfo for Bar
$ clang++-17 -O3 -fPIC -c -o a.o a.cc && clang++-17 -O3 -fPIC -shared -o liba_no_lto.so a.o
$ readelf -Wl --syms liba_no_lto.so | c++filt | grep 'for Bar'
11: 0000000000002005 5 OBJECT WEAK DEFAULT 14 typeinfo name for Bar
13: 0000000000003db0 32 OBJECT WEAK DEFAULT 19 vtable for Bar
15: 0000000000003dd0 24 OBJECT WEAK DEFAULT 19 typeinfo for Bar
47: 0000000000002005 5 OBJECT WEAK DEFAULT 14 typeinfo name for Bar
55: 0000000000003db0 32 OBJECT WEAK DEFAULT 19 vtable for Bar
57: 0000000000003dd0 24 OBJECT WEAK DEFAULT 19 typeinfo for Bar
I am using Ubuntu clang version 17.0.4 (++20231025123955+afbe3549af4d-1~exp1~20231025124009.57)