//===--- Analysis.cpp -----------------------------------------------------===// // // 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 "clang-include-cleaner/Analysis.h" #include "AnalysisInternal.h" #include "clang-include-cleaner/IncludeSpeller.h" #include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/Basic/DirectoryEntry.h" #include "clang/Basic/FileEntry.h" #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include #include #include namespace clang::include_cleaner { namespace { bool shouldIgnoreMacroReference(const Preprocessor &PP, const Macro &M) { auto *MI = PP.getMacroInfo(M.Name); // Macros that expand to themselves are confusing from user's point of view. // They usually aspect the usage to be attributed to the underlying decl and // not the macro definition. So ignore such macros (e.g. std{in,out,err} are // implementation defined macros, that just resolve to themselves in // practice). return MI && MI->getNumTokens() == 1 && MI->isObjectLike() && MI->getReplacementToken(0).getIdentifierInfo() == M.Name; } } // namespace void walkUsed(llvm::ArrayRef ASTRoots, llvm::ArrayRef MacroRefs, const PragmaIncludes *PI, const Preprocessor &PP, UsedSymbolCB CB) { const auto &SM = PP.getSourceManager(); // This is duplicated in writeHTMLReport, changes should be mirrored there. tooling::stdlib::Recognizer Recognizer; for (auto *Root : ASTRoots) { walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) { auto FID = SM.getFileID(SM.getSpellingLoc(Loc)); if (FID != SM.getMainFileID() && FID != SM.getPreambleFileID()) return; // FIXME: Most of the work done here is repetitive. It might be useful to // have a cache/batching. SymbolReference SymRef{ND, Loc, RT}; return CB(SymRef, headersForSymbol(ND, PP, PI)); }); } for (const SymbolReference &MacroRef : MacroRefs) { assert(MacroRef.Target.kind() == Symbol::Macro); if (!SM.isWrittenInMainFile(SM.getSpellingLoc(MacroRef.RefLocation)) || shouldIgnoreMacroReference(PP, MacroRef.Target.macro())) continue; CB(MacroRef, headersForSymbol(MacroRef.Target, PP, PI)); } } AnalysisResults analyze(llvm::ArrayRef ASTRoots, llvm::ArrayRef MacroRefs, const Includes &Inc, const PragmaIncludes *PI, const Preprocessor &PP, llvm::function_ref HeaderFilter) { auto &SM = PP.getSourceManager(); const auto MainFile = *SM.getFileEntryRefForID(SM.getMainFileID()); llvm::DenseSet Used; llvm::StringMap
Missing; constexpr auto DefaultHeaderFilter = [](llvm::StringRef) { return false; }; if (!HeaderFilter) HeaderFilter = DefaultHeaderFilter; OptionalDirectoryEntryRef ResourceDir = PP.getHeaderSearchInfo().getModuleMap().getBuiltinDir(); walkUsed(ASTRoots, MacroRefs, PI, PP, [&](const SymbolReference &Ref, llvm::ArrayRef
Providers) { bool Satisfied = false; for (const Header &H : Providers) { if (H.kind() == Header::Physical && (H.physical() == MainFile || H.physical().getDir() == ResourceDir)) { Satisfied = true; } for (const Include *I : Inc.match(H)) { Used.insert(I); Satisfied = true; } } // Bail out if we can't (or need not) insert an include. if (Satisfied || Providers.empty() || Ref.RT != RefType::Explicit) return; if (HeaderFilter(Providers.front().resolvedPath())) return; // Check if we have any headers with the same spelling, in edge // cases like `#include_next "foo.h"`, the user can't ever // include the physical foo.h, but can have a spelling that // refers to it. auto Spelling = spellHeader( {Providers.front(), PP.getHeaderSearchInfo(), MainFile}); for (const Include *I : Inc.match(Header{Spelling})) { Used.insert(I); Satisfied = true; } if (!Satisfied) Missing.try_emplace(std::move(Spelling), Providers.front()); }); AnalysisResults Results; for (const Include &I : Inc.all()) { if (Used.contains(&I) || !I.Resolved || HeaderFilter(I.Resolved->getName()) || I.Resolved->getDir() == ResourceDir) continue; if (PI) { if (PI->shouldKeep(*I.Resolved)) continue; // Check if main file is the public interface for a private header. If so // we shouldn't diagnose it as unused. if (auto PHeader = PI->getPublic(*I.Resolved); !PHeader.empty()) { PHeader = PHeader.trim("<>\""); // Since most private -> public mappings happen in a verbatim way, we // check textually here. This might go wrong in presence of symlinks or // header mappings. But that's not different than rest of the places. if (MainFile.getName().ends_with(PHeader)) continue; } } Results.Unused.push_back(&I); } for (auto &E : Missing) Results.Missing.emplace_back(E.first().str(), E.second); llvm::sort(Results.Missing); return Results; } std::string fixIncludes(const AnalysisResults &Results, llvm::StringRef FileName, llvm::StringRef Code, const format::FormatStyle &Style) { assert(Style.isCpp() && "Only C++ style supports include insertions!"); tooling::Replacements R; // Encode insertions/deletions in the magic way clang-format understands. for (const Include *I : Results.Unused) cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote()))); for (auto &[Spelled, _] : Results.Missing) cantFail(R.add( tooling::Replacement(FileName, UINT_MAX, 0, "#include " + Spelled))); // "cleanup" actually turns the UINT_MAX replacements into concrete edits. auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style)); return cantFail(tooling::applyAllReplacements(Code, Positioned)); } } // namespace clang::include_cleaner