Skip to content

Commit 7e690ae

Browse files
committed
Fix the other failing test case
A virtual destructor still needs to be accessible to a destroying delete because the actual delete operator called depends on the dynamic type of the pointer, not the static type.
1 parent b42fb13 commit 7e690ae

File tree

3 files changed

+56
-24
lines changed

3 files changed

+56
-24
lines changed

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3768,6 +3768,28 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal,
37683768
DeclarationName DeleteName = Context.DeclarationNames.getCXXOperatorName(
37693769
ArrayForm ? OO_Array_Delete : OO_Delete);
37703770

3771+
// C++20 [expr.delete]p6: If the value of the operand of the delete-
3772+
// expression is not a null pointer value and the selected deallocation
3773+
// function (see below) is not a destroying operator delete, the delete-
3774+
// expression will invoke the destructor (if any) for the object or the
3775+
// elements of the array being deleted.
3776+
//
3777+
// This means we should not look at the destructor for a destroying
3778+
// delete operator, as that destructor is never called, unless the
3779+
// destructor is virtual (see [expr.delete]p8.1) because then the
3780+
// selected operator depends on the dynamic type of the pointer.
3781+
auto IsDtorCalled = [](const CXXMethodDecl *Dtor,
3782+
const FunctionDecl *OperatorDelete) {
3783+
if (!OperatorDelete)
3784+
return true;
3785+
3786+
if (!OperatorDelete->isDestroyingOperatorDelete())
3787+
return true;
3788+
3789+
// We have a destroying operator delete, so it depends on the dtor.
3790+
return Dtor->isVirtual();
3791+
};
3792+
37713793
if (PointeeRD) {
37723794
if (!UseGlobal &&
37733795
FindDeallocationFunction(StartLoc, PointeeRD, DeleteName,
@@ -3792,21 +3814,14 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal,
37923814
.HasSizeT;
37933815
}
37943816

3795-
// C++20 [expr.delete]p6: If the value of the operand of the delete-
3796-
// expression is not a null pointer value and the selected deallocation
3797-
// function (see below) is not a destroying operator delete, the delete-
3798-
// expression will invoke the destructor (if any) for the object or the
3799-
// elements of the array being deleted.
3800-
//
3801-
// This means we should not look at the destructor for a destroying
3802-
// delete operator, as that destructor is never called.
3803-
if (!PointeeRD->hasIrrelevantDestructor() &&
3804-
(!OperatorDelete || !OperatorDelete->isDestroyingOperatorDelete())) {
3817+
if (!PointeeRD->hasIrrelevantDestructor()) {
38053818
if (CXXDestructorDecl *Dtor = LookupDestructor(PointeeRD)) {
3806-
MarkFunctionReferenced(StartLoc,
3807-
const_cast<CXXDestructorDecl *>(Dtor));
3808-
if (DiagnoseUseOfDecl(Dtor, StartLoc))
3809-
return ExprError();
3819+
if (IsDtorCalled(Dtor, OperatorDelete)) {
3820+
MarkFunctionReferenced(StartLoc,
3821+
const_cast<CXXDestructorDecl *>(Dtor));
3822+
if (DiagnoseUseOfDecl(Dtor, StartLoc))
3823+
return ExprError();
3824+
}
38103825
}
38113826
}
38123827

@@ -3839,13 +3854,13 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal,
38393854
MarkFunctionReferenced(StartLoc, OperatorDelete);
38403855

38413856
// Check access and ambiguity of destructor if we're going to call it.
3842-
// Note that this is required even for a virtual delete, but not for a
3843-
// destroying delete operator.
3857+
// Note that this is required even for a virtual delete.
38443858
bool IsVirtualDelete = false;
3845-
if (PointeeRD && !OperatorDelete->isDestroyingOperatorDelete()) {
3859+
if (PointeeRD) {
38463860
if (CXXDestructorDecl *Dtor = LookupDestructor(PointeeRD)) {
3847-
CheckDestructorAccess(Ex.get()->getExprLoc(), Dtor,
3848-
PDiag(diag::err_access_dtor) << PointeeElem);
3861+
if (IsDtorCalled(Dtor, OperatorDelete))
3862+
CheckDestructorAccess(Ex.get()->getExprLoc(), Dtor,
3863+
PDiag(diag::err_access_dtor) << PointeeElem);
38493864
IsVirtualDelete = Dtor->isVirtual();
38503865
}
38513866
}

clang/test/CXX/expr/expr.unary/expr.delete/p10.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,27 @@ struct T {
4444
~T();
4545
};
4646

47-
void foo(S *s) {
47+
void foo(S *s, T *t) {
4848
delete s; // Was rejected, is intended to be accepted.
49+
delete t; // Was rejected, is intended to be accepted.
4950
}
5051

51-
void bar(T *t) {
52-
delete t; // Was rejected, is intended to be accepted.
52+
// However, if the destructor is virtual, then it has to be accessible because
53+
// the behavior depends on which operator delete is selected and that is based
54+
// on the dynamic type of the pointer.
55+
struct U {
56+
virtual ~U() = delete; // expected-note {{here}}
57+
void operator delete(U *, std::destroying_delete_t) noexcept {}
58+
};
59+
60+
struct V {
61+
void operator delete(V *, std::destroying_delete_t) noexcept {}
62+
private:
63+
virtual ~V(); // expected-note {{here}}
64+
};
65+
66+
void bar(U *u, V *v) {
67+
// Both should be rejected because they have virtual destructors.
68+
delete u; // expected-error {{attempt to use a deleted function}}
69+
delete v; // expected-error {{calling a private destructor of class 'V'}}
5370
}

clang/test/SemaCXX/cxx2a-destroying-delete.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ namespace dtor_access {
156156
struct T {
157157
void operator delete(T *, std::destroying_delete_t);
158158
protected:
159-
virtual ~T();
159+
virtual ~T(); // expected-note {{here}}
160160
};
161161

162162
struct U : T {
@@ -165,7 +165,7 @@ namespace dtor_access {
165165
~U() override;
166166
};
167167

168-
void g() { delete (T *)new U; }
168+
void g() { delete (T *)new U; } // expected-error {{calling a protected destructor of class 'T'}}
169169
}
170170

171171
namespace delete_from_new {

0 commit comments

Comments
 (0)