1//===-- RecordTest.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 "clang-include-cleaner/Record.h"
10#include "clang-include-cleaner/Types.h"
11#include "clang/AST/Decl.h"
12#include "clang/Basic/Diagnostic.h"
13#include "clang/Basic/FileEntry.h"
14#include "clang/Basic/LLVM.h"
15#include "clang/Basic/SourceLocation.h"
16#include "clang/Frontend/CompilerInvocation.h"
17#include "clang/Frontend/FrontendAction.h"
18#include "clang/Frontend/FrontendActions.h"
19#include "clang/Frontend/FrontendOptions.h"
20#include "clang/Serialization/PCHContainerOperations.h"
21#include "clang/Testing/TestAST.h"
22#include "clang/Tooling/Inclusions/StandardLibrary.h"
23#include "llvm/ADT/ArrayRef.h"
24#include "llvm/ADT/IntrusiveRefCntPtr.h"
25#include "llvm/ADT/StringRef.h"
26#include "llvm/Support/Error.h"
27#include "llvm/Support/MemoryBuffer.h"
28#include "llvm/Support/Path.h"
29#include "llvm/Support/VirtualFileSystem.h"
30#include "llvm/Support/raw_ostream.h"
31#include "llvm/Testing/Annotations/Annotations.h"
32#include "gmock/gmock.h"
33#include "gtest/gtest.h"
34#include <cassert>
35#include <memory>
36#include <optional>
37#include <utility>
38
39namespace clang::include_cleaner {
40namespace {
41using testing::ElementsAreArray;
42using testing::IsEmpty;
43
44// Matches a Decl* if it is a NamedDecl with the given name.
45MATCHER_P(named, N, "") {
46 if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(arg)) {
47 if (N == ND->getNameAsString())
48 return true;
49 }
50 std::string S;
51 llvm::raw_string_ostream OS(S);
52 arg->dump(OS);
53 *result_listener << S;
54 return false;
55}
56
57MATCHER_P(FileNamed, N, "") {
58 llvm::StringRef ActualName =
59 llvm::sys::path::remove_leading_dotslash(path: arg.getName());
60 if (ActualName == N)
61 return true;
62 *result_listener << ActualName.str();
63 return false;
64}
65
66class RecordASTTest : public ::testing::Test {
67protected:
68 TestInputs Inputs;
69 RecordedAST Recorded;
70
71 RecordASTTest() {
72 struct RecordAction : public ASTFrontendAction {
73 RecordedAST &Out;
74 RecordAction(RecordedAST &Out) : Out(Out) {}
75 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
76 StringRef) override {
77 return Out.record();
78 }
79 };
80 Inputs.MakeAction = [this] {
81 return std::make_unique<RecordAction>(args&: Recorded);
82 };
83 }
84
85 TestAST build() { return TestAST(Inputs); }
86};
87
88// Top-level decl from the main file is a root, nested ones aren't.
89TEST_F(RecordASTTest, Namespace) {
90 Inputs.Code =
91 R"cpp(
92 namespace ns {
93 int x;
94 namespace {
95 int y;
96 }
97 }
98 )cpp";
99 auto AST = build();
100 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("ns")));
101}
102
103// Decl in included file is not a root.
104TEST_F(RecordASTTest, Inclusion) {
105 Inputs.ExtraFiles["header.h"] = "void headerFunc();";
106 Inputs.Code = R"cpp(
107 #include "header.h"
108 void mainFunc();
109 )cpp";
110 auto AST = build();
111 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("mainFunc")));
112}
113
114// Decl from macro expanded into the main file is a root.
115TEST_F(RecordASTTest, Macros) {
116 Inputs.ExtraFiles["header.h"] = "#define X void x();";
117 Inputs.Code = R"cpp(
118 #include "header.h"
119 X
120 )cpp";
121 auto AST = build();
122 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("x")));
123}
124
125// Decl from template instantiation is filtered out from roots.
126TEST_F(RecordASTTest, ImplicitTemplates) {
127 Inputs.ExtraFiles["dispatch.h"] = R"cpp(
128 struct A {
129 static constexpr int value = 1;
130 };
131 template <class Getter>
132 int dispatch() {
133 return Getter::template get<A>();
134 }
135 )cpp";
136 Inputs.Code = R"cpp(
137 #include "dispatch.h"
138 struct MyGetter {
139 template <class T> static int get() { return T::value; }
140 };
141 int v = dispatch<MyGetter>();
142 )cpp";
143 auto AST = build();
144 EXPECT_THAT(Recorded.Roots,
145 testing::ElementsAre(named("MyGetter"), named("v")));
146}
147
148class RecordPPTest : public ::testing::Test {
149protected:
150 TestInputs Inputs;
151 RecordedPP Recorded;
152
153 RecordPPTest() {
154 struct RecordAction : public PreprocessOnlyAction {
155 RecordedPP &Out;
156 RecordAction(RecordedPP &Out) : Out(Out) {}
157
158 void ExecuteAction() override {
159 auto &PP = getCompilerInstance().getPreprocessor();
160 PP.addPPCallbacks(C: Out.record(PP));
161 PreprocessOnlyAction::ExecuteAction();
162 }
163 };
164 Inputs.MakeAction = [this] {
165 return std::make_unique<RecordAction>(args&: Recorded);
166 };
167 }
168
169 TestAST build() { return TestAST(Inputs); }
170};
171
172// Matches an Include with a particular spelling.
173MATCHER_P(spelled, S, "") { return arg.Spelled == S; }
174
175TEST_F(RecordPPTest, CapturesIncludes) {
176 llvm::Annotations MainFile(R"cpp(
177 $H^#include "./header.h"
178 $M^#include <missing.h>
179 )cpp");
180 Inputs.Code = MainFile.code();
181 Inputs.ExtraFiles["header.h"] = "";
182 Inputs.ErrorOK = true; // missing header
183 auto AST = build();
184
185 ASSERT_THAT(
186 Recorded.Includes.all(),
187 testing::ElementsAre(spelled("./header.h"), spelled("missing.h")));
188
189 auto &H = Recorded.Includes.all().front();
190 EXPECT_EQ(H.Line, 2u);
191 EXPECT_EQ(H.HashLocation,
192 AST.sourceManager().getComposedLoc(
193 AST.sourceManager().getMainFileID(), MainFile.point("H")));
194 EXPECT_EQ(H.Resolved, *AST.fileManager().getOptionalFileRef("header.h"));
195 EXPECT_FALSE(H.Angled);
196
197 auto &M = Recorded.Includes.all().back();
198 EXPECT_EQ(M.Line, 3u);
199 EXPECT_EQ(M.HashLocation,
200 AST.sourceManager().getComposedLoc(
201 AST.sourceManager().getMainFileID(), MainFile.point("M")));
202 EXPECT_EQ(M.Resolved, std::nullopt);
203 EXPECT_TRUE(M.Angled);
204}
205
206TEST_F(RecordPPTest, CapturesMacroRefs) {
207 llvm::Annotations Header(R"cpp(
208 #define $def^X 1
209
210 // Refs, but not in main file.
211 #define Y X
212 int one = X;
213 )cpp");
214 llvm::Annotations MainFile(R"cpp(
215 #define EARLY X // not a ref, no definition
216 #include "header.h"
217 #define LATE ^X
218 #define LATE2 ^X // a ref even if not expanded
219
220 int uno = ^X;
221 int jeden = $exp^LATE; // a ref in LATE's expansion
222
223 #define IDENT(X) X // not a ref, shadowed
224 int eins = IDENT(^X);
225
226 #undef ^X
227 // Not refs, rather a new macro with the same name.
228 #define X 2
229 int two = X;
230 )cpp");
231 Inputs.Code = MainFile.code();
232 Inputs.ExtraFiles["header.h"] = Header.code();
233 auto AST = build();
234 const auto &SM = AST.sourceManager();
235
236 SourceLocation Def = SM.getComposedLoc(
237 FID: SM.translateFile(SourceFile: *AST.fileManager().getOptionalFileRef(Filename: "header.h")),
238 Offset: Header.point(Name: "def"));
239 ASSERT_THAT(Recorded.MacroReferences, Not(IsEmpty()));
240 Symbol OrigX = Recorded.MacroReferences.front().Target;
241 EXPECT_EQ("X", OrigX.macro().Name->getName());
242 EXPECT_EQ(Def, OrigX.macro().Definition);
243
244 std::vector<unsigned> RefOffsets;
245 std::vector<unsigned> ExpOffsets; // Expansion locs of refs in macro locs.
246 for (const auto &Ref : Recorded.MacroReferences) {
247 if (Ref.Target == OrigX) {
248 auto [FID, Off] = SM.getDecomposedLoc(Loc: Ref.RefLocation);
249 if (FID == SM.getMainFileID()) {
250 RefOffsets.push_back(x: Off);
251 } else if (Ref.RefLocation.isMacroID() &&
252 SM.isWrittenInMainFile(Loc: SM.getExpansionLoc(Loc: Ref.RefLocation))) {
253 ExpOffsets.push_back(
254 x: SM.getDecomposedExpansionLoc(Loc: Ref.RefLocation).second);
255 } else {
256 ADD_FAILURE() << Ref.RefLocation.printToString(SM);
257 }
258 }
259 }
260 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
261 EXPECT_THAT(ExpOffsets, ElementsAreArray(MainFile.points("exp")));
262}
263
264TEST_F(RecordPPTest, CapturesConditionalMacroRefs) {
265 llvm::Annotations MainFile(R"cpp(
266 #define X 1
267
268 #ifdef ^X
269 #endif
270
271 #if defined(^X)
272 #endif
273
274 #ifndef ^X
275 #endif
276
277 #ifdef Y
278 #elifdef ^X
279 #endif
280
281 #ifndef ^X
282 #elifndef ^X
283 #endif
284 )cpp");
285
286 Inputs.Code = MainFile.code();
287 Inputs.ExtraArgs.push_back(x: "-std=c++2b");
288 auto AST = build();
289
290 std::vector<unsigned> RefOffsets;
291 SourceManager &SM = AST.sourceManager();
292 for (const auto &Ref : Recorded.MacroReferences) {
293 auto [FID, Off] = SM.getDecomposedLoc(Loc: Ref.RefLocation);
294 ASSERT_EQ(FID, SM.getMainFileID());
295 EXPECT_EQ(Ref.RT, RefType::Ambiguous);
296 EXPECT_EQ("X", Ref.Target.macro().Name->getName());
297 RefOffsets.push_back(x: Off);
298 }
299 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
300}
301
302class PragmaIncludeTest : public ::testing::Test {
303protected:
304 // We don't build an AST, we just run a preprocessor action!
305 TestInputs Inputs;
306 PragmaIncludes PI;
307
308 PragmaIncludeTest() {
309 Inputs.MakeAction = [this] {
310 struct Hook : public PreprocessOnlyAction {
311 public:
312 Hook(PragmaIncludes *Out) : Out(Out) {}
313 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
314 Out->record(CI);
315 return true;
316 }
317 PragmaIncludes *Out;
318 };
319 return std::make_unique<Hook>(args: &PI);
320 };
321 }
322
323 TestAST build(bool ResetPragmaIncludes = true) {
324 if (ResetPragmaIncludes)
325 PI = PragmaIncludes();
326 return TestAST(Inputs);
327 }
328
329 void createEmptyFiles(llvm::ArrayRef<StringRef> FileNames) {
330 for (llvm::StringRef File : FileNames)
331 Inputs.ExtraFiles[File] = "#pragma once";
332 }
333};
334
335TEST_F(PragmaIncludeTest, IWYUKeep) {
336 Inputs.Code = R"cpp(
337 #include "keep1.h" // IWYU pragma: keep
338 #include "keep2.h" /* IWYU pragma: keep */
339
340 #include "export1.h" // IWYU pragma: export
341 // IWYU pragma: begin_exports
342 #include "export2.h"
343 #include "export3.h"
344 // IWYU pragma: end_exports
345
346 #include "normal.h"
347
348 // IWYU pragma: begin_keep
349 #include "keep3.h"
350 // IWYU pragma: end_keep
351
352 // IWYU pragma: begin_keep
353 #include "keep4.h"
354 // IWYU pragma: begin_keep
355 #include "keep5.h"
356 // IWYU pragma: end_keep
357 #include "keep6.h"
358 // IWYU pragma: end_keep
359 #include <vector>
360 #include <map> // IWYU pragma: keep
361 #include <set> // IWYU pragma: export
362 )cpp";
363 createEmptyFiles(FileNames: {"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h",
364 "keep6.h", "export1.h", "export2.h", "export3.h",
365 "normal.h", "std/vector", "std/map", "std/set"});
366
367 Inputs.ExtraArgs.push_back(x: "-isystemstd");
368 TestAST Processed = build();
369 auto &FM = Processed.fileManager();
370
371 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("normal.h")));
372 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("std/vector")));
373
374 // Keep
375 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep1.h")));
376 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep2.h")));
377 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep3.h")));
378 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep4.h")));
379 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep5.h")));
380 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep6.h")));
381 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/map")));
382
383 // Exports
384 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export1.h")));
385 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export2.h")));
386 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export3.h")));
387 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/set")));
388}
389
390TEST_F(PragmaIncludeTest, AssociatedHeader) {
391 createEmptyFiles(FileNames: {"foo/main.h", "bar/main.h", "bar/other.h", "std/vector"});
392 auto IsKeep = [&](llvm::StringRef Name, TestAST &AST) {
393 return PI.shouldKeep(FE: *AST.fileManager().getOptionalFileRef(Filename: Name));
394 };
395
396 Inputs.FileName = "main.cc";
397 Inputs.ExtraArgs.push_back(x: "-isystemstd");
398 {
399 Inputs.Code = R"cpp(
400 #include "foo/main.h"
401 #include "bar/main.h"
402 )cpp";
403 auto AST = build();
404 EXPECT_TRUE(IsKeep("foo/main.h", AST));
405 EXPECT_FALSE(IsKeep("bar/main.h", AST)) << "not first include";
406 }
407
408 {
409 Inputs.Code = R"cpp(
410 #include "bar/other.h"
411 #include "bar/main.h"
412 )cpp";
413 auto AST = build();
414 EXPECT_FALSE(IsKeep("bar/other.h", AST));
415 EXPECT_FALSE(IsKeep("bar/main.h", AST)) << "not first include";
416 }
417
418 {
419 Inputs.Code = R"cpp(
420 #include "foo/main.h"
421 #include "bar/other.h" // IWYU pragma: associated
422 #include <vector> // IWYU pragma: associated
423 )cpp";
424 auto AST = build();
425 EXPECT_TRUE(IsKeep("foo/main.h", AST));
426 EXPECT_TRUE(IsKeep("bar/other.h", AST));
427 EXPECT_TRUE(IsKeep("std/vector", AST));
428 }
429
430 Inputs.FileName = "vector.cc";
431 {
432 Inputs.Code = R"cpp(
433 #include <vector>
434 )cpp";
435 auto AST = build();
436 EXPECT_FALSE(IsKeep("std/vector", AST)) << "stdlib is not associated";
437 }
438}
439
440TEST_F(PragmaIncludeTest, IWYUPrivate) {
441 Inputs.Code = R"cpp(
442 #include "public.h"
443 )cpp";
444 Inputs.ExtraFiles["public.h"] = R"cpp(
445 #include "private.h"
446 #include "private2.h"
447 )cpp";
448 Inputs.ExtraFiles["private.h"] = R"cpp(
449 // IWYU pragma: private, include "public2.h"
450 )cpp";
451 Inputs.ExtraFiles["private2.h"] = R"cpp(
452 // IWYU pragma: private
453 )cpp";
454 TestAST Processed = build();
455 auto PrivateFE = Processed.fileManager().getOptionalFileRef(Filename: "private.h");
456 assert(PrivateFE);
457 EXPECT_TRUE(PI.isPrivate(*PrivateFE));
458 EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public2.h\"");
459
460 auto PublicFE = Processed.fileManager().getOptionalFileRef(Filename: "public.h");
461 assert(PublicFE);
462 EXPECT_EQ(PI.getPublic(*PublicFE), ""); // no mapping.
463 EXPECT_FALSE(PI.isPrivate(*PublicFE));
464
465 auto Private2FE = Processed.fileManager().getOptionalFileRef(Filename: "private2.h");
466 assert(Private2FE);
467 EXPECT_TRUE(PI.isPrivate(*Private2FE));
468}
469
470TEST_F(PragmaIncludeTest, IWYUExport) {
471 Inputs.Code = R"cpp(// Line 1
472 #include "export1.h"
473 #include "export2.h"
474 )cpp";
475 Inputs.ExtraFiles["export1.h"] = R"cpp(
476 #include "private.h" // IWYU pragma: export
477 )cpp";
478 Inputs.ExtraFiles["export2.h"] = R"cpp(
479 #include "export3.h"
480 )cpp";
481 Inputs.ExtraFiles["export3.h"] = R"cpp(
482 #include "private.h" // IWYU pragma: export
483 )cpp";
484 Inputs.ExtraFiles["private.h"] = "";
485 TestAST Processed = build();
486 const auto &SM = Processed.sourceManager();
487 auto &FM = Processed.fileManager();
488
489 EXPECT_THAT(PI.getExporters(*FM.getOptionalFileRef("private.h"), FM),
490 testing::UnorderedElementsAre(FileNamed("export1.h"),
491 FileNamed("export3.h")));
492
493 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export1.h"), FM).empty());
494 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export2.h"), FM).empty());
495 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export3.h"), FM).empty());
496 EXPECT_TRUE(
497 PI.getExporters(SM.getFileEntryForID(SM.getMainFileID()), FM).empty());
498}
499
500TEST_F(PragmaIncludeTest, IWYUExportForStandardHeaders) {
501 Inputs.Code = R"cpp(
502 #include "export.h"
503 )cpp";
504 Inputs.ExtraFiles["export.h"] = R"cpp(
505 #include <string> // IWYU pragma: export
506 )cpp";
507 Inputs.ExtraFiles["string"] = "";
508 Inputs.ExtraArgs = {"-isystem."};
509 TestAST Processed = build();
510 auto &FM = Processed.fileManager();
511 EXPECT_THAT(PI.getExporters(*tooling::stdlib::Header::named("<string>"), FM),
512 testing::UnorderedElementsAre(FileNamed("export.h")));
513 EXPECT_THAT(PI.getExporters(llvm::cantFail(FM.getFileRef("string")), FM),
514 testing::UnorderedElementsAre(FileNamed("export.h")));
515}
516
517TEST_F(PragmaIncludeTest, IWYUExportBlock) {
518 Inputs.Code = R"cpp(// Line 1
519 #include "normal.h"
520 )cpp";
521 Inputs.ExtraFiles["normal.h"] = R"cpp(
522 #include "foo.h"
523
524 // IWYU pragma: begin_exports
525 #include "export1.h"
526 #include "private1.h"
527 // IWYU pragma: end_exports
528 )cpp";
529 Inputs.ExtraFiles["export1.h"] = R"cpp(
530 // IWYU pragma: begin_exports
531 #include "private1.h"
532 #include "private2.h"
533 // IWYU pragma: end_exports
534
535 #include "bar.h"
536 #include "private3.h" // IWYU pragma: export
537 )cpp";
538 createEmptyFiles(
539 FileNames: {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"});
540 TestAST Processed = build();
541 auto &FM = Processed.fileManager();
542
543 auto GetNames = [](llvm::ArrayRef<FileEntryRef> FEs) {
544 std::string Result;
545 llvm::raw_string_ostream OS(Result);
546 for (auto &FE : FEs) {
547 OS << FE.getName() << " ";
548 }
549 return Result;
550 };
551 auto Exporters = PI.getExporters(File: *FM.getOptionalFileRef(Filename: "private1.h"), FM);
552 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h"),
553 FileNamed("normal.h")))
554 << GetNames(Exporters);
555
556 Exporters = PI.getExporters(File: *FM.getOptionalFileRef(Filename: "private2.h"), FM);
557 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h")))
558 << GetNames(Exporters);
559
560 Exporters = PI.getExporters(File: *FM.getOptionalFileRef(Filename: "private3.h"), FM);
561 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h")))
562 << GetNames(Exporters);
563
564 Exporters = PI.getExporters(File: *FM.getOptionalFileRef(Filename: "foo.h"), FM);
565 EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters);
566
567 Exporters = PI.getExporters(File: *FM.getOptionalFileRef(Filename: "bar.h"), FM);
568 EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters);
569}
570
571TEST_F(PragmaIncludeTest, SelfContained) {
572 Inputs.Code = R"cpp(
573 #include "guarded.h"
574
575 #include "unguarded.h"
576 )cpp";
577 Inputs.ExtraFiles["guarded.h"] = R"cpp(
578 #pragma once
579 )cpp";
580 Inputs.ExtraFiles["unguarded.h"] = "";
581 TestAST Processed = build();
582 auto &FM = Processed.fileManager();
583 EXPECT_TRUE(PI.isSelfContained(*FM.getOptionalFileRef("guarded.h")));
584 EXPECT_FALSE(PI.isSelfContained(*FM.getOptionalFileRef("unguarded.h")));
585}
586
587TEST_F(PragmaIncludeTest, AlwaysKeep) {
588 Inputs.Code = R"cpp(
589 #include "always_keep.h"
590 #include "usual.h"
591 )cpp";
592 Inputs.ExtraFiles["always_keep.h"] = R"cpp(
593 #pragma once
594 // IWYU pragma: always_keep
595 )cpp";
596 Inputs.ExtraFiles["usual.h"] = "#pragma once";
597 TestAST Processed = build();
598 auto &FM = Processed.fileManager();
599 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("always_keep.h")));
600 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("usual.h")));
601}
602
603TEST_F(PragmaIncludeTest, ExportInUnnamedBuffer) {
604 llvm::StringLiteral Filename = "test.cpp";
605 auto Code = R"cpp(#include "exporter.h")cpp";
606 Inputs.ExtraFiles["exporter.h"] = R"cpp(
607 #pragma once
608 #include "foo.h" // IWYU pragma: export
609 )cpp";
610 Inputs.ExtraFiles["foo.h"] = "";
611
612 // Create unnamed memory buffers for all the files.
613 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
614 VFS->addFile(Path: Filename, /*ModificationTime=*/0,
615 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: Code, /*BufferName=*/""));
616 for (const auto &Extra : Inputs.ExtraFiles)
617 VFS->addFile(Path: Extra.getKey(), /*ModificationTime=*/0,
618 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: Extra.getValue(),
619 /*BufferName=*/""));
620
621 DiagnosticOptions DiagOpts;
622 auto Diags = CompilerInstance::createDiagnostics(VFS&: *VFS, Opts&: DiagOpts);
623 auto Invocation = std::make_unique<CompilerInvocation>();
624 ASSERT_TRUE(CompilerInvocation::CreateFromArgs(*Invocation, {Filename.data()},
625 *Diags, "clang"));
626
627 auto Clang = std::make_unique<CompilerInstance>(args: std::move(Invocation));
628 Clang->createDiagnostics(VFS&: *VFS);
629
630 auto *FM = Clang->createFileManager(VFS);
631 ASSERT_TRUE(Clang->ExecuteAction(*Inputs.MakeAction()));
632 EXPECT_THAT(
633 PI.getExporters(llvm::cantFail(FM->getFileRef("foo.h")), *FM),
634 testing::ElementsAre(llvm::cantFail(FM->getFileRef("exporter.h"))));
635}
636
637TEST_F(PragmaIncludeTest, OutlivesFMAndSM) {
638 Inputs.Code = R"cpp(
639 #include "public.h"
640 )cpp";
641 Inputs.ExtraFiles["public.h"] = R"cpp(
642 #include "private.h"
643 #include "private2.h" // IWYU pragma: export
644 )cpp";
645 Inputs.ExtraFiles["private.h"] = R"cpp(
646 // IWYU pragma: private, include "public.h"
647 )cpp";
648 Inputs.ExtraFiles["private2.h"] = R"cpp(
649 // IWYU pragma: private
650 )cpp";
651 build(); // Fills up PI, file/source manager used is destroyed afterwards.
652 Inputs.MakeAction = nullptr; // Don't populate PI anymore.
653
654 // Now this build gives us a new File&Source Manager.
655 TestAST Processed = build(/*ResetPragmaIncludes=*/false);
656 auto &FM = Processed.fileManager();
657 auto PrivateFE = FM.getOptionalFileRef(Filename: "private.h");
658 assert(PrivateFE);
659 EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public.h\"");
660
661 auto Private2FE = FM.getOptionalFileRef(Filename: "private2.h");
662 assert(Private2FE);
663 EXPECT_THAT(PI.getExporters(*Private2FE, FM),
664 testing::ElementsAre(llvm::cantFail(FM.getFileRef("public.h"))));
665}
666
667TEST_F(PragmaIncludeTest, CanRecordManyTimes) {
668 Inputs.Code = R"cpp(
669 #include "public.h"
670 )cpp";
671 Inputs.ExtraFiles["public.h"] = R"cpp(
672 #include "private.h"
673 )cpp";
674 Inputs.ExtraFiles["private.h"] = R"cpp(
675 // IWYU pragma: private, include "public.h"
676 )cpp";
677
678 TestAST Processed = build();
679 auto &FM = Processed.fileManager();
680 auto PrivateFE = FM.getOptionalFileRef(Filename: "private.h");
681 llvm::StringRef Public = PI.getPublic(File: *PrivateFE);
682 EXPECT_EQ(Public, "\"public.h\"");
683
684 // This build populates same PI during build, but this time we don't have
685 // any IWYU pragmas. Make sure strings from previous recordings are still
686 // alive.
687 Inputs.Code = "";
688 build(/*ResetPragmaIncludes=*/false);
689 EXPECT_EQ(Public, "\"public.h\"");
690}
691} // namespace
692} // namespace clang::include_cleaner
693

source code of clang-tools-extra/include-cleaner/unittests/RecordTest.cpp