[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