clang-tools 22.0.0git
UseInternalLinkageCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
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"
21
22using namespace clang::ast_matchers;
23
24namespace clang::tidy {
25
26template <>
27struct OptionEnumMapping<misc::UseInternalLinkageCheck::FixModeKind> {
28 static llvm::ArrayRef<
29 std::pair<misc::UseInternalLinkageCheck::FixModeKind, StringRef>>
31 static constexpr std::pair<misc::UseInternalLinkageCheck::FixModeKind,
32 StringRef>
33 Mapping[] = {
36 "UseStatic"},
37 };
38 return {Mapping};
39 }
40};
41
42} // namespace clang::tidy
43
44namespace clang::tidy::misc {
45
46static bool isInMainFile(SourceLocation L, SourceManager &SM,
47 const FileExtensionsSet &HeaderFileExtensions) {
48 for (;;) {
49 if (utils::isExpansionLocInHeaderFile(L, SM, HeaderFileExtensions))
50 return false;
51 if (SM.isInMainFile(L))
52 return true;
53 // not in header file but not in main file
54 L = SM.getIncludeLoc(SM.getFileID(L));
55 if (L.isValid())
56 continue;
57 // Conservative about the unknown
58 return false;
59 }
60}
61
62namespace {
63
64AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }
65
66AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }
67
68AST_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);
74 });
75}
76
77AST_POLYMORPHIC_MATCHER(isExternStorageClass,
78 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
79 VarDecl)) {
80 return Node.getStorageClass() == SC_Extern;
81}
82
83AST_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,
95 };
96 return OverloadedOperators.contains(Node.getOverloadedOperator());
97}
98
99AST_POLYMORPHIC_MATCHER(isExplicitlyExternC,
100 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
101 VarDecl)) {
102 return Finder->getASTContext().getLangOpts().CPlusPlus && Node.isExternC();
103}
104
105AST_MATCHER(TagDecl, hasNameForLinkage) { return Node.hasNameForLinkage(); }
106
107AST_MATCHER(CXXRecordDecl, isExplicitTemplateInstantiation) {
108 return Node.getTemplateSpecializationKind() ==
109 TSK_ExplicitInstantiationDefinition;
110}
111
112} // namespace
113
115 ClangTidyContext *Context)
116 : ClangTidyCheck(Name, Context),
117 HeaderFileExtensions(Context->getHeaderFileExtensions()),
118 FixMode(Options.get("FixMode", FixModeKind::UseStatic)),
119 AnalyzeFunctions(Options.get("AnalyzeFunctions", true)),
120 AnalyzeVariables(Options.get("AnalyzeVariables", true)),
121 AnalyzeTypes(Options.get("AnalyzeTypes", true)) {
122 if (!AnalyzeFunctions && !AnalyzeVariables && !AnalyzeTypes)
123 configurationDiag(
124 "the 'misc-use-internal-linkage' check will not perform any "
125 "analysis because its 'AnalyzeFunctions', 'AnalyzeVariables', "
126 "and 'AnalyzeTypes' options have all been set to false");
127}
128
130 Options.store(Opts, "FixMode", FixMode);
131 Options.store(Opts, "AnalyzeFunctions", AnalyzeFunctions);
132 Options.store(Opts, "AnalyzeVariables", AnalyzeVariables);
133 Options.store(Opts, "AnalyzeTypes", AnalyzeTypes);
134}
135
137 auto Common =
138 allOf(isFirstDecl(), isAllRedeclsInMainFile(HeaderFileExtensions),
139 unless(anyOf(
140 // 1. internal linkage
141 isInAnonymousNamespace(), hasAncestor(decl(anyOf(
142 // 2. friend
143 friendDecl(),
144 // 3. module export decl
145 exportDecl()))))));
146 if (AnalyzeFunctions)
147 Finder->addMatcher(
148 functionDecl(
149 Common, hasBody(),
150 unless(anyOf(
151 isExplicitlyExternC(), isStaticStorageClass(),
152 isExternStorageClass(), isExplicitTemplateSpecialization(),
153 cxxMethodDecl(), isConsteval(),
154 isAllocationOrDeallocationOverloadedFunction(), isMain())))
155 .bind("fn"),
156 this);
157 if (AnalyzeVariables)
158 Finder->addMatcher(
159 varDecl(Common, hasGlobalStorage(),
160 unless(anyOf(isExplicitlyExternC(), isStaticStorageClass(),
161 isExternStorageClass(),
162 isExplicitTemplateSpecialization(),
163 hasThreadStorageDuration())))
164 .bind("var"),
165 this);
166 if (getLangOpts().CPlusPlus && AnalyzeTypes)
167 Finder->addMatcher(
168 tagDecl(Common, isDefinition(), hasNameForLinkage(),
169 hasDeclContext(anyOf(translationUnitDecl(), namespaceDecl())),
170 unless(anyOf(
171 classTemplatePartialSpecializationDecl(),
172 cxxRecordDecl(anyOf(isExplicitTemplateSpecialization(),
173 isExplicitTemplateInstantiation())))))
174 .bind("tag"),
175 this);
176}
177
178static constexpr StringRef Message =
179 "%0 %1 can be made static %select{|or moved into an anonymous namespace }2"
180 "to enforce internal linkage";
181
182void UseInternalLinkageCheck::check(const MatchFinder::MatchResult &Result) {
183 if (const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("fn")) {
184 const DiagnosticBuilder DB = diag(FD->getLocation(), Message)
185 << "function" << FD << getLangOpts().CPlusPlus;
186 const SourceLocation FixLoc = FD->getInnerLocStart();
187 if (FixLoc.isInvalid() || FixLoc.isMacroID())
188 return;
189 if (FixMode == FixModeKind::UseStatic)
190 DB << FixItHint::CreateInsertion(FixLoc, "static ");
191 return;
192 }
193 if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var")) {
194 // In C++, const variables at file scope have implicit internal linkage,
195 // so we should not warn there. This is not the case in C.
196 // https://eel.is/c++draft/diff#basic-3
197 if (getLangOpts().CPlusPlus && VD->getType().isConstQualified())
198 return;
199
200 const DiagnosticBuilder DB = diag(VD->getLocation(), Message)
201 << "variable" << VD << getLangOpts().CPlusPlus;
202 const SourceLocation FixLoc = VD->getInnerLocStart();
203 if (FixLoc.isInvalid() || FixLoc.isMacroID())
204 return;
205 if (FixMode == FixModeKind::UseStatic)
206 DB << FixItHint::CreateInsertion(FixLoc, "static ");
207 return;
208 }
209 if (const auto *TD = Result.Nodes.getNodeAs<TagDecl>("tag")) {
210 diag(TD->getLocation(), "%0 %1 can be moved into an anonymous namespace "
211 "to enforce internal linkage")
212 << TD->getKindName() << TD;
213 return;
214 }
215 llvm_unreachable("");
216}
217
218} // namespace clang::tidy::misc
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
UseInternalLinkageCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
AST_MATCHER(BinaryOperator, isRelationalOperator)
static constexpr StringRef Message
static bool isInMainFile(SourceLocation L, SourceManager &SM, const FileExtensionsSet &HeaderFileExtensions)
bool isExpansionLocInHeaderFile(SourceLocation Loc, const SourceManager &SM, const FileExtensionsSet &HeaderFileExtensions)
Checks whether expansion location of Loc is in header file.
llvm::SmallSet< llvm::StringRef, 5 > FileExtensionsSet
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< misc::UseInternalLinkageCheck::FixModeKind, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...