Skip to content

Commit 272bd6f

Browse files
committed
[WPD][LLD] Add option to validate RTTI is enabled on all native types and prevent devirtualization on types with native RTTI
Discussion about this approach: https://discourse.llvm.org/t/rfc-safer-whole-program-class-hierarchy-analysis/65144/18 When enabling WPD in an environment where native binaries are present, types we want to optimize can be derived from inside these native files and devirtualizing them can lead to correctness issues. RTTI can be used as a way to determine all such types in native files and exclude them from WPD providing a safe checked way to enable WPD. The approach is: 1. In the linker, identify if RTTI is available for all native types. If not, under `--lto-validate-all-vtables-have-type-infos` `--lto-whole-program-visibility` is automatically disabled. This is done by examining all .symtab symbols in object files and .dynsym symbols in DSOs for vtable (_ZTV) and typeinfo (_ZTI) symbols and ensuring there's always a match for every vtable symbol. 2. During thinlink, if `--lto-validate-all-vtables-have-type-infos` is set and RTTI is available for all native types, identify all typename (_ZTS) symbols via their corresponding typeinfo (_ZTI) symbols that are used natively or outside of our summary and exclude them from WPD. Testing: ninja check-all large Meta service that uses boost, glog and libstdc++.so runs successfully with WPD via --lto-whole-program-visibility. Previously, native types in boost caused incorrect devirtualization that led to crashes. Reviewed By: MaskRay, tejohnson Differential Revision: https://reviews.llvm.org/D155659
1 parent 4176ce6 commit 272bd6f

19 files changed

+1074
-26
lines changed

lld/ELF/Config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ struct Config {
247247
bool ltoDebugPassManager;
248248
bool ltoEmitAsm;
249249
bool ltoUniqueBasicBlockSectionNames;
250+
bool ltoValidateAllVtablesHaveTypeInfos;
250251
bool ltoWholeProgramVisibility;
251252
bool mergeArmExidx;
252253
bool mipsN32Abi = false;
@@ -475,6 +476,9 @@ struct Ctx {
475476
std::atomic<bool> hasTlsIe{false};
476477
// True if we need to reserve two .got entries for local-dynamic TLS model.
477478
std::atomic<bool> needsTlsLd{false};
479+
// True if all native vtable symbols have corresponding type info symbols
480+
// during LTO.
481+
bool ltoAllVtablesHaveTypeInfos;
478482

479483
// Each symbol assignment and DEFINED(sym) reference is assigned an increasing
480484
// order. Each DEFINED(sym) evaluation checks whether the reference happens

lld/ELF/Driver.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ void Ctx::reset() {
110110
needsTlsLd.store(false, std::memory_order_relaxed);
111111
scriptSymOrderCounter = 1;
112112
scriptSymOrder.clear();
113+
ltoAllVtablesHaveTypeInfos = false;
113114
}
114115

115116
llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
@@ -1036,6 +1037,63 @@ template <class ELFT> static void readCallGraphsFromObjectFiles() {
10361037
}
10371038
}
10381039

1040+
template <class ELFT>
1041+
static void ltoValidateAllVtablesHaveTypeInfos(opt::InputArgList &args) {
1042+
DenseSet<StringRef> typeInfoSymbols;
1043+
SmallSetVector<StringRef, 0> vtableSymbols;
1044+
auto processVtableAndTypeInfoSymbols = [&](StringRef name) {
1045+
if (name.consume_front("_ZTI"))
1046+
typeInfoSymbols.insert(name);
1047+
else if (name.consume_front("_ZTV"))
1048+
vtableSymbols.insert(name);
1049+
};
1050+
1051+
// Examine all native symbol tables.
1052+
for (ELFFileBase *f : ctx.objectFiles) {
1053+
using Elf_Sym = typename ELFT::Sym;
1054+
for (const Elf_Sym &s : f->template getGlobalELFSyms<ELFT>()) {
1055+
if (s.st_shndx != SHN_UNDEF) {
1056+
StringRef name = check(s.getName(f->getStringTable()));
1057+
processVtableAndTypeInfoSymbols(name);
1058+
}
1059+
}
1060+
}
1061+
1062+
for (SharedFile *f : ctx.sharedFiles) {
1063+
using Elf_Sym = typename ELFT::Sym;
1064+
for (const Elf_Sym &s : f->template getELFSyms<ELFT>()) {
1065+
if (s.st_shndx != SHN_UNDEF) {
1066+
StringRef name = check(s.getName(f->getStringTable()));
1067+
processVtableAndTypeInfoSymbols(name);
1068+
}
1069+
}
1070+
}
1071+
1072+
SmallSetVector<StringRef, 0> vtableSymbolsWithNoRTTI;
1073+
for (StringRef s : vtableSymbols)
1074+
if (!typeInfoSymbols.count(s))
1075+
vtableSymbolsWithNoRTTI.insert(s);
1076+
1077+
// Remove known safe symbols.
1078+
for (auto *arg : args.filtered(OPT_lto_known_safe_vtables)) {
1079+
StringRef knownSafeName = arg->getValue();
1080+
if (!knownSafeName.consume_front("_ZTV"))
1081+
error("--lto-known-safe-vtables=: expected symbol to start with _ZTV, "
1082+
"but got " +
1083+
knownSafeName);
1084+
vtableSymbolsWithNoRTTI.remove(knownSafeName);
1085+
}
1086+
1087+
ctx.ltoAllVtablesHaveTypeInfos = vtableSymbolsWithNoRTTI.empty();
1088+
// Check for unmatched RTTI symbols
1089+
for (StringRef s : vtableSymbolsWithNoRTTI) {
1090+
message(
1091+
"--lto-validate-all-vtables-have-type-infos: RTTI missing for vtable "
1092+
"_ZTV" +
1093+
s + ", --lto-whole-program-visibility disabled");
1094+
}
1095+
}
1096+
10391097
static DebugCompressionType getCompressionType(StringRef s, StringRef option) {
10401098
DebugCompressionType type = StringSwitch<DebugCompressionType>(s)
10411099
.Case("zlib", DebugCompressionType::Zlib)
@@ -1236,6 +1294,9 @@ static void readConfigs(opt::InputArgList &args) {
12361294
config->ltoWholeProgramVisibility =
12371295
args.hasFlag(OPT_lto_whole_program_visibility,
12381296
OPT_no_lto_whole_program_visibility, false);
1297+
config->ltoValidateAllVtablesHaveTypeInfos =
1298+
args.hasFlag(OPT_lto_validate_all_vtables_have_type_infos,
1299+
OPT_no_lto_validate_all_vtables_have_type_infos, false);
12391300
config->ltoo = args::getInteger(args, OPT_lto_O, 2);
12401301
if (config->ltoo > 3)
12411302
error("invalid optimization level for LTO: " + Twine(config->ltoo));
@@ -2815,6 +2876,10 @@ void LinkerDriver::link(opt::InputArgList &args) {
28152876
config->ltoEmitAsm ||
28162877
!config->thinLTOModulesToCompile.empty();
28172878

2879+
// Handle --lto-validate-all-vtables-have-type-infos.
2880+
if (config->ltoValidateAllVtablesHaveTypeInfos)
2881+
invokeELFT(ltoValidateAllVtablesHaveTypeInfos, args);
2882+
28182883
// Do link-time optimization if given files are LLVM bitcode files.
28192884
// This compiles bitcode files into real object files.
28202885
//

lld/ELF/LTO.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ static lto::Config createConfig() {
152152
c.DwoDir = std::string(config->dwoDir);
153153

154154
c.HasWholeProgramVisibility = config->ltoWholeProgramVisibility;
155+
c.ValidateAllVtablesHaveTypeInfos =
156+
config->ltoValidateAllVtablesHaveTypeInfos;
157+
c.AllVtablesHaveTypeInfos = ctx.ltoAllVtablesHaveTypeInfos;
155158
c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
156159

157160
for (const llvm::StringRef &name : config->thinLTOModulesToCompile)

lld/ELF/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,9 +604,14 @@ def lto_cs_profile_file: JJ<"lto-cs-profile-file=">,
604604
defm lto_pgo_warn_mismatch: BB<"lto-pgo-warn-mismatch",
605605
"turn on warnings about profile cfg mismatch (default)",
606606
"turn off warnings about profile cfg mismatch">;
607+
defm lto_known_safe_vtables : EEq<"lto-known-safe-vtables",
608+
"When --lto-validate-all-vtables-have-type-infos is enabled, skip validation on these vtables (_ZTV symbols)">;
607609
def lto_obj_path_eq: JJ<"lto-obj-path=">;
608610
def lto_sample_profile: JJ<"lto-sample-profile=">,
609611
HelpText<"Sample profile file path">;
612+
defm lto_validate_all_vtables_have_type_infos: BB<"lto-validate-all-vtables-have-type-infos",
613+
"Validate that all vtables have type infos for LTO link",
614+
"Do not validate that all vtables have type infos for LTO link">;
610615
defm lto_whole_program_visibility: BB<"lto-whole-program-visibility",
611616
"Asserts that the LTO link has whole program visibility",
612617
"Asserts that the LTO link does not have whole program visibility">;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
; REQUIRES: x86
2+
3+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
4+
target triple = "x86_64-unknown-linux-gnu"
5+
6+
%struct.A = type { ptr }
7+
%struct.Native = type { %struct.A }
8+
9+
@_ZTV6Native = linkonce_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI6Native, ptr @_ZN1A1nEi, ptr @_ZN6Native1fEi] }
10+
@_ZTS6Native = linkonce_odr constant [8 x i8] c"6Native\00"
11+
@_ZTI6Native = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS6Native, ptr @_ZTI1A }
12+
13+
; Base type A does not need to emit a vtable if it's never instantiated. However, RTTI still gets generated
14+
@_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00"
15+
@_ZTI1A = linkonce_odr constant { ptr, ptr } { ptr null, ptr @_ZTS1A }
16+
17+
18+
define linkonce_odr i32 @_ZN6Native1fEi(ptr %this, i32 %a) #0 {
19+
ret i32 1;
20+
}
21+
22+
define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
23+
ret i32 0;
24+
}
25+
26+
attributes #0 = { noinline optnone }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
; REQUIRES: x86
2+
3+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
4+
target triple = "x86_64-unknown-linux-gnu"
5+
6+
%struct.A = type { ptr }
7+
%struct.Native = type { %struct.A }
8+
9+
@_ZTV6Native = linkonce_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1A1nEi, ptr @_ZN6Native1fEi] }
10+
11+
define linkonce_odr i32 @_ZN6Native1fEi(ptr %this, i32 %a) #0 {
12+
ret i32 1;
13+
}
14+
15+
define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
16+
ret i32 0;
17+
}
18+
19+
attributes #0 = { noinline optnone }
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
;; Source code:
2+
;; cat > a.h <<'eof'
3+
;; struct A { virtual int foo(); };
4+
;; int bar(A *a);
5+
;; eof
6+
;; cat > b.cc <<'eof'
7+
;; #include "a.h"
8+
;; struct B : A { int foo() { return 2; } };
9+
;; int baz() { B b; return bar(&b); }
10+
;; eof
11+
;; clang++ -flto=thin b.cc -c
12+
13+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
14+
target triple = "x86_64-unknown-linux-gnu"
15+
16+
%struct.B = type { %struct.A }
17+
%struct.A = type { ptr }
18+
19+
@_ZTV1B = linkonce_odr dso_local unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B3fooEv] }, !type !0, !type !1, !type !2, !type !3
20+
@_ZTS1B = linkonce_odr dso_local constant [3 x i8] c"1B\00"
21+
@_ZTI1A = external constant ptr
22+
@_ZTI1B = linkonce_odr dso_local constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1B, ptr @_ZTI1A }
23+
@_ZTV1A = external unnamed_addr constant { [3 x ptr] }
24+
25+
define dso_local noundef i32 @_Z3bazv() #0 {
26+
entry:
27+
%b = alloca %struct.B
28+
call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %b)
29+
%call = call noundef i32 @_Z3barP1A(ptr noundef %b)
30+
ret i32 %call
31+
}
32+
33+
define linkonce_odr dso_local void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
34+
entry:
35+
%this.addr = alloca ptr
36+
store ptr %this, ptr %this.addr
37+
%this1 = load ptr, ptr %this.addr
38+
call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this1)
39+
store ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV1B, i32 0, inrange i32 0, i32 2), ptr %this1
40+
ret void
41+
}
42+
43+
declare i32 @_Z3barP1A(ptr noundef)
44+
45+
define linkonce_odr dso_local void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
46+
entry:
47+
%this.addr = alloca ptr
48+
store ptr %this, ptr %this.addr
49+
%this1 = load ptr, ptr %this.addr
50+
store ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV1A, i32 0, inrange i32 0, i32 2), ptr %this1
51+
ret void
52+
}
53+
54+
define linkonce_odr i32 @_ZN1B3fooEv(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
55+
entry:
56+
%this.addr = alloca ptr
57+
store ptr %this, ptr %this.addr
58+
%this1 = load ptr, ptr %this.addr
59+
ret i32 2
60+
}
61+
62+
;; Make sure we don't inline or otherwise optimize out the direct calls.
63+
attributes #0 = { noinline optnone }
64+
65+
!0 = !{i64 16, !"_ZTS1A"}
66+
!1 = !{i64 16, !"_ZTSM1AFivE.virtual"}
67+
!2 = !{i64 16, !"_ZTS1B"}
68+
!3 = !{i64 16, !"_ZTSM1BFivE.virtual"}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
; REQUIRES: x86
2+
3+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
4+
target triple = "x86_64-unknown-linux-gnu"
5+
6+
@_ZTV1B = external unnamed_addr constant { [4 x ptr] }
7+
8+
define linkonce_odr void @_ZN1BC2Ev(ptr %this) #0 {
9+
%this.addr = alloca ptr, align 8
10+
store ptr %this, ptr %this.addr, align 8
11+
%this1 = load ptr, ptr %this.addr, align 8
12+
store ptr getelementptr inbounds ({ [4 x ptr] }, ptr @_ZTV1B, i32 0, inrange i32 0, i32 2), ptr %this1, align 8
13+
ret void
14+
}
15+
16+
attributes #0 = { noinline optnone }

0 commit comments

Comments
 (0)