[clang] [clang][Sema] Check exported default constructors in nested classes (PR #214275)

via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 11 21:53:39 PDT 2026


https://github.com/vangthao95 updated https://github.com/llvm/llvm-project/pull/214275

>From e731d1a4d0ba96ef3e638626faca06e43ff0bee2 Mon Sep 17 00:00:00 2001
From: Vang Thao <Vang.Thao at amd.com>
Date: Fri, 24 Jul 2026 18:56:04 -0400
Subject: [PATCH 1/4] [clang][Sema] Check exported default constructors in
 nested classes

Teach Clang to check exported default constructors declared in nested classes
and nested class templates. This ensures their Microsoft ABI constructor
closures are prepared before CodeGen.

Assisted by: GPT-5.6 Sol
---
 clang/lib/Sema/SemaDeclCXX.cpp                     | 14 ++++++++++++++
 .../CodeGenCXX/dllexport-ctor-closure-nested.cpp   | 11 +++++++++++
 clang/test/PCH/dllexport-default-arg-closure.cpp   |  9 +++++++++
 clang/test/SemaCXX/default-arg-closures.cpp        |  9 +++++++++
 clang/test/SemaCXX/dllexport.cpp                   | 14 ++++++++++++++
 5 files changed, 57 insertions(+)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 47b01b913b428..cb1b6daa022da 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6387,8 +6387,22 @@ static void checkForMultipleExportedDefaultConstructors(Sema &S,
   if (!S.Context.getTargetInfo().getCXXABI().isMicrosoft())
     return;
 
+  if (Class->isInvalidDecl())
+    return;
+
   CXXConstructorDecl *LastExportedDefaultCtor = nullptr;
   for (Decl *Member : Class->decls()) {
+    // Nested classes finish delayed default argument parsing with the outermost
+    // class, so check each nested definition and class template pattern here.
+    CXXRecordDecl *NestedClass = dyn_cast<CXXRecordDecl>(Member);
+    if (auto *NestedTemplate = dyn_cast<ClassTemplateDecl>(Member))
+      NestedClass = NestedTemplate->getTemplatedDecl();
+    if (NestedClass) {
+      if (NestedClass->isThisDeclarationADefinition())
+        checkForMultipleExportedDefaultConstructors(S, NestedClass);
+      continue;
+    }
+
     // Look for exported default constructors.
     auto *CD = dyn_cast<CXXConstructorDecl>(Member);
     if (!CD || !CD->isDefaultConstructor())
diff --git a/clang/test/CodeGenCXX/dllexport-ctor-closure-nested.cpp b/clang/test/CodeGenCXX/dllexport-ctor-closure-nested.cpp
index 36dab8600e21b..e949bc82686c7 100644
--- a/clang/test/CodeGenCXX/dllexport-ctor-closure-nested.cpp
+++ b/clang/test/CodeGenCXX/dllexport-ctor-closure-nested.cpp
@@ -18,3 +18,14 @@ struct __declspec(dllexport) CtorClosureOuter {
 
 // CHECK-LABEL: $"??1HasImplicitDtor1@@QAE at XZ" = comdat any
 // CHECK-LABEL: define weak_odr dso_local dllexport x86_thiscallcc void @"??_FCtorClosureInner at CtorClosureOuter@@QAEXXZ"({{.*}}) {{#[0-9]+}} comdat
+
+// Member-level dllexport on a nested default constructor needs constructor
+// closure default arguments before the enclosing class is emitted.
+struct MemberExportedCtorClosureOuter {
+  struct MemberExportedCtorClosureInner {
+    __declspec(dllexport) MemberExportedCtorClosureInner(
+        const HasImplicitDtor1 &v = {}) {}
+  };
+};
+
+// CHECK-LABEL: define weak_odr dso_local dllexport x86_thiscallcc void @"??_FMemberExportedCtorClosureInner at MemberExportedCtorClosureOuter@@QAEXXZ"({{.*}}) {{#[0-9]+}} comdat
diff --git a/clang/test/PCH/dllexport-default-arg-closure.cpp b/clang/test/PCH/dllexport-default-arg-closure.cpp
index b5ac7815b8111..b56d166e8d9f3 100644
--- a/clang/test/PCH/dllexport-default-arg-closure.cpp
+++ b/clang/test/PCH/dllexport-default-arg-closure.cpp
@@ -20,6 +20,15 @@ struct __declspec(dllexport) Foo {
 // CHECK: define weak_odr dso_local dllexport void @"??_FFoo@@QEAAXXZ"(ptr{{.*}})
 // CHECK:   call noundef ptr @"??0Foo@@QEAA at W4E@0@@Z"(ptr {{.*}}, i32 noundef 0)
 
+struct PCHMemberExportOuter {
+  struct Inner {
+    enum E { E0 } e;
+    __declspec(dllexport) Inner(E e = E0) : e(e) {}
+  };
+};
+
+// CHECK: define weak_odr dso_local dllexport void @"??_FInner at PCHMemberExportOuter@@QEAAXXZ"(ptr{{.*}})
+
 #else
 
 
diff --git a/clang/test/SemaCXX/default-arg-closures.cpp b/clang/test/SemaCXX/default-arg-closures.cpp
index 676bd486105f9..86c44f0ead9f7 100644
--- a/clang/test/SemaCXX/default-arg-closures.cpp
+++ b/clang/test/SemaCXX/default-arg-closures.cpp
@@ -25,6 +25,15 @@ struct DependentDefaultCopyArg {
 struct HasMember {
   enum { member = 0 };
 };
+
+template <typename T>
+struct NestedDependentDefaultCtorArg {
+  struct Inner {
+    __declspec(dllexport) Inner(int n = T::member) {}
+  };
+};
+NestedDependentDefaultCtorArg<HasMember>::Inner ValidNestedDependentArg;
+
 void UseDependentArg() { throw DependentDefaultCopyArg<HasMember>(); }
 
 void ErrorInDependentArg() {
diff --git a/clang/test/SemaCXX/dllexport.cpp b/clang/test/SemaCXX/dllexport.cpp
index d625b15557762..262612db5835d 100644
--- a/clang/test/SemaCXX/dllexport.cpp
+++ b/clang/test/SemaCXX/dllexport.cpp
@@ -746,6 +746,20 @@ struct ClassTemplateWithMultipleDefaultCtors {
   __declspec(dllexport) ClassTemplateWithMultipleDefaultCtors(int = 30, ...) {} // ms-note{{declared here}}
 };
 
+struct ClassWithNestedMultipleDefaultCtors {
+  struct Nested {
+    __declspec(dllexport) Nested(int = 40) {}      // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
+    __declspec(dllexport) Nested(int = 30, ...) {} // ms-note{{declared here}}
+  };
+};
+
+struct ClassWithNestedClassTemplateMultipleDefaultCtors {
+  template <typename T> struct Nested {
+    __declspec(dllexport) Nested(int = 40) {}      // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
+    __declspec(dllexport) Nested(int = 30, ...) {} // ms-note{{declared here}}
+  };
+};
+
 template <typename T> struct HasDefaults {
   HasDefaults(int x = sizeof(T)) {} // ms-error {{invalid application of 'sizeof'}}
 };

>From 38a33809e626718afeaf6baea23be9f289661f19 Mon Sep 17 00:00:00 2001
From: Vang Thao <Vang.Thao at amd.com>
Date: Thu, 6 Aug 2026 19:57:12 -0400
Subject: [PATCH 2/4] Add nested template test

---
 clang/test/SemaCXX/dllexport.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/clang/test/SemaCXX/dllexport.cpp b/clang/test/SemaCXX/dllexport.cpp
index 262612db5835d..ed38c7de9cf5f 100644
--- a/clang/test/SemaCXX/dllexport.cpp
+++ b/clang/test/SemaCXX/dllexport.cpp
@@ -759,6 +759,19 @@ struct ClassWithNestedClassTemplateMultipleDefaultCtors {
     __declspec(dllexport) Nested(int = 30, ...) {} // ms-note{{declared here}}
   };
 };
+template struct ClassWithNestedClassTemplateMultipleDefaultCtors::Nested<int>;
+template struct ClassWithNestedClassTemplateMultipleDefaultCtors::Nested<double>;
+
+template <typename T>
+struct ClassTemplateWithNestedClassTemplateMultipleDefaultCtors {
+  __declspec(dllexport)
+  ClassTemplateWithNestedClassTemplateMultipleDefaultCtors(int = 40) {}
+  template <typename R> struct Nested {
+    __declspec(dllexport) Nested(int = 30) {}      // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
+    __declspec(dllexport) Nested(int = 20, ...) {} // ms-note{{declared here}}
+  };
+};
+template struct ClassTemplateWithNestedClassTemplateMultipleDefaultCtors<int>;
 
 template <typename T> struct HasDefaults {
   HasDefaults(int x = sizeof(T)) {} // ms-error {{invalid application of 'sizeof'}}

>From dbbbfae740791ceee4b9297b74b3b5355da8e9c5 Mon Sep 17 00:00:00 2001
From: Vang Thao <Vang.Thao at amd.com>
Date: Fri, 7 Aug 2026 19:39:22 -0400
Subject: [PATCH 3/4] Remove template handling

---
 clang/lib/Sema/SemaDeclCXX.cpp   |  7 ++-----
 clang/test/SemaCXX/dllexport.cpp | 20 --------------------
 2 files changed, 2 insertions(+), 25 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index cb1b6daa022da..6eb03bf572a0a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6393,11 +6393,8 @@ static void checkForMultipleExportedDefaultConstructors(Sema &S,
   CXXConstructorDecl *LastExportedDefaultCtor = nullptr;
   for (Decl *Member : Class->decls()) {
     // Nested classes finish delayed default argument parsing with the outermost
-    // class, so check each nested definition and class template pattern here.
-    CXXRecordDecl *NestedClass = dyn_cast<CXXRecordDecl>(Member);
-    if (auto *NestedTemplate = dyn_cast<ClassTemplateDecl>(Member))
-      NestedClass = NestedTemplate->getTemplatedDecl();
-    if (NestedClass) {
+    // class, so check each nested definition here.
+    if (auto *NestedClass = dyn_cast<CXXRecordDecl>(Member)) {
       if (NestedClass->isThisDeclarationADefinition())
         checkForMultipleExportedDefaultConstructors(S, NestedClass);
       continue;
diff --git a/clang/test/SemaCXX/dllexport.cpp b/clang/test/SemaCXX/dllexport.cpp
index ed38c7de9cf5f..6a1441be50bc5 100644
--- a/clang/test/SemaCXX/dllexport.cpp
+++ b/clang/test/SemaCXX/dllexport.cpp
@@ -753,26 +753,6 @@ struct ClassWithNestedMultipleDefaultCtors {
   };
 };
 
-struct ClassWithNestedClassTemplateMultipleDefaultCtors {
-  template <typename T> struct Nested {
-    __declspec(dllexport) Nested(int = 40) {}      // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
-    __declspec(dllexport) Nested(int = 30, ...) {} // ms-note{{declared here}}
-  };
-};
-template struct ClassWithNestedClassTemplateMultipleDefaultCtors::Nested<int>;
-template struct ClassWithNestedClassTemplateMultipleDefaultCtors::Nested<double>;
-
-template <typename T>
-struct ClassTemplateWithNestedClassTemplateMultipleDefaultCtors {
-  __declspec(dllexport)
-  ClassTemplateWithNestedClassTemplateMultipleDefaultCtors(int = 40) {}
-  template <typename R> struct Nested {
-    __declspec(dllexport) Nested(int = 30) {}      // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
-    __declspec(dllexport) Nested(int = 20, ...) {} // ms-note{{declared here}}
-  };
-};
-template struct ClassTemplateWithNestedClassTemplateMultipleDefaultCtors<int>;
-
 template <typename T> struct HasDefaults {
   HasDefaults(int x = sizeof(T)) {} // ms-error {{invalid application of 'sizeof'}}
 };

>From 0765f0e6ef8284242a8cb649aaf78ce92419f015 Mon Sep 17 00:00:00 2001
From: Vang Thao <Vang.Thao at amd.com>
Date: Wed, 12 Aug 2026 00:27:40 -0400
Subject: [PATCH 4/4] Add test with no delayed constructor

---
 clang/test/SemaCXX/dllexport.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/test/SemaCXX/dllexport.cpp b/clang/test/SemaCXX/dllexport.cpp
index 6a1441be50bc5..70e7f1398ad05 100644
--- a/clang/test/SemaCXX/dllexport.cpp
+++ b/clang/test/SemaCXX/dllexport.cpp
@@ -753,6 +753,13 @@ struct ClassWithNestedMultipleDefaultCtors {
   };
 };
 
+struct ClassWithNestedObviousMultipleDefaultCtors {
+  struct Nested {
+    __declspec(dllexport) Nested() {}    // ms-error{{'__declspec(dllexport)' cannot be applied to more than one default constructor}}
+    __declspec(dllexport) Nested(...) {} // ms-note{{declared here}}
+  };
+};
+
 template <typename T> struct HasDefaults {
   HasDefaults(int x = sizeof(T)) {} // ms-error {{invalid application of 'sizeof'}}
 };



More information about the cfe-commits mailing list