1 | //===--- FindHeadersTest.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 "AnalysisInternal.h" |
10 | #include "TypesInternal.h" |
11 | #include "clang-include-cleaner/Analysis.h" |
12 | #include "clang-include-cleaner/Record.h" |
13 | #include "clang-include-cleaner/Types.h" |
14 | #include "clang/AST/Expr.h" |
15 | #include "clang/AST/RecursiveASTVisitor.h" |
16 | #include "clang/Basic/FileEntry.h" |
17 | #include "clang/Basic/FileManager.h" |
18 | #include "clang/Basic/LLVM.h" |
19 | #include "clang/Frontend/FrontendActions.h" |
20 | #include "clang/Testing/TestAST.h" |
21 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
22 | #include "llvm/ADT/SmallVector.h" |
23 | #include "llvm/ADT/StringRef.h" |
24 | #include "gmock/gmock.h" |
25 | #include "gtest/gtest.h" |
26 | #include <cassert> |
27 | #include <memory> |
28 | |
29 | namespace clang::include_cleaner { |
30 | namespace { |
31 | using testing::ElementsAre; |
32 | using testing::UnorderedElementsAre; |
33 | |
34 | std::string guard(llvm::StringRef Code) { |
35 | return "#pragma once\n" + Code.str(); |
36 | } |
37 | |
38 | class : public testing::Test { |
39 | protected: |
40 | TestInputs ; |
41 | PragmaIncludes ; |
42 | std::unique_ptr<TestAST> ; |
43 | () { |
44 | Inputs.MakeAction = [this] { |
45 | struct Hook : public SyntaxOnlyAction { |
46 | public: |
47 | Hook(PragmaIncludes *Out) : Out(Out) {} |
48 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
49 | Out->record(CI); |
50 | return true; |
51 | } |
52 | |
53 | PragmaIncludes *Out; |
54 | }; |
55 | return std::make_unique<Hook>(args: &PI); |
56 | }; |
57 | } |
58 | void () { AST = std::make_unique<TestAST>(args&: Inputs); } |
59 | |
60 | llvm::SmallVector<Hinted<Header>> (llvm::StringRef FileName) { |
61 | return include_cleaner::findHeaders( |
62 | Loc: AST->sourceManager().translateFileLineCol( |
63 | SourceFile: AST->fileManager().getFile(Filename: FileName).get(), |
64 | /*Line=*/1, /*Col=*/1), |
65 | SM: AST->sourceManager(), PI: &PI); |
66 | } |
67 | FileEntryRef (llvm::StringRef FileName) { |
68 | return *AST->fileManager().getOptionalFileRef(Filename: FileName); |
69 | }; |
70 | }; |
71 | |
72 | TEST_F(FindHeadersTest, IWYUPrivateToPublic) { |
73 | Inputs.Code = R"cpp( |
74 | #include "private.h" |
75 | )cpp" ; |
76 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
77 | // IWYU pragma: private, include "path/public.h" |
78 | )cpp" ); |
79 | buildAST(); |
80 | EXPECT_THAT(findHeaders("private.h" ), |
81 | UnorderedElementsAre(physicalHeader("private.h" ), |
82 | Header("\"path/public.h\"" ))); |
83 | } |
84 | |
85 | TEST_F(FindHeadersTest, IWYUExport) { |
86 | Inputs.Code = R"cpp( |
87 | #include "exporter.h" |
88 | )cpp" ; |
89 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
90 | #include "exported1.h" // IWYU pragma: export |
91 | |
92 | // IWYU pragma: begin_exports |
93 | #include "exported2.h" |
94 | // IWYU pragma: end_exports |
95 | |
96 | #include "normal.h" |
97 | )cpp" ); |
98 | Inputs.ExtraFiles["exported1.h" ] = guard(Code: "" ); |
99 | Inputs.ExtraFiles["exported2.h" ] = guard(Code: "" ); |
100 | Inputs.ExtraFiles["normal.h" ] = guard(Code: "" ); |
101 | |
102 | buildAST(); |
103 | EXPECT_THAT(findHeaders("exported1.h" ), |
104 | UnorderedElementsAre(physicalHeader("exported1.h" ), |
105 | physicalHeader("exporter.h" ))); |
106 | EXPECT_THAT(findHeaders("exported2.h" ), |
107 | UnorderedElementsAre(physicalHeader("exported2.h" ), |
108 | physicalHeader("exporter.h" ))); |
109 | EXPECT_THAT(findHeaders("normal.h" ), |
110 | UnorderedElementsAre(physicalHeader("normal.h" ))); |
111 | EXPECT_THAT(findHeaders("exporter.h" ), |
112 | UnorderedElementsAre(physicalHeader("exporter.h" ))); |
113 | } |
114 | |
115 | TEST_F(FindHeadersTest, IWYUExportForStandardHeaders) { |
116 | Inputs.Code = R"cpp( |
117 | #include "exporter.h" |
118 | )cpp" ; |
119 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
120 | #include <string> // IWYU pragma: export |
121 | )cpp" ); |
122 | Inputs.ExtraFiles["string" ] = guard(Code: "" ); |
123 | Inputs.ExtraArgs.push_back(x: "-isystem." ); |
124 | buildAST(); |
125 | tooling::stdlib::Symbol StdString = |
126 | *tooling::stdlib::Symbol::named(Scope: "std::" , Name: "string" ); |
127 | EXPECT_THAT( |
128 | include_cleaner::findHeaders(StdString, AST->sourceManager(), &PI), |
129 | UnorderedElementsAre(physicalHeader("exporter.h" ), StdString.header())); |
130 | } |
131 | |
132 | TEST_F(FindHeadersTest, SelfContained) { |
133 | Inputs.Code = R"cpp( |
134 | #include "header.h" |
135 | )cpp" ; |
136 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
137 | #include "fragment.inc" |
138 | )cpp" ); |
139 | Inputs.ExtraFiles["fragment.inc" ] = "" ; |
140 | buildAST(); |
141 | EXPECT_THAT(findHeaders("fragment.inc" ), |
142 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
143 | physicalHeader("header.h" ))); |
144 | } |
145 | |
146 | TEST_F(FindHeadersTest, NonSelfContainedTraversePrivate) { |
147 | Inputs.Code = R"cpp( |
148 | #include "header.h" |
149 | )cpp" ; |
150 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
151 | #include "fragment.inc" |
152 | )cpp" ); |
153 | Inputs.ExtraFiles["fragment.inc" ] = R"cpp( |
154 | // IWYU pragma: private, include "public.h" |
155 | )cpp" ; |
156 | |
157 | buildAST(); |
158 | // There is a IWYU private mapping in the non self-contained header, verify |
159 | // that we don't emit its includer. |
160 | EXPECT_THAT(findHeaders("fragment.inc" ), |
161 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
162 | Header("\"public.h\"" ))); |
163 | } |
164 | |
165 | TEST_F(FindHeadersTest, NonSelfContainedTraverseExporter) { |
166 | Inputs.Code = R"cpp( |
167 | #include "exporter.h" |
168 | )cpp" ; |
169 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
170 | #include "exported.h" // IWYU pragma: export |
171 | )cpp" ); |
172 | Inputs.ExtraFiles["exported.h" ] = guard(Code: R"cpp( |
173 | #include "fragment.inc" |
174 | )cpp" ); |
175 | Inputs.ExtraFiles["fragment.inc" ] = "" ; |
176 | buildAST(); |
177 | // Verify that we emit exporters for each header on the path. |
178 | EXPECT_THAT(findHeaders("fragment.inc" ), |
179 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
180 | physicalHeader("exported.h" ), |
181 | physicalHeader("exporter.h" ))); |
182 | } |
183 | |
184 | TEST_F(FindHeadersTest, TargetIsExpandedFromMacroInHeader) { |
185 | struct CustomVisitor : RecursiveASTVisitor<CustomVisitor> { |
186 | const Decl *Out = nullptr; |
187 | bool VisitNamedDecl(const NamedDecl *ND) { |
188 | if (ND->getName() == "FLAG_foo" || ND->getName() == "Foo" ) { |
189 | EXPECT_TRUE(Out == nullptr); |
190 | Out = ND; |
191 | } |
192 | return true; |
193 | } |
194 | }; |
195 | |
196 | struct { |
197 | llvm::StringRef ; |
198 | llvm::StringRef ; |
199 | } TestCases[] = { |
200 | {/*MacroHeader=*/R"cpp( |
201 | #define DEFINE_CLASS(name) class name {}; |
202 | )cpp" , |
203 | /*DeclareHeader=*/R"cpp( |
204 | #include "macro.h" |
205 | DEFINE_CLASS(Foo) |
206 | )cpp" }, |
207 | {/*MacroHeader=*/R"cpp( |
208 | #define DEFINE_Foo class Foo {}; |
209 | )cpp" , |
210 | /*DeclareHeader=*/R"cpp( |
211 | #include "macro.h" |
212 | DEFINE_Foo |
213 | )cpp" }, |
214 | {/*MacroHeader=*/R"cpp( |
215 | #define DECLARE_FLAGS(name) extern int FLAG_##name |
216 | )cpp" , |
217 | /*DeclareHeader=*/R"cpp( |
218 | #include "macro.h" |
219 | DECLARE_FLAGS(foo); |
220 | )cpp" }, |
221 | }; |
222 | |
223 | for (const auto &T : TestCases) { |
224 | Inputs.Code = R"cpp(#include "declare.h")cpp" ; |
225 | Inputs.ExtraFiles["declare.h" ] = guard(Code: T.DeclareHeader); |
226 | Inputs.ExtraFiles["macro.h" ] = guard(Code: T.MacroHeader); |
227 | buildAST(); |
228 | |
229 | CustomVisitor Visitor; |
230 | Visitor.TraverseDecl(AST->context().getTranslationUnitDecl()); |
231 | |
232 | auto = clang::include_cleaner::findHeaders( |
233 | Loc: Visitor.Out->getLocation(), SM: AST->sourceManager(), |
234 | /*PragmaIncludes=*/PI: nullptr); |
235 | EXPECT_THAT(Headers, UnorderedElementsAre(physicalHeader("declare.h" ))); |
236 | } |
237 | } |
238 | |
239 | MATCHER_P2(, , , "" ) { |
240 | return std::tie(arg.Hint, arg) == std::tie(Hint, Header); |
241 | } |
242 | |
243 | TEST_F(FindHeadersTest, PublicHeaderHint) { |
244 | Inputs.Code = R"cpp( |
245 | #include "public.h" |
246 | )cpp" ; |
247 | Inputs.ExtraFiles["public.h" ] = guard(Code: R"cpp( |
248 | #include "private.h" |
249 | #include "private.inc" |
250 | )cpp" ); |
251 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
252 | // IWYU pragma: private |
253 | )cpp" ); |
254 | Inputs.ExtraFiles["private.inc" ] = "" ; |
255 | buildAST(); |
256 | // Non self-contained files and headers marked with IWYU private pragma |
257 | // shouldn't have PublicHeader hint. |
258 | EXPECT_THAT( |
259 | findHeaders("private.inc" ), |
260 | UnorderedElementsAre( |
261 | HintedHeader(physicalHeader("private.inc" ), Hints::OriginHeader), |
262 | HintedHeader(physicalHeader("public.h" ), Hints::PublicHeader))); |
263 | EXPECT_THAT(findHeaders("private.h" ), |
264 | UnorderedElementsAre(HintedHeader(physicalHeader("private.h" ), |
265 | Hints::OriginHeader))); |
266 | } |
267 | |
268 | TEST_F(FindHeadersTest, PreferredHeaderHint) { |
269 | Inputs.Code = R"cpp( |
270 | #include "private.h" |
271 | )cpp" ; |
272 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
273 | // IWYU pragma: private, include "public.h" |
274 | )cpp" ); |
275 | buildAST(); |
276 | // Headers explicitly marked should've preferred signal. |
277 | EXPECT_THAT( |
278 | findHeaders("private.h" ), |
279 | UnorderedElementsAre( |
280 | HintedHeader(physicalHeader("private.h" ), Hints::OriginHeader), |
281 | HintedHeader(Header("\"public.h\"" ), |
282 | Hints::PreferredHeader | Hints::PublicHeader))); |
283 | } |
284 | |
285 | class : public FindHeadersTest { |
286 | protected: |
287 | llvm::SmallVector<Header> (llvm::StringRef Name) { |
288 | struct Visitor : public RecursiveASTVisitor<Visitor> { |
289 | const NamedDecl *Out = nullptr; |
290 | llvm::StringRef Name; |
291 | Visitor(llvm::StringRef Name) : Name(Name) {} |
292 | bool VisitNamedDecl(const NamedDecl *ND) { |
293 | if (auto *TD = ND->getDescribedTemplate()) |
294 | ND = TD; |
295 | |
296 | if (ND->getName() == Name) { |
297 | EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl()) |
298 | << "Found multiple matches for " << Name << "." ; |
299 | Out = cast<NamedDecl>(ND->getCanonicalDecl()); |
300 | } |
301 | return true; |
302 | } |
303 | }; |
304 | Visitor V(Name); |
305 | V.TraverseDecl(AST->context().getTranslationUnitDecl()); |
306 | if (!V.Out) |
307 | ADD_FAILURE() << "Couldn't find any decls named " << Name << "." ; |
308 | assert(V.Out); |
309 | return headersForSymbol(*V.Out, AST->sourceManager(), &PI); |
310 | } |
311 | llvm::SmallVector<Header> () { return headersFor(Name: "foo" ); } |
312 | }; |
313 | |
314 | TEST_F(HeadersForSymbolTest, Deduplicates) { |
315 | Inputs.Code = R"cpp( |
316 | #include "foo.h" |
317 | )cpp" ; |
318 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
319 | // IWYU pragma: private, include "foo.h" |
320 | void foo(); |
321 | void foo(); |
322 | )cpp" ); |
323 | buildAST(); |
324 | EXPECT_THAT( |
325 | headersForFoo(), |
326 | UnorderedElementsAre(physicalHeader("foo.h" ), |
327 | // FIXME: de-duplicate across different kinds. |
328 | Header("\"foo.h\"" ))); |
329 | } |
330 | |
331 | TEST_F(HeadersForSymbolTest, RankByName) { |
332 | Inputs.Code = R"cpp( |
333 | #include "fox.h" |
334 | #include "bar.h" |
335 | )cpp" ; |
336 | Inputs.ExtraFiles["fox.h" ] = guard(Code: R"cpp( |
337 | void foo(); |
338 | )cpp" ); |
339 | Inputs.ExtraFiles["bar.h" ] = guard(Code: R"cpp( |
340 | void foo(); |
341 | )cpp" ); |
342 | buildAST(); |
343 | EXPECT_THAT(headersForFoo(), |
344 | ElementsAre(physicalHeader("bar.h" ), physicalHeader("fox.h" ))); |
345 | } |
346 | |
347 | TEST_F(HeadersForSymbolTest, Ranking) { |
348 | // Sorting is done over (public, complete, canonical, origin)-tuple. |
349 | Inputs.Code = R"cpp( |
350 | #include "private.h" |
351 | #include "public.h" |
352 | #include "public_complete.h" |
353 | #include "exporter.h" |
354 | )cpp" ; |
355 | Inputs.ExtraFiles["public.h" ] = guard(Code: R"cpp( |
356 | struct foo; |
357 | )cpp" ); |
358 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
359 | // IWYU pragma: private, include "canonical.h" |
360 | struct foo; |
361 | )cpp" ); |
362 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
363 | #include "private.h" // IWYU pragma: export |
364 | )cpp" ); |
365 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: "struct foo {};" ); |
366 | buildAST(); |
367 | EXPECT_THAT(headersForFoo(), |
368 | ElementsAre(physicalHeader("public_complete.h" ), |
369 | Header("\"canonical.h\"" ), physicalHeader("public.h" ), |
370 | physicalHeader("exporter.h" ), |
371 | physicalHeader("private.h" ))); |
372 | } |
373 | |
374 | TEST_F(HeadersForSymbolTest, PreferPublicOverComplete) { |
375 | Inputs.Code = R"cpp( |
376 | #include "complete_private.h" |
377 | #include "public.h" |
378 | )cpp" ; |
379 | Inputs.ExtraFiles["complete_private.h" ] = guard(Code: R"cpp( |
380 | // IWYU pragma: private |
381 | struct foo {}; |
382 | )cpp" ); |
383 | Inputs.ExtraFiles["public.h" ] = guard(Code: "struct foo;" ); |
384 | buildAST(); |
385 | EXPECT_THAT(headersForFoo(), |
386 | ElementsAre(physicalHeader("public.h" ), |
387 | physicalHeader("complete_private.h" ))); |
388 | } |
389 | |
390 | TEST_F(HeadersForSymbolTest, PreferNameMatch) { |
391 | Inputs.Code = R"cpp( |
392 | #include "public_complete.h" |
393 | #include "test/foo.fwd.h" |
394 | )cpp" ; |
395 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: "struct foo {};" ); |
396 | Inputs.ExtraFiles["test/foo.fwd.h" ] = guard(Code: "struct foo;" ); |
397 | buildAST(); |
398 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public_complete.h" ), |
399 | physicalHeader("test/foo.fwd.h" ))); |
400 | } |
401 | |
402 | TEST_F(HeadersForSymbolTest, MainFile) { |
403 | Inputs.Code = R"cpp( |
404 | #include "public_complete.h" |
405 | struct foo; |
406 | )cpp" ; |
407 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: R"cpp( |
408 | struct foo {}; |
409 | )cpp" ); |
410 | buildAST(); |
411 | auto &SM = AST->sourceManager(); |
412 | // FIXME: Symbols provided by main file should be treated specially. |
413 | EXPECT_THAT( |
414 | headersForFoo(), |
415 | ElementsAre(physicalHeader("public_complete.h" ), |
416 | Header(*SM.getFileEntryRefForID(SM.getMainFileID())))); |
417 | } |
418 | |
419 | TEST_F(HeadersForSymbolTest, PreferExporterOfPrivate) { |
420 | Inputs.Code = R"cpp( |
421 | #include "private.h" |
422 | #include "exporter.h" |
423 | )cpp" ; |
424 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
425 | // IWYU pragma: private |
426 | struct foo {}; |
427 | )cpp" ); |
428 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
429 | #include "private.h" // IWYU pragma: export |
430 | )cpp" ); |
431 | buildAST(); |
432 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h" ), |
433 | physicalHeader("private.h" ))); |
434 | } |
435 | |
436 | TEST_F(HeadersForSymbolTest, ExporterIsDownRanked) { |
437 | Inputs.Code = R"cpp( |
438 | #include "exporter.h" |
439 | #include "zoo.h" |
440 | )cpp" ; |
441 | // Deliberately named as zoo to make sure it doesn't get name-match boost and |
442 | // also gets lexicographically bigger order than "exporter". |
443 | Inputs.ExtraFiles["zoo.h" ] = guard(Code: R"cpp( |
444 | struct foo {}; |
445 | )cpp" ); |
446 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
447 | #include "zoo.h" // IWYU pragma: export |
448 | )cpp" ); |
449 | buildAST(); |
450 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h" ), |
451 | physicalHeader("exporter.h" ))); |
452 | } |
453 | |
454 | TEST_F(HeadersForSymbolTest, PreferPublicOverNameMatchOnPrivate) { |
455 | Inputs.Code = R"cpp( |
456 | #include "foo.h" |
457 | )cpp" ; |
458 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
459 | // IWYU pragma: private, include "public.h" |
460 | struct foo {}; |
461 | )cpp" ); |
462 | buildAST(); |
463 | EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"" )), |
464 | physicalHeader("foo.h" ))); |
465 | } |
466 | |
467 | TEST_F(HeadersForSymbolTest, PublicOverPrivateWithoutUmbrella) { |
468 | Inputs.Code = R"cpp( |
469 | #include "bar.h" |
470 | #include "foo.h" |
471 | )cpp" ; |
472 | Inputs.ExtraFiles["bar.h" ] = |
473 | guard(Code: R"cpp(#include "foo.h" // IWYU pragma: export)cpp" ); |
474 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
475 | // IWYU pragma: private |
476 | struct foo {}; |
477 | )cpp" ); |
478 | buildAST(); |
479 | EXPECT_THAT(headersForFoo(), |
480 | ElementsAre(physicalHeader("bar.h" ), physicalHeader("foo.h" ))); |
481 | } |
482 | |
483 | TEST_F(HeadersForSymbolTest, IWYUTransitiveExport) { |
484 | Inputs.Code = R"cpp( |
485 | #include "export1.h" |
486 | )cpp" ; |
487 | Inputs.ExtraFiles["export1.h" ] = guard(Code: R"cpp( |
488 | #include "export2.h" // IWYU pragma: export |
489 | )cpp" ); |
490 | Inputs.ExtraFiles["export2.h" ] = guard(Code: R"cpp( |
491 | #include "foo.h" // IWYU pragma: export |
492 | )cpp" ); |
493 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
494 | struct foo {}; |
495 | )cpp" ); |
496 | buildAST(); |
497 | EXPECT_THAT(headersForFoo(), |
498 | ElementsAre(physicalHeader("foo.h" ), physicalHeader("export1.h" ), |
499 | physicalHeader("export2.h" ))); |
500 | } |
501 | |
502 | TEST_F(HeadersForSymbolTest, IWYUTransitiveExportWithPrivate) { |
503 | Inputs.Code = R"cpp( |
504 | #include "export1.h" |
505 | void bar() { foo();} |
506 | )cpp" ; |
507 | Inputs.ExtraFiles["export1.h" ] = guard(Code: R"cpp( |
508 | // IWYU pragma: private, include "public1.h" |
509 | #include "export2.h" // IWYU pragma: export |
510 | void foo(); |
511 | )cpp" ); |
512 | Inputs.ExtraFiles["export2.h" ] = guard(Code: R"cpp( |
513 | // IWYU pragma: private, include "public2.h" |
514 | #include "export3.h" // IWYU pragma: export |
515 | )cpp" ); |
516 | Inputs.ExtraFiles["export3.h" ] = guard(Code: R"cpp( |
517 | // IWYU pragma: private, include "public3.h" |
518 | #include "foo.h" // IWYU pragma: export |
519 | )cpp" ); |
520 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
521 | void foo(); |
522 | )cpp" ); |
523 | buildAST(); |
524 | EXPECT_THAT(headersForFoo(), |
525 | ElementsAre(physicalHeader("foo.h" ), |
526 | Header(StringRef("\"public1.h\"" )), |
527 | physicalHeader("export1.h" ), |
528 | physicalHeader("export2.h" ), |
529 | physicalHeader("export3.h" ))); |
530 | } |
531 | |
532 | TEST_F(HeadersForSymbolTest, AmbiguousStdSymbols) { |
533 | struct { |
534 | llvm::StringRef Code; |
535 | llvm::StringRef Name; |
536 | |
537 | llvm::StringRef ; |
538 | } TestCases[] = { |
539 | { |
540 | .Code: R"cpp( |
541 | namespace std { |
542 | template <typename InputIt, typename OutputIt> |
543 | constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest); |
544 | })cpp" , |
545 | .Name: "move" , |
546 | .ExpectedHeader: "<algorithm>" , |
547 | }, |
548 | { |
549 | .Code: R"cpp( |
550 | namespace std { |
551 | template<class ExecutionPolicy, class ForwardIt1, class ForwardIt2> |
552 | ForwardIt2 move(ExecutionPolicy&& policy, |
553 | ForwardIt1 first, ForwardIt1 last, ForwardIt2 d_first); |
554 | })cpp" , |
555 | .Name: "move" , |
556 | .ExpectedHeader: "<algorithm>" , |
557 | }, |
558 | { |
559 | .Code: R"cpp( |
560 | namespace std { |
561 | template<typename T> constexpr T move(T&& t) noexcept; |
562 | })cpp" , |
563 | .Name: "move" , |
564 | .ExpectedHeader: "<utility>" , |
565 | }, |
566 | { |
567 | .Code: R"cpp( |
568 | namespace std { |
569 | template<class ForwardIt, class T> |
570 | ForwardIt remove(ForwardIt first, ForwardIt last, const T& value); |
571 | })cpp" , |
572 | .Name: "remove" , |
573 | .ExpectedHeader: "<algorithm>" , |
574 | }, |
575 | { |
576 | .Code: "namespace std { int remove(const char*); }" , |
577 | .Name: "remove" , |
578 | .ExpectedHeader: "<cstdio>" , |
579 | }, |
580 | }; |
581 | |
582 | for (const auto &T : TestCases) { |
583 | Inputs.Code = T.Code; |
584 | buildAST(); |
585 | EXPECT_THAT(headersFor(T.Name), |
586 | UnorderedElementsAre( |
587 | Header(*tooling::stdlib::Header::named(T.ExpectedHeader)))); |
588 | } |
589 | } |
590 | |
591 | TEST_F(HeadersForSymbolTest, AmbiguousStdSymbolsUsingShadow) { |
592 | Inputs.Code = R"cpp( |
593 | void remove(char*); |
594 | namespace std { using ::remove; } |
595 | |
596 | void k() { |
597 | std::remove("abc"); |
598 | } |
599 | )cpp" ; |
600 | buildAST(); |
601 | |
602 | // Find the DeclRefExpr in the std::remove("abc") function call. |
603 | struct Visitor : public RecursiveASTVisitor<Visitor> { |
604 | const DeclRefExpr *Out = nullptr; |
605 | bool VisitDeclRefExpr(const DeclRefExpr *DRE) { |
606 | EXPECT_TRUE(Out == nullptr) << "Found multiple DeclRefExpr!" ; |
607 | Out = DRE; |
608 | return true; |
609 | } |
610 | }; |
611 | Visitor V; |
612 | V.TraverseDecl(AST->context().getTranslationUnitDecl()); |
613 | ASSERT_TRUE(V.Out) << "Couldn't find a DeclRefExpr!" ; |
614 | EXPECT_THAT(headersForSymbol(*(V.Out->getFoundDecl()), |
615 | AST->sourceManager(), &PI), |
616 | UnorderedElementsAre( |
617 | Header(*tooling::stdlib::Header::named("<cstdio>" )))); |
618 | } |
619 | |
620 | |
621 | TEST_F(HeadersForSymbolTest, StandardHeaders) { |
622 | Inputs.Code = "void assert();" ; |
623 | buildAST(); |
624 | EXPECT_THAT( |
625 | headersFor("assert" ), |
626 | // Respect the ordering from the stdlib mapping. |
627 | UnorderedElementsAre(tooling::stdlib::Header::named("<cassert>" ), |
628 | tooling::stdlib::Header::named("<assert.h>" ))); |
629 | } |
630 | |
631 | TEST_F(HeadersForSymbolTest, ExporterNoNameMatch) { |
632 | Inputs.Code = R"cpp( |
633 | #include "exporter/foo.h" |
634 | #include "foo_public.h" |
635 | )cpp" ; |
636 | Inputs.ExtraArgs.emplace_back(args: "-I." ); |
637 | // Deliberately named as foo_public to make sure it doesn't get name-match |
638 | // boost and also gets lexicographically bigger order than "exporter/foo.h". |
639 | Inputs.ExtraFiles["foo_public.h" ] = guard(Code: R"cpp( |
640 | struct foo {}; |
641 | )cpp" ); |
642 | Inputs.ExtraFiles["exporter/foo.h" ] = guard(Code: R"cpp( |
643 | #include "foo_public.h" // IWYU pragma: export |
644 | )cpp" ); |
645 | buildAST(); |
646 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("foo_public.h" ), |
647 | physicalHeader("exporter/foo.h" ))); |
648 | } |
649 | |
650 | } // namespace |
651 | } // namespace clang::include_cleaner |
652 | |