Skip to content

Commit f351d4e

Browse files
committed
Update noexcept behavior based on review feedback
1 parent a3b032c commit f351d4e

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

clang/lib/Sema/SemaExceptionSpec.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,21 +1200,35 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
12001200

12011201
case Expr::CXXDeleteExprClass: {
12021202
auto *DE = cast<CXXDeleteExpr>(S);
1203-
CanThrowResult CT;
1203+
CanThrowResult CT = CT_Cannot;
12041204
QualType DTy = DE->getDestroyedType();
12051205
if (DTy.isNull() || DTy->isDependentType()) {
12061206
CT = CT_Dependent;
12071207
} else {
1208+
// C++20 [expr.delete]p6: If the value of the operand of the delete-
1209+
// expression is not a null pointer value and the selected deallocation
1210+
// function (see below) is not a destroying operator delete, the delete-
1211+
// expression will invoke the destructor (if any) for the object or the
1212+
// elements of the array being deleted.
1213+
//
1214+
// So if the destructor is virtual, we only look at the exception
1215+
// specification of the destructor regardless of what operator delete is
1216+
// selected. Otherwise, we look at the selected operator delete, and if
1217+
// it is not a destroying delete, then we look at the destructor.
12081218
const FunctionDecl *OperatorDelete = DE->getOperatorDelete();
1209-
CT = canCalleeThrow(*this, DE, OperatorDelete);
1210-
if (!OperatorDelete->isDestroyingOperatorDelete()) {
1211-
if (const auto *RD = DTy->getAsCXXRecordDecl()) {
1212-
if (const CXXDestructorDecl *DD = RD->getDestructor())
1213-
CT = mergeCanThrow(CT, canCalleeThrow(*this, DE, DD));
1219+
if (const auto *RD = DTy->getAsCXXRecordDecl()) {
1220+
if (const CXXDestructorDecl *DD = RD->getDestructor()) {
1221+
if (DD->isVirtual() || !OperatorDelete->isDestroyingOperatorDelete())
1222+
CT = canCalleeThrow(*this, DE, DD);
12141223
}
1215-
if (CT == CT_Can)
1216-
return CT;
12171224
}
1225+
1226+
// We always look at the exception specification of the operator delete.
1227+
CT = mergeCanThrow(CT, canCalleeThrow(*this, DE, OperatorDelete));
1228+
1229+
// If we know we can throw, we're done.
1230+
if (CT == CT_Can)
1231+
return CT;
12181232
}
12191233
return mergeCanThrow(CT, canSubStmtsThrow(*this, DE));
12201234
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -Wno-unevaluated-expression -std=c++20 %s
2-
// expected-no-diagnostics
32

43
namespace std {
54
struct destroying_delete_t {
@@ -31,3 +30,19 @@ ThrowingDestroyingDelete *pn = nullptr;
3130
// noexcept should return false here because the destroying delete itself is a
3231
// potentially throwing function.
3332
static_assert(!noexcept(delete(pn)));
33+
34+
35+
struct A {
36+
virtual ~A(); // implicitly noexcept
37+
};
38+
struct B : A {
39+
void operator delete(B *p, std::destroying_delete_t) { throw "oh no"; } // expected-warning {{'operator delete' has a non-throwing exception specification but can still throw}} \
40+
expected-note {{deallocator has a implicit non-throwing exception specification}}
41+
};
42+
A *p = new B;
43+
44+
// Because the destructor for A is virtual, it is the only thing we consider
45+
// when determining whether the delete expression can throw or not, despite the
46+
// fact that the selected operator delete is a destroying delete. For virtual
47+
// destructors, it's the dynamic type that matters.
48+
static_assert(noexcept(delete p));

0 commit comments

Comments
 (0)