[clang] [Clang][Sema] Don't delay the access check when computing implicit deletion (PR #210254)
Akash Agrawal via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 22 06:08:28 PDT 2026
https://github.com/akashagrwl updated https://github.com/llvm/llvm-project/pull/210254
>From c2f6ad63e6f8937494b6233891fa38996d8b5b47 Mon Sep 17 00:00:00 2001
From: Akash Agrawal <akashag at qti.qualcomm.com>
Date: Thu, 16 Jul 2026 23:06:52 -0700
Subject: [PATCH 1/2] [Clang][Sema] Don't delay the access check when computing
implicit deletion
Sema::isMemberAccessibleForDeletion treats AR_delayed as unreachable, but
CheckAccess returns AR_delayed whenever it runs inside an enclosing
delayed-diagnostics scope. That happens when deletion checking runs
synchronously while parsing a later declaration -- e.g. while explaining why a
defaulted operator<=> is deleted for an expression in that declaration's
initializer. The caller cannot consume a delayed diagnostic, so letting
CheckAccess delay always hits
llvm_unreachable("cannot delay =delete computation") and crashes.
Force an immediate answer by wrapping the CheckAccess call in
DelayedDiagnostics.pushUndelayed()/popUndelayed() via llvm::scope_exit,
mirroring the existing Sema::CheckEnableIf pattern in SemaOverload.cpp.
---
clang/lib/Sema/SemaAccess.cpp | 11 +++++++++++
clang/test/SemaCXX/cxx20-default-compare.cpp | 18 ++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 17415b4185eff..f2e19488a6ee2 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -21,6 +21,7 @@
#include "clang/Sema/DelayedDiagnostic.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
+#include "llvm/ADT/ScopeExit.h"
using namespace clang;
using namespace sema;
@@ -1612,6 +1613,16 @@ bool Sema::isMemberAccessibleForDeletion(CXXRecordDecl *NamingClass,
// Suppress diagnostics.
Entity.setDiag(Diag);
+ // Deletion checking can run while we are inside an enclosing
+ // delayed-diagnostics scope (e.g. when parsing a later declaration whose
+ // initializer requires explaining why a defaulted comparison operator is
+ // deleted). CheckAccess would then return AR_delayed, but the result must be
+ // known immediately here. Force an undelayed check, mirroring CheckEnableIf.
+ llvm::scope_exit UndelayDiags(
+ [&, CurrentState(DelayedDiagnostics.pushUndelayed())] {
+ DelayedDiagnostics.popUndelayed(CurrentState);
+ });
+
switch (CheckAccess(*this, Loc, Entity)) {
case AR_accessible: return true;
case AR_inaccessible: return false;
diff --git a/clang/test/SemaCXX/cxx20-default-compare.cpp b/clang/test/SemaCXX/cxx20-default-compare.cpp
index 3e4673c31e489..cb2886608d6f6 100644
--- a/clang/test/SemaCXX/cxx20-default-compare.cpp
+++ b/clang/test/SemaCXX/cxx20-default-compare.cpp
@@ -65,3 +65,21 @@ struct MutCheck : MutOnly {
// bool operator==(this MutCheck, MutCheck) = default;
};
}
+
+namespace delayed_deletion_check {
+// Explaining why a defaulted comparison operator is deleted can run while we
+// are parsing a later declaration, i.e. inside an enclosing delayed-diagnostics
+// scope. The access check for the deleted-ness computation must produce an
+// immediate answer rather than being delayed. Previously this crashed.
+struct HasPrivateSpaceship {
+private:
+ std::strong_ordering operator<=>(const HasPrivateSpaceship &) const; // expected-note 2 {{declared private here}}
+};
+
+struct S {
+ HasPrivateSpaceship member; // expected-note 2 {{because it would invoke a private 'operator<=>' member of 'delayed_deletion_check::HasPrivateSpaceship' to compare member 'member'}}
+ auto operator<=>(const S &) const = default; // expected-warning {{explicitly defaulted three-way comparison operator is implicitly deleted}} expected-note {{replace 'default' with 'delete'}} expected-note {{explicitly defaulted function was implicitly deleted here}}
+};
+
+bool b = (S{} < S{}); // expected-error {{object of type 'S' cannot be compared because its 'operator<=>' is implicitly deleted}}
+}
>From 62811efb260c7d6c7cd3b889f23af649b5905f57 Mon Sep 17 00:00:00 2001
From: Akash Agrawal <akashag at qti.qualcomm.com>
Date: Wed, 22 Jul 2026 05:57:08 -0700
Subject: [PATCH 2/2] renaming delayed_deletion_check to
immediate_deletion_check
---
clang/test/SemaCXX/cxx20-default-compare.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/SemaCXX/cxx20-default-compare.cpp b/clang/test/SemaCXX/cxx20-default-compare.cpp
index cb2886608d6f6..04e95cede930e 100644
--- a/clang/test/SemaCXX/cxx20-default-compare.cpp
+++ b/clang/test/SemaCXX/cxx20-default-compare.cpp
@@ -66,7 +66,7 @@ struct MutCheck : MutOnly {
};
}
-namespace delayed_deletion_check {
+namespace immediate_deletion_check {
// Explaining why a defaulted comparison operator is deleted can run while we
// are parsing a later declaration, i.e. inside an enclosing delayed-diagnostics
// scope. The access check for the deleted-ness computation must produce an
@@ -77,7 +77,7 @@ struct HasPrivateSpaceship {
};
struct S {
- HasPrivateSpaceship member; // expected-note 2 {{because it would invoke a private 'operator<=>' member of 'delayed_deletion_check::HasPrivateSpaceship' to compare member 'member'}}
+ HasPrivateSpaceship member; // expected-note 2 {{because it would invoke a private 'operator<=>' member of 'immediate_deletion_check ::HasPrivateSpaceship' to compare member 'member'}}
auto operator<=>(const S &) const = default; // expected-warning {{explicitly defaulted three-way comparison operator is implicitly deleted}} expected-note {{replace 'default' with 'delete'}} expected-note {{explicitly defaulted function was implicitly deleted here}}
};
More information about the cfe-commits
mailing list