[AMDGPU][AsmParser][NFC] Translate parsed MIMG instructions to MCInsts automatically.
[llvm-project.git] / clang-tools-extra / include-cleaner / unittests / RecordTest.cpp
blob60d05128523fc1f7a982422f92888d0e902e4272
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 //===----------------------------------------------------------------------===//
9 #include "clang-include-cleaner/Record.h"
10 #include "clang/Basic/SourceLocation.h"
11 #include "clang/Frontend/FrontendAction.h"
12 #include "clang/Frontend/FrontendActions.h"
13 #include "clang/Testing/TestAST.h"
14 #include "clang/Tooling/Inclusions/StandardLibrary.h"
15 #include "llvm/ADT/ArrayRef.h"
16 #include "llvm/Support/raw_ostream.h"
17 #include "llvm/Testing/Annotations/Annotations.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
21 namespace clang::include_cleaner {
22 namespace {
23 using testing::ElementsAreArray;
24 using testing::IsEmpty;
26 // Matches a Decl* if it is a NamedDecl with the given name.
27 MATCHER_P(named, N, "") {
28 if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(arg)) {
29 if (N == ND->getNameAsString())
30 return true;
32 std::string S;
33 llvm::raw_string_ostream OS(S);
34 arg->dump(OS);
35 *result_listener << S;
36 return false;
39 MATCHER_P(FileNamed, N, "") {
40 if (arg->tryGetRealPathName() == N)
41 return true;
42 *result_listener << arg->tryGetRealPathName().str();
43 return false;
46 class RecordASTTest : public ::testing::Test {
47 protected:
48 TestInputs Inputs;
49 RecordedAST Recorded;
51 RecordASTTest() {
52 struct RecordAction : public ASTFrontendAction {
53 RecordedAST &Out;
54 RecordAction(RecordedAST &Out) : Out(Out) {}
55 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
56 StringRef) override {
57 return Out.record();
60 Inputs.MakeAction = [this] {
61 return std::make_unique<RecordAction>(Recorded);
65 TestAST build() { return TestAST(Inputs); }
68 // Top-level decl from the main file is a root, nested ones aren't.
69 TEST_F(RecordASTTest, Namespace) {
70 Inputs.Code =
71 R"cpp(
72 namespace ns {
73 int x;
74 namespace {
75 int y;
78 )cpp";
79 auto AST = build();
80 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("ns")));
83 // Decl in included file is not a root.
84 TEST_F(RecordASTTest, Inclusion) {
85 Inputs.ExtraFiles["header.h"] = "void headerFunc();";
86 Inputs.Code = R"cpp(
87 #include "header.h"
88 void mainFunc();
89 )cpp";
90 auto AST = build();
91 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("mainFunc")));
94 // Decl from macro expanded into the main file is a root.
95 TEST_F(RecordASTTest, Macros) {
96 Inputs.ExtraFiles["header.h"] = "#define X void x();";
97 Inputs.Code = R"cpp(
98 #include "header.h"
100 )cpp";
101 auto AST = build();
102 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("x")));
105 // Decl from template instantiation is filtered out from roots.
106 TEST_F(RecordASTTest, ImplicitTemplates) {
107 Inputs.ExtraFiles["dispatch.h"] = R"cpp(
108 struct A {
109 static constexpr int value = 1;
111 template <class Getter>
112 int dispatch() {
113 return Getter::template get<A>();
115 )cpp";
116 Inputs.Code = R"cpp(
117 #include "dispatch.h"
118 struct MyGetter {
119 template <class T> static int get() { return T::value; }
121 int v = dispatch<MyGetter>();
122 )cpp";
123 auto AST = build();
124 EXPECT_THAT(Recorded.Roots,
125 testing::ElementsAre(named("MyGetter"), named("v")));
128 class RecordPPTest : public ::testing::Test {
129 protected:
130 TestInputs Inputs;
131 RecordedPP Recorded;
133 RecordPPTest() {
134 struct RecordAction : public PreprocessOnlyAction {
135 RecordedPP &Out;
136 RecordAction(RecordedPP &Out) : Out(Out) {}
138 void ExecuteAction() override {
139 auto &PP = getCompilerInstance().getPreprocessor();
140 PP.addPPCallbacks(Out.record(PP));
141 PreprocessOnlyAction::ExecuteAction();
144 Inputs.MakeAction = [this] {
145 return std::make_unique<RecordAction>(Recorded);
149 TestAST build() { return TestAST(Inputs); }
152 // Matches an Include with a particular spelling.
153 MATCHER_P(spelled, S, "") { return arg.Spelled == S; }
155 TEST_F(RecordPPTest, CapturesIncludes) {
156 llvm::Annotations MainFile(R"cpp(
157 $H^#include "./header.h"
158 $M^#include <missing.h>
159 )cpp");
160 Inputs.Code = MainFile.code();
161 Inputs.ExtraFiles["header.h"] = "";
162 Inputs.ErrorOK = true; // missing header
163 auto AST = build();
165 ASSERT_THAT(
166 Recorded.Includes.all(),
167 testing::ElementsAre(spelled("./header.h"), spelled("missing.h")));
169 auto &H = Recorded.Includes.all().front();
170 EXPECT_EQ(H.Line, 2u);
171 EXPECT_EQ(H.HashLocation,
172 AST.sourceManager().getComposedLoc(
173 AST.sourceManager().getMainFileID(), MainFile.point("H")));
174 EXPECT_EQ(H.Resolved, AST.fileManager().getFile("header.h").get());
175 EXPECT_FALSE(H.Angled);
177 auto &M = Recorded.Includes.all().back();
178 EXPECT_EQ(M.Line, 3u);
179 EXPECT_EQ(M.HashLocation,
180 AST.sourceManager().getComposedLoc(
181 AST.sourceManager().getMainFileID(), MainFile.point("M")));
182 EXPECT_EQ(M.Resolved, nullptr);
183 EXPECT_TRUE(M.Angled);
186 TEST_F(RecordPPTest, CapturesMacroRefs) {
187 llvm::Annotations Header(R"cpp(
188 #define $def^X 1
190 // Refs, but not in main file.
191 #define Y X
192 int one = X;
193 )cpp");
194 llvm::Annotations MainFile(R"cpp(
195 #define EARLY X // not a ref, no definition
196 #include "header.h"
197 #define LATE ^X
198 #define LATE2 ^X // a ref even if not expanded
200 int uno = ^X;
201 int jeden = $exp^LATE; // a ref in LATE's expansion
203 #define IDENT(X) X // not a ref, shadowed
204 int eins = IDENT(^X);
206 #undef ^X
207 // Not refs, rather a new macro with the same name.
208 #define X 2
209 int two = X;
210 )cpp");
211 Inputs.Code = MainFile.code();
212 Inputs.ExtraFiles["header.h"] = Header.code();
213 auto AST = build();
214 const auto &SM = AST.sourceManager();
216 SourceLocation Def = SM.getComposedLoc(
217 SM.translateFile(AST.fileManager().getFile("header.h").get()),
218 Header.point("def"));
219 ASSERT_THAT(Recorded.MacroReferences, Not(IsEmpty()));
220 Symbol OrigX = Recorded.MacroReferences.front().Target;
221 EXPECT_EQ("X", OrigX.macro().Name->getName());
222 EXPECT_EQ(Def, OrigX.macro().Definition);
224 std::vector<unsigned> RefOffsets;
225 std::vector<unsigned> ExpOffsets; // Expansion locs of refs in macro locs.
226 for (const auto &Ref : Recorded.MacroReferences) {
227 if (Ref.Target == OrigX) {
228 auto [FID, Off] = SM.getDecomposedLoc(Ref.RefLocation);
229 if (FID == SM.getMainFileID()) {
230 RefOffsets.push_back(Off);
231 } else if (Ref.RefLocation.isMacroID() &&
232 SM.isWrittenInMainFile(SM.getExpansionLoc(Ref.RefLocation))) {
233 ExpOffsets.push_back(
234 SM.getDecomposedExpansionLoc(Ref.RefLocation).second);
235 } else {
236 ADD_FAILURE() << Ref.RefLocation.printToString(SM);
240 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
241 EXPECT_THAT(ExpOffsets, ElementsAreArray(MainFile.points("exp")));
244 TEST_F(RecordPPTest, CapturesConditionalMacroRefs) {
245 llvm::Annotations MainFile(R"cpp(
246 #define X 1
248 #ifdef ^X
249 #endif
251 #if defined(^X)
252 #endif
254 #ifndef ^X
255 #endif
257 #ifdef Y
258 #elifdef ^X
259 #endif
261 #ifndef ^X
262 #elifndef ^X
263 #endif
264 )cpp");
266 Inputs.Code = MainFile.code();
267 Inputs.ExtraArgs.push_back("-std=c++2b");
268 auto AST = build();
270 std::vector<unsigned> RefOffsets;
271 SourceManager &SM = AST.sourceManager();
272 for (const auto &Ref : Recorded.MacroReferences) {
273 auto [FID, Off] = SM.getDecomposedLoc(Ref.RefLocation);
274 ASSERT_EQ(FID, SM.getMainFileID());
275 EXPECT_EQ(Ref.RT, RefType::Ambiguous);
276 EXPECT_EQ("X", Ref.Target.macro().Name->getName());
277 RefOffsets.push_back(Off);
279 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
282 class PragmaIncludeTest : public ::testing::Test {
283 protected:
284 // We don't build an AST, we just run a preprocessor action!
285 TestInputs Inputs;
286 PragmaIncludes PI;
288 PragmaIncludeTest() {
289 Inputs.MakeAction = [this] {
290 struct Hook : public PreprocessOnlyAction {
291 public:
292 Hook(PragmaIncludes *Out) : Out(Out) {}
293 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
294 Out->record(CI);
295 return true;
297 PragmaIncludes *Out;
299 return std::make_unique<Hook>(&PI);
303 TestAST build() { return TestAST(Inputs); }
305 void createEmptyFiles(llvm::ArrayRef<StringRef> FileNames) {
306 for (llvm::StringRef File : FileNames)
307 Inputs.ExtraFiles[File] = "";
311 TEST_F(PragmaIncludeTest, IWYUKeep) {
312 llvm::Annotations MainFile(R"cpp(
313 $keep1^#include "keep1.h" // IWYU pragma: keep
314 $keep2^#include "keep2.h" /* IWYU pragma: keep */
316 $export1^#include "export1.h" // IWYU pragma: export
317 $begin_exports^// IWYU pragma: begin_exports
318 $export2^#include "export2.h"
319 $export3^#include "export3.h"
320 $end_exports^// IWYU pragma: end_exports
322 $normal^#include "normal.h"
324 $begin_keep^// IWYU pragma: begin_keep
325 $keep3^#include "keep3.h"
326 $end_keep^// IWYU pragma: end_keep
328 // IWYU pragma: begin_keep
329 $keep4^#include "keep4.h"
330 // IWYU pragma: begin_keep
331 $keep5^#include "keep5.h"
332 // IWYU pragma: end_keep
333 $keep6^#include "keep6.h"
334 // IWYU pragma: end_keep
335 )cpp");
337 auto OffsetToLineNum = [&MainFile](size_t Offset) {
338 int Count = MainFile.code().substr(0, Offset).count('\n');
339 return Count + 1;
342 Inputs.Code = MainFile.code();
343 createEmptyFiles({"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h",
344 "keep6.h", "export1.h", "export2.h", "export3.h",
345 "normal.h"});
347 TestAST Processed = build();
348 EXPECT_FALSE(PI.shouldKeep(1));
350 // Keep
351 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep1"))));
352 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep2"))));
354 EXPECT_FALSE(PI.shouldKeep(
355 OffsetToLineNum(MainFile.point("begin_keep")))); // no # directive
356 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep3"))));
357 EXPECT_FALSE(PI.shouldKeep(
358 OffsetToLineNum(MainFile.point("end_keep")))); // no # directive
360 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep4"))));
361 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep5"))));
362 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("keep6"))));
364 // Exports
365 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("export1"))));
366 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("export2"))));
367 EXPECT_TRUE(PI.shouldKeep(OffsetToLineNum(MainFile.point("export3"))));
368 EXPECT_FALSE(PI.shouldKeep(
369 OffsetToLineNum(MainFile.point("begin_exports")))); // no # directive
370 EXPECT_FALSE(PI.shouldKeep(
371 OffsetToLineNum(MainFile.point("end_exports")))); // no # directive
373 EXPECT_FALSE(PI.shouldKeep(OffsetToLineNum(MainFile.point("normal"))));
376 TEST_F(PragmaIncludeTest, IWYUPrivate) {
377 Inputs.Code = R"cpp(
378 #include "public.h"
379 )cpp";
380 Inputs.ExtraFiles["public.h"] = R"cpp(
381 #include "private.h"
382 #include "private2.h"
383 )cpp";
384 Inputs.ExtraFiles["private.h"] = R"cpp(
385 // IWYU pragma: private, include "public2.h"
386 )cpp";
387 Inputs.ExtraFiles["private2.h"] = R"cpp(
388 // IWYU pragma: private
389 )cpp";
390 TestAST Processed = build();
391 auto PrivateFE = Processed.fileManager().getFile("private.h");
392 assert(PrivateFE);
393 EXPECT_TRUE(PI.isPrivate(PrivateFE.get()));
394 EXPECT_EQ(PI.getPublic(PrivateFE.get()), "\"public2.h\"");
396 auto PublicFE = Processed.fileManager().getFile("public.h");
397 assert(PublicFE);
398 EXPECT_EQ(PI.getPublic(PublicFE.get()), ""); // no mapping.
399 EXPECT_FALSE(PI.isPrivate(PublicFE.get()));
401 auto Private2FE = Processed.fileManager().getFile("private2.h");
402 assert(Private2FE);
403 EXPECT_TRUE(PI.isPrivate(Private2FE.get()));
406 TEST_F(PragmaIncludeTest, IWYUExport) {
407 Inputs.Code = R"cpp(// Line 1
408 #include "export1.h"
409 #include "export2.h"
410 )cpp";
411 Inputs.ExtraFiles["export1.h"] = R"cpp(
412 #include "private.h" // IWYU pragma: export
413 )cpp";
414 Inputs.ExtraFiles["export2.h"] = R"cpp(
415 #include "export3.h"
416 )cpp";
417 Inputs.ExtraFiles["export3.h"] = R"cpp(
418 #include "private.h" // IWYU pragma: export
419 )cpp";
420 Inputs.ExtraFiles["private.h"] = "";
421 TestAST Processed = build();
422 const auto &SM = Processed.sourceManager();
423 auto &FM = Processed.fileManager();
425 EXPECT_THAT(PI.getExporters(FM.getFile("private.h").get(), FM),
426 testing::UnorderedElementsAre(FileNamed("export1.h"),
427 FileNamed("export3.h")));
429 EXPECT_TRUE(PI.getExporters(FM.getFile("export1.h").get(), FM).empty());
430 EXPECT_TRUE(PI.getExporters(FM.getFile("export2.h").get(), FM).empty());
431 EXPECT_TRUE(PI.getExporters(FM.getFile("export3.h").get(), FM).empty());
432 EXPECT_TRUE(
433 PI.getExporters(SM.getFileEntryForID(SM.getMainFileID()), FM).empty());
436 TEST_F(PragmaIncludeTest, IWYUExportForStandardHeaders) {
437 Inputs.Code = R"cpp(
438 #include "export.h"
439 )cpp";
440 Inputs.ExtraFiles["export.h"] = R"cpp(
441 #include <string> // IWYU pragma: export
442 )cpp";
443 Inputs.ExtraFiles["string"] = "";
444 Inputs.ExtraArgs = {"-isystem."};
445 TestAST Processed = build();
446 auto &FM = Processed.fileManager();
447 EXPECT_THAT(PI.getExporters(*tooling::stdlib::Header::named("<string>"), FM),
448 testing::UnorderedElementsAre(FileNamed("export.h")));
451 TEST_F(PragmaIncludeTest, IWYUExportBlock) {
452 Inputs.Code = R"cpp(// Line 1
453 #include "normal.h"
454 )cpp";
455 Inputs.ExtraFiles["normal.h"] = R"cpp(
456 #include "foo.h"
458 // IWYU pragma: begin_exports
459 #include "export1.h"
460 #include "private1.h"
461 // IWYU pragma: end_exports
462 )cpp";
463 Inputs.ExtraFiles["export1.h"] = R"cpp(
464 // IWYU pragma: begin_exports
465 #include "private1.h"
466 #include "private2.h"
467 // IWYU pragma: end_exports
469 #include "bar.h"
470 #include "private3.h" // IWYU pragma: export
471 )cpp";
472 createEmptyFiles(
473 {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"});
474 TestAST Processed = build();
475 auto &FM = Processed.fileManager();
477 EXPECT_THAT(PI.getExporters(FM.getFile("private1.h").get(), FM),
478 testing::UnorderedElementsAre(FileNamed("export1.h"),
479 FileNamed("normal.h")));
480 EXPECT_THAT(PI.getExporters(FM.getFile("private2.h").get(), FM),
481 testing::UnorderedElementsAre(FileNamed("export1.h")));
482 EXPECT_THAT(PI.getExporters(FM.getFile("private3.h").get(), FM),
483 testing::UnorderedElementsAre(FileNamed("export1.h")));
485 EXPECT_TRUE(PI.getExporters(FM.getFile("foo.h").get(), FM).empty());
486 EXPECT_TRUE(PI.getExporters(FM.getFile("bar.h").get(), FM).empty());
489 TEST_F(PragmaIncludeTest, SelfContained) {
490 Inputs.Code = R"cpp(
491 #include "guarded.h"
493 #include "unguarded.h"
494 )cpp";
495 Inputs.ExtraFiles["guarded.h"] = R"cpp(
496 #pragma once
497 )cpp";
498 Inputs.ExtraFiles["unguarded.h"] = "";
499 TestAST Processed = build();
500 auto &FM = Processed.fileManager();
501 EXPECT_TRUE(PI.isSelfContained(FM.getFile("guarded.h").get()));
502 EXPECT_FALSE(PI.isSelfContained(FM.getFile("unguarded.h").get()));
505 } // namespace
506 } // namespace clang::include_cleaner