diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 755418e9550cf..381f48e2cd206 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -654,6 +654,9 @@ Bug Fixes in This Version - Fixed a crash when GNU statement expression contains invalid statement (#GH113468). - Fixed a failed assertion when using ``__attribute__((noderef))`` on an ``_Atomic``-qualified type (#GH116124). +- No longer return ``false`` for ``noexcept`` expressions involving a + ``delete`` which resolves to a destroying delete but the type of the object + being deleted has a potentially throwing destructor (#GH118660). Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index ecfd79a50542c..6a9f43d6f5215 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1205,15 +1205,16 @@ CanThrowResult Sema::canThrow(const Stmt *S) { if (DTy.isNull() || DTy->isDependentType()) { CT = CT_Dependent; } else { - CT = canCalleeThrow(*this, DE, DE->getOperatorDelete()); - if (const RecordType *RT = DTy->getAs()) { - const CXXRecordDecl *RD = cast(RT->getDecl()); - const CXXDestructorDecl *DD = RD->getDestructor(); - if (DD) - CT = mergeCanThrow(CT, canCalleeThrow(*this, DE, DD)); + const FunctionDecl *OperatorDelete = DE->getOperatorDelete(); + CT = canCalleeThrow(*this, DE, OperatorDelete); + if (!OperatorDelete->isDestroyingOperatorDelete()) { + if (const auto *RD = DTy->getAsCXXRecordDecl()) { + if (const CXXDestructorDecl *DD = RD->getDestructor()) + CT = mergeCanThrow(CT, canCalleeThrow(*this, DE, DD)); + } + if (CT == CT_Can) + return CT; } - if (CT == CT_Can) - return CT; } return mergeCanThrow(CT, canSubStmtsThrow(*this, DE)); } diff --git a/clang/test/SemaCXX/noexcept-destroying-delete.cpp b/clang/test/SemaCXX/noexcept-destroying-delete.cpp new file mode 100644 index 0000000000000..92ccbc1fb3f96 --- /dev/null +++ b/clang/test/SemaCXX/noexcept-destroying-delete.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -Wno-unevaluated-expression -std=c++20 %s +// expected-no-diagnostics + +namespace std { + struct destroying_delete_t { + explicit destroying_delete_t() = default; + }; + + inline constexpr destroying_delete_t destroying_delete{}; +} + +struct Explicit { + ~Explicit() noexcept(false) {} + + void operator delete(Explicit*, std::destroying_delete_t) noexcept { + } +}; + +Explicit *qn = nullptr; +// This assertion used to fail, see GH118660 +static_assert(noexcept(delete(qn))); + +struct ThrowingDestroyingDelete { + ~ThrowingDestroyingDelete() noexcept(false) {} + + void operator delete(ThrowingDestroyingDelete*, std::destroying_delete_t) noexcept(false) { + } +}; + +ThrowingDestroyingDelete *pn = nullptr; +// noexcept should return false here because the destroying delete itself is a +// potentially throwing function. +static_assert(!noexcept(delete(pn)));