1 //===-- RecordTest.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 "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/LLVM.h"
14 #include "clang/Basic/SourceLocation.h"
15 #include "clang/Frontend/CompilerInvocation.h"
16 #include "clang/Frontend/FrontendAction.h"
17 #include "clang/Frontend/FrontendActions.h"
18 #include "clang/Frontend/FrontendOptions.h"
19 #include "clang/Serialization/PCHContainerOperations.h"
20 #include "clang/Testing/TestAST.h"
21 #include "clang/Tooling/Inclusions/StandardLibrary.h"
22 #include "llvm/ADT/ArrayRef.h"
23 #include "llvm/ADT/IntrusiveRefCntPtr.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/Error.h"
26 #include "llvm/Support/MemoryBuffer.h"
27 #include "llvm/Support/VirtualFileSystem.h"
28 #include "llvm/Support/raw_ostream.h"
29 #include "llvm/Testing/Annotations/Annotations.h"
30 #include "gmock/gmock.h"
31 #include "gtest/gtest.h"
37 namespace clang::include_cleaner
{
39 using testing::ElementsAreArray
;
40 using testing::IsEmpty
;
42 // Matches a Decl* if it is a NamedDecl with the given name.
43 MATCHER_P(named
, N
, "") {
44 if (const NamedDecl
*ND
= llvm::dyn_cast
<NamedDecl
>(arg
)) {
45 if (N
== ND
->getNameAsString())
49 llvm::raw_string_ostream
OS(S
);
51 *result_listener
<< S
;
55 MATCHER_P(FileNamed
, N
, "") {
56 if (arg
.getFileEntry().tryGetRealPathName() == N
)
58 *result_listener
<< arg
.getFileEntry().tryGetRealPathName().str();
62 class RecordASTTest
: public ::testing::Test
{
68 struct RecordAction
: public ASTFrontendAction
{
70 RecordAction(RecordedAST
&Out
) : Out(Out
) {}
71 std::unique_ptr
<ASTConsumer
> CreateASTConsumer(CompilerInstance
&CI
,
76 Inputs
.MakeAction
= [this] {
77 return std::make_unique
<RecordAction
>(Recorded
);
81 TestAST
build() { return TestAST(Inputs
); }
84 // Top-level decl from the main file is a root, nested ones aren't.
85 TEST_F(RecordASTTest
, Namespace
) {
96 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("ns")));
99 // Decl in included file is not a root.
100 TEST_F(RecordASTTest
, Inclusion
) {
101 Inputs
.ExtraFiles
["header.h"] = "void headerFunc();";
107 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("mainFunc")));
110 // Decl from macro expanded into the main file is a root.
111 TEST_F(RecordASTTest
, Macros
) {
112 Inputs
.ExtraFiles
["header.h"] = "#define X void x();";
118 EXPECT_THAT(Recorded
.Roots
, testing::ElementsAre(named("x")));
121 // Decl from template instantiation is filtered out from roots.
122 TEST_F(RecordASTTest
, ImplicitTemplates
) {
123 Inputs
.ExtraFiles
["dispatch.h"] = R
"cpp(
125 static constexpr int value = 1;
127 template <class Getter>
129 return Getter::template get<A>();
133 #include "dispatch
.h
"
135 template <class T> static int get() { return T::value; }
137 int v = dispatch<MyGetter>();
140 EXPECT_THAT(Recorded
.Roots
,
141 testing::ElementsAre(named("MyGetter"), named("v")));
144 class RecordPPTest
: public ::testing::Test
{
150 struct RecordAction
: public PreprocessOnlyAction
{
152 RecordAction(RecordedPP
&Out
) : Out(Out
) {}
154 void ExecuteAction() override
{
155 auto &PP
= getCompilerInstance().getPreprocessor();
156 PP
.addPPCallbacks(Out
.record(PP
));
157 PreprocessOnlyAction::ExecuteAction();
160 Inputs
.MakeAction
= [this] {
161 return std::make_unique
<RecordAction
>(Recorded
);
165 TestAST
build() { return TestAST(Inputs
); }
168 // Matches an Include with a particular spelling.
169 MATCHER_P(spelled
, S
, "") { return arg
.Spelled
== S
; }
171 TEST_F(RecordPPTest
, CapturesIncludes
) {
172 llvm::Annotations
MainFile(R
"cpp(
173 $H^#include "./header
.h
"
174 $M^#include <missing.h>
176 Inputs
.Code
= MainFile
.code();
177 Inputs
.ExtraFiles
["header.h"] = "";
178 Inputs
.ErrorOK
= true; // missing header
182 Recorded
.Includes
.all(),
183 testing::ElementsAre(spelled("./header.h"), spelled("missing.h")));
185 auto &H
= Recorded
.Includes
.all().front();
186 EXPECT_EQ(H
.Line
, 2u);
187 EXPECT_EQ(H
.HashLocation
,
188 AST
.sourceManager().getComposedLoc(
189 AST
.sourceManager().getMainFileID(), MainFile
.point("H")));
190 EXPECT_EQ(H
.Resolved
, *AST
.fileManager().getOptionalFileRef("header.h"));
191 EXPECT_FALSE(H
.Angled
);
193 auto &M
= Recorded
.Includes
.all().back();
194 EXPECT_EQ(M
.Line
, 3u);
195 EXPECT_EQ(M
.HashLocation
,
196 AST
.sourceManager().getComposedLoc(
197 AST
.sourceManager().getMainFileID(), MainFile
.point("M")));
198 EXPECT_EQ(M
.Resolved
, std::nullopt
);
199 EXPECT_TRUE(M
.Angled
);
202 TEST_F(RecordPPTest
, CapturesMacroRefs
) {
203 llvm::Annotations
Header(R
"cpp(
206 // Refs, but not in main file.
210 llvm::Annotations
MainFile(R
"cpp(
211 #define EARLY X // not a ref, no definition
214 #define LATE2 ^X // a ref even if not expanded
217 int jeden = $exp^LATE; // a ref in LATE's expansion
219 #define IDENT(X) X // not a ref, shadowed
220 int eins = IDENT(^X);
223 // Not refs, rather a new macro with the same name.
227 Inputs
.Code
= MainFile
.code();
228 Inputs
.ExtraFiles
["header.h"] = Header
.code();
230 const auto &SM
= AST
.sourceManager();
232 SourceLocation Def
= SM
.getComposedLoc(
233 SM
.translateFile(AST
.fileManager().getFile("header.h").get()),
234 Header
.point("def"));
235 ASSERT_THAT(Recorded
.MacroReferences
, Not(IsEmpty()));
236 Symbol OrigX
= Recorded
.MacroReferences
.front().Target
;
237 EXPECT_EQ("X", OrigX
.macro().Name
->getName());
238 EXPECT_EQ(Def
, OrigX
.macro().Definition
);
240 std::vector
<unsigned> RefOffsets
;
241 std::vector
<unsigned> ExpOffsets
; // Expansion locs of refs in macro locs.
242 for (const auto &Ref
: Recorded
.MacroReferences
) {
243 if (Ref
.Target
== OrigX
) {
244 auto [FID
, Off
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
245 if (FID
== SM
.getMainFileID()) {
246 RefOffsets
.push_back(Off
);
247 } else if (Ref
.RefLocation
.isMacroID() &&
248 SM
.isWrittenInMainFile(SM
.getExpansionLoc(Ref
.RefLocation
))) {
249 ExpOffsets
.push_back(
250 SM
.getDecomposedExpansionLoc(Ref
.RefLocation
).second
);
252 ADD_FAILURE() << Ref
.RefLocation
.printToString(SM
);
256 EXPECT_THAT(RefOffsets
, ElementsAreArray(MainFile
.points()));
257 EXPECT_THAT(ExpOffsets
, ElementsAreArray(MainFile
.points("exp")));
260 TEST_F(RecordPPTest
, CapturesConditionalMacroRefs
) {
261 llvm::Annotations
MainFile(R
"cpp(
282 Inputs
.Code
= MainFile
.code();
283 Inputs
.ExtraArgs
.push_back("-std=c++2b");
286 std::vector
<unsigned> RefOffsets
;
287 SourceManager
&SM
= AST
.sourceManager();
288 for (const auto &Ref
: Recorded
.MacroReferences
) {
289 auto [FID
, Off
] = SM
.getDecomposedLoc(Ref
.RefLocation
);
290 ASSERT_EQ(FID
, SM
.getMainFileID());
291 EXPECT_EQ(Ref
.RT
, RefType::Ambiguous
);
292 EXPECT_EQ("X", Ref
.Target
.macro().Name
->getName());
293 RefOffsets
.push_back(Off
);
295 EXPECT_THAT(RefOffsets
, ElementsAreArray(MainFile
.points()));
298 class PragmaIncludeTest
: public ::testing::Test
{
300 // We don't build an AST, we just run a preprocessor action!
304 PragmaIncludeTest() {
305 Inputs
.MakeAction
= [this] {
306 struct Hook
: public PreprocessOnlyAction
{
308 Hook(PragmaIncludes
*Out
) : Out(Out
) {}
309 bool BeginSourceFileAction(clang::CompilerInstance
&CI
) override
{
315 return std::make_unique
<Hook
>(&PI
);
319 TestAST
build() { return TestAST(Inputs
); }
321 void createEmptyFiles(llvm::ArrayRef
<StringRef
> FileNames
) {
322 for (llvm::StringRef File
: FileNames
)
323 Inputs
.ExtraFiles
[File
] = "#pragma once";
327 TEST_F(PragmaIncludeTest
, IWYUKeep
) {
329 #include "keep1
.h
" // IWYU pragma: keep
330 #include "keep2
.h
" /* IWYU pragma: keep */
332 #include "export1
.h
" // IWYU pragma: export
333 // IWYU pragma: begin_exports
336 // IWYU pragma: end_exports
340 // IWYU pragma: begin_keep
342 // IWYU pragma: end_keep
344 // IWYU pragma: begin_keep
346 // IWYU pragma: begin_keep
348 // IWYU pragma: end_keep
350 // IWYU pragma: end_keep
352 #include <map> // IWYU pragma: keep
353 #include <set> // IWYU pragma: export
355 createEmptyFiles({"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h",
356 "keep6.h", "export1.h", "export2.h", "export3.h",
357 "normal.h", "std/vector", "std/map", "std/set"});
359 Inputs
.ExtraArgs
.push_back("-isystemstd");
360 TestAST Processed
= build();
361 auto &FM
= Processed
.fileManager();
363 EXPECT_FALSE(PI
.shouldKeep(FM
.getFile("normal.h").get()));
364 EXPECT_FALSE(PI
.shouldKeep(FM
.getFile("std/vector").get()));
367 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep1.h").get()));
368 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep2.h").get()));
369 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep3.h").get()));
370 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep4.h").get()));
371 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep5.h").get()));
372 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("keep6.h").get()));
373 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("std/map").get()));
376 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("export1.h").get()));
377 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("export2.h").get()));
378 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("export3.h").get()));
379 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("std/set").get()));
382 TEST_F(PragmaIncludeTest
, IWYUPrivate
) {
386 Inputs
.ExtraFiles
["public.h"] = R
"cpp(
388 #include "private2
.h
"
390 Inputs
.ExtraFiles
["private.h"] = R
"cpp(
391 // IWYU pragma: private, include "public2
.h
"
393 Inputs
.ExtraFiles
["private2.h"] = R
"cpp(
394 // IWYU pragma: private
396 TestAST Processed
= build();
397 auto PrivateFE
= Processed
.fileManager().getFile("private.h");
399 EXPECT_TRUE(PI
.isPrivate(PrivateFE
.get()));
400 EXPECT_EQ(PI
.getPublic(PrivateFE
.get()), "\"public2.h\"");
402 auto PublicFE
= Processed
.fileManager().getFile("public.h");
404 EXPECT_EQ(PI
.getPublic(PublicFE
.get()), ""); // no mapping.
405 EXPECT_FALSE(PI
.isPrivate(PublicFE
.get()));
407 auto Private2FE
= Processed
.fileManager().getFile("private2.h");
409 EXPECT_TRUE(PI
.isPrivate(Private2FE
.get()));
412 TEST_F(PragmaIncludeTest
, IWYUExport
) {
413 Inputs
.Code
= R
"cpp(// Line 1
417 Inputs
.ExtraFiles
["export1.h"] = R
"cpp(
418 #include "private.h
" // IWYU pragma: export
420 Inputs
.ExtraFiles
["export2.h"] = R
"cpp(
423 Inputs
.ExtraFiles
["export3.h"] = R
"cpp(
424 #include "private.h
" // IWYU pragma: export
426 Inputs
.ExtraFiles
["private.h"] = "";
427 TestAST Processed
= build();
428 const auto &SM
= Processed
.sourceManager();
429 auto &FM
= Processed
.fileManager();
431 EXPECT_THAT(PI
.getExporters(FM
.getFile("private.h").get(), FM
),
432 testing::UnorderedElementsAre(FileNamed("export1.h"),
433 FileNamed("export3.h")));
435 EXPECT_TRUE(PI
.getExporters(FM
.getFile("export1.h").get(), FM
).empty());
436 EXPECT_TRUE(PI
.getExporters(FM
.getFile("export2.h").get(), FM
).empty());
437 EXPECT_TRUE(PI
.getExporters(FM
.getFile("export3.h").get(), FM
).empty());
439 PI
.getExporters(SM
.getFileEntryForID(SM
.getMainFileID()), FM
).empty());
442 TEST_F(PragmaIncludeTest
, IWYUExportForStandardHeaders
) {
446 Inputs
.ExtraFiles
["export.h"] = R
"cpp(
447 #include <string> // IWYU pragma: export
449 Inputs
.ExtraFiles
["string"] = "";
450 Inputs
.ExtraArgs
= {"-isystem."};
451 TestAST Processed
= build();
452 auto &FM
= Processed
.fileManager();
453 EXPECT_THAT(PI
.getExporters(*tooling::stdlib::Header::named("<string>"), FM
),
454 testing::UnorderedElementsAre(FileNamed("export.h")));
457 TEST_F(PragmaIncludeTest
, IWYUExportBlock
) {
458 Inputs
.Code
= R
"cpp(// Line 1
461 Inputs
.ExtraFiles
["normal.h"] = R
"cpp(
464 // IWYU pragma: begin_exports
466 #include "private1
.h
"
467 // IWYU pragma: end_exports
469 Inputs
.ExtraFiles
["export1.h"] = R
"cpp(
470 // IWYU pragma: begin_exports
471 #include "private1
.h
"
472 #include "private2
.h
"
473 // IWYU pragma: end_exports
476 #include "private3
.h
" // IWYU pragma: export
479 {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"});
480 TestAST Processed
= build();
481 auto &FM
= Processed
.fileManager();
483 EXPECT_THAT(PI
.getExporters(FM
.getFile("private1.h").get(), FM
),
484 testing::UnorderedElementsAre(FileNamed("export1.h"),
485 FileNamed("normal.h")));
486 EXPECT_THAT(PI
.getExporters(FM
.getFile("private2.h").get(), FM
),
487 testing::UnorderedElementsAre(FileNamed("export1.h")));
488 EXPECT_THAT(PI
.getExporters(FM
.getFile("private3.h").get(), FM
),
489 testing::UnorderedElementsAre(FileNamed("export1.h")));
491 EXPECT_TRUE(PI
.getExporters(FM
.getFile("foo.h").get(), FM
).empty());
492 EXPECT_TRUE(PI
.getExporters(FM
.getFile("bar.h").get(), FM
).empty());
495 TEST_F(PragmaIncludeTest
, SelfContained
) {
499 #include "unguarded
.h
"
501 Inputs
.ExtraFiles
["guarded.h"] = R
"cpp(
504 Inputs
.ExtraFiles
["unguarded.h"] = "";
505 TestAST Processed
= build();
506 auto &FM
= Processed
.fileManager();
507 EXPECT_TRUE(PI
.isSelfContained(FM
.getFile("guarded.h").get()));
508 EXPECT_FALSE(PI
.isSelfContained(FM
.getFile("unguarded.h").get()));
511 TEST_F(PragmaIncludeTest
, AlwaysKeep
) {
513 #include "always_keep
.h
"
516 Inputs
.ExtraFiles
["always_keep.h"] = R
"cpp(
518 // IWYU pragma: always_keep
520 Inputs
.ExtraFiles
["usual.h"] = "#pragma once";
521 TestAST Processed
= build();
522 auto &FM
= Processed
.fileManager();
523 EXPECT_TRUE(PI
.shouldKeep(FM
.getFile("always_keep.h").get()));
524 EXPECT_FALSE(PI
.shouldKeep(FM
.getFile("usual.h").get()));
527 TEST_F(PragmaIncludeTest
, ExportInUnnamedBuffer
) {
528 llvm::StringLiteral Filename
= "test.cpp";
529 auto Code
= R
"cpp(#include "exporter
.h
")cpp";
530 Inputs
.ExtraFiles
["exporter.h"] = R
"cpp(
532 #include "foo
.h
" // IWYU pragma: export
534 Inputs
.ExtraFiles
["foo.h"] = "";
536 auto Clang
= std::make_unique
<CompilerInstance
>(
537 std::make_shared
<PCHContainerOperations
>());
538 Clang
->createDiagnostics();
540 Clang
->setInvocation(std::make_unique
<CompilerInvocation
>());
541 ASSERT_TRUE(CompilerInvocation::CreateFromArgs(
542 Clang
->getInvocation(), {Filename
.data()}, Clang
->getDiagnostics(),
545 // Create unnamed memory buffers for all the files.
546 auto VFS
= llvm::makeIntrusiveRefCnt
<llvm::vfs::InMemoryFileSystem
>();
547 VFS
->addFile(Filename
, /*ModificationTime=*/0,
548 llvm::MemoryBuffer::getMemBufferCopy(Code
, /*BufferName=*/""));
549 for (const auto &Extra
: Inputs
.ExtraFiles
)
550 VFS
->addFile(Extra
.getKey(), /*ModificationTime=*/0,
551 llvm::MemoryBuffer::getMemBufferCopy(Extra
.getValue(),
553 auto *FM
= Clang
->createFileManager(VFS
);
554 ASSERT_TRUE(Clang
->ExecuteAction(*Inputs
.MakeAction()));
556 PI
.getExporters(llvm::cantFail(FM
->getFileRef("foo.h")), *FM
),
557 testing::ElementsAre(llvm::cantFail(FM
->getFileRef("exporter.h"))));
560 } // namespace clang::include_cleaner