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) {
41 auto *MI = PP.getMacroInfo(II: 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 and
44 // not the macro definition. So ignore such macros (e.g. std{in,out,err} are
45 // implementation defined macros, that just resolve to themselves in
46 // practice).
47 return MI && MI->getNumTokens() == 1 && MI->isObjectLike() &&
48 MI->getReplacementToken(Tok: 0).getIdentifierInfo() == M.Name;
49}
50} // namespace
51
52void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,
53 llvm::ArrayRef<SymbolReference> MacroRefs,
54 const PragmaIncludes *PI, const Preprocessor &PP,
55 UsedSymbolCB CB) {
56 const auto &SM = PP.getSourceManager();
57 // This is duplicated in writeHTMLReport, changes should be mirrored there.
58 tooling::stdlib::Recognizer Recognizer;
59 for (auto *Root : ASTRoots) {
60 walkAST(*Root, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) {
61 auto FID = SM.getFileID(SpellingLoc: SM.getSpellingLoc(Loc));
62 if (FID != SM.getMainFileID() && FID != SM.getPreambleFileID())
63 return;
64 // FIXME: Most of the work done here is repetitive. It might be useful to
65 // have a cache/batching.
66 SymbolReference SymRef{ND, .RefLocation: Loc, .RT: RT};
67 return CB(SymRef, headersForSymbol(ND, SM, PI));
68 });
69 }
70 for (const SymbolReference &MacroRef : MacroRefs) {
71 assert(MacroRef.Target.kind() == Symbol::Macro);
72 if (!SM.isWrittenInMainFile(Loc: SM.getSpellingLoc(Loc: MacroRef.RefLocation)) ||
73 shouldIgnoreMacroReference(PP, M: MacroRef.Target.macro()))
74 continue;
75 CB(MacroRef, headersForSymbol(S: MacroRef.Target, SM, PI));
76 }
77}
78
79AnalysisResults
80analyze(llvm::ArrayRef<Decl *> ASTRoots,
81 llvm::ArrayRef<SymbolReference> MacroRefs, const Includes &Inc,
82 const PragmaIncludes *PI, const Preprocessor &PP,
83 llvm::function_ref<bool(llvm::StringRef)> HeaderFilter) {
84 auto &SM = PP.getSourceManager();
85 const FileEntry *MainFile = SM.getFileEntryForID(FID: SM.getMainFileID());
86 llvm::DenseSet<const Include *> Used;
87 llvm::StringSet<> Missing;
88 if (!HeaderFilter)
89 HeaderFilter = [](llvm::StringRef) { return false; };
90 OptionalDirectoryEntryRef ResourceDir =
91 PP.getHeaderSearchInfo().getModuleMap().getBuiltinDir();
92 walkUsed(ASTRoots, MacroRefs, PI, PP,
93 CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
94 bool Satisfied = false;
95 for (const Header &H : Providers) {
96 if (H.kind() == Header::Physical &&
97 (H.physical() == MainFile ||
98 (ResourceDir && H.physical().getDir() == *ResourceDir))) {
99 Satisfied = true;
100 }
101 for (const Include *I : Inc.match(H)) {
102 Used.insert(V: I);
103 Satisfied = true;
104 }
105 }
106 if (!Satisfied && !Providers.empty() &&
107 Ref.RT == RefType::Explicit &&
108 !HeaderFilter(Providers.front().resolvedPath()))
109 Missing.insert(key: spellHeader(
110 Input: {.H: Providers.front(), .HS: PP.getHeaderSearchInfo(), .Main: MainFile}));
111 });
112
113 AnalysisResults Results;
114 for (const Include &I : Inc.all()) {
115 if (Used.contains(V: &I) || !I.Resolved ||
116 HeaderFilter(I.Resolved->getFileEntry().tryGetRealPathName()) ||
117 (ResourceDir && I.Resolved->getFileEntry().getDir() == *ResourceDir))
118 continue;
119 if (PI) {
120 if (PI->shouldKeep(FE: *I.Resolved))
121 continue;
122 // Check if main file is the public interface for a private header. If so
123 // we shouldn't diagnose it as unused.
124 if (auto PHeader = PI->getPublic(File: *I.Resolved); !PHeader.empty()) {
125 PHeader = PHeader.trim(Chars: "<>\"");
126 // Since most private -> public mappings happen in a verbatim way, we
127 // check textually here. This might go wrong in presence of symlinks or
128 // header mappings. But that's not different than rest of the places.
129 if (MainFile->tryGetRealPathName().ends_with(Suffix: PHeader))
130 continue;
131 }
132 }
133 Results.Unused.push_back(x: &I);
134 }
135 for (llvm::StringRef S : Missing.keys())
136 Results.Missing.push_back(x: S.str());
137 llvm::sort(C&: Results.Missing);
138 return Results;
139}
140
141std::string fixIncludes(const AnalysisResults &Results,
142 llvm::StringRef FileName, llvm::StringRef Code,
143 const format::FormatStyle &Style) {
144 assert(Style.isCpp() && "Only C++ style supports include insertions!");
145 tooling::Replacements R;
146 // Encode insertions/deletions in the magic way clang-format understands.
147 for (const Include *I : Results.Unused)
148 cantFail(Err: R.add(R: tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));
149 for (llvm::StringRef Spelled : Results.Missing)
150 cantFail(Err: R.add(R: tooling::Replacement(FileName, UINT_MAX, 0,
151 ("#include " + Spelled).str())));
152 // "cleanup" actually turns the UINT_MAX replacements into concrete edits.
153 auto Positioned = cantFail(ValOrErr: format::cleanupAroundReplacements(Code, Replaces: R, Style));
154 return cantFail(ValOrErr: tooling::applyAllReplacements(Code, Replaces: Positioned));
155}
156
157} // namespace clang::include_cleaner
158

source code of clang-tools-extra/include-cleaner/lib/Analysis.cpp