Author: Akash Manna
Date: 2026-08-28T14:05:11+02:00
New Revision: f29af2982b3f526bfaa21abff9835bc7c2f3cc3a

URL: 
https://github.com/llvm/llvm-project/commit/f29af2982b3f526bfaa21abff9835bc7c2f3cc3a
DIFF: 
https://github.com/llvm/llvm-project/commit/f29af2982b3f526bfaa21abff9835bc7c2f3cc3a.diff

LOG: [Clang] Fix "Unions cannot be dynamic classes" assertion (#217942)

Fixes #213854

An ill-formed qualified member definition like `void B::foo() {}` inside
a union gets diagnosed, but recovery keeps the declaration around: it
belongs to `B` semantically while sitting lexically inside the union.
Since `B::foo` overrides a virtual function, it counted as a virtual
member of the union when it was added to it, so the union was marked
polymorphic and record layout later asserted with `"Unions cannot be
dynamic classes."`. A non-union enclosing class hits the same bug
silently and just gets a bogus vtable pointer.

The fix is in `HandleDeclarator`: once the qualifier is diagnosed as
naming a different class, the declaration is marked invalid, the same
way we already handle `virtual` in a union. Invalid declarations don't
contribute to the properties of the class they're added to, so the class
can't become polymorphic, while the body is still parsed and the node
stays in the AST. As a side effect, the qualified specializations in
cwg727 no longer get a bogus follow-up "no function template matches"
error, so that expectation is updated. Also added a regression test
(reduced union/struct cases plus the verbatim reproducer) and a release
note.

LLM tools were used for this contribution. I've reviewed, built, and
tested the change myself before pushing to GitHub.

Added: 
    clang/test/SemaCXX/GH213854.cpp

Modified: 
    clang/docs/ReleaseNotes.md
    clang/lib/Sema/SemaDecl.cpp
    clang/test/CXX/drs/cwg7xx.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index b28898e95d32e..3487c6eff744d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -536,6 +536,10 @@ features cannot lower the translation-unit ABI level;
   parameter that follows a parameter pack (e.g.
   `template <typename... T> S::S(T..., int = 10) {}`).  (#GH216211)
 
+- Fixed an assertion when an ill-formed qualified member function definition
+  inside a union caused the union to be treated as a polymorphic class.
+  (#GH213854)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 0a8b8bb7ec61a..07c6157ab8f31 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -6584,6 +6584,8 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
         if (DC->isRecord())
           return nullptr;
 
+        D.setInvalidType();
+      } else if (CurContext->isRecord() && !CurContext->Equals(DC)) {
         D.setInvalidType();
       }
     }

diff  --git a/clang/test/CXX/drs/cwg7xx.cpp b/clang/test/CXX/drs/cwg7xx.cpp
index ca712333f9410..8dc9e1f292b93 100644
--- a/clang/test/CXX/drs/cwg7xx.cpp
+++ b/clang/test/CXX/drs/cwg7xx.cpp
@@ -138,7 +138,6 @@ namespace cwg727 { // cwg727: partial
       //   expected-note@#cwg727-C {{explicitly specialized declaration is 
here}}
       template<> void A::f<double>();
       // expected-error@-1 {{non-friend class member 'f' cannot have a 
qualified name}}
-      // expected-error@-2 {{no function template matches function template 
specialization 'f'}}
       template<> int A::N<double>;
       // expected-error@-1 {{non-friend class member 'N' cannot have a 
qualified name}}
       // expected-error@-2 {{variable template specialization of 'N' not in 
class 'A' or an enclosing namespace}}

diff  --git a/clang/test/SemaCXX/GH213854.cpp b/clang/test/SemaCXX/GH213854.cpp
new file mode 100644
index 0000000000000..ac15ca5081e32
--- /dev/null
+++ b/clang/test/SemaCXX/GH213854.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+namespace reduced {
+union Union {
+  class A {
+    virtual void foo();
+  };
+  class B : public A {
+  };
+  void B::foo() {} // expected-error {{non-friend class member 'foo' cannot 
have a qualified name}}
+};
+
+static_assert(!__is_polymorphic(Union), "");
+
+void uni(void (*fn)(Union), Union arg1) {
+  fn(arg1);
+}
+
+struct Struct {
+  class A {
+    virtual void foo();
+  };
+  class B : public A {
+  };
+  void B::foo() {} // expected-error {{non-friend class member 'foo' cannot 
have a qualified name}}
+};
+
+static_assert(!__is_polymorphic(Struct), "");
+
+class A {
+  virtual void A::foo() {} // expected-error {{extra qualification on member 
'foo'}}
+};
+
+static_assert(__is_polymorphic(A), "");
+} // namespace reduced
+
+// Verbatim reproducer from GH213854; the missing closing brace is intentional.
+union Union { // expected-note {{to match this '{'}}
+  class A {
+  virtual void foo();
+  };
+  class B : public A {
+  };
+  void B::foo() {} // expected-error {{non-friend class member 'foo' cannot 
have a qualified name}}
+void uni(void (*fn)(union Union), union Union arg1) {
+    fn(arg1);
+}
+// expected-error {{expected '}'}} expected-error@-1 {{expected ';' after 
union}}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to