Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ C++ Specific Potentially Breaking Changes
- Clang now correctly rejects ``export`` declarations in module implementation
partitions. (#GH107602)

- Clang now correctly rejects explicit instantiations and specializations
marked with ``export`` or a language linkage specification, completing
its implementation of P2615. (#GH160016)

ABI Changes in This Version
---------------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -3538,6 +3538,10 @@ class ExplicitInstantiationDecl final
static bool classofKind(Kind K) { return K == ExplicitInstantiation; }
};

/// Determine what kind of template specialization the given declaration
/// is.
TemplateSpecializationKind getTemplateSpecializationKind(const Decl *D);

} // namespace clang

#endif // LLVM_CLANG_AST_DECLTEMPLATE_H
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12854,6 +12854,15 @@ def err_invalid_module_name : Error<"%0 is an invalid name for a module">;
def err_extern_def_in_header_unit : Error<
"non-inline external definitions are not permitted in C++ header units">;

def err_meaningless_export : Error<
Comment thread
localspook marked this conversation as resolved.
Resolved
"%select{an explicit instantiation|a specialization}0 cannot be "
"marked 'export'">;
def note_meaningless_export_explanation : Note<
"it is exported if the primary template is exported">;
def err_invalid_decl_in_linkage_spec : Error<
"language linkage cannot be specified for "
"%select{an explicit instantiation|a specialization|an export declaration}0">;

def warn_exposure : Warning <
"TU local entity %0 is exposed">,
InGroup<DiagGroup<"TU-local-entity-exposure">>;
Expand Down
26 changes: 23 additions & 3 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ enum class EmbedResult {
};

struct CXXStandardLibraryVersionInfo {
enum Library { Unknown, LibStdCXX };
enum Library { Unknown, LibStdCXX, MsvcStl };
Library Lib;
std::uint64_t Version;
};
Expand Down Expand Up @@ -2823,9 +2823,29 @@ class Preprocessor {
// Standard Library Identification
std::optional<CXXStandardLibraryVersionInfo> CXXStandardLibraryVersion;

void ComputeCXXStandardLibraryVersion();

bool NeedsCXXStandardLibraryWorkaroundBefore(
uint64_t FixedVersion, CXXStandardLibraryVersionInfo::Library Lib) {
ComputeCXXStandardLibraryVersion();
return CXXStandardLibraryVersion && CXXStandardLibraryVersion->Lib == Lib &&
CXXStandardLibraryVersion->Version < FixedVersion;
}
Comment on lines +2826 to +2833

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need these logics in this patch...


public:
std::optional<std::uint64_t> getStdLibCxxVersion();
bool NeedsStdLibCxxWorkaroundBefore(std::uint64_t FixedVersion);
bool NeedsStdLibCxxWorkaroundBefore(std::uint64_t FixedVersion) {
assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
"invalid value for __GLIBCXX__");
return NeedsCXXStandardLibraryWorkaroundBefore(
FixedVersion, CXXStandardLibraryVersionInfo::LibStdCXX);
}

bool NeedsMsvcStlWorkaroundBefore(std::uint64_t FixedVersion) {
assert(FixedVersion >= 2000'00 && FixedVersion <= 2100'00 &&
"invalid value for _MSVC_STL_UPDATE");
return NeedsCXXStandardLibraryWorkaroundBefore(
FixedVersion, CXXStandardLibraryVersionInfo::MsvcStl);
}

private:
//===--------------------------------------------------------------------===//
Expand Down
27 changes: 25 additions & 2 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -3192,7 +3192,7 @@ class Parser : public CodeCompletionHandler {
/// \verbatim
/// linkage-specification: [C++ 7.5p2: dcl.link]
/// 'extern' string-literal '{' declaration-seq[opt] '}'
/// 'extern' string-literal declaration
/// 'extern' string-literal name-declaration
/// \endverbatim
///
Decl *ParseLinkage(ParsingDeclSpec &DS, DeclaratorContext Context);
Expand All @@ -3201,7 +3201,7 @@ class Parser : public CodeCompletionHandler {
///
/// \verbatim
/// export-declaration:
/// 'export' declaration
/// 'export' name-declaration
/// 'export' '{' declaration-seq[opt] '}'
/// \endverbatim
///
Expand All @@ -3217,6 +3217,29 @@ class Parser : public CodeCompletionHandler {
///
Decl *ParseExportDeclaration();

/// Ensure the declaration in an unbraced linkage-specification or
/// export-declaration is not an explicit-instantiation,
/// explicit-specialization, or export-declaration:
///
/// \verbatim
/// export-declaration: [C++: module.interface]
/// export name-declaration
///
/// linkage-specification: [C++: dcl.link]
/// export name-declaration
///
/// declaration: [C++: dcl.pre]
/// name-declaration
/// special-declaration
///
/// special-declaration: [C++: dcl.pre]
/// explicit-instantiation
/// explicit-specialization
/// export-declaration
/// \endverbatim
///
void CheckUnbracedLinkageOrExportDeclaration(Decl *LinkageOrExportDecl);

/// ParseUsingDirectiveOrDeclaration - Parse C++ using using-declaration or
/// using-directive. Assumes that current token is 'using'.
DeclGroupPtrTy ParseUsingDirectiveOrDeclaration(
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/AST/DeclTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,3 +1913,19 @@ SourceRange ExplicitInstantiationDecl::getSourceRange() const {
SourceLocation Begin = ExternLoc.isValid() ? ExternLoc : getLocation();
return SourceRange(Begin, getEndLoc());
}

TemplateSpecializationKind clang::getTemplateSpecializationKind(const Decl *D) {
if (!D)
return TSK_Undeclared;

if (const auto *Record = dyn_cast<CXXRecordDecl>(D))
return Record->getTemplateSpecializationKind();
if (const auto *Function = dyn_cast<FunctionDecl>(D))
return Function->getTemplateSpecializationKind();
if (const auto *Var = dyn_cast<VarDecl>(D))
return Var->getTemplateSpecializationKind();
if (const auto *EID = dyn_cast<ExplicitInstantiationDecl>(D))
return EID->getTemplateSpecializationKind();

return TSK_Undeclared;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Slightly odd to return TSK_Undeclared for declaration unrelated to templates.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but it's consistent with all the node-specific getTemplateSpecializationKind methods.

}
34 changes: 14 additions & 20 deletions clang/lib/Lex/PPExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,24 +1005,18 @@ getCXXStandardLibraryVersion(Preprocessor &PP, StringRef MacroName,
0};
}

std::optional<uint64_t> Preprocessor::getStdLibCxxVersion() {
if (!CXXStandardLibraryVersion)
CXXStandardLibraryVersion = getCXXStandardLibraryVersion(
*this, "__GLIBCXX__", CXXStandardLibraryVersionInfo::LibStdCXX);
if (!CXXStandardLibraryVersion)
return std::nullopt;

if (CXXStandardLibraryVersion->Lib ==
CXXStandardLibraryVersionInfo::LibStdCXX)
return CXXStandardLibraryVersion->Version;
return std::nullopt;
}

bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(uint64_t FixedVersion) {
assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
"invalid value for __GLIBCXX__");
std::optional<std::uint64_t> Ver = getStdLibCxxVersion();
if (!Ver)
return false;
return *Ver < FixedVersion;
void Preprocessor::ComputeCXXStandardLibraryVersion() {
if (CXXStandardLibraryVersion)
return;

static constexpr std::pair<StringRef, CXXStandardLibraryVersionInfo::Library>
VersionMacros[] = {
{"__GLIBCXX__", CXXStandardLibraryVersionInfo::LibStdCXX},
{"_MSVC_STL_UPDATE", CXXStandardLibraryVersionInfo::MsvcStl},
};

for (const auto &[MacroName, Lib] : VersionMacros)
if (std::optional<CXXStandardLibraryVersionInfo> VersionInfo =
getCXXStandardLibraryVersion(*this, MacroName, Lib))
CXXStandardLibraryVersion = VersionInfo;
}
63 changes: 60 additions & 3 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,11 @@ Decl *Parser::ParseLinkage(ParsingDeclSpec &DS, DeclaratorContext Context) {
// ... but anyway remember that such an "extern" was seen.
DS.setExternInLinkageSpec(true);
ParseExternalDeclaration(DeclAttrs, DeclSpecAttrs, &DS);
return LinkageSpec ? Actions.ActOnFinishLinkageSpecification(
getCurScope(), LinkageSpec, SourceLocation())
: nullptr;
if (!LinkageSpec)
return nullptr;
CheckUnbracedLinkageOrExportDeclaration(LinkageSpec);
return Actions.ActOnFinishLinkageSpecification(getCurScope(), LinkageSpec,
SourceLocation());
}

DS.abort();
Expand Down Expand Up @@ -420,6 +422,7 @@ Decl *Parser::ParseExportDeclaration() {
MaybeParseCXX11Attributes(DeclAttrs);
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
ParseExternalDeclaration(DeclAttrs, EmptyDeclSpecAttrs);
CheckUnbracedLinkageOrExportDeclaration(ExportDecl);
return Actions.ActOnFinishExportDecl(getCurScope(), ExportDecl,
SourceLocation());
}
Expand All @@ -440,6 +443,60 @@ Decl *Parser::ParseExportDeclaration() {
T.getCloseLocation());
}

void Parser::CheckUnbracedLinkageOrExportDeclaration(
Decl *LinkageOrExportDecl) {
const auto *DC = cast<DeclContext>(LinkageOrExportDecl);
assert(!DC->decls_empty());
const Decl *D = *DC->decls_begin();

// Nested export declarations are diagnosed elsewhere.
if (isa<LinkageSpecDecl>(LinkageOrExportDecl) && isa<ExportDecl>(D)) {
Diag(LinkageOrExportDecl->getLocation(),
diag::err_invalid_decl_in_linkage_spec)
<< /*export declaration*/ 2;
return;
}

// [module.interface]/1 says:
//
// The name-declaration of an export-declaration shall not declare a
// partial specialization.
//
// But there's no equivalent wording for linkage-specification.
if (isa<ClassTemplatePartialSpecializationDecl,
VarTemplatePartialSpecializationDecl>(D) &&
isa<LinkageSpecDecl>(LinkageOrExportDecl))
return;

TemplateSpecializationKind TSK = getTemplateSpecializationKind(D);
if (TSK == TSK_Undeclared)
return;

if (const auto *ED = dyn_cast<ExportDecl>(LinkageOrExportDecl)) {
Diag(ED->getExportLoc(), diag::err_meaningless_export)
<< (TSK == TSK_ExplicitSpecialization)
<< FixItHint::CreateRemoval(ED->getExportLoc());
Comment thread
localspook marked this conversation as resolved.
Resolved
Diag(ED->getExportLoc(), diag::note_meaningless_export_explanation);
return;
}

if (const auto *LS = dyn_cast<LinkageSpecDecl>(LinkageOrExportDecl)) {
// Old versions of the MSVC STL used to have linkage specifications
// on some template specializations, but it would be too disruptive to
// reject them. This was fixed in
// https://github.com/microsoft/STL/pull/6074, merged on 2026-02-11.
if (LinkageOrExportDecl->isInStdNamespace() &&
getPreprocessor().NeedsMsvcStlWorkaroundBefore(2026'03))
return;

Diag(LS->getLocation(), diag::err_invalid_decl_in_linkage_spec)
<< (TSK == TSK_ExplicitSpecialization);
return;
}

llvm_unreachable("Expected either an ExportDecl or a LinkageSpecDecl");
}

Parser::DeclGroupPtrTy Parser::ParseUsingDirectiveOrDeclaration(
DeclaratorContext Context, const ParsedTemplateInfo &TemplateInfo,
SourceLocation &DeclEnd, ParsedAttributes &Attrs) {
Expand Down
18 changes: 0 additions & 18 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4155,8 +4155,6 @@ static bool CheckTemplateSpecializationScope(Sema &S, NamedDecl *Specialized,
SourceLocation Loc,
bool IsPartialSpecialization);

static TemplateSpecializationKind getTemplateSpecializationKind(Decl *D);

static bool isTemplateArgumentTemplateParameter(const TemplateArgument &Arg,
unsigned Depth,
unsigned Index) {
Expand Down Expand Up @@ -8504,22 +8502,6 @@ Sema::CheckTemplateDeclScope(Scope *S, TemplateParameterList *TemplateParams) {
<< TemplateParams->getSourceRange();
}

/// Determine what kind of template specialization the given declaration
/// is.
static TemplateSpecializationKind getTemplateSpecializationKind(Decl *D) {
if (!D)
return TSK_Undeclared;

if (CXXRecordDecl *Record = dyn_cast<CXXRecordDecl>(D))
return Record->getTemplateSpecializationKind();
if (FunctionDecl *Function = dyn_cast<FunctionDecl>(D))
return Function->getTemplateSpecializationKind();
if (VarDecl *Var = dyn_cast<VarDecl>(D))
return Var->getTemplateSpecializationKind();

return TSK_Undeclared;
}

/// Check whether a specialization is well-formed in the current
/// context.
///
Expand Down
66 changes: 66 additions & 0 deletions clang/test/CXX/drs/cwg2443.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %clang_cc1 -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify -verify-directives
// RUN: %clang_cc1 -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify -verify-directives
// RUN: %clang_cc1 -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify -verify-directives

export module foo;

namespace cwg2443 { // cwg2443: 23

export template <typename T> class s1 {};
export template <typename T> class s1<T *> {};
// expected-error@-1 {{a specialization cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export template <> class s1<int> {};
// expected-error@-1 {{a specialization cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export template class s1<char>;
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export extern template class s1<void>;
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}

export template <typename T> int v1 = 0;
export template <typename T> int v1<T *> = 0;
// expected-error@-1 {{a specialization cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export template <> int v1<int> = 0;
// expected-error@-1 {{a specialization cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export template int v1<char>;
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export extern template int v1<void>;
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}

export template <typename T> void f1() {}
export template <> void f1<int>() {}
// expected-error@-1 {{a specialization cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export template void f1<char>();
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}
export extern template void f1<void>();
// expected-error@-1 {{an explicit instantiation cannot be marked 'export'}}
// expected-note@-2 {{it is exported if the primary template is exported}}


export { template <typename T> class s2 {}; }
export { template <typename T> class s2<T *> {}; }
export { template <> class s2<int> {}; }
export { template class s2<char>; }
export { extern template class s2<void>; }

export { template <typename T> int v2 = 0; }
export { template <typename T> int v2<T *> = 0; }
export { template <> int v2<int> = 0; }
export { template int v2<char>; }
export { extern template int v2<void>; }

export { template <typename T> void f2() {} }
export { template <> void f2<int>() {} }
export { template void f2<char>(); }
export { extern template void f2<void>(); }

} // namespace cwg2443
3 changes: 3 additions & 0 deletions clang/test/CXX/drs/cwg24xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ struct S {
};
} // namespace cwg2430


// cwg2443 is in cwg2443.cpp

namespace cwg2450 { // cwg2450: 18
#if __cplusplus >= 202302L
struct S {int a;};
Expand Down
Loading
Loading