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
