xref: /llvm-project/clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp (revision 6504546abcd38159256c3030286b1c02b401c4f8)
1 //===--- UseInternalLinkageCheck.cpp - clang-tidy--------------------------===//
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 
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"
21 
22 using namespace clang::ast_matchers;
23 
24 namespace clang::tidy {
25 
26 template <>
27 struct OptionEnumMapping<misc::UseInternalLinkageCheck::FixModeKind> {
28   static llvm::ArrayRef<
29       std::pair<misc::UseInternalLinkageCheck::FixModeKind, StringRef>>
30   getEnumMapping() {
31     static constexpr std::pair<misc::UseInternalLinkageCheck::FixModeKind,
32                                StringRef>
33         Mapping[] = {
34             {misc::UseInternalLinkageCheck::FixModeKind::None, "None"},
35             {misc::UseInternalLinkageCheck::FixModeKind::UseStatic,
36              "UseStatic"},
37         };
38     return {Mapping};
39   }
40 };
41 
42 } // namespace clang::tidy
43 
44 namespace clang::tidy::misc {
45 
46 namespace {
47 
48 AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }
49 
50 AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }
51 
52 static bool isInMainFile(SourceLocation L, SourceManager &SM,
53                          const FileExtensionsSet &HeaderFileExtensions) {
54   for (;;) {
55     if (utils::isSpellingLocInHeaderFile(L, SM, HeaderFileExtensions))
56       return false;
57     if (SM.isInMainFile(L))
58       return true;
59     // not in header file but not in main file
60     L = SM.getIncludeLoc(SM.getFileID(L));
61     if (L.isValid())
62       continue;
63     // Conservative about the unknown
64     return false;
65   }
66 }
67 
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);
74   });
75 }
76 
77 AST_POLYMORPHIC_MATCHER(isExternStorageClass,
78                         AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl,
79                                                         VarDecl)) {
80   return Node.getStorageClass() == SC_Extern;
81 }
82 
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,
95   };
96   return OverloadedOperators.contains(Node.getOverloadedOperator());
97 }
98 
99 } // namespace
100 
101 UseInternalLinkageCheck::UseInternalLinkageCheck(StringRef Name,
102                                                  ClangTidyContext *Context)
103     : ClangTidyCheck(Name, Context),
104       HeaderFileExtensions(Context->getHeaderFileExtensions()),
105       FixMode(Options.get("FixMode", FixModeKind::UseStatic)) {}
106 
107 void UseInternalLinkageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
108   Options.store(Opts, "FixMode", FixMode);
109 }
110 
111 void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
112   auto Common =
113       allOf(isFirstDecl(), isAllRedeclsInMainFile(HeaderFileExtensions),
114             unless(anyOf(
115                 // 1. internal linkage
116                 isStaticStorageClass(), isInAnonymousNamespace(),
117                 // 2. explicit external linkage
118                 isExternStorageClass(), isExternC(),
119                 // 3. template
120                 isExplicitTemplateSpecialization(),
121                 hasAncestor(decl(anyOf(
122                     // 4. friend
123                     friendDecl(),
124                     // 5. module export decl
125                     exportDecl()))))));
126   Finder->addMatcher(
127       functionDecl(Common, hasBody(),
128                    unless(anyOf(cxxMethodDecl(), isConsteval(),
129                                 isAllocationOrDeallocationOverloadedFunction(),
130                                 isMain())))
131           .bind("fn"),
132       this);
133   Finder->addMatcher(varDecl(Common, hasGlobalStorage()).bind("var"), this);
134 }
135 
136 static constexpr StringRef Message =
137     "%0 %1 can be made static or moved into an anonymous namespace "
138     "to enforce internal linkage";
139 
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())
145       return;
146     if (FixMode == FixModeKind::UseStatic)
147       DB << FixItHint::CreateInsertion(FixLoc, "static ");
148     return;
149   }
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())
155       return;
156 
157     DiagnosticBuilder DB = diag(VD->getLocation(), Message) << "variable" << VD;
158     const SourceLocation FixLoc = VD->getInnerLocStart();
159     if (FixLoc.isInvalid() || FixLoc.isMacroID())
160       return;
161     if (FixMode == FixModeKind::UseStatic)
162       DB << FixItHint::CreateInsertion(FixLoc, "static ");
163     return;
164   }
165   llvm_unreachable("");
166 }
167 
168 } // namespace clang::tidy::misc
169