https://github.com/chinmaydd updated https://github.com/llvm/llvm-project/pull/186497
>From 3d654b03247664e76cbca28e0019638be7cd87f9 Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Fri, 13 Mar 2026 11:42:06 -0700 Subject: [PATCH 1/2] [clang] Skip dllexport of inherited constructors with unsatisfied constraints 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 --- clang/lib/Sema/SemaDeclCXX.cpp | 86 ++++++++++++------- .../CodeGenCXX/dllexport-inherited-ctor.cpp | 57 +++++++++++- .../dllexport-constrained-inherited-ctor.cpp | 24 ++++++ 3 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp 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..a3f5c287f9c8f 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,52 @@ 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 { + ConstrainedBase() requires(!B) = delete; + ConstrainedBase() requires(B) {} + ConstrainedBase(int); +}; + +// B=false: the default ctor with requires(B) is not satisfied, and the +// deleted ctor with requires(!B) is satisfied (but deleted). 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 default constructor should NOT be exported. +// MSVC-NOT: dllexport{{.*}}ConstrainedChild@@QEAA@XZ +// M32-NOT: dllexport{{.*}}ConstrainedChild@@QAE@XZ + +// 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 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..b764f8d922b47 --- /dev/null +++ b/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp @@ -0,0 +1,24 @@ +// 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 + +template <bool B> +struct ConstrainedBase { + ConstrainedBase() requires(!B) = delete; + ConstrainedBase() requires(B) {} + ConstrainedBase(int); +}; + +struct __declspec(dllexport) ConstrainedChild : ConstrainedBase<false> { + using ConstrainedBase::ConstrainedBase; +}; >From d14816d0f3fa8a6cbe358a97826ad7bbb707263f Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Fri, 13 Mar 2026 14:02:13 -0700 Subject: [PATCH 2/2] [Clang] Add tests for non-constructor case --- .../CodeGenCXX/dllexport-inherited-ctor.cpp | 24 +++++++++++++++++++ .../dllexport-constrained-inherited-ctor.cpp | 11 +++++++++ 2 files changed, 35 insertions(+) diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp index a3f5c287f9c8f..e9ee4e464a2cb 100644 --- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -269,6 +269,7 @@ struct __declspec(dllexport) ConstrainedChild : ConstrainedBase<false> { // The constrained default constructor 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> @@ -290,3 +291,26 @@ struct __declspec(dllexport) SelectiveChild : SelectiveBase<char> { // 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) {} + 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 index b764f8d922b47..206a76e77e042 100644 --- a/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp +++ b/clang/test/SemaCXX/dllexport-constrained-inherited-ctor.cpp @@ -22,3 +22,14 @@ struct ConstrainedBase { 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) {} + void bar() {} +}; + +struct __declspec(dllexport) MethodChild : BaseWithConstrainedMethod<int> {}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
