1 //===--- Headers.cpp - Include headers ---------------------------*- C++-*-===//
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 //===----------------------------------------------------------------------===//
11 #include "SourceCode.h"
12 #include "clang/Basic/SourceLocation.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Frontend/CompilerInstance.h"
15 #include "clang/Lex/DirectoryLookup.h"
16 #include "clang/Lex/HeaderSearch.h"
17 #include "clang/Lex/PPCallbacks.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Tooling/Inclusions/HeaderAnalysis.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/ADT/StringRef.h"
22 #include "llvm/Support/Path.h"
30 class IncludeStructure::RecordHeaders
: public PPCallbacks
{
32 RecordHeaders(const CompilerInstance
&CI
, IncludeStructure
*Out
)
33 : SM(CI
.getSourceManager()),
36 // Record existing #includes - both written and resolved paths. Only #includes
37 // in the main file are collected.
38 void InclusionDirective(SourceLocation HashLoc
, const Token
&IncludeTok
,
39 llvm::StringRef FileName
, bool IsAngled
,
40 CharSourceRange
/*FilenameRange*/,
41 OptionalFileEntryRef File
,
42 llvm::StringRef
/*SearchPath*/,
43 llvm::StringRef
/*RelativePath*/,
44 const clang::Module
* /*SuggestedModule*/,
45 bool /*ModuleImported*/,
46 SrcMgr::CharacteristicKind FileKind
) override
{
47 auto MainFID
= SM
.getMainFileID();
48 // If an include is part of the preamble patch, translate #line directives.
50 HashLoc
= translatePreamblePatchLocation(HashLoc
, SM
);
52 // Record main-file inclusions (including those mapped from the preamble
54 if (isInsideMainFile(HashLoc
, SM
)) {
55 Out
->MainFileIncludes
.emplace_back();
56 auto &Inc
= Out
->MainFileIncludes
.back();
58 (IsAngled
? "<" + FileName
+ ">" : "\"" + FileName
+ "\"").str();
59 Inc
.Resolved
= std::string(
60 File
? getCanonicalPath(*File
, SM
.getFileManager()).value_or("")
62 Inc
.HashOffset
= SM
.getFileOffset(HashLoc
);
64 SM
.getLineNumber(SM
.getFileID(HashLoc
), Inc
.HashOffset
) - 1;
65 Inc
.FileKind
= FileKind
;
66 Inc
.Directive
= IncludeTok
.getIdentifierInfo()->getPPKeywordID();
68 IncludeStructure::HeaderID HID
= Out
->getOrCreateID(*File
);
69 Inc
.HeaderID
= static_cast<unsigned>(HID
);
71 if (auto StdlibHeader
= tooling::stdlib::Header::named(Inc
.Written
)) {
72 auto &IDs
= Out
->StdlibHeaders
[*StdlibHeader
];
73 // Few physical files for one stdlib header name, linear scan is ok.
74 if (!llvm::is_contained(IDs
, HID
))
78 Out
->MainFileIncludesBySpelling
.try_emplace(Inc
.Written
)
79 .first
->second
.push_back(Out
->MainFileIncludes
.size() - 1);
82 // Record include graph (not just for main-file includes)
84 auto IncludingFileEntry
= SM
.getFileEntryRefForID(SM
.getFileID(HashLoc
));
85 if (!IncludingFileEntry
) {
86 assert(SM
.getBufferName(HashLoc
).starts_with("<") &&
87 "Expected #include location to be a file or <built-in>");
88 // Treat as if included from the main file.
89 IncludingFileEntry
= SM
.getFileEntryRefForID(MainFID
);
91 auto IncludingID
= Out
->getOrCreateID(*IncludingFileEntry
),
92 IncludedID
= Out
->getOrCreateID(*File
);
93 Out
->IncludeChildren
[IncludingID
].push_back(IncludedID
);
97 void FileChanged(SourceLocation Loc
, FileChangeReason Reason
,
98 SrcMgr::CharacteristicKind FileType
,
99 FileID PrevFID
) override
{
101 case PPCallbacks::EnterFile
:
103 if (BuiltinFile
.isInvalid() && SM
.isWrittenInBuiltinFile(Loc
)) {
104 BuiltinFile
= SM
.getFileID(Loc
);
105 InBuiltinFile
= true;
108 case PPCallbacks::ExitFile
: {
110 if (PrevFID
== BuiltinFile
)
111 InBuiltinFile
= false;
114 case PPCallbacks::RenameFile
:
115 case PPCallbacks::SystemHeaderPragma
:
121 // Keeps track of include depth for the current file. It's 1 for main file.
123 bool inMainFile() const { return Level
== 1; }
125 const SourceManager
&SM
;
126 // Set after entering the <built-in> file.
128 // Indicates whether <built-in> file is part of include stack.
129 bool InBuiltinFile
= false;
131 IncludeStructure
*Out
;
134 bool isLiteralInclude(llvm::StringRef Include
) {
135 return Include
.starts_with("<") || Include
.starts_with("\"");
138 bool HeaderFile::valid() const {
139 return (Verbatim
&& isLiteralInclude(File
)) ||
140 (!Verbatim
&& llvm::sys::path::is_absolute(File
));
143 llvm::Expected
<HeaderFile
> toHeaderFile(llvm::StringRef Header
,
144 llvm::StringRef HintPath
) {
145 if (isLiteralInclude(Header
))
146 return HeaderFile
{Header
.str(), /*Verbatim=*/true};
147 auto U
= URI::parse(Header
);
149 return U
.takeError();
151 auto IncludePath
= URI::includeSpelling(*U
);
153 return IncludePath
.takeError();
154 if (!IncludePath
->empty())
155 return HeaderFile
{std::move(*IncludePath
), /*Verbatim=*/true};
157 auto Resolved
= URI::resolve(*U
, HintPath
);
159 return Resolved
.takeError();
160 return HeaderFile
{std::move(*Resolved
), /*Verbatim=*/false};
163 llvm::SmallVector
<SymbolInclude
, 1> getRankedIncludes(const Symbol
&Sym
) {
164 auto Includes
= Sym
.IncludeHeaders
;
165 // Sort in descending order by reference count and header length.
166 llvm::sort(Includes
, [](const Symbol::IncludeHeaderWithReferences
&LHS
,
167 const Symbol::IncludeHeaderWithReferences
&RHS
) {
168 if (LHS
.References
== RHS
.References
)
169 return LHS
.IncludeHeader
.size() < RHS
.IncludeHeader
.size();
170 return LHS
.References
> RHS
.References
;
172 llvm::SmallVector
<SymbolInclude
, 1> Headers
;
173 for (const auto &Include
: Includes
)
174 Headers
.push_back({Include
.IncludeHeader
, Include
.supportedDirectives()});
178 void IncludeStructure::collect(const CompilerInstance
&CI
) {
179 auto &SM
= CI
.getSourceManager();
180 MainFileEntry
= SM
.getFileEntryForID(SM
.getMainFileID());
181 auto Collector
= std::make_unique
<RecordHeaders
>(CI
, this);
182 CI
.getPreprocessor().addPPCallbacks(std::move(Collector
));
184 // If we're reusing a preamble, don't repopulate SearchPathsCanonical.
185 // The entries will be the same, but canonicalizing to find out is expensive!
186 if (SearchPathsCanonical
.empty()) {
187 for (const auto &Dir
:
188 CI
.getPreprocessor().getHeaderSearchInfo().search_dir_range()) {
189 if (Dir
.getLookupType() == DirectoryLookup::LT_NormalDir
)
190 SearchPathsCanonical
.emplace_back(
191 SM
.getFileManager().getCanonicalName(*Dir
.getDirRef()));
196 std::optional
<IncludeStructure::HeaderID
>
197 IncludeStructure::getID(const FileEntry
*Entry
) const {
198 // HeaderID of the main file is always 0;
199 if (Entry
== MainFileEntry
) {
200 return static_cast<IncludeStructure::HeaderID
>(0u);
202 auto It
= UIDToIndex
.find(Entry
->getUniqueID());
203 if (It
== UIDToIndex
.end())
208 IncludeStructure::HeaderID
IncludeStructure::getOrCreateID(FileEntryRef Entry
) {
209 // Main file's FileEntry was not known at IncludeStructure creation time.
210 if (&Entry
.getFileEntry() == MainFileEntry
) {
211 if (RealPathNames
.front().empty())
212 RealPathNames
.front() = MainFileEntry
->tryGetRealPathName().str();
215 auto R
= UIDToIndex
.try_emplace(
217 static_cast<IncludeStructure::HeaderID
>(RealPathNames
.size()));
219 RealPathNames
.emplace_back();
220 IncludeStructure::HeaderID Result
= R
.first
->getSecond();
221 std::string
&RealPathName
= RealPathNames
[static_cast<unsigned>(Result
)];
222 if (RealPathName
.empty())
223 RealPathName
= Entry
.getFileEntry().tryGetRealPathName().str();
227 llvm::DenseMap
<IncludeStructure::HeaderID
, unsigned>
228 IncludeStructure::includeDepth(HeaderID Root
) const {
229 // Include depth 0 is the main file only.
230 llvm::DenseMap
<HeaderID
, unsigned> Result
;
231 assert(static_cast<unsigned>(Root
) < RealPathNames
.size());
233 std::vector
<IncludeStructure::HeaderID
> CurrentLevel
;
234 CurrentLevel
.push_back(Root
);
235 llvm::DenseSet
<IncludeStructure::HeaderID
> Seen
;
238 // Each round of BFS traversal finds the next depth level.
239 std::vector
<IncludeStructure::HeaderID
> PreviousLevel
;
240 for (unsigned Level
= 1; !CurrentLevel
.empty(); ++Level
) {
241 PreviousLevel
.clear();
242 PreviousLevel
.swap(CurrentLevel
);
243 for (const auto &Parent
: PreviousLevel
) {
244 for (const auto &Child
: IncludeChildren
.lookup(Parent
)) {
245 if (Seen
.insert(Child
).second
) {
246 CurrentLevel
.push_back(Child
);
247 Result
[Child
] = Level
;
255 llvm::SmallVector
<const Inclusion
*>
256 IncludeStructure::mainFileIncludesWithSpelling(llvm::StringRef Spelling
) const {
257 llvm::SmallVector
<const Inclusion
*> Includes
;
258 for (auto Idx
: MainFileIncludesBySpelling
.lookup(Spelling
))
259 Includes
.push_back(&MainFileIncludes
[Idx
]);
263 void IncludeInserter::addExisting(const Inclusion
&Inc
) {
264 IncludedHeaders
.insert(Inc
.Written
);
265 if (!Inc
.Resolved
.empty())
266 IncludedHeaders
.insert(Inc
.Resolved
);
269 /// FIXME(ioeric): we might not want to insert an absolute include path if the
270 /// path is not shortened.
271 bool IncludeInserter::shouldInsertInclude(
272 PathRef DeclaringHeader
, const HeaderFile
&InsertedHeader
) const {
273 assert(InsertedHeader
.valid());
274 if (!HeaderSearchInfo
&& !InsertedHeader
.Verbatim
)
276 if (FileName
== DeclaringHeader
|| FileName
== InsertedHeader
.File
)
278 auto Included
= [&](llvm::StringRef Header
) {
279 return IncludedHeaders
.contains(Header
);
281 return !Included(DeclaringHeader
) && !Included(InsertedHeader
.File
);
284 std::optional
<std::string
>
285 IncludeInserter::calculateIncludePath(const HeaderFile
&InsertedHeader
,
286 llvm::StringRef IncludingFile
) const {
287 assert(InsertedHeader
.valid());
288 if (InsertedHeader
.Verbatim
)
289 return InsertedHeader
.File
;
290 bool IsAngled
= false;
291 std::string Suggested
;
292 if (HeaderSearchInfo
) {
293 Suggested
= HeaderSearchInfo
->suggestPathToFileForDiagnostics(
294 InsertedHeader
.File
, BuildDir
, IncludingFile
, &IsAngled
);
296 // Calculate include relative to including file only.
297 StringRef IncludingDir
= llvm::sys::path::parent_path(IncludingFile
);
298 SmallString
<256> RelFile(InsertedHeader
.File
);
299 // Replacing with "" leaves "/RelFile" if IncludingDir doesn't end in "/".
300 llvm::sys::path::replace_path_prefix(RelFile
, IncludingDir
, "./");
301 Suggested
= llvm::sys::path::convert_to_slash(
302 llvm::sys::path::remove_leading_dotslash(RelFile
));
304 // FIXME: should we allow (some limited number of) "../header.h"?
305 if (llvm::sys::path::is_absolute(Suggested
))
308 Suggested
= "<" + Suggested
+ ">";
310 Suggested
= "\"" + Suggested
+ "\"";
314 std::optional
<TextEdit
>
315 IncludeInserter::insert(llvm::StringRef VerbatimHeader
,
316 tooling::IncludeDirective Directive
) const {
317 std::optional
<TextEdit
> Edit
;
319 Inserter
.insert(VerbatimHeader
.trim("\"<>"),
320 VerbatimHeader
.starts_with("<"), Directive
))
321 Edit
= replacementToEdit(Code
, *Insertion
);
325 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const Inclusion
&Inc
) {
326 return OS
<< Inc
.Written
<< " = "
327 << (!Inc
.Resolved
.empty() ? Inc
.Resolved
: "[unresolved]")
328 << " at line" << Inc
.HashLine
;
331 bool operator==(const Inclusion
&LHS
, const Inclusion
&RHS
) {
332 return std::tie(LHS
.Directive
, LHS
.FileKind
, LHS
.HashOffset
, LHS
.HashLine
,
333 LHS
.Resolved
, LHS
.Written
) ==
334 std::tie(RHS
.Directive
, RHS
.FileKind
, RHS
.HashOffset
, RHS
.HashLine
,
335 RHS
.Resolved
, RHS
.Written
);
338 } // namespace clangd