[clang] 4da37d3 - [clang][bytecode] Check for non-trivial default ctors in unions (#174745)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jan 7 21:07:16 PST 2026
Author: Timm Baeder
Date: 2026-01-08T06:07:12+01:00
New Revision: 4da37d3295fbd0fd28b903812ba48025e267260f
URL: https://github.com/llvm/llvm-project/commit/4da37d3295fbd0fd28b903812ba48025e267260f
DIFF: https://github.com/llvm/llvm-project/commit/4da37d3295fbd0fd28b903812ba48025e267260f.diff
LOG: [clang][bytecode] Check for non-trivial default ctors in unions (#174745)
When activating a union member, none of the unions in that path can have
a non-trivial constructor. Unfortunately, this is something we have to
do when evaluating the bytecode, not while compiling it.
Added:
Modified:
clang/lib/AST/ByteCode/Compiler.cpp
clang/lib/AST/ByteCode/Interp.cpp
clang/lib/AST/ByteCode/Interp.h
clang/test/AST/ByteCode/unions.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index a539f31db0ca6..e85ccafc0e53b 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5570,7 +5570,8 @@ bool Compiler<Emitter>::maybeEmitDeferredVarInit(const VarDecl *VD) {
static bool hasTrivialDefaultCtorParent(const FieldDecl *FD) {
assert(FD);
assert(FD->getParent()->isUnion());
- const auto *CXXRD = dyn_cast<CXXRecordDecl>(FD->getParent());
+ const CXXRecordDecl *CXXRD =
+ FD->getType()->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
return !CXXRD || CXXRD->hasTrivialDefaultConstructor();
}
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index fe3069a4d8ef8..6444025d6fcf8 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -326,12 +326,13 @@ bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
}
bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- AccessKinds AK) {
+ AccessKinds AK, bool WillActivate) {
if (Ptr.isActive())
return true;
assert(Ptr.inUnion());
+ // Find the outermost union.
Pointer U = Ptr.getBase();
Pointer C = Ptr;
while (!U.isRoot() && !U.isActive()) {
@@ -346,6 +347,7 @@ bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
U = U.getBase();
}
assert(C.isField());
+ assert(C.getBase() == U);
// Consider:
// union U {
@@ -362,6 +364,25 @@ bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (!U.getFieldDesc()->isUnion())
return true;
+ // When we will activate Ptr, check that none of the unions in its path have a
+ // non-trivial default constructor.
+ if (WillActivate) {
+ bool Fails = false;
+ Pointer It = Ptr;
+ while (!It.isRoot() && !It.isActive()) {
+ if (const Record *R = It.getRecord(); R && R->isUnion()) {
+ if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl());
+ CXXRD && !CXXRD->hasTrivialDefaultConstructor()) {
+ Fails = true;
+ break;
+ }
+ }
+ It = It.getBase();
+ }
+ if (!Fails)
+ return true;
+ }
+
// Get the inactive field descriptor.
assert(!C.isActive());
const FieldDecl *InactiveField = C.getField();
@@ -890,7 +911,7 @@ bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
if (!CheckRange(S, OpPC, Ptr, AK_Assign))
return false;
- if (!WillBeActivated && !CheckActive(S, OpPC, Ptr, AK_Assign))
+ if (!CheckActive(S, OpPC, Ptr, AK_Assign, WillBeActivated))
return false;
if (!CheckGlobal(S, OpPC, Ptr))
return false;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 155d96fc1652b..7ccd690d22fb7 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -94,7 +94,7 @@ bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
const Pointer &Ptr);
bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- AccessKinds AK);
+ AccessKinds AK, bool WillActivate = false);
/// Sets the given integral value to the pointer, which is of
/// a std::{weak,partial,strong}_ordering type.
@@ -2017,7 +2017,7 @@ bool StoreActivate(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WilLBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2032,7 +2032,7 @@ bool StoreActivatePop(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WilLBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2047,7 +2047,7 @@ bool StoreBitField(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WilLBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr))
return false;
if (Ptr.canBeInitialized())
Ptr.initialize();
@@ -2078,7 +2078,7 @@ bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WilLBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
diff --git a/clang/test/AST/ByteCode/unions.cpp b/clang/test/AST/ByteCode/unions.cpp
index 414070417a02a..2123a932fce10 100644
--- a/clang/test/AST/ByteCode/unions.cpp
+++ b/clang/test/AST/ByteCode/unions.cpp
@@ -986,4 +986,25 @@ namespace ActicvateInvalidPtr {
foo.a[1] = 0; // both-note {{assignment to dereferenced one-past-the-end pointer}}
}
}
+
+namespace NonTrivialUnionCtor {
+ union A {
+ int y = 5;
+ };
+ union D {
+ constexpr D(int n) : n(n) {}
+ constexpr D() : n(3) {}
+
+ A a;
+ int n;
+ };
+
+ constexpr bool j() {
+ D d;
+ d.a.y = 3; // both-note {{assignment to member 'a' of union with active member 'n'}}
+ return d.a.y == 3;
+ }
+ static_assert(j()); // both-error {{not an integral constant expression}} \
+ // both-note {{in call to}}
+}
#endif
More information about the cfe-commits
mailing list