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