llvm-project
157 строк · 6.5 Кб
1//===--- Analysis.cpp -----------------------------------------------------===//
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 "clang-include-cleaner/Analysis.h"10#include "AnalysisInternal.h"11#include "clang-include-cleaner/IncludeSpeller.h"12#include "clang-include-cleaner/Record.h"13#include "clang-include-cleaner/Types.h"14#include "clang/AST/Decl.h"15#include "clang/AST/DeclBase.h"16#include "clang/Basic/DirectoryEntry.h"17#include "clang/Basic/FileEntry.h"18#include "clang/Basic/SourceManager.h"19#include "clang/Format/Format.h"20#include "clang/Lex/HeaderSearch.h"21#include "clang/Lex/Preprocessor.h"22#include "clang/Tooling/Core/Replacement.h"23#include "clang/Tooling/Inclusions/StandardLibrary.h"24#include "llvm/ADT/ArrayRef.h"25#include "llvm/ADT/DenseSet.h"26#include "llvm/ADT/STLExtras.h"27#include "llvm/ADT/STLFunctionalExtras.h"28#include "llvm/ADT/SmallVector.h"29#include "llvm/ADT/StringRef.h"30#include "llvm/ADT/StringSet.h"31#include "llvm/Support/Error.h"32#include "llvm/Support/ErrorHandling.h"33#include <cassert>34#include <climits>35#include <string>36
37namespace clang::include_cleaner {38
39namespace {40bool shouldIgnoreMacroReference(const Preprocessor &PP, const Macro &M) {41auto *MI = PP.getMacroInfo(M.Name);42// Macros that expand to themselves are confusing from user's point of view.43// They usually aspect the usage to be attributed to the underlying decl and44// not the macro definition. So ignore such macros (e.g. std{in,out,err} are45// implementation defined macros, that just resolve to themselves in46// practice).47return MI && MI->getNumTokens() == 1 && MI->isObjectLike() &&48MI->getReplacementToken(0).getIdentifierInfo() == M.Name;49}
50} // namespace51
52void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,53llvm::ArrayRef<SymbolReference> MacroRefs,54const PragmaIncludes *PI, const Preprocessor &PP,55UsedSymbolCB CB) {56const auto &SM = PP.getSourceManager();57// This is duplicated in writeHTMLReport, changes should be mirrored there.58tooling::stdlib::Recognizer Recognizer;59for (auto *Root : ASTRoots) {60walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) {61auto FID = SM.getFileID(SM.getSpellingLoc(Loc));62if (FID != SM.getMainFileID() && FID != SM.getPreambleFileID())63return;64// FIXME: Most of the work done here is repetitive. It might be useful to65// have a cache/batching.66SymbolReference SymRef{ND, Loc, RT};67return CB(SymRef, headersForSymbol(ND, SM, PI));68});69}70for (const SymbolReference &MacroRef : MacroRefs) {71assert(MacroRef.Target.kind() == Symbol::Macro);72if (!SM.isWrittenInMainFile(SM.getSpellingLoc(MacroRef.RefLocation)) ||73shouldIgnoreMacroReference(PP, MacroRef.Target.macro()))74continue;75CB(MacroRef, headersForSymbol(MacroRef.Target, SM, PI));76}77}
78
79AnalysisResults
80analyze(llvm::ArrayRef<Decl *> ASTRoots,81llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,82const PragmaIncludes *PI, const Preprocessor &PP,83llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {84auto &SM = PP.getSourceManager();85const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());86llvm::DenseSet<const Include *> Used;87llvm::StringSet<> Missing;88if (!HeaderFilter)89HeaderFilter = [](llvm::StringRef) { return false; };90OptionalDirectoryEntryRef ResourceDir =91PP.getHeaderSearchInfo().getModuleMap().getBuiltinDir();92walkUsed(ASTRoots, MacroRefs, PI, PP,93[&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {94bool Satisfied = false;95for (const Header &H : Providers) {96if (H.kind() == Header::Physical &&97(H.physical() == MainFile ||98(ResourceDir && H.physical().getDir() == *ResourceDir))) {99Satisfied = true;100}101for (const Include *I : Inc.match(H)) {102Used.insert(I);103Satisfied = true;104}105}106if (!Satisfied && !Providers.empty() &&107Ref.RT == RefType::Explicit &&108!HeaderFilter(Providers.front().resolvedPath()))109Missing.insert(spellHeader(110{Providers.front(), PP.getHeaderSearchInfo(), MainFile}));111});112
113AnalysisResults Results;114for (const Include &I : Inc.all()) {115if (Used.contains(&I) || !I.Resolved ||116HeaderFilter(I.Resolved->getFileEntry().tryGetRealPathName()) ||117(ResourceDir && I.Resolved->getFileEntry().getDir() == *ResourceDir))118continue;119if (PI) {120if (PI->shouldKeep(*I.Resolved))121continue;122// Check if main file is the public interface for a private header. If so123// we shouldn't diagnose it as unused.124if (auto PHeader = PI->getPublic(*I.Resolved); !PHeader.empty()) {125PHeader = PHeader.trim("<>\"");126// Since most private -> public mappings happen in a verbatim way, we127// check textually here. This might go wrong in presence of symlinks or128// header mappings. But that's not different than rest of the places.129if (MainFile->tryGetRealPathName().ends_with(PHeader))130continue;131}132}133Results.Unused.push_back(&I);134}135for (llvm::StringRef S : Missing.keys())136Results.Missing.push_back(S.str());137llvm::sort(Results.Missing);138return Results;139}
140
141std::string fixIncludes(const AnalysisResults &Results,142llvm::StringRef FileName, llvm::StringRef Code,143const format::FormatStyle &Style) {144assert(Style.isCpp() && "Only C++ style supports include insertions!");145tooling::Replacements R;146// Encode insertions/deletions in the magic way clang-format understands.147for (const Include *I : Results.Unused)148cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));149for (llvm::StringRef Spelled : Results.Missing)150cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 0,151("#include " + Spelled).str())));152// "cleanup" actually turns the UINT_MAX replacements into concrete edits.153auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style));154return cantFail(tooling::applyAllReplacements(Code, Positioned));155}
156
157} // namespace clang::include_cleaner158