1 //===--- UseInternalLinkageCheck.cpp - clang-tidy--------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "UseInternalLinkageCheck.h"
10 #include "../utils/FileExtensionsUtils.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/ASTMatchers/ASTMatchersMacros.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "clang/Basic/Specifiers.h"
17 #include "clang/Lex/Token.h"
18 #include "llvm/ADT/DenseSet.h"
19 #include "llvm/ADT/STLExtras.h"
20 #include "llvm/ADT/SmallVector.h"
22 using namespace clang::ast_matchers
;
24 namespace clang::tidy
{
27 struct OptionEnumMapping
<misc::UseInternalLinkageCheck::FixModeKind
> {
28 static llvm::ArrayRef
<
29 std::pair
<misc::UseInternalLinkageCheck::FixModeKind
, StringRef
>>
31 static constexpr std::pair
<misc::UseInternalLinkageCheck::FixModeKind
,
34 {misc::UseInternalLinkageCheck::FixModeKind::None
, "None"},
35 {misc::UseInternalLinkageCheck::FixModeKind::UseStatic
,
42 } // namespace clang::tidy
44 namespace clang::tidy::misc
{
48 AST_MATCHER(Decl
, isFirstDecl
) { return Node
.isFirstDecl(); }
50 AST_MATCHER(FunctionDecl
, hasBody
) { return Node
.hasBody(); }
52 static bool isInMainFile(SourceLocation L
, SourceManager
&SM
,
53 const FileExtensionsSet
&HeaderFileExtensions
) {
55 if (utils::isSpellingLocInHeaderFile(L
, SM
, HeaderFileExtensions
))
57 if (SM
.isInMainFile(L
))
59 // not in header file but not in main file
60 L
= SM
.getIncludeLoc(SM
.getFileID(L
));
63 // Conservative about the unknown
68 AST_MATCHER_P(Decl
, isAllRedeclsInMainFile
, FileExtensionsSet
,
69 HeaderFileExtensions
) {
70 return llvm::all_of(Node
.redecls(), [&](const Decl
*D
) {
71 return isInMainFile(D
->getLocation(),
72 Finder
->getASTContext().getSourceManager(),
73 HeaderFileExtensions
);
77 AST_POLYMORPHIC_MATCHER(isExternStorageClass
,
78 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl
,
80 return Node
.getStorageClass() == SC_Extern
;
83 AST_MATCHER(FunctionDecl
, isAllocationOrDeallocationOverloadedFunction
) {
84 // [basic.stc.dynamic.allocation]
85 // An allocation function that is not a class member function shall belong to
86 // the global scope and not have a name with internal linkage.
87 // [basic.stc.dynamic.deallocation]
88 // A deallocation function that is not a class member function shall belong to
89 // the global scope and not have a name with internal linkage.
90 static const llvm::DenseSet
<OverloadedOperatorKind
> OverloadedOperators
{
91 OverloadedOperatorKind::OO_New
,
92 OverloadedOperatorKind::OO_Array_New
,
93 OverloadedOperatorKind::OO_Delete
,
94 OverloadedOperatorKind::OO_Array_Delete
,
96 return OverloadedOperators
.contains(Node
.getOverloadedOperator());
101 UseInternalLinkageCheck::UseInternalLinkageCheck(StringRef Name
,
102 ClangTidyContext
*Context
)
103 : ClangTidyCheck(Name
, Context
),
104 HeaderFileExtensions(Context
->getHeaderFileExtensions()),
105 FixMode(Options
.get("FixMode", FixModeKind::UseStatic
)) {}
107 void UseInternalLinkageCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
108 Options
.store(Opts
, "FixMode", FixMode
);
111 void UseInternalLinkageCheck::registerMatchers(MatchFinder
*Finder
) {
113 allOf(isFirstDecl(), isAllRedeclsInMainFile(HeaderFileExtensions
),
115 // 1. internal linkage
116 isStaticStorageClass(), isInAnonymousNamespace(),
117 // 2. explicit external linkage
118 isExternStorageClass(), isExternC(),
120 isExplicitTemplateSpecialization(),
121 hasAncestor(decl(anyOf(
124 // 5. module export decl
127 functionDecl(Common
, hasBody(),
128 unless(anyOf(cxxMethodDecl(),
129 isAllocationOrDeallocationOverloadedFunction(),
133 Finder
->addMatcher(varDecl(Common
, hasGlobalStorage()).bind("var"), this);
136 static constexpr StringRef Message
=
137 "%0 %1 can be made static or moved into an anonymous namespace "
138 "to enforce internal linkage";
140 void UseInternalLinkageCheck::check(const MatchFinder::MatchResult
&Result
) {
141 if (const auto *FD
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("fn")) {
142 DiagnosticBuilder DB
= diag(FD
->getLocation(), Message
) << "function" << FD
;
143 const SourceLocation FixLoc
= FD
->getInnerLocStart();
144 if (FixLoc
.isInvalid() || FixLoc
.isMacroID())
146 if (FixMode
== FixModeKind::UseStatic
)
147 DB
<< FixItHint::CreateInsertion(FixLoc
, "static ");
150 if (const auto *VD
= Result
.Nodes
.getNodeAs
<VarDecl
>("var")) {
151 // In C++, const variables at file scope have implicit internal linkage,
152 // so we should not warn there. This is not the case in C.
153 // https://eel.is/c++draft/diff#basic-3
154 if (getLangOpts().CPlusPlus
&& VD
->getType().isConstQualified())
157 DiagnosticBuilder DB
= diag(VD
->getLocation(), Message
) << "variable" << VD
;
158 const SourceLocation FixLoc
= VD
->getInnerLocStart();
159 if (FixLoc
.isInvalid() || FixLoc
.isMacroID())
161 if (FixMode
== FixModeKind::UseStatic
)
162 DB
<< FixItHint::CreateInsertion(FixLoc
, "static ");
165 llvm_unreachable("");
168 } // namespace clang::tidy::misc