[clang] 8b25820 - [clang][bytecode] Diagnose copying empty mutable unions (#195529)
via cfe-commits
cfe-commits at lists.llvm.org
Mon May 4 02:44:30 PDT 2026
Author: Timm Baeder
Date: 2026-05-04T11:44:26+02:00
New Revision: 8b258206819d48ff6410ea99f3c63738318bd178
URL: https://github.com/llvm/llvm-project/commit/8b258206819d48ff6410ea99f3c63738318bd178
DIFF: https://github.com/llvm/llvm-project/commit/8b258206819d48ff6410ea99f3c63738318bd178.diff
LOG: [clang][bytecode] Diagnose copying empty mutable unions (#195529)
We had a special case for copy/move ctors of empty unions. Remove that.
Everything else is just so we don't regress diagnostics.
Added:
Modified:
clang/lib/AST/ByteCode/Compiler.cpp
clang/lib/AST/ByteCode/Function.cpp
clang/lib/AST/ByteCode/Function.h
clang/lib/AST/ByteCode/Interp.cpp
clang/lib/AST/ByteCode/InterpFrame.cpp
clang/test/AST/ByteCode/records.cpp
Removed:
################################################################################
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 9b18155d47ea3..2f78d2054c5a4 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -6809,12 +6809,13 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) {
return false;
bool IsUnion = R->isUnion();
+ // Union copy and move ctors are special.
if (IsUnion && Ctor->isCopyOrMoveConstructor()) {
LocOverrideScope<Emitter> LOS(this, SourceInfo{});
- if (R->getNumFields() == 0)
- return this->emitRetVoid(Ctor);
- // union copy and move ctors are special.
+ // No special case for NumFields == 0 here, so the Memcpy op
+ // below also does its checks in those cases.
+
assert(cast<CompoundStmt>(Ctor->getBody())->body_empty());
if (!this->emitThis(Ctor))
return false;
diff --git a/clang/lib/AST/ByteCode/Function.cpp b/clang/lib/AST/ByteCode/Function.cpp
index 06a18d4db036e..e08e2790dbd4f 100644
--- a/clang/lib/AST/ByteCode/Function.cpp
+++ b/clang/lib/AST/ByteCode/Function.cpp
@@ -29,7 +29,8 @@ Function::Function(Program &P, FunctionDeclTy Source, unsigned ArgSize,
Constexpr = F->isConstexpr();
if (const auto *CD = dyn_cast<CXXConstructorDecl>(F)) {
Virtual = CD->isVirtual();
- Kind = FunctionKind::Ctor;
+ Kind = CD->isCopyOrMoveConstructor() ? FunctionKind::CopyOrMoveCtor
+ : FunctionKind::Ctor;
} else if (const auto *CD = dyn_cast<CXXDestructorDecl>(F)) {
Virtual = CD->isVirtual();
Kind = FunctionKind::Dtor;
diff --git a/clang/lib/AST/ByteCode/Function.h b/clang/lib/AST/ByteCode/Function.h
index 90732d6dc01a4..653eb171ba81c 100644
--- a/clang/lib/AST/ByteCode/Function.h
+++ b/clang/lib/AST/ByteCode/Function.h
@@ -101,6 +101,7 @@ class Function final {
enum class FunctionKind {
Normal,
Ctor,
+ CopyOrMoveCtor,
Dtor,
LambdaStaticInvoker,
LambdaCallOperator,
@@ -185,7 +186,13 @@ class Function final {
bool isConstexpr() const { return Constexpr; }
/// Checks if the function is a constructor.
- bool isConstructor() const { return Kind == FunctionKind::Ctor; }
+ bool isConstructor() const {
+ return Kind == FunctionKind::Ctor || Kind == FunctionKind::CopyOrMoveCtor;
+ }
+ bool isCopyOrMoveConstructor() const {
+ return Kind == FunctionKind::CopyOrMoveCtor;
+ }
+
/// Checks if the function is a destructor.
bool isDestructor() const { return Kind == FunctionKind::Dtor; }
/// Checks if the function is copy or move operator.
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 8563ab8d844b7..0fa4021cae1f6 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -910,6 +910,8 @@ static bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
if (!IsCtorDtor && !CheckLifetime(S, OpPC, Ptr, AK_MemberCall))
return false;
+ if (!CheckMutable(S, OpPC, Ptr))
+ return false;
}
return true;
}
@@ -1781,6 +1783,14 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
if (!CheckInvoke(S, OpPC, ThisPtr,
Func->isConstructor() || Func->isDestructor()))
return cleanup();
+
+ if (Func->isCopyOrMoveOperator() || Func->isCopyOrMoveConstructor()) {
+ const Pointer &RVOPtr =
+ S.Stk.peek<Pointer>(ThisOffset - align(sizeof(Pointer)));
+ if (!CheckInvoke(S, OpPC, RVOPtr, /*IsCtorDtor=*/true))
+ return cleanup();
+ }
+
if (!Func->isConstructor() && !Func->isDestructor() &&
!CheckActive(S, OpPC, ThisPtr, AK_MemberCall))
return false;
diff --git a/clang/lib/AST/ByteCode/InterpFrame.cpp b/clang/lib/AST/ByteCode/InterpFrame.cpp
index c72ac3b825337..9c9318fe0e55a 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.cpp
+++ b/clang/lib/AST/ByteCode/InterpFrame.cpp
@@ -150,11 +150,6 @@ static bool shouldSkipInBacktrace(const Function *F) {
MD && MD->getParent()->isAnonymousStructOrUnion())
return true;
- if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(FD);
- Ctor && Ctor->isDefaulted() && Ctor->isTrivial() &&
- Ctor->isCopyOrMoveConstructor() && Ctor->inits().empty())
- return true;
-
return false;
}
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index bc3900812c7a1..2b21f5f8ecf45 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -1964,3 +1964,39 @@ namespace FieldLifetimeNotStarted {
// both-note {{declared here}} \
// both-note {{in implicit default constructor for 'FieldLifetimeNotStarted::R' first required here}}
}
+
+namespace EmptyRecords {
+ struct E1 {} e1;
+ union E2 {} e2; // both-note 4{{here}}
+ struct E3 : E1 {} e3;
+
+ template<typename E>
+ constexpr int f(E &a, int kind) {
+ switch (kind) {
+ case 0: { E e(a); return 0; } // both-note {{read}} \
+ // both-note {{in call}}
+ case 1: { E e(static_cast<E&&>(a)); return 0; } // both-note {{read}} \
+ // both-note {{in call}}
+ case 2: { E e; e = a; return 0; } // both-note {{read}} \
+ // both-note {{in call}}
+ case 3: { E e; e = static_cast<E&&>(a); return 0; } // both-note {{read}} \
+ // both-note {{in call}}
+ }
+ }
+ constexpr int test1 = f(e1, 0);
+ constexpr int test2 = f(e2, 0); // both-error {{constant expression}} \
+ // both-note {{in call}}
+ constexpr int test3 = f(e3, 0);
+ constexpr int test4 = f(e1, 1);
+ constexpr int test5 = f(e2, 1); // both-error {{constant expression}} \
+ // both-note {{in call}}
+ constexpr int test6 = f(e3, 1);
+ constexpr int test7 = f(e1, 2);
+ constexpr int test8 = f(e2, 2); // both-error {{constant expression}} \
+ // both-note {{in call}}
+ constexpr int test9 = f(e3, 2);
+ constexpr int testa = f(e1, 3);
+ constexpr int testb = f(e2, 3); // both-error {{constant expression}} \
+ // both-note {{in call}}
+ constexpr int testc = f(e3, 3);
+}
More information about the cfe-commits
mailing list