Author: Chinmay Deshpande
Date: 2026-03-15T19:35:04-07:00
New Revision: 696e82db339ce6dc907378bd977ec7857cc892e9

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

LOG: [clang] Skip dllexport of inherited constructors with unsatisfied 
constraints (#186497)

When a class is marked `__declspec(dllexport)`, Clang eagerly creates
inherited constructors via `findInheritingConstructor` and propagates
the dllexport attribute to all members. This bypasses overload
resolution, which would normally filter out constructors whose requires
clause is not satisfied. As a result, Clang attempted to instantiate
constructor bodies that should never be available, causing spurious
compilation errors.

Add constraint satisfaction checks in `checkClassLevelDLLAttribute` to
match MSVC behavior:

1. Before eagerly creating inherited constructors, verify that the base
constructor's `requires` clause is satisfied. Skip creation otherwise.

2. Before applying dllexport to non-inherited methods of class template
specializations, verify constraint satisfaction. This handles the case
where `dllexport` propagates to a base template specialization whose own
members have unsatisfied constraints.

Inherited constructors skip the second check since their constraints
were already verified at creation time.

Fixes #185924

Followup to https://github.com/llvm/llvm-project/pull/182706

Assisted by: Cursor // Claude Opus 4.6

Added: 
    clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp

Modified: 
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2ae6e5de0e3ee..56f315e005320 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6595,10 +6595,21 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl 
*Class) {
     for (Decl *D : Class->decls())
       if (auto *S = dyn_cast<ConstructorUsingShadowDecl>(D))
         Shadows.push_back(S);
-    for (ConstructorUsingShadowDecl *S : Shadows)
-      if (auto *BC = dyn_cast<CXXConstructorDecl>(S->getTargetDecl());
-          BC && !BC->isDeleted())
-        findInheritingConstructor(Class->getLocation(), BC, S);
+    for (ConstructorUsingShadowDecl *S : Shadows) {
+      CXXConstructorDecl *BC = 
dyn_cast<CXXConstructorDecl>(S->getTargetDecl());
+      if (!BC || BC->isDeleted())
+        continue;
+      // Skip constructors whose requires clause is not satisfied.
+      // Normally overload resolution filters these, but we are bypassing
+      // it to eagerly create inherited constructors for dllexport.
+      if (BC->getTrailingRequiresClause()) {
+        ConstraintSatisfaction Satisfaction;
+        if (CheckFunctionConstraints(BC, Satisfaction) ||
+            !Satisfaction.IsSatisfied)
+          continue;
+      }
+      findInheritingConstructor(Class->getLocation(), BC, S);
+    }
   }
 
   // FIXME: MSVC's docs say all bases must be exportable, but this doesn't
@@ -6621,38 +6632,53 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl 
*Class) {
       if (MD->isDeleted())
         continue;
 
-      // Don't export inherited constructors whose parameters prevent ABI-
-      // compatible forwarding. When canEmitDelegateCallArgs (in CodeGen)
-      // returns false, Clang inlines the constructor body instead of
-      // emitting a forwarding thunk, producing code that is not ABI-
-      // compatible with MSVC. Suppress the export and warn so the user
-      // gets a linker error rather than a silent runtime mismatch.
       if (ClassExported) {
-        if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) {
-          if (CD->getInheritedConstructor()) {
-            if (CD->isVariadic()) {
+        CXXConstructorDecl *CD = dyn_cast<CXXConstructorDecl>(MD);
+        if (CD && CD->getInheritedConstructor()) {
+          // Inherited constructors already had their base constructor's
+          // constraints checked before creation via
+          // findInheritingConstructor, so only ABI-compatibility checks
+          // are needed here.
+          //
+          // Don't export inherited constructors whose parameters prevent
+          // ABI-compatible forwarding. When canEmitDelegateCallArgs (in
+          // CodeGen) returns false, Clang inlines the constructor body
+          // instead of emitting a forwarding thunk, producing code that
+          // is not ABI-compatible with MSVC. Suppress the export and warn
+          // so the user gets a linker error rather than a silent runtime
+          // mismatch.
+          if (CD->isVariadic()) {
+            Diag(CD->getLocation(),
+                 diag::warn_dllexport_inherited_ctor_unsupported)
+                << /*variadic=*/0;
+            continue;
+          }
+          if (Context.getTargetInfo()
+                  .getCXXABI()
+                  .areArgsDestroyedLeftToRightInCallee()) {
+            bool HasCalleeCleanupParam = false;
+            for (const ParmVarDecl *P : CD->parameters())
+              if (P->needsDestruction(Context)) {
+                HasCalleeCleanupParam = true;
+                break;
+              }
+            if (HasCalleeCleanupParam) {
               Diag(CD->getLocation(),
                    diag::warn_dllexport_inherited_ctor_unsupported)
-                  << /*variadic=*/0;
+                  << /*callee-cleanup=*/1;
               continue;
             }
-            if (Context.getTargetInfo()
-                    .getCXXABI()
-                    .areArgsDestroyedLeftToRightInCallee()) {
-              bool HasCalleeCleanupParam = false;
-              for (const auto *P : CD->parameters())
-                if (P->needsDestruction(Context)) {
-                  HasCalleeCleanupParam = true;
-                  break;
-                }
-              if (HasCalleeCleanupParam) {
-                Diag(CD->getLocation(),
-                     diag::warn_dllexport_inherited_ctor_unsupported)
-                    << /*callee-cleanup=*/1;
-                continue;
-              }
-            }
           }
+        } else if (MD->getTrailingRequiresClause()) {
+          // Don't export methods whose requires clause is not satisfied.
+          // For class template specializations, member constraints may
+          // depend on template arguments and an unsatisfied constraint
+          // means the member should not be available in this
+          // specialization.
+          ConstraintSatisfaction Satisfaction;
+          if (CheckFunctionConstraints(MD, Satisfaction) ||
+              !Satisfaction.IsSatisfied)
+            continue;
         }
       }
 

diff  --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp 
b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp
index cad081fd7f999..03026f843eda6 100644
--- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp
+++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp
@@ -1,7 +1,7 @@
-// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc 
-emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=MSVC %s
-// RUN: %clang_cc1 -no-enable-noundef-analysis -triple i686-windows-msvc 
-emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=M32 %s
-// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-gnu 
-emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=GNU %s
-// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc 
-emit-llvm -std=c++17 -fms-extensions -fno-dllexport-inlines -O0 -o - %s | 
FileCheck --check-prefix=NOINLINE %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc 
-emit-llvm -std=c++20 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=MSVC %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -triple i686-windows-msvc 
-emit-llvm -std=c++20 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=M32 %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-gnu 
-emit-llvm -std=c++20 -fms-extensions -O0 -o - %s | FileCheck 
--check-prefix=GNU %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc 
-emit-llvm -std=c++20 -fms-extensions -fno-dllexport-inlines -O0 -o - %s | 
FileCheck --check-prefix=NOINLINE %s
 
 // Test that inherited constructors via 'using Base::Base' in a dllexport
 // class are properly exported 
(https://github.com/llvm/llvm-project/issues/162640).
@@ -241,3 +241,77 @@ struct __declspec(dllexport) CalleeCleanupChild : 
CalleeCleanupBase {
 // The implicit default ctor is a regular inline method, NOT an inherited
 // constructor, so -fno-dllexport-inlines correctly suppresses it.
 // NOINLINE-NOT: define {{.*}}dllexport{{.*}} @"??0AllDefChild@@QEAA@XZ"
+
+//===----------------------------------------------------------------------===//
+// Constrained constructors: inherited constructors whose requires clause is
+// not satisfied should not be exported.
+// Regression test for https://github.com/llvm/llvm-project/issues/185924
+//===----------------------------------------------------------------------===//
+
+template <bool B>
+struct ConstrainedBase {
+  struct Enabler {};
+  ConstrainedBase(Enabler) requires(B) {}
+  ConstrainedBase() requires(B) : ConstrainedBase(Enabler{}) {}
+  ConstrainedBase(int);
+};
+
+// B=false: both the default ctor and the Enabler ctor have requires(B) which
+// is not satisfied. Only the inherited ConstrainedChild(int) should be
+// exported.
+struct __declspec(dllexport) ConstrainedChild : ConstrainedBase<false> {
+  using ConstrainedBase::ConstrainedBase;
+};
+
+// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} 
@"??0ConstrainedChild@@QEAA@H@Z"
+// M32-DAG: define weak_odr dso_local dllexport {{.*}} 
@"??0ConstrainedChild@@QAE@H@Z"
+// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} 
@_ZN16ConstrainedChildCI115ConstrainedBaseILb0EEEi(
+
+// The constrained constructors should NOT be exported.
+// MSVC-NOT: dllexport{{.*}}ConstrainedChild@@QEAA@XZ
+// M32-NOT: dllexport{{.*}}ConstrainedChild@@QAE@XZ
+// GNU-NOT: dllexport{{.*}}ConstrainedBaseILb0EEEv
+
+// Constrained non-default constructor: only export when the constraint is met.
+template <typename T>
+struct SelectiveBase {
+  SelectiveBase(int) requires(sizeof(T) > 1) {}
+  SelectiveBase(double);
+};
+
+// sizeof(char)==1, so SelectiveBase(int) requires(sizeof(char)>1) is not
+// satisfied. Only the SelectiveChild(double) constructor should be exported.
+struct __declspec(dllexport) SelectiveChild : SelectiveBase<char> {
+  using SelectiveBase::SelectiveBase;
+};
+
+// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} 
@"??0SelectiveChild@@QEAA@N@Z"
+// M32-DAG: define weak_odr dso_local dllexport {{.*}} 
@"??0SelectiveChild@@QAE@N@Z"
+// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} 
@_ZN14SelectiveChildCI113SelectiveBaseIcEEd(
+
+// The constrained int constructor should NOT be exported.
+// MSVC-NOT: dllexport{{.*}}SelectiveChild@@QEAA@H@Z
+// M32-NOT: dllexport{{.*}}SelectiveChild@@QAE@H@Z
+// GNU-NOT: dllexport{{.*}}SelectiveBaseIcEEi
+
+//===----------------------------------------------------------------------===//
+// Non-constructor constrained method: when dllexport propagates to a base
+// template specialization, methods with unsatisfied constraints should not
+// be exported.
+//===----------------------------------------------------------------------===//
+
+template <typename T>
+struct BaseWithConstrainedMethod {
+  void foo() requires(sizeof(T) > 100) { T::nonexistent(); }
+  void bar() {}
+};
+
+struct __declspec(dllexport) MethodChild : BaseWithConstrainedMethod<int> {};
+
+// bar() should be exported (no constraint).
+// MSVC-DAG: define {{.*}}dllexport {{.*}} 
@"?bar@?$BaseWithConstrainedMethod@H@@QEAAXXZ"
+// M32-DAG: define {{.*}}dllexport {{.*}} 
@"?bar@?$BaseWithConstrainedMethod@H@@QAEXXZ"
+
+// foo() should NOT be exported (constraint not satisfied).
+// MSVC-NOT: dllexport{{.*}}foo@?$BaseWithConstrainedMethod@H
+// M32-NOT: dllexport{{.*}}foo@?$BaseWithConstrainedMethod@H

diff  --git a/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp 
b/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp
new file mode 100644
index 0000000000000..019f0a17bdf1e
--- /dev/null
+++ b/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -fsyntax-only -fms-extensions 
-verify -std=c++20 %s
+// RUN: %clang_cc1 -triple x86_64-windows-gnu  -fsyntax-only -fms-extensions 
-verify -std=c++20 %s
+
+// expected-no-diagnostics
+
+// Regression test for https://github.com/llvm/llvm-project/issues/185924
+// dllexport should not attempt to instantiate inherited constructors whose
+// requires clause is not satisfied.
+//
+// This exercises two paths in checkClassLevelDLLAttribute:
+//   1) findInheritingConstructor must skip constrained-out base ctors
+//   2) dllexport propagated to the base template specialization must not
+//      export members whose requires clause is not satisfied
+//
+// The constructor/method bodies are intentionally ill-formed when the
+// constraint is not satisfied, so that forced instantiation via dllexport
+// would produce an error without the correct fix.
+
+template <bool B>
+struct ConstrainedBase {
+  struct Enabler {};
+  ConstrainedBase(Enabler) requires(B) {}
+  ConstrainedBase() requires(B) : ConstrainedBase(Enabler{}) {}
+  ConstrainedBase(int);
+};
+
+struct __declspec(dllexport) ConstrainedChild : ConstrainedBase<false> {
+  using ConstrainedBase::ConstrainedBase;
+};
+
+// Non-constructor constrained method on a base template specialization.
+// When dllexport propagates to the base, methods whose requires clause
+// is not satisfied must be skipped.
+template <typename T>
+struct BaseWithConstrainedMethod {
+  void foo() requires(sizeof(T) > 100) { T::nonexistent(); }
+  void bar() {}
+};
+
+struct __declspec(dllexport) MethodChild : BaseWithConstrainedMethod<int> {};


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

Reply via email to