[clang] 696e82d - [clang] Skip dllexport of inherited constructors with unsatisfied constraints (#186497)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 15 19:35:09 PDT 2026
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 at 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 at H@Z"
+// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ConstrainedChild@@QAE at H@Z"
+// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN16ConstrainedChildCI115ConstrainedBaseILb0EEEi(
+
+// The constrained constructors should NOT be exported.
+// MSVC-NOT: dllexport{{.*}}ConstrainedChild@@QEAA at XZ
+// M32-NOT: dllexport{{.*}}ConstrainedChild@@QAE at 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 at N@Z"
+// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0SelectiveChild@@QAE at N@Z"
+// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN14SelectiveChildCI113SelectiveBaseIcEEd(
+
+// The constrained int constructor should NOT be exported.
+// MSVC-NOT: dllexport{{.*}}SelectiveChild@@QEAA at H@Z
+// M32-NOT: dllexport{{.*}}SelectiveChild@@QAE at 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 at H@@QEAAXXZ"
+// M32-DAG: define {{.*}}dllexport {{.*}} @"?bar@?$BaseWithConstrainedMethod at H@@QAEXXZ"
+
+// foo() should NOT be exported (constraint not satisfied).
+// MSVC-NOT: dllexport{{.*}}foo@?$BaseWithConstrainedMethod at H
+// M32-NOT: dllexport{{.*}}foo@?$BaseWithConstrainedMethod at 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> {};
More information about the cfe-commits
mailing list