[clang] [clang][Sema] Function effect analysis was missing the implicit call to the destructor in a CXXDeleteExpr. (PR #184460)

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 13 10:46:52 PDT 2026


https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/184460

>From f2cfed7045e0329375a069a69da4b79eb56a52ff Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 3 Mar 2026 15:19:58 -0800
Subject: [PATCH 1/2] [clang][Sema] Function effect analysis was missing the
 implicit call to the destructor in a CXXDeleteExpr.

---
 clang/lib/Sema/SemaFunctionEffects.cpp        | 16 ++++++--
 .../Sema/attr-nonblocking-constraints.cpp     | 40 +++++++++++++++++++
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 124e23cc97556..975281175aebb 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1255,15 +1255,23 @@ class Analyzer {
     }
 
     bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) override {
+      FunctionDecl *OpDelete = Delete->getOperatorDelete();
+
+      // RecursiveASTVisitor does not visit the called destructor.
+      // But a destroying operator delete means that no destructor is called.
+      if (OpDelete == nullptr || !OpDelete->isDestroyingOperatorDelete()) {
+        if (QualType QT = Delete->getDestroyedType(); !QT.isNull()) {
+          followTypeDtor(QT, Delete->getBeginLoc());
+        }
+      }
+
       // RecursiveASTVisitor does not visit the implicit call to operator
       // delete.
-      if (FunctionDecl *FD = Delete->getOperatorDelete()) {
-        CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
+      if (OpDelete != nullptr) {
+        CallableInfo CI(*OpDelete, SpecialFuncType::OperatorDelete);
         followCall(CI, Delete->getBeginLoc());
       }
 
-      // It DOES however visit the called destructor
-
       return true;
     }
 
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 012c017798a1f..400057768523c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -5,6 +5,8 @@
 // This diagnostic is re-enabled and exercised in isolation later in this file.
 #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
+typedef __SIZE_TYPE__ size_t;
+
 // --- CONSTRAINTS ---
 
 void nb1() [[clang::nonblocking]]
@@ -430,6 +432,44 @@ struct HasDtor {
 	~HasDtor() {}
 };
 
+struct SafeDeleteUnsafeDestroy {
+	static unsigned char storage[256];
+
+	void* operator new(size_t) { return storage; }
+	void operator delete(void* ptr) {}
+
+	void* operator new[](size_t sz) { return storage; }
+	void operator delete[](void* ptr) {}
+
+	SafeDeleteUnsafeDestroy(); // expected-note 2 {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+	~SafeDeleteUnsafeDestroy(); // expected-note 2 {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+};
+
+void testSafeDeleteUnsafeDestroy() [[clang::nonblocking]]
+{
+	auto *ptr = new SafeDeleteUnsafeDestroy; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'SafeDeleteUnsafeDestroy::SafeDeleteUnsafeDestroy'}}
+	delete ptr; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' destructor 'SafeDeleteUnsafeDestroy::~SafeDeleteUnsafeDestroy'}}
+
+	auto *arr = new SafeDeleteUnsafeDestroy[2]; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'SafeDeleteUnsafeDestroy::SafeDeleteUnsafeDestroy'}}
+	delete[] arr; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' destructor 'SafeDeleteUnsafeDestroy::~SafeDeleteUnsafeDestroy'}}
+}
+
+namespace std {
+struct destroying_delete_t { explicit destroying_delete_t() = default; };
+inline constexpr destroying_delete_t destroying_delete{};
+}
+
+struct DestroyingDelete {
+    ~DestroyingDelete();
+    void operator delete(DestroyingDelete*, std::destroying_delete_t) {
+        // do nothing
+    }
+};
+
+void testDestroyingDelete(DestroyingDelete* d) [[clang::nonblocking]] {
+    delete d;
+}
+
 template <typename T>
 struct Optional {
 	union {

>From 569c8f0d3a342a60501bc860667cf5d8bd802bf8 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 7 Jul 2026 11:03:50 -0700
Subject: [PATCH 2/2] Add release note

---
 clang/docs/ReleaseNotes.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ae45a45fe3897..5657b6017b45d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -781,6 +781,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
 - Fixed assertion failures involving code completion with delayed default arguments and exception specifications. (#GH200879)
 - Fixed a regression where calling a function that takes a class-type parameter by value inside `decltype` of a concept could be incorrectly rejected when used as a non-type template argument. (#GH175831)
 - Fixed a crash in the constant evaluator when an ill-formed array new-expression whose bound could not be determined (e.g. `new int[]()`) was used in a constant expression. (#GH200139)
+- Fixed a case where function effect analysis (`nonblocking` etc.) did not visit a destructor invoked from a `delete` expression. (#GH184460)
 
 #### Bug Fixes to Compiler Builtins
 



More information about the cfe-commits mailing list