-
Notifications
You must be signed in to change notification settings - Fork 10.7k
/
IncludeCleaner.cpp
219 lines (191 loc) · 6.86 KB
/
IncludeCleaner.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- C++ -*-===//
//
// 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 "IncludeCleaner.h"
#include "support/Logger.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceLocation.h"
namespace clang {
namespace clangd {
namespace {
/// Crawler traverses the AST and feeds in the locations of (sometimes
/// implicitly) used symbols into \p Result.
class ReferencedLocationCrawler
: public RecursiveASTVisitor<ReferencedLocationCrawler> {
public:
ReferencedLocationCrawler(ReferencedLocations &Result) : Result(Result) {}
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
add(DRE->getDecl());
add(DRE->getFoundDecl());
return true;
}
bool VisitMemberExpr(MemberExpr *ME) {
add(ME->getMemberDecl());
add(ME->getFoundDecl().getDecl());
return true;
}
bool VisitTagType(TagType *TT) {
add(TT->getDecl());
return true;
}
bool VisitCXXConstructExpr(CXXConstructExpr *CCE) {
add(CCE->getConstructor());
return true;
}
bool VisitTemplateSpecializationType(TemplateSpecializationType *TST) {
if (isNew(TST)) {
add(TST->getTemplateName().getAsTemplateDecl()); // Primary template.
add(TST->getAsCXXRecordDecl()); // Specialization
}
return true;
}
bool VisitTypedefType(TypedefType *TT) {
add(TT->getDecl());
return true;
}
// Consider types of any subexpression used, even if the type is not named.
// This is helpful in getFoo().bar(), where Foo must be complete.
// FIXME(kirillbobyrev): Should we tweak this? It may not be desirable to
// consider types "used" when they are not directly spelled in code.
bool VisitExpr(Expr *E) {
TraverseType(E->getType());
return true;
}
bool TraverseType(QualType T) {
if (isNew(T.getTypePtrOrNull())) { // don't care about quals
Base::TraverseType(T);
}
return true;
}
bool VisitUsingDecl(UsingDecl *D) {
for (const auto *Shadow : D->shadows()) {
add(Shadow->getTargetDecl());
}
return true;
}
// Enums may be usefully forward-declared as *complete* types by specifying
// an underlying type. In this case, the definition should see the declaration
// so they can be checked for compatibility.
bool VisitEnumDecl(EnumDecl *D) {
if (D->isThisDeclarationADefinition() && D->getIntegerTypeSourceInfo())
add(D);
return false;
}
private:
using Base = RecursiveASTVisitor<ReferencedLocationCrawler>;
void add(const Decl *D) {
if (!D || !isNew(D->getCanonicalDecl())) {
return;
}
for (const Decl *Redecl : D->redecls()) {
Result.insert(Redecl->getLocation());
}
}
bool isNew(const void *P) { return P && Visited.insert(P).second; }
ReferencedLocations &Result;
llvm::DenseSet<const void *> Visited;
};
// Given a set of referenced FileIDs, determines all the potentially-referenced
// files and macros by traversing expansion/spelling locations of macro IDs.
// This is used to map the referenced SourceLocations onto real files.
struct ReferencedFiles {
ReferencedFiles(const SourceManager &SM) : SM(SM) {}
llvm::DenseSet<FileID> Files;
llvm::DenseSet<FileID> Macros;
const SourceManager &SM;
void add(SourceLocation Loc) { add(SM.getFileID(Loc), Loc); }
void add(FileID FID, SourceLocation Loc) {
if (FID.isInvalid())
return;
assert(SM.isInFileID(Loc, FID));
if (Loc.isFileID()) {
Files.insert(FID);
return;
}
// Don't process the same macro FID twice.
if (!Macros.insert(FID).second)
return;
const auto &Exp = SM.getSLocEntry(FID).getExpansion();
// For token pasting operator in macros, spelling and expansion locations
// can be within a temporary buffer that Clang creates (scratch space or
// ScratchBuffer). That is not a real file we can include.
if (!SM.isWrittenInScratchSpace(Exp.getSpellingLoc()))
add(Exp.getSpellingLoc());
if (!SM.isWrittenInScratchSpace(Exp.getExpansionLocStart()))
add(Exp.getExpansionLocStart());
if (!SM.isWrittenInScratchSpace(Exp.getExpansionLocEnd()))
add(Exp.getExpansionLocEnd());
}
};
} // namespace
ReferencedLocations findReferencedLocations(ParsedAST &AST) {
ReferencedLocations Result;
ReferencedLocationCrawler Crawler(Result);
Crawler.TraverseAST(AST.getASTContext());
// FIXME(kirillbobyrev): Handle macros.
return Result;
}
llvm::DenseSet<FileID>
findReferencedFiles(const llvm::DenseSet<SourceLocation> &Locs,
const SourceManager &SM) {
std::vector<SourceLocation> Sorted{Locs.begin(), Locs.end()};
llvm::sort(Sorted); // Group by FileID.
ReferencedFiles Result(SM);
for (auto It = Sorted.begin(); It < Sorted.end();) {
FileID FID = SM.getFileID(*It);
Result.add(FID, *It);
// Cheaply skip over all the other locations from the same FileID.
// This avoids lots of redundant Loc->File lookups for the same file.
do
++It;
while (It != Sorted.end() && SM.isInFileID(*It, FID));
}
return std::move(Result.Files);
}
std::vector<const Inclusion *>
getUnused(const IncludeStructure &Structure,
const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles) {
std::vector<const Inclusion *> Unused;
for (const Inclusion &MFI : Structure.MainFileIncludes) {
// FIXME: Skip includes that are not self-contained.
if (!MFI.HeaderID) {
elog("File {0} not found.", MFI.Written);
continue;
}
auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
if (!ReferencedFiles.contains(IncludeID)) {
Unused.push_back(&MFI);
}
dlog("{0} is {1}", MFI.Written,
ReferencedFiles.contains(IncludeID) ? "USED" : "UNUSED");
}
return Unused;
}
llvm::DenseSet<IncludeStructure::HeaderID>
translateToHeaderIDs(const llvm::DenseSet<FileID> &Files,
const IncludeStructure &Includes,
const SourceManager &SM) {
llvm::DenseSet<IncludeStructure::HeaderID> TranslatedHeaderIDs;
TranslatedHeaderIDs.reserve(Files.size());
for (FileID FID : Files) {
const FileEntry *FE = SM.getFileEntryForID(FID);
assert(FE);
const auto File = Includes.getID(FE);
assert(File);
TranslatedHeaderIDs.insert(*File);
}
return TranslatedHeaderIDs;
}
std::vector<const Inclusion *> computeUnusedIncludes(ParsedAST &AST) {
const auto &SM = AST.getSourceManager();
auto Refs = findReferencedLocations(AST);
auto ReferencedFiles = translateToHeaderIDs(findReferencedFiles(Refs, SM),
AST.getIncludeStructure(), SM);
return getUnused(AST.getIncludeStructure(), ReferencedFiles);
}
} // namespace clangd
} // namespace clang