Skip to content

Commit 91354fb

Browse files
authored
[C++20] Destroying delete can cause a type to be noexcept when deleting (#118687)
Given a `noexcept` operator with an operand that calls `delete`, Clang was not considering whether the selected `operator delete` function was a destroying delete or not when inspecting whether the deleted object type has a throwing destructor. Thus, the operator would return `false` for a type with a potentially throwing destructor even though that destructor would not be called due to the destroying delete. Clang now takes the kind of delete operator into consideration. Fixes #118660
1 parent 2214e02 commit 91354fb

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@ Bug Fixes in This Version
677677
- Fixed a crash when GNU statement expression contains invalid statement (#GH113468).
678678
- Fixed a failed assertion when using ``__attribute__((noderef))`` on an
679679
``_Atomic``-qualified type (#GH116124).
680+
- No longer return ``false`` for ``noexcept`` expressions involving a
681+
``delete`` which resolves to a destroying delete but the type of the object
682+
being deleted has a potentially throwing destructor (#GH118660).
680683

681684
Bug Fixes to Compiler Builtins
682685
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/SemaExceptionSpec.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,15 +1205,16 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
12051205
if (DTy.isNull() || DTy->isDependentType()) {
12061206
CT = CT_Dependent;
12071207
} else {
1208-
CT = canCalleeThrow(*this, DE, DE->getOperatorDelete());
1209-
if (const RecordType *RT = DTy->getAs<RecordType>()) {
1210-
const CXXRecordDecl *RD = cast<CXXRecordDecl>(RT->getDecl());
1211-
const CXXDestructorDecl *DD = RD->getDestructor();
1212-
if (DD)
1213-
CT = mergeCanThrow(CT, canCalleeThrow(*this, DE, DD));
1208+
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));
1214+
}
1215+
if (CT == CT_Can)
1216+
return CT;
12141217
}
1215-
if (CT == CT_Can)
1216-
return CT;
12171218
}
12181219
return mergeCanThrow(CT, canSubStmtsThrow(*this, DE));
12191220
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -Wno-unevaluated-expression -std=c++20 %s
2+
// expected-no-diagnostics
3+
4+
namespace std {
5+
struct destroying_delete_t {
6+
explicit destroying_delete_t() = default;
7+
};
8+
9+
inline constexpr destroying_delete_t destroying_delete{};
10+
}
11+
12+
struct Explicit {
13+
~Explicit() noexcept(false) {}
14+
15+
void operator delete(Explicit*, std::destroying_delete_t) noexcept {
16+
}
17+
};
18+
19+
Explicit *qn = nullptr;
20+
// This assertion used to fail, see GH118660
21+
static_assert(noexcept(delete(qn)));
22+
23+
struct ThrowingDestroyingDelete {
24+
~ThrowingDestroyingDelete() noexcept(false) {}
25+
26+
void operator delete(ThrowingDestroyingDelete*, std::destroying_delete_t) noexcept(false) {
27+
}
28+
};
29+
30+
ThrowingDestroyingDelete *pn = nullptr;
31+
// noexcept should return false here because the destroying delete itself is a
32+
// potentially throwing function.
33+
static_assert(!noexcept(delete(pn)));

0 commit comments

Comments
 (0)