[clang] [clang][constexpr] Move anonymous-union check before getDestructor() in HandleDestructionImpl (PR #197416)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 15 01:55:19 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/197416
>From 62032911518c77d884841589fe5cdf3327a85f6b Mon Sep 17 00:00:00 2001
From: Arendelle <Arendelle_ at outlook.com>
Date: Wed, 13 May 2026 17:55:04 +0800
Subject: [PATCH 1/3] [clang][constexpr] Move anonymous-union check before
getDestructor() in HandleDestructionImpl
When HandleDestructionImpl destroys a class object during constant
evaluation, it calls getDestructor() before checking whether the record
is an anonymous union. For an anonymous union whose members have
non-trivial destructors, getDestructor() may return null while
hasTrivialDestructor() returns false. This causes us to incorrectly
reject valid code with a "subexpression not valid in a constant
expression" diagnostic.
Anonymous unions should not have their destructors invoked directly;
their lifetime is ended by the enclosing class's destructor. Move the
anonymous-union short-circuit before the getDestructor() call so that
we avoid the erroneous destructor lookup.
This fixes cases where an object with an implicitly-defined constexpr
destructor stored inside an anonymous union member was incorrectly
rejected during constant evaluation.
---
clang/lib/AST/ExprConstant.cpp | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 5f09c9ea4a7b8..efe9d793d2fb2 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -7370,23 +7370,26 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange,
return false;
}
+ // If an anonymous union would be destroyed, some enclosing destructor must
+ // have been explicitly defined, and the anonymous union destruction should
+ // have no effect.
+ if (RD->isAnonymousStructOrUnion() && RD->isUnion()) {
+ Value = APValue();
+ return true;
+ }
+
const CXXDestructorDecl *DD = RD->getDestructor();
if (!DD && !RD->hasTrivialDestructor()) {
Info.FFDiag(CallRange.getBegin());
return false;
}
- if (!DD || DD->isTrivial() ||
- (RD->isAnonymousStructOrUnion() && RD->isUnion())) {
+ if (!DD || DD->isTrivial()) {
// A trivial destructor just ends the lifetime of the object. Check for
// this case before checking for a body, because we might not bother
// building a body for a trivial destructor. Note that it doesn't matter
// whether the destructor is constexpr in this case; all trivial
// destructors are constexpr.
- //
- // If an anonymous union would be destroyed, some enclosing destructor must
- // have been explicitly defined, and the anonymous union destruction should
- // have no effect.
Value = APValue();
return true;
}
>From 698048528e629fe1806acddedfad1872455dde82 Mon Sep 17 00:00:00 2001
From: Arendelle <Arendelle_ at outlook.com>
Date: Wed, 13 May 2026 18:33:29 +0800
Subject: [PATCH 2/3] add test
---
.../SemaCXX/constant-expression-cxx2a.cpp | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
index 396a8df21a3e3..0748c6be0179f 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp
@@ -1518,3 +1518,23 @@ namespace GH150705 {
constexpr const A& a = b;
constexpr auto x = (a.*q)(); // expected-error {{constant expression}}
}
+
+namespace GH197403 {
+ struct Inner {
+ constexpr ~Inner() noexcept {}
+ };
+ struct Outer {
+ Inner inner;
+ };
+ template<typename T>
+ struct BugTrigger {
+ union { T value; int dummy; };
+ constexpr BugTrigger() : value{} {}
+ constexpr ~BugTrigger() noexcept { value.~T(); }
+ };
+ consteval int test() {
+ BugTrigger<Outer> bt;
+ return 0;
+ }
+ static_assert(test() == 0);
+}
>From f538d13fef9adc05ca0a3d5b657b3d90be4a374c Mon Sep 17 00:00:00 2001
From: Arendelle <Arendelle_ at outlook.com>
Date: Fri, 15 May 2026 08:41:11 +0800
Subject: [PATCH 3/3] test for ByteCode interprater
---
clang/test/AST/ByteCode/unions.cpp | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang/test/AST/ByteCode/unions.cpp b/clang/test/AST/ByteCode/unions.cpp
index 399c4c891be00..33ca3d64aaa42 100644
--- a/clang/test/AST/ByteCode/unions.cpp
+++ b/clang/test/AST/ByteCode/unions.cpp
@@ -1071,4 +1071,24 @@ namespace Revive {
static_assert(h() == 20); // both-error {{not an integral constant expression}} \
// both-note {{in call to}}
}
+
+namespace GH197403 {
+ struct Inner {
+ constexpr ~Inner() noexcept {}
+ };
+ struct Outer {
+ Inner inner;
+ };
+ template<typename T>
+ struct BugTrigger {
+ union { T value; int dummy; };
+ constexpr BugTrigger() : value{} {}
+ constexpr ~BugTrigger() noexcept { value.~T(); }
+ };
+ consteval int test() {
+ BugTrigger<Outer> bt;
+ return 0;
+ }
+ static_assert(test() == 0);
+}
#endif
More information about the cfe-commits
mailing list