Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12854,6 +12854,17 @@ 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 warn_meaningless_export : Warning<
"%select{an explicit instantiation|a specialization}0 cannot be "
"marked 'export'">,
InGroup<DiagGroup<"meaningless-export">>;
def note_meaningless_export_explanation : Note<
"it is exported if the primary template is exported">;
def warn_invalid_decl_in_linkage_spec : Warning<
"language linkage cannot be specified for "
"%select{an explicit instantiation|a specialization|an export declaration}0">,
InGroup<DiagGroup<"meaningless-language-linkage">>;

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;
}
65 changes: 62 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,62 @@ Decl *Parser::ParseExportDeclaration() {
T.getCloseLocation());
}

void Parser::CheckUnbracedLinkageOrExportDeclaration(
Decl *LinkageOrExportDecl) {
const auto *DC = cast<DeclContext>(LinkageOrExportDecl);
if (DC->decls_empty())
return;

const Decl *D = *DC->decls_begin();

// Nested export declarations are diagnosed elsewhere.
if (isa<LinkageSpecDecl>(LinkageOrExportDecl) && isa<ExportDecl>(D)) {
Diag(LinkageOrExportDecl->getLocation(),
diag::warn_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::warn_meaningless_export)
<< (TSK == TSK_ExplicitSpecialization)
<< FixItHint::CreateRemoval(ED->getExportLoc());
Comment thread
localspook marked this conversation as 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 (getLangOpts().MicrosoftExt && LinkageOrExportDecl->isInStdNamespace() &&
getPreprocessor().NeedsMsvcStlWorkaroundBefore(2026'03))
return;

Diag(LS->getLocation(), diag::warn_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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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-warning@-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