Skip to content

Commit 55efb68

Browse files
committed
[MS] Mark vbase dtors used when marking dtor used
In the MS C++ ABI, the complete destructor variant for a class with virtual bases is emitted whereever it is needed, instead of directly alongside the base destructor variant. The complete destructor calls the base destructor of the current class and the base destructors of each virtual base. In order for this to work reliably, translation units that use the destructor of a class also need to mark destructors of virtual bases of that class used. Fixes PR38521 Reviewed By: rsmith Differential Revision: https://reviews.llvm.org/D77081
1 parent 994d84b commit 55efb68

File tree

6 files changed

+216
-14
lines changed

6 files changed

+216
-14
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6667,6 +6667,22 @@ class Sema final {
66676667
void MarkBaseAndMemberDestructorsReferenced(SourceLocation Loc,
66686668
CXXRecordDecl *Record);
66696669

6670+
/// Mark destructors of virtual bases of this class referenced. In the Itanium
6671+
/// C++ ABI, this is done when emitting a destructor for any non-abstract
6672+
/// class. In the Microsoft C++ ABI, this is done any time a class's
6673+
/// destructor is referenced.
6674+
void MarkVirtualBaseDestructorsReferenced(
6675+
SourceLocation Location, CXXRecordDecl *ClassDecl,
6676+
llvm::SmallPtrSetImpl<const RecordType *> *DirectVirtualBases = nullptr);
6677+
6678+
/// Do semantic checks to allow the complete destructor variant to be emitted
6679+
/// when the destructor is defined in another translation unit. In the Itanium
6680+
/// C++ ABI, destructor variants are emitted together. In the MS C++ ABI, they
6681+
/// can be emitted in separate TUs. To emit the complete variant, run a subset
6682+
/// of the checks performed when emitting a regular destructor.
6683+
void CheckCompleteDestructorVariant(SourceLocation CurrentLocation,
6684+
CXXDestructorDecl *Dtor);
6685+
66706686
/// The list of classes whose vtables have been used within
66716687
/// this translation unit, and the source locations at which the
66726688
/// first use occurred.

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5443,6 +5443,15 @@ Sema::MarkBaseAndMemberDestructorsReferenced(SourceLocation Location,
54435443
// subobjects.
54445444
bool VisitVirtualBases = !ClassDecl->isAbstract();
54455445

5446+
// If the destructor exists and has already been marked used in the MS ABI,
5447+
// then virtual base destructors have already been checked and marked used.
5448+
// Skip checking them again to avoid duplicate diagnostics.
5449+
if (Context.getTargetInfo().getCXXABI().isMicrosoft()) {
5450+
CXXDestructorDecl *Dtor = ClassDecl->getDestructor();
5451+
if (Dtor && Dtor->isUsed())
5452+
VisitVirtualBases = false;
5453+
}
5454+
54465455
llvm::SmallPtrSet<const RecordType *, 8> DirectVirtualBases;
54475456

54485457
// Bases.
@@ -5477,16 +5486,21 @@ Sema::MarkBaseAndMemberDestructorsReferenced(SourceLocation Location,
54775486
DiagnoseUseOfDecl(Dtor, Location);
54785487
}
54795488

5480-
if (!VisitVirtualBases)
5481-
return;
5489+
if (VisitVirtualBases)
5490+
MarkVirtualBaseDestructorsReferenced(Location, ClassDecl,
5491+
&DirectVirtualBases);
5492+
}
54825493

5494+
void Sema::MarkVirtualBaseDestructorsReferenced(
5495+
SourceLocation Location, CXXRecordDecl *ClassDecl,
5496+
llvm::SmallPtrSetImpl<const RecordType *> *DirectVirtualBases) {
54835497
// Virtual bases.
54845498
for (const auto &VBase : ClassDecl->vbases()) {
54855499
// Bases are always records in a well-formed non-dependent class.
54865500
const RecordType *RT = VBase.getType()->castAs<RecordType>();
54875501

5488-
// Ignore direct virtual bases.
5489-
if (DirectVirtualBases.count(RT))
5502+
// Ignore already visited direct virtual bases.
5503+
if (DirectVirtualBases && DirectVirtualBases->count(RT))
54905504
continue;
54915505

54925506
CXXRecordDecl *BaseClassDecl = cast<CXXRecordDecl>(RT->getDecl());
@@ -13202,6 +13216,25 @@ void Sema::DefineImplicitDestructor(SourceLocation CurrentLocation,
1320213216
}
1320313217
}
1320413218

13219+
void Sema::CheckCompleteDestructorVariant(SourceLocation CurrentLocation,
13220+
CXXDestructorDecl *Destructor) {
13221+
if (Destructor->isInvalidDecl())
13222+
return;
13223+
13224+
CXXRecordDecl *ClassDecl = Destructor->getParent();
13225+
assert(Context.getTargetInfo().getCXXABI().isMicrosoft() &&
13226+
"implicit complete dtors unneeded outside MS ABI");
13227+
assert(ClassDecl->getNumVBases() > 0 &&
13228+
"complete dtor only exists for classes with vbases");
13229+
13230+
SynthesizedFunctionScope Scope(*this, Destructor);
13231+
13232+
// Add a context note for diagnostics produced after this point.
13233+
Scope.addContextNote(CurrentLocation);
13234+
13235+
MarkVirtualBaseDestructorsReferenced(Destructor->getLocation(), ClassDecl);
13236+
}
13237+
1320513238
/// Perform any semantic analysis which needs to be delayed until all
1320613239
/// pending class member declarations have been parsed.
1320713240
void Sema::ActOnFinishCXXMemberDecls() {

clang/lib/Sema/SemaExpr.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16488,6 +16488,20 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
1648816488
if (funcHasParameterSizeMangling(*this, Func))
1648916489
CheckCompleteParameterTypesForMangler(*this, Func, Loc);
1649016490

16491+
// In the MS C++ ABI, the compiler emits destructor variants where they are
16492+
// used. If the destructor is used here but defined elsewhere, mark the
16493+
// virtual base destructors referenced. If those virtual base destructors
16494+
// are inline, this will ensure they are defined when emitting the complete
16495+
// destructor variant. This checking may be redundant if the destructor is
16496+
// provided later in this TU.
16497+
if (Context.getTargetInfo().getCXXABI().isMicrosoft()) {
16498+
if (auto *Dtor = dyn_cast<CXXDestructorDecl>(Func)) {
16499+
CXXRecordDecl *Parent = Dtor->getParent();
16500+
if (Parent->getNumVBases() > 0 && !Dtor->getBody())
16501+
CheckCompleteDestructorVariant(Loc, Dtor);
16502+
}
16503+
}
16504+
1649116505
Func->markUsed(Context);
1649216506
}
1649316507
}

clang/test/CXX/class.access/p4.cpp

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++98 %s
2-
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++11 %s
3-
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify %s
1+
// RUN: %clang_cc1 -triple %itanium_abi_triple -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++98 %s
2+
// RUN: %clang_cc1 -triple %itanium_abi_triple -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++11 %s
3+
// RUN: %clang_cc1 -triple %itanium_abi_triple -fcxx-exceptions -fexceptions -fsyntax-only -verify %s
4+
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility-version=19 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++98 %s
5+
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility-version=19 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++11 %s
6+
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility-version=19 -fcxx-exceptions -fexceptions -fsyntax-only -verify %s
47

58
// C++0x [class.access]p4:
69

@@ -139,7 +142,7 @@ namespace test3 {
139142
A local; // expected-error {{variable of type 'test3::A' has private destructor}}
140143
}
141144

142-
#if __cplusplus < 201103L
145+
#if __cplusplus < 201103L && !defined(_MSC_VER)
143146
template <unsigned N> class Base { ~Base(); }; // expected-note 14 {{declared private here}}
144147
class Base2 : virtual Base<2> { ~Base2(); }; // expected-note 3 {{declared private here}} \
145148
// expected-error {{base class 'Base<2>' has private destructor}}
@@ -161,15 +164,43 @@ namespace test3 {
161164

162165
class Derived3 : // expected-error 2 {{inherited virtual base class 'Base<2>' has private destructor}} \
163166
// expected-error 2 {{inherited virtual base class 'Base<3>' has private destructor}} \
164-
// expected-note 2{{implicit default constructor}}
167+
// expected-note 2{{implicit default constructor}}
165168
Base<0>, // expected-error 2 {{base class 'Base<0>' has private destructor}}
166169
virtual Base<1>, // expected-error 2 {{base class 'Base<1>' has private destructor}}
167170
Base2, // expected-error 2 {{base class 'test3::Base2' has private destructor}}
168171
virtual Base3
169-
{};
170-
Derived3 d3; // expected-note 3{{implicit default constructor}}\
171-
// expected-note{{implicit destructor}}}
172-
#else
172+
{};
173+
Derived3 d3; // expected-note{{implicit destructor}}} \
174+
// expected-note 3 {{implicit default constructor}}
175+
#elif __cplusplus < 201103L && defined(_MSC_VER)
176+
template <unsigned N> class Base { ~Base(); }; // expected-note 14 {{declared private here}}
177+
class Base2 : virtual Base<2> { ~Base2(); }; // expected-note 3 {{declared private here}} \
178+
// expected-error {{base class 'Base<2>' has private destructor}}
179+
class Base3 : virtual Base<3> { public: ~Base3(); }; // expected-error {{base class 'Base<3>' has private destructor}}
180+
181+
// These don't cause diagnostics because we don't need the destructor.
182+
class Derived0 : Base<0> { ~Derived0(); };
183+
class Derived1 : Base<1> { };
184+
185+
class Derived2 : // expected-error {{inherited virtual base class 'Base<2>' has private destructor}} \
186+
// expected-error {{inherited virtual base class 'Base<3>' has private destructor}}
187+
Base<0>, // expected-error {{base class 'Base<0>' has private destructor}}
188+
virtual Base<1>, // expected-error {{base class 'Base<1>' has private destructor}}
189+
Base2, // expected-error {{base class 'test3::Base2' has private destructor}}
190+
virtual Base3
191+
{
192+
~Derived2() {} // expected-note 2{{in implicit destructor}}
193+
};
194+
195+
class Derived3 : // expected-error 2 {{inherited virtual base class 'Base<2>' has private destructor}} \
196+
// expected-error 2 {{inherited virtual base class 'Base<3>' has private destructor}}
197+
Base<0>, // expected-error 2 {{base class 'Base<0>' has private destructor}}
198+
virtual Base<1>, // expected-error 2 {{base class 'Base<1>' has private destructor}}
199+
Base2, // expected-error 2 {{base class 'test3::Base2' has private destructor}}
200+
virtual Base3
201+
{};
202+
Derived3 d3; // expected-note{{implicit destructor}}} expected-note {{implicit default constructor}}
203+
#elif __cplusplus >= 201103L && !defined(_MSC_VER)
173204
template <unsigned N> class Base { ~Base(); }; // expected-note 4{{declared private here}}
174205
class Base2 : virtual Base<2> { ~Base2(); }; // expected-note 1{{declared private here}}
175206
class Base3 : virtual Base<3> { public: ~Base3(); };
@@ -193,8 +224,40 @@ namespace test3 {
193224
virtual Base<1>,
194225
Base2,
195226
virtual Base3
196-
{};
227+
{};
197228
Derived3 d3; // expected-error {{implicitly-deleted default constructor}}
229+
#elif __cplusplus >= 201103L && defined(_MSC_VER)
230+
template <unsigned N> class Base { ~Base(); }; // expected-note 6{{declared private here}}
231+
// expected-error@+1 {{inherited virtual base class 'Base<2>' has private destructor}}
232+
class Base2 : virtual Base<2> { ~Base2(); }; // expected-note 1{{declared private here}}
233+
// expected-error@+1 {{inherited virtual base class 'Base<3>' has private destructor}}
234+
class Base3 : virtual Base<3> { public: ~Base3(); };
235+
236+
// These don't cause diagnostics because we don't need the destructor.
237+
class Derived0 : Base<0> { ~Derived0(); };
238+
class Derived1 : Base<1> { };
239+
240+
class Derived2 : // expected-error {{inherited virtual base class 'Base<2>' has private destructor}} \
241+
// expected-error {{inherited virtual base class 'Base<3>' has private destructor}}
242+
Base<0>, // expected-error {{base class 'Base<0>' has private destructor}}
243+
virtual Base<1>, // expected-error {{base class 'Base<1>' has private destructor}}
244+
Base2, // expected-error {{base class 'test3::Base2' has private destructor}}
245+
virtual Base3
246+
{
247+
// expected-note@+2 {{in implicit destructor for 'test3::Base2' first required here}}
248+
// expected-note@+1 {{in implicit destructor for 'test3::Base3' first required here}}
249+
~Derived2() {}
250+
};
251+
252+
class Derived3 :
253+
Base<0>, // expected-note {{deleted because base class 'Base<0>' has an inaccessible destructor}}
254+
virtual Base<1>,
255+
Base2,
256+
virtual Base3
257+
{};
258+
Derived3 d3; // expected-error {{implicitly-deleted default constructor}}
259+
#else
260+
#error "missing case of MSVC cross C++ versions"
198261
#endif
199262
}
200263

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %clang_cc1 -std=c++17 -emit-llvm %s -triple x86_64-windows-msvc -o - | FileCheck %s
2+
3+
// Make sure virtual base base destructors get referenced and emitted if
4+
// necessary when the complete ("vbase") destructor is emitted. In this case,
5+
// clang previously did not emit ~DefaultedDtor.
6+
struct HasDtor { ~HasDtor(); };
7+
struct DefaultedDtor {
8+
~DefaultedDtor() = default;
9+
HasDtor o;
10+
};
11+
struct HasCompleteDtor : virtual DefaultedDtor {
12+
~HasCompleteDtor();
13+
};
14+
void useCompleteDtor(HasCompleteDtor *p) { delete p; }
15+
16+
// CHECK-LABEL: define dso_local void @"?useCompleteDtor@@YAXPEAUHasCompleteDtor@@@Z"(%struct.HasCompleteDtor* %p)
17+
// CHECK: call void @"??_DHasCompleteDtor@@QEAAXXZ"({{.*}})
18+
19+
// CHECK-LABEL: define linkonce_odr dso_local void @"??_DHasCompleteDtor@@QEAAXXZ"(%struct.HasCompleteDtor* %this)
20+
// CHECK: call void @"??1HasCompleteDtor@@QEAA@XZ"({{.*}})
21+
// CHECK: call void @"??1DefaultedDtor@@QEAA@XZ"({{.*}})
22+
23+
// CHECK-LABEL: define linkonce_odr dso_local void @"??1DefaultedDtor@@QEAA@XZ"(%struct.DefaultedDtor* %this)
24+
// CHECK: call void @"??1HasDtor@@QEAA@XZ"(%struct.HasDtor* %{{.*}})
25+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %clang_cc1 -std=c++17 -verify -Wno-defaulted-function-deleted %s -triple x86_64-windows-msvc
2+
3+
// MSVC emits the complete destructor as if it were its own special member.
4+
// Clang attempts to do the same. This affects the diagnostics clang emits,
5+
// because deleting a type with a user declared constructor implicitly
6+
// references the destructors of virtual bases, which might be deleted or access
7+
// controlled.
8+
9+
namespace t1 {
10+
struct A {
11+
~A() = delete; // expected-note {{deleted here}}
12+
};
13+
struct B {
14+
B() = default;
15+
A o; // expected-note {{destructor of 'B' is implicitly deleted because field 'o' has a deleted destructor}}
16+
};
17+
struct C : virtual B {
18+
~C(); // expected-error {{attempt to use a deleted function}}
19+
};
20+
void delete1(C *p) { delete p; } // expected-note {{in implicit destructor for 't1::C' first required here}}
21+
void delete2(C *p) { delete p; }
22+
}
23+
24+
namespace t2 {
25+
struct A {
26+
private:
27+
~A();
28+
};
29+
struct B {
30+
B() = default;
31+
A o; // expected-note {{destructor of 'B' is implicitly deleted because field 'o' has an inaccessible destructor}}
32+
};
33+
struct C : virtual B {
34+
~C(); // expected-error {{attempt to use a deleted function}}
35+
};
36+
void useCompleteDtor(C *p) { delete p; } // expected-note {{in implicit destructor for 't2::C' first required here}}
37+
}
38+
39+
namespace t3 {
40+
template <unsigned N>
41+
class Base { ~Base(); }; // expected-note 1{{declared private here}}
42+
// No diagnostic.
43+
class Derived0 : virtual Base<0> { ~Derived0(); };
44+
class Derived1 : virtual Base<1> {};
45+
// Emitting complete dtor causes a diagnostic.
46+
struct Derived2 : // expected-error {{inherited virtual base class 'Base<2>' has private destructor}}
47+
virtual Base<2> {
48+
~Derived2();
49+
};
50+
void useCompleteDtor(Derived2 *p) { delete p; } // expected-note {{in implicit destructor for 't3::Derived2' first required here}}
51+
}

0 commit comments

Comments
 (0)