//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "UseInternalLinkageCheck.h" #include "../utils/FileExtensionsUtils.h" #include "clang/AST/Decl.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" #include "clang/Basic/Module.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" #include "clang/Lex/Token.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" using namespace clang::ast_matchers; namespace clang::tidy { template <> struct OptionEnumMapping { static llvm::ArrayRef< std::pair> getEnumMapping() { static constexpr std::pair Mapping[] = { {misc::UseInternalLinkageCheck::FixModeKind::None, "None"}, {misc::UseInternalLinkageCheck::FixModeKind::UseStatic, "UseStatic"}, }; return {Mapping}; } }; } // namespace clang::tidy namespace clang::tidy::misc { static bool isInMainFile(SourceLocation L, SourceManager &SM, const FileExtensionsSet &HeaderFileExtensions) { for (;;) { if (utils::isExpansionLocInHeaderFile(L, SM, HeaderFileExtensions)) return false; if (SM.isInMainFile(L)) return true; // not in header file but not in main file L = SM.getIncludeLoc(SM.getFileID(L)); if (L.isValid()) continue; // Conservative about the unknown return false; } } namespace { AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); } AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); } AST_MATCHER(Decl, isInImportableModuleUnit) { if (const Module *OwningModule = Node.getOwningModule()) if (OwningModule->Kind == Module::ModuleInterfaceUnit || OwningModule->Kind == Module::ModulePartitionInterface || OwningModule->Kind == Module::ModulePartitionImplementation) return true; return false; } AST_MATCHER_P(Decl, isAllRedeclsInMainFile, const FileExtensionsSet *, HeaderFileExtensions) { return llvm::all_of(Node.redecls(), [&](const Decl *D) { return isInMainFile(D->getLocation(), Finder->getASTContext().getSourceManager(), *HeaderFileExtensions); }); } AST_POLYMORPHIC_MATCHER(isExternStorageClass, AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl)) { return Node.getStorageClass() == SC_Extern; } AST_MATCHER(FunctionDecl, isAllocationOrDeallocationOverloadedFunction) { // [basic.stc.dynamic.allocation] // An allocation function that is not a class member function shall belong to // the global scope and not have a name with internal linkage. // [basic.stc.dynamic.deallocation] // A deallocation function that is not a class member function shall belong to // the global scope and not have a name with internal linkage. static const llvm::DenseSet OverloadedOperators{ OverloadedOperatorKind::OO_New, OverloadedOperatorKind::OO_Array_New, OverloadedOperatorKind::OO_Delete, OverloadedOperatorKind::OO_Array_Delete, }; return OverloadedOperators.contains(Node.getOverloadedOperator()); } AST_POLYMORPHIC_MATCHER(isExplicitlyExternC, AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl)) { return Finder->getASTContext().getLangOpts().CPlusPlus && Node.isExternC(); } AST_MATCHER(TagDecl, hasNameForLinkage) { return Node.hasNameForLinkage(); } AST_MATCHER(CXXRecordDecl, isExplicitTemplateInstantiation) { return Node.getTemplateSpecializationKind() == TSK_ExplicitInstantiationDefinition; } } // namespace UseInternalLinkageCheck::UseInternalLinkageCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), FixMode(Options.get("FixMode", FixModeKind::UseStatic)), AnalyzeFunctions(Options.get("AnalyzeFunctions", true)), AnalyzeVariables(Options.get("AnalyzeVariables", true)), AnalyzeTypes(Options.get("AnalyzeTypes", true)) { if (!AnalyzeFunctions && !AnalyzeVariables && !AnalyzeTypes) configurationDiag( "the 'misc-use-internal-linkage' check will not perform any " "analysis because its 'AnalyzeFunctions', 'AnalyzeVariables', " "and 'AnalyzeTypes' options have all been set to false"); } void UseInternalLinkageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "FixMode", FixMode); Options.store(Opts, "AnalyzeFunctions", AnalyzeFunctions); Options.store(Opts, "AnalyzeVariables", AnalyzeVariables); Options.store(Opts, "AnalyzeTypes", AnalyzeTypes); } void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) { const auto Common = allOf(isFirstDecl(), isAllRedeclsInMainFile(&getHeaderFileExtensions()), unless(anyOf(isInAnonymousNamespace(), isInImportableModuleUnit(), hasAncestor(decl(friendDecl()))))); if (AnalyzeFunctions) Finder->addMatcher( functionDecl( Common, hasBody(), unless(anyOf( isExplicitlyExternC(), isStaticStorageClass(), isExternStorageClass(), isExplicitTemplateSpecialization(), cxxMethodDecl(), isConsteval(), isAllocationOrDeallocationOverloadedFunction(), isMain()))) .bind("fn"), this); if (AnalyzeVariables) Finder->addMatcher( varDecl(Common, hasGlobalStorage(), unless(anyOf(isExplicitlyExternC(), isStaticStorageClass(), isExternStorageClass(), isExplicitTemplateSpecialization(), hasThreadStorageDuration()))) .bind("var"), this); if (getLangOpts().CPlusPlus && AnalyzeTypes) Finder->addMatcher( tagDecl(Common, isDefinition(), hasNameForLinkage(), hasDeclContext(anyOf(translationUnitDecl(), namespaceDecl())), unless(anyOf( classTemplatePartialSpecializationDecl(), cxxRecordDecl(anyOf(isExplicitTemplateSpecialization(), isExplicitTemplateInstantiation()))))) .bind("tag"), this); } static constexpr StringRef Message = "%0 %1 can be made static %select{|or moved into an anonymous namespace }2" "to enforce internal linkage"; void UseInternalLinkageCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *FD = Result.Nodes.getNodeAs("fn")) { const DiagnosticBuilder DB = diag(FD->getLocation(), Message) << "function" << FD << getLangOpts().CPlusPlus; const SourceLocation FixLoc = FD->getInnerLocStart(); if (FixLoc.isInvalid() || FixLoc.isMacroID()) return; if (FixMode == FixModeKind::UseStatic) DB << FixItHint::CreateInsertion(FixLoc, "static "); return; } if (const auto *VD = Result.Nodes.getNodeAs("var")) { // In C++, const variables at file scope have implicit internal linkage, // so we should not warn there. This is not the case in C. // https://eel.is/c++draft/diff#basic-3 if (getLangOpts().CPlusPlus && VD->getType().isConstQualified()) return; const DiagnosticBuilder DB = diag(VD->getLocation(), Message) << "variable" << VD << getLangOpts().CPlusPlus; const SourceLocation FixLoc = VD->getInnerLocStart(); if (FixLoc.isInvalid() || FixLoc.isMacroID()) return; if (FixMode == FixModeKind::UseStatic) DB << FixItHint::CreateInsertion(FixLoc, "static "); return; } if (const auto *TD = Result.Nodes.getNodeAs("tag")) { diag(TD->getLocation(), "%0 %1 can be moved into an anonymous namespace " "to enforce internal linkage") << TD->getKindName() << TD; return; } llvm_unreachable(""); } } // namespace clang::tidy::misc