clang: lib/Tooling/Inclusions/HeaderIncludes.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
13#include "llvm/Support/FormatVariadic.h"
14#include "llvm/Support/Path.h"
15#include
16
18namespace tooling {
19namespace {
20
21LangOptions createLangOpts() {
22 LangOptions LangOpts;
23 LangOpts.CPlusPlus = 1;
24 LangOpts.CPlusPlus11 = 1;
25 LangOpts.CPlusPlus14 = 1;
26 LangOpts.LineComment = 1;
27 LangOpts.CXXOperatorNames = 1;
28 LangOpts.Bool = 1;
29 LangOpts.ObjC = 1;
30 LangOpts.MicrosoftExt = 1;
31 LangOpts.DeclSpecKeyword = 1;
32 LangOpts.WChar = 1;
33 return LangOpts;
34}
35
36
37
38
39
40unsigned getOffsetAfterTokenSequence(
41 StringRef FileName, StringRef Code, const IncludeStyle &Style,
42 llvm::function_ref<unsigned(const SourceManager &, Lexer &, Token &)>
43 GetOffsetAfterSequence) {
44 SourceManagerForFile VirtualSM(FileName, Code);
45 SourceManager &SM = VirtualSM.get();
46 LangOptions LangOpts = createLangOpts();
47 Lexer Lex(SM.getMainFileID(), SM.getBufferOrFake(SM.getMainFileID()), SM,
48 LangOpts);
49 Token Tok;
50
51 Lex.LexFromRawLexer(Tok);
52 return GetOffsetAfterSequence(SM, Lex, Tok);
53}
54
55
56
57
58
59bool checkAndConsumeDirectiveWithName(
60 Lexer &Lex, StringRef Name, Token &Tok,
61 std::optional RawIDName = std::nullopt) {
62 bool Matched = Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) &&
63 Tok.is(tok::raw_identifier) &&
64 Tok.getRawIdentifier() == Name && !Lex.LexFromRawLexer(Tok) &&
65 Tok.is(tok::raw_identifier) &&
66 (!RawIDName || Tok.getRawIdentifier() == *RawIDName);
67 if (Matched)
68 Lex.LexFromRawLexer(Tok);
69 return Matched;
70}
71
72void skipComments(Lexer &Lex, Token &Tok) {
73 while (Tok.is(tok::comment))
74 if (Lex.LexFromRawLexer(Tok))
75 return;
76}
77
78
79
80
81
82unsigned getOffsetAfterHeaderGuardsAndComments(StringRef FileName,
83 StringRef Code,
84 const IncludeStyle &Style) {
85
86
87 auto ConsumeHeaderGuardAndComment =
88 [&](std::function<unsigned(const SourceManager &SM, Lexer &Lex,
89 Token Tok)>
90 Consume) {
91 return getOffsetAfterTokenSequence(
93 [&Consume](const SourceManager &SM, Lexer &Lex, Token Tok) {
94 skipComments(Lex, Tok);
95 unsigned InitialOffset = SM.getFileOffset(Tok.getLocation());
96 return std::max(InitialOffset, Consume(SM, Lex, Tok));
97 });
98 };
99 return std::max(
100
101 ConsumeHeaderGuardAndComment(
102 [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned {
103 if (checkAndConsumeDirectiveWithName(Lex, "ifndef", Tok)) {
104 skipComments(Lex, Tok);
105 if (checkAndConsumeDirectiveWithName(Lex, "define", Tok) &&
106 Tok.isAtStartOfLine())
107 return SM.getFileOffset(Tok.getLocation());
108 }
109 return 0;
110 }),
111
112 ConsumeHeaderGuardAndComment(
113 [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned {
114 if (checkAndConsumeDirectiveWithName(Lex, "pragma", Tok,
115 StringRef("once")))
116 return SM.getFileOffset(Tok.getLocation());
117 return 0;
118 }));
119}
120
121
122
123
124
125bool checkAndConsumeInclusiveDirective(Lexer &Lex, Token &Tok) {
126 auto Matched = [&]() {
127 Lex.LexFromRawLexer(Tok);
128 return true;
129 };
130 if (Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) &&
131 Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == "include") {
132 if (Lex.LexFromRawLexer(Tok))
133 return false;
134 if (Tok.is(tok::string_literal))
135 return Matched();
136 if (Tok.is(tok::less)) {
137 while (!Lex.LexFromRawLexer(Tok) && Tok.isNot(tok::greater)) {
138 }
139 if (Tok.is(tok::greater))
140 return Matched();
141 }
142 }
143 return false;
144}
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159unsigned getMaxHeaderInsertionOffset(StringRef FileName, StringRef Code,
160 const IncludeStyle &Style) {
161 return getOffsetAfterTokenSequence(
163 [](const SourceManager &SM, Lexer &Lex, Token Tok) {
164 skipComments(Lex, Tok);
165 unsigned MaxOffset = SM.getFileOffset(Tok.getLocation());
166 while (checkAndConsumeInclusiveDirective(Lex, Tok))
167 MaxOffset = SM.getFileOffset(Tok.getLocation());
169 });
170}
171
172inline StringRef trimInclude(StringRef IncludeName) {
173 return IncludeName.trim("\"<>");
174}
175
176const char IncludeRegexPattern[] =
177 R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))";
178
179
180
181
182
183
184StringRef matchingStem(llvm::StringRef Path) {
185 StringRef Name = llvm::sys::path::filename(Path);
186 return Name.substr(0, Name.find('.', 1));
187}
188
189}
190
194 for (const auto &Category : Style.IncludeCategories) {
195 CategoryRegexs.emplace_back(Category.Regex, Category.RegexIsCaseSensitive
196 ? llvm::Regex::NoFlags
197 : llvm::Regex::IgnoreCase);
198 }
199 IsMainFile = FileName.ends_with(".c") || FileName.ends_with(".cc") ||
203 if (!Style.IncludeIsMainSourceRegex.empty()) {
204 llvm::Regex MainFileRegex(Style.IncludeIsMainSourceRegex);
205 IsMainFile |= MainFileRegex.match(FileName);
206 }
207}
208
210 bool CheckMainHeader) const {
212 for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i)
213 if (CategoryRegexs[i].match(IncludeName)) {
214 Ret = Style.IncludeCategories[i].Priority;
215 break;
216 }
217 if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName))
218 Ret = 0;
219 return Ret;
220}
221
223 bool CheckMainHeader) const {
225 for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i)
226 if (CategoryRegexs[i].match(IncludeName)) {
227 Ret = Style.IncludeCategories[i].SortPriority;
228 if (Ret == 0)
229 Ret = Style.IncludeCategories[i].Priority;
230 break;
231 }
232 if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName))
233 Ret = 0;
234 return Ret;
235}
236bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const {
237 switch (Style.MainIncludeChar) {
239 if (!IncludeName.starts_with("\""))
240 return false;
241 break;
243 if (!IncludeName.starts_with("<"))
244 return false;
245 break;
247 break;
248 }
249
250 IncludeName =
251 IncludeName.drop_front(1).drop_back(1);
252
253
254 StringRef HeaderStem = llvm::sys::path::stem(IncludeName);
255 StringRef FileStem = llvm::sys::path::stem(FileName);
256 StringRef MatchingFileStem = matchingStem(FileName);
257
258
259
260
261
262
263
264
265 StringRef Matching;
266 if (MatchingFileStem.starts_with_insensitive(HeaderStem))
267 Matching = MatchingFileStem;
268 else if (FileStem.equals_insensitive(HeaderStem))
269 Matching = FileStem;
270 if (!Matching.empty()) {
271 llvm::Regex MainIncludeRegex(HeaderStem.str() + Style.IncludeIsMainRegex,
272 llvm::Regex::IgnoreCase);
273 if (MainIncludeRegex.match(Matching))
274 return true;
275 }
276 return false;
277}
278
280
284 MinInsertOffset(
285 getOffsetAfterHeaderGuardsAndComments(FileName, Code, Style)),
286 MaxInsertOffset(MinInsertOffset +
287 getMaxHeaderInsertionOffset(
288 FileName, Code.drop_front(MinInsertOffset), Style)),
289 MainIncludeFound(false),
290 Categories(Style, FileName) {
291
292
293 Priorities = {0, INT_MAX};
294 for (const auto &Category : Style.IncludeCategories)
295 Priorities.insert(Category.Priority);
297 Code.drop_front(MinInsertOffset).split(Lines, "\n");
298
299 unsigned Offset = MinInsertOffset;
300 unsigned NextLineOffset;
302 for (auto Line : Lines) {
303 NextLineOffset = std::min(Code.size(), Offset + Line.size() + 1);
305
306
307 addExistingInclude(
310 Offset, std::min(Line.size() + 1, Code.size() - Offset)),
313 NextLineOffset);
314 }
315 Offset = NextLineOffset;
316 }
317
318
319
320
321
322 auto Highest = Priorities.begin();
323 auto [It, Inserted] = CategoryEndOffsets.try_emplace(*Highest);
324 if (Inserted)
325 It->second = FirstIncludeOffset >= 0 ? FirstIncludeOffset : MinInsertOffset;
326
327
328
329
330 for (auto I = ++Priorities.begin(), E = Priorities.end(); I != E; ++I)
331 if (CategoryEndOffsets.find(*I) == CategoryEndOffsets.end())
332 CategoryEndOffsets[*I] = CategoryEndOffsets[*std::prev(I)];
333}
334
335
336void HeaderIncludes::addExistingInclude(Include IncludeToAdd,
337 unsigned NextLineOffset) {
338 auto &Incs = ExistingIncludes[trimInclude(IncludeToAdd.Name)];
339 Incs.push_back(std::move(IncludeToAdd));
340 auto &CurInclude = Incs.back();
341
342
343 if (CurInclude.R.getOffset() <= MaxInsertOffset) {
345 CurInclude.Name, !MainIncludeFound);
347 MainIncludeFound = true;
348 CategoryEndOffsets[Priority] = NextLineOffset;
349 IncludesByPriority[Priority].push_back(&CurInclude);
350 if (FirstIncludeOffset < 0)
351 FirstIncludeOffset = CurInclude.R.getOffset();
352 }
353}
354
355std::optionaltooling::Replacement
358 assert(IncludeName == trimInclude(IncludeName));
359
360
361
362 auto It = ExistingIncludes.find(IncludeName);
363 if (It != ExistingIncludes.end()) {
364 for (const auto &Inc : It->second)
365 if (Inc.Directive == Directive &&
366 ((IsAngled && StringRef(Inc.Name).starts_with("<")) ||
367 (!IsAngled && StringRef(Inc.Name).starts_with("\""))))
368 return std::nullopt;
369 }
370 std::string Quoted =
371 std::string(llvm::formatv(IsAngled ? "<{0}>" : "\"{0}\"", IncludeName));
372 StringRef QuotedName = Quoted;
374 QuotedName, !MainIncludeFound);
375 auto CatOffset = CategoryEndOffsets.find(Priority);
376 assert(CatOffset != CategoryEndOffsets.end());
377 unsigned InsertOffset = CatOffset->second;
378 auto Iter = IncludesByPriority.find(Priority);
379 if (Iter != IncludesByPriority.end()) {
380 for (const auto *Inc : Iter->second) {
381 if (QuotedName < Inc->Name) {
382 InsertOffset = Inc->R.getOffset();
383 break;
384 }
385 }
386 }
387 assert(InsertOffset <= Code.size());
388 llvm::StringRef DirectiveSpelling =
390 std::string NewInclude =
391 llvm::formatv("#{0} {1}\n", DirectiveSpelling, QuotedName);
392
393
394
395
396 if (InsertOffset == Code.size() && (!Code.empty() && Code.back() != '\n'))
397 NewInclude = "\n" + NewInclude;
399}
400
402 bool IsAngled) const {
403 assert(IncludeName == trimInclude(IncludeName));
405 auto Iter = ExistingIncludes.find(IncludeName);
406 if (Iter == ExistingIncludes.end())
408 for (const auto &Inc : Iter->second) {
409 if ((IsAngled && StringRef(Inc.Name).starts_with("\"")) ||
410 (!IsAngled && StringRef(Inc.Name).starts_with("<")))
411 continue;
413 FileName, Inc.R.getOffset(), Inc.R.getLength(), ""));
414 if (Err) {
415 auto ErrMsg = "Unexpected conflicts in #include deletions: " +
416 llvm::toString(std::move(Err));
417 llvm_unreachable(ErrMsg.c_str());
418 }
419 }
421}
422
423}
424}
Defines the clang::FileManager interface and associated types.
Defines the SourceManager interface.
Directive - Abstract class representing a parsed verify directive.
SmallVector< BoundNodes, 1 > match(MatcherT Matcher, const NodeT &Node, ASTContext &Context)
Returns the results of matching Matcher on Node.
The JSON file list parser is used to communicate input to InstallAPI.
@ Result
The result type of a method or function.