clang-tools 23.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/Module.h"
16#include "clang/Basic/SourceLocation.h"
17#include "clang/Basic/Specifiers.h"
18#include "clang/Lex/Token.h"
19#include "llvm/ADT/DenseSet.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/SmallVector.h"
22
23using namespace clang::ast_matchers;
24
25namespace clang::tidy {
26
27template <>
28struct OptionEnumMapping<misc::UseInternalLinkageCheck::FixModeKind> {
29 static llvm::ArrayRef<
30 std::pair<misc::UseInternalLinkageCheck::FixModeKind, StringRef>>
32 static constexpr std::pair<misc::UseInternalLinkageCheck::FixModeKind,
33 StringRef>
34 Mapping[] = {
37 "UseStatic"},
38 };
39 return {Mapping};
40 }
41};
42
43} // namespace clang::tidy
44
45namespace clang::tidy::misc {
46
47static bool isInMainFile(SourceLocation L, SourceManager &SM,
48 const FileExtensionsSet &HeaderFileExtensions) {
49 for (;;) {
50 if (utils::isExpansionLocInHeaderFile(L, SM, HeaderFileExtensions))
51 return false;
52 if (SM.isInMainFile(L))
53 return true;
54 // not in header file but not in main file
55 L = SM.getIncludeLoc(SM.getFileID(L));
56 if (L.isValid())
57 continue;
58 // Conservative about the unknown
59 return false;
60 }
61}
62
63namespace {
64
65AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }
66
67AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }
68
69AST_MATCHER(Decl, isInImportableModuleUnit) {
70 if (const Module *OwningModule = Node.getOwningModule())
71 if (OwningModule->Kind == Module::ModuleInterfaceUnit ||
72 OwningModule->Kind == Module::ModulePartitionInterface ||
73 OwningModule->Kind == Module::ModulePartitionImplementation)
74 return true;
75 return false;
76}
77
78AST_MATCHER_P(Decl, isAllRedeclsInMainFile, const FileExtensionsSet *,
79 HeaderFileExtensions) {
80 return llvm::all_of(Node.redecls(), [&](const Decl *D) {
81 return isInMainFile(D->getLocation(),
82 Finder->getASTContext().getSourceManager(),
83 *HeaderFileExtensions);
84 });
85}
86
87AST_POLYMORPHIC_MATCHER(isExternStorageClass,
88 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
89 VarDecl)) {
90 return Node.getStorageClass() == SC_Extern;
91}
92
93AST_MATCHER(FunctionDecl, isAllocationOrDeallocationOverloadedFunction) {
94 // [basic.stc.dynamic.allocation]
95 // An allocation function that is not a class member function shall belong to
96 // the global scope and not have a name with internal linkage.
97 // [basic.stc.dynamic.deallocation]
98 // A deallocation function that is not a class member function shall belong to
99 // the global scope and not have a name with internal linkage.
100 static const llvm::DenseSet<OverloadedOperatorKind> OverloadedOperators{
101 OverloadedOperatorKind::OO_New,
102 OverloadedOperatorKind::OO_Array_New,
103 OverloadedOperatorKind::OO_Delete,
104 OverloadedOperatorKind::OO_Array_Delete,
105 };
106 return OverloadedOperators.contains(Node.getOverloadedOperator());
107}
108
109AST_POLYMORPHIC_MATCHER(isExplicitlyExternC,
110 AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
111 VarDecl)) {
112 return Finder->getASTContext().getLangOpts().CPlusPlus && Node.isExternC();
113}
114
115AST_MATCHER(TagDecl, hasNameForLinkage) { return Node.hasNameForLinkage(); }
116
117AST_MATCHER(CXXRecordDecl, isExplicitTemplateInstantiation) {
118 return Node.getTemplateSpecializationKind() ==
119 TSK_ExplicitInstantiationDefinition;
120}
121
122} // namespace
123
125 ClangTidyContext *Context)
126 : ClangTidyCheck(Name, Context),
127 FixMode(Options.get("FixMode", FixModeKind::UseStatic)),
128 AnalyzeFunctions(Options.get("AnalyzeFunctions", true)),
129 AnalyzeVariables(Options.get("AnalyzeVariables", true)),
130 AnalyzeTypes(Options.get("AnalyzeTypes", true)) {
131 if (!AnalyzeFunctions && !AnalyzeVariables && !AnalyzeTypes)
132 configurationDiag(
133 "the 'misc-use-internal-linkage' check will not perform any "
134 "analysis because its 'AnalyzeFunctions', 'AnalyzeVariables', "
135 "and 'AnalyzeTypes' options have all been set to false");
136}
137
139 Options.store(Opts, "FixMode", FixMode);
140 Options.store(Opts, "AnalyzeFunctions", AnalyzeFunctions);
141 Options.store(Opts, "AnalyzeVariables", AnalyzeVariables);
142 Options.store(Opts, "AnalyzeTypes", AnalyzeTypes);
143}
144
146 const auto Common =
147 allOf(isFirstDecl(), isAllRedeclsInMainFile(&getHeaderFileExtensions()),
148 unless(anyOf(isInAnonymousNamespace(), isInImportableModuleUnit(),
149 hasAncestor(decl(friendDecl())))));
150
151 if (AnalyzeFunctions)
152 Finder->addMatcher(
153 functionDecl(
154 Common, hasBody(),
155 unless(anyOf(
156 isExplicitlyExternC(), isStaticStorageClass(),
157 isExternStorageClass(), isExplicitTemplateSpecialization(),
158 cxxMethodDecl(), isConsteval(),
159 isAllocationOrDeallocationOverloadedFunction(), isMain())))
160 .bind("fn"),
161 this);
162
163 if (AnalyzeVariables)
164 Finder->addMatcher(
165 varDecl(Common, hasGlobalStorage(),
166 unless(anyOf(isExplicitlyExternC(), isStaticStorageClass(),
167 isExternStorageClass(),
168 isExplicitTemplateSpecialization(),
169 hasThreadStorageDuration())))
170 .bind("var"),
171 this);
172
173 if (getLangOpts().CPlusPlus && AnalyzeTypes)
174 Finder->addMatcher(
175 tagDecl(Common, isDefinition(), hasNameForLinkage(),
176 hasDeclContext(anyOf(translationUnitDecl(), namespaceDecl())),
177 unless(anyOf(
178 classTemplatePartialSpecializationDecl(),
179 cxxRecordDecl(anyOf(isExplicitTemplateSpecialization(),
180 isExplicitTemplateInstantiation())))))
181 .bind("tag"),
182 this);
183}
184
185static constexpr StringRef Message =
186 "%0 %1 can be made static %select{|or moved into an anonymous namespace }2"
187 "to enforce internal linkage";
188
189void UseInternalLinkageCheck::check(const MatchFinder::MatchResult &Result) {
190 if (const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("fn")) {
191 const DiagnosticBuilder DB = diag(FD->getLocation(), Message)
192 << "function" << FD << getLangOpts().CPlusPlus;
193 const SourceLocation FixLoc = FD->getInnerLocStart();
194 if (FixLoc.isInvalid() || FixLoc.isMacroID())
195 return;
196 if (FixMode == FixModeKind::UseStatic)
197 DB << FixItHint::CreateInsertion(FixLoc, "static ");
198 return;
199 }
200 if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var")) {
201 // In C++, const variables at file scope have implicit internal linkage,
202 // so we should not warn there. This is not the case in C.
203 // https://eel.is/c++draft/diff#basic-3
204 if (getLangOpts().CPlusPlus && VD->getType().isConstQualified())
205 return;
206
207 const DiagnosticBuilder DB = diag(VD->getLocation(), Message)
208 << "variable" << VD << getLangOpts().CPlusPlus;
209 const SourceLocation FixLoc = VD->getInnerLocStart();
210 if (FixLoc.isInvalid() || FixLoc.isMacroID())
211 return;
212 if (FixMode == FixModeKind::UseStatic)
213 DB << FixItHint::CreateInsertion(FixLoc, "static ");
214 return;
215 }
216 if (const auto *TD = Result.Nodes.getNodeAs<TagDecl>("tag")) {
217 diag(TD->getLocation(), "%0 %1 can be moved into an anonymous namespace "
218 "to enforce internal linkage")
219 << TD->getKindName() << TD;
220 return;
221 }
222 llvm_unreachable("");
223}
224
225} // 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_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
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...