1 //===--- FindHeaders.cpp --------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "AnalysisInternal.h"
10 #include "TypesInternal.h"
11 #include "clang-include-cleaner/Record.h"
12 #include "clang-include-cleaner/Types.h"
13 #include "clang/AST/ASTContext.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/AST/DeclBase.h"
16 #include "clang/Basic/Builtins.h"
17 #include "clang/Basic/FileEntry.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Tooling/Inclusions/StandardLibrary.h"
21 #include "llvm/ADT/ArrayRef.h"
22 #include "llvm/ADT/STLExtras.h"
23 #include "llvm/ADT/SmallVector.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/Casting.h"
26 #include "llvm/Support/ErrorHandling.h"
30 namespace clang::include_cleaner
{
32 llvm::SmallVector
<Hinted
<Header
>>
33 applyHints(llvm::SmallVector
<Hinted
<Header
>> Headers
, Hints H
) {
34 for (auto &Header
: Headers
)
39 llvm::SmallVector
<Header
> ranked(llvm::SmallVector
<Hinted
<Header
>> Headers
) {
40 llvm::stable_sort(llvm::reverse(Headers
),
41 [](const Hinted
<Header
> &LHS
, const Hinted
<Header
> &RHS
) {
44 return llvm::SmallVector
<Header
>(Headers
.begin(), Headers
.end());
47 // Return the basename from a verbatim header spelling, leaves only the file
49 llvm::StringRef
basename(llvm::StringRef Header
) {
50 Header
= Header
.trim("<>\"");
51 if (auto LastSlash
= Header
.rfind('/'); LastSlash
!= Header
.npos
)
52 Header
= Header
.drop_front(LastSlash
+ 1);
53 // Drop everything after first `.` (dot).
56 Header
= Header
.substr(0, Header
.find('.'));
60 // Check if spelling of \p H matches \p DeclName.
61 bool nameMatch(llvm::StringRef DeclName
, Header H
) {
63 case Header::Physical
:
64 return basename(H
.physical()->getName()).equals_insensitive(DeclName
);
65 case Header::Standard
:
66 return basename(H
.standard().name()).equals_insensitive(DeclName
);
67 case Header::Verbatim
:
68 return basename(H
.verbatim()).equals_insensitive(DeclName
);
70 llvm_unreachable("unhandled Header kind!");
73 llvm::StringRef
symbolName(const Symbol
&S
) {
75 case Symbol::Declaration
:
76 // Unnamed decls like operators and anonymous structs won't get any name
78 if (const auto *ND
= llvm::dyn_cast
<NamedDecl
>(&S
.declaration()))
79 if (auto *II
= ND
->getIdentifier())
83 return S
.macro().Name
->getName();
85 llvm_unreachable("unhandled Symbol kind!");
88 Hints
isPublicHeader(const FileEntry
*FE
, const PragmaIncludes
&PI
) {
89 if (PI
.isPrivate(FE
) || !PI
.isSelfContained(FE
))
91 return Hints::PublicHeader
;
94 llvm::SmallVector
<Hinted
<Header
>>
95 hintedHeadersForStdHeaders(llvm::ArrayRef
<tooling::stdlib::Header
> Headers
,
96 const SourceManager
&SM
, const PragmaIncludes
*PI
) {
97 llvm::SmallVector
<Hinted
<Header
>> Results
;
98 for (const auto &H
: Headers
) {
99 Results
.emplace_back(H
, Hints::PublicHeader
| Hints::OriginHeader
);
102 for (const auto *Export
: PI
->getExporters(H
, SM
.getFileManager()))
103 Results
.emplace_back(Header(Export
), isPublicHeader(Export
, *PI
));
105 // StandardLibrary returns headers in preference order, so only mark the
107 if (!Results
.empty())
108 Results
.front().Hint
|= Hints::PreferredHeader
;
112 // Symbol to header mapping for std::move and std::remove, based on number of
114 std::optional
<tooling::stdlib::Header
>
115 headerForAmbiguousStdSymbol(const NamedDecl
*ND
) {
116 if (!ND
->isInStdNamespace())
118 const auto *FD
= ND
->getAsFunction();
121 llvm::StringRef FName
= symbolName(*ND
);
122 if (FName
== "move") {
123 if (FD
->getNumParams() == 1)
125 return tooling::stdlib::Header::named("<utility>");
126 if (FD
->getNumParams() == 3)
127 // move(InputIt first, InputIt last, OutputIt dest);
128 return tooling::stdlib::Header::named("<algorithm>");
129 } else if (FName
== "remove") {
130 if (FD
->getNumParams() == 1)
131 // remove(const char*);
132 return tooling::stdlib::Header::named("<cstdio>");
133 if (FD
->getNumParams() == 3)
134 // remove(ForwardIt first, ForwardIt last, const T& value);
135 return tooling::stdlib::Header::named("<algorithm>");
140 // Special-case symbols without proper locations, like the ambiguous standard
141 // library symbols (e.g. std::move) or builtin declarations.
142 std::optional
<llvm::SmallVector
<Hinted
<Header
>>>
143 headersForSpecialSymbol(const Symbol
&S
, const SourceManager
&SM
,
144 const PragmaIncludes
*PI
) {
145 // Our special casing logic only deals with decls, so bail out early for
147 if (S
.kind() != Symbol::Declaration
)
149 const auto *ND
= llvm::cast
<NamedDecl
>(&S
.declaration());
150 // We map based on names, so again bail out early if there are no names.
153 auto *II
= ND
->getIdentifier();
157 // Check first for symbols that are part of our stdlib mapping. As we have
158 // header names for those.
159 if (auto Header
= headerForAmbiguousStdSymbol(ND
)) {
160 return applyHints(hintedHeadersForStdHeaders({*Header
}, SM
, PI
),
161 Hints::CompleteSymbol
);
164 // Now check for builtin symbols, we shouldn't suggest any headers for ones
165 // without any headers.
166 if (auto ID
= II
->getBuiltinID()) {
167 const char *BuiltinHeader
=
168 ND
->getASTContext().BuiltinInfo
.getHeaderName(ID
);
170 return llvm::SmallVector
<Hinted
<Header
>>{};
171 // FIXME: Use the header mapping for builtins with a known header.
178 llvm::SmallVector
<Hinted
<Header
>> findHeaders(const SymbolLocation
&Loc
,
179 const SourceManager
&SM
,
180 const PragmaIncludes
*PI
) {
181 llvm::SmallVector
<Hinted
<Header
>> Results
;
182 switch (Loc
.kind()) {
183 case SymbolLocation::Physical
: {
184 FileID FID
= SM
.getFileID(SM
.getExpansionLoc(Loc
.physical()));
185 const FileEntry
*FE
= SM
.getFileEntryForID(FID
);
189 return {{FE
, Hints::PublicHeader
| Hints::OriginHeader
}};
190 bool IsOrigin
= true;
192 Results
.emplace_back(FE
,
193 isPublicHeader(FE
, *PI
) |
194 (IsOrigin
? Hints::OriginHeader
: Hints::None
));
195 // FIXME: compute transitive exporter headers.
196 for (const auto *Export
: PI
->getExporters(FE
, SM
.getFileManager()))
197 Results
.emplace_back(Export
, isPublicHeader(Export
, *PI
));
199 if (auto Verbatim
= PI
->getPublic(FE
); !Verbatim
.empty()) {
200 Results
.emplace_back(Verbatim
,
201 Hints::PublicHeader
| Hints::PreferredHeader
);
204 if (PI
->isSelfContained(FE
) || FID
== SM
.getMainFileID())
207 // Walkup the include stack for non self-contained headers.
208 FID
= SM
.getDecomposedIncludedLoc(FID
).first
;
209 FE
= SM
.getFileEntryForID(FID
);
214 case SymbolLocation::Standard
: {
215 return hintedHeadersForStdHeaders(Loc
.standard().headers(), SM
, PI
);
218 llvm_unreachable("unhandled SymbolLocation kind!");
221 llvm::SmallVector
<Header
> headersForSymbol(const Symbol
&S
,
222 const SourceManager
&SM
,
223 const PragmaIncludes
*PI
) {
224 // Get headers for all the locations providing Symbol. Same header can be
225 // reached through different traversals, deduplicate those into a single
226 // Header by merging their hints.
227 llvm::SmallVector
<Hinted
<Header
>> Headers
;
228 if (auto SpecialHeaders
= headersForSpecialSymbol(S
, SM
, PI
)) {
229 Headers
= std::move(*SpecialHeaders
);
231 for (auto &Loc
: locateSymbol(S
))
232 Headers
.append(applyHints(findHeaders(Loc
, SM
, PI
), Loc
.Hint
));
234 // If two Headers probably refer to the same file (e.g. Verbatim(foo.h) and
235 // Physical(/path/to/foo.h), we won't deduplicate them or merge their hints
237 Headers
, [](const Hinted
<Header
> &LHS
, const Hinted
<Header
> &RHS
) {
238 return static_cast<Header
>(LHS
) < static_cast<Header
>(RHS
);
240 auto *Write
= Headers
.begin();
241 for (auto *Read
= Headers
.begin(); Read
!= Headers
.end(); ++Write
) {
243 while (Read
!= Headers
.end() &&
244 static_cast<Header
>(*Write
) == static_cast<Header
>(*Read
)) {
245 Write
->Hint
|= Read
->Hint
;
249 Headers
.erase(Write
, Headers
.end());
251 // Add name match hints to deduplicated providers.
252 llvm::StringRef SymbolName
= symbolName(S
);
253 for (auto &H
: Headers
) {
254 // Don't apply name match hints to standard headers as the standard headers
255 // are already ranked in the stdlib mapping.
256 if (H
.kind() == Header::Standard
)
258 if (nameMatch(SymbolName
, H
))
259 H
.Hint
|= Hints::PreferredHeader
;
262 // FIXME: Introduce a MainFile header kind or signal and boost it.
263 return ranked(std::move(Headers
));
265 } // namespace clang::include_cleaner