[AMDGPU][AsmParser][NFC] Translate parsed MIMG instructions to MCInsts automatically.
[llvm-project.git] / clang-tools-extra / include-cleaner / unittests / AnalysisTest.cpp
blob3ad632a18c563adaa2eb6e9c181177fc06b6c396
1 //===--- AnalysisTest.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/Analysis.h"
10 #include "AnalysisInternal.h"
11 #include "TypesInternal.h"
12 #include "clang-include-cleaner/Record.h"
13 #include "clang-include-cleaner/Types.h"
14 #include "clang/AST/ASTContext.h"
15 #include "clang/Basic/FileManager.h"
16 #include "clang/Basic/IdentifierTable.h"
17 #include "clang/Basic/SourceLocation.h"
18 #include "clang/Basic/SourceManager.h"
19 #include "clang/Format/Format.h"
20 #include "clang/Frontend/FrontendActions.h"
21 #include "clang/Testing/TestAST.h"
22 #include "clang/Tooling/Inclusions/StandardLibrary.h"
23 #include "llvm/ADT/ArrayRef.h"
24 #include "llvm/ADT/SmallVector.h"
25 #include "llvm/ADT/StringRef.h"
26 #include "llvm/Support/ScopedPrinter.h"
27 #include "llvm/Testing/Annotations/Annotations.h"
28 #include "gmock/gmock.h"
29 #include "gtest/gtest.h"
30 #include <cstddef>
31 #include <map>
32 #include <memory>
33 #include <string>
34 #include <vector>
36 namespace clang::include_cleaner {
37 namespace {
38 using testing::AllOf;
39 using testing::Contains;
40 using testing::ElementsAre;
41 using testing::Pair;
42 using testing::UnorderedElementsAre;
44 std::string guard(llvm::StringRef Code) {
45 return "#pragma once\n" + Code.str();
48 class WalkUsedTest : public testing::Test {
49 protected:
50 TestInputs Inputs;
51 PragmaIncludes PI;
52 WalkUsedTest() {
53 Inputs.MakeAction = [this] {
54 struct Hook : public SyntaxOnlyAction {
55 public:
56 Hook(PragmaIncludes *Out) : Out(Out) {}
57 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
58 Out->record(CI);
59 return true;
62 PragmaIncludes *Out;
64 return std::make_unique<Hook>(&PI);
68 std::multimap<size_t, std::vector<Header>>
69 offsetToProviders(TestAST &AST, SourceManager &SM,
70 llvm::ArrayRef<SymbolReference> MacroRefs = {}) {
71 llvm::SmallVector<Decl *> TopLevelDecls;
72 for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
73 if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation())))
74 continue;
75 TopLevelDecls.emplace_back(D);
77 std::multimap<size_t, std::vector<Header>> OffsetToProviders;
78 walkUsed(TopLevelDecls, MacroRefs, &PI, SM,
79 [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
80 auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation);
81 if (FID != SM.getMainFileID())
82 ADD_FAILURE() << "Reference outside of the main file!";
83 OffsetToProviders.emplace(Offset, Providers.vec());
84 });
85 return OffsetToProviders;
89 TEST_F(WalkUsedTest, Basic) {
90 llvm::Annotations Code(R"cpp(
91 #include "header.h"
92 #include "private.h"
94 // No reference reported for the Parameter "p".
95 void $bar^bar($private^Private p) {
96 $foo^foo();
97 std::$vector^vector $vconstructor^$v^v;
98 $builtin^__builtin_popcount(1);
99 std::$move^move(3);
101 )cpp");
102 Inputs.Code = Code.code();
103 Inputs.ExtraFiles["header.h"] = guard(R"cpp(
104 void foo();
105 namespace std { class vector {}; int&& move(int&&); }
106 )cpp");
107 Inputs.ExtraFiles["private.h"] = guard(R"cpp(
108 // IWYU pragma: private, include "path/public.h"
109 class Private {};
110 )cpp");
112 TestAST AST(Inputs);
113 auto &SM = AST.sourceManager();
114 auto HeaderFile = Header(AST.fileManager().getFile("header.h").get());
115 auto PrivateFile = Header(AST.fileManager().getFile("private.h").get());
116 auto PublicFile = Header("\"path/public.h\"");
117 auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID()));
118 auto VectorSTL = Header(*tooling::stdlib::Header::named("<vector>"));
119 auto UtilitySTL = Header(*tooling::stdlib::Header::named("<utility>"));
120 EXPECT_THAT(
121 offsetToProviders(AST, SM),
122 UnorderedElementsAre(
123 Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
124 Pair(Code.point("private"),
125 UnorderedElementsAre(PublicFile, PrivateFile)),
126 Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)),
127 Pair(Code.point("vector"), UnorderedElementsAre(VectorSTL)),
128 Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)),
129 Pair(Code.point("v"), UnorderedElementsAre(MainFile)),
130 Pair(Code.point("builtin"), testing::IsEmpty()),
131 Pair(Code.point("move"), UnorderedElementsAre(UtilitySTL))));
134 TEST_F(WalkUsedTest, MultipleProviders) {
135 llvm::Annotations Code(R"cpp(
136 #include "header1.h"
137 #include "header2.h"
138 void foo();
140 void bar() {
141 $foo^foo();
143 )cpp");
144 Inputs.Code = Code.code();
145 Inputs.ExtraFiles["header1.h"] = guard(R"cpp(
146 void foo();
147 )cpp");
148 Inputs.ExtraFiles["header2.h"] = guard(R"cpp(
149 void foo();
150 )cpp");
152 TestAST AST(Inputs);
153 auto &SM = AST.sourceManager();
154 auto HeaderFile1 = Header(AST.fileManager().getFile("header1.h").get());
155 auto HeaderFile2 = Header(AST.fileManager().getFile("header2.h").get());
156 auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID()));
157 EXPECT_THAT(
158 offsetToProviders(AST, SM),
159 Contains(Pair(Code.point("foo"),
160 UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
163 TEST_F(WalkUsedTest, MacroRefs) {
164 llvm::Annotations Code(R"cpp(
165 #include "hdr.h"
166 int $3^x = $1^ANSWER;
167 int $4^y = $2^ANSWER;
168 )cpp");
169 llvm::Annotations Hdr(guard("#define ^ANSWER 42"));
170 Inputs.Code = Code.code();
171 Inputs.ExtraFiles["hdr.h"] = Hdr.code();
172 TestAST AST(Inputs);
173 auto &SM = AST.sourceManager();
174 const auto *HdrFile = SM.getFileManager().getFile("hdr.h").get();
175 auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID()));
177 auto HdrID = SM.translateFile(HdrFile);
179 IdentifierTable Idents;
180 Symbol Answer1 =
181 Macro{&Idents.get("ANSWER"), SM.getComposedLoc(HdrID, Hdr.point())};
182 Symbol Answer2 =
183 Macro{&Idents.get("ANSWER"), SM.getComposedLoc(HdrID, Hdr.point())};
184 EXPECT_THAT(
185 offsetToProviders(
186 AST, SM,
187 {SymbolReference{
188 Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1")),
189 RefType::Explicit},
190 SymbolReference{
191 Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2")),
192 RefType::Explicit}}),
193 UnorderedElementsAre(
194 Pair(Code.point("1"), UnorderedElementsAre(HdrFile)),
195 Pair(Code.point("2"), UnorderedElementsAre(HdrFile)),
196 Pair(Code.point("3"), UnorderedElementsAre(MainFile)),
197 Pair(Code.point("4"), UnorderedElementsAre(MainFile))));
200 class AnalyzeTest : public testing::Test {
201 protected:
202 TestInputs Inputs;
203 PragmaIncludes PI;
204 RecordedPP PP;
205 AnalyzeTest() {
206 Inputs.MakeAction = [this] {
207 struct Hook : public SyntaxOnlyAction {
208 public:
209 Hook(RecordedPP &PP, PragmaIncludes &PI) : PP(PP), PI(PI) {}
210 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
211 CI.getPreprocessor().addPPCallbacks(PP.record(CI.getPreprocessor()));
212 PI.record(CI);
213 return true;
216 RecordedPP &PP;
217 PragmaIncludes &PI;
219 return std::make_unique<Hook>(PP, PI);
224 TEST_F(AnalyzeTest, Basic) {
225 Inputs.Code = R"cpp(
226 #include "a.h"
227 #include "b.h"
228 #include "keep.h" // IWYU pragma: keep
230 int x = a + c;
231 )cpp";
232 Inputs.ExtraFiles["a.h"] = guard("int a;");
233 Inputs.ExtraFiles["b.h"] = guard(R"cpp(
234 #include "c.h"
235 int b;
236 )cpp");
237 Inputs.ExtraFiles["c.h"] = guard("int c;");
238 Inputs.ExtraFiles["keep.h"] = guard("");
239 TestAST AST(Inputs);
240 auto Decls = AST.context().getTranslationUnitDecl()->decls();
241 auto Results =
242 analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
243 PP.MacroReferences, PP.Includes, &PI, AST.sourceManager(),
244 AST.preprocessor().getHeaderSearchInfo());
246 const Include *B = PP.Includes.atLine(3);
247 ASSERT_EQ(B->Spelled, "b.h");
248 EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\""));
249 EXPECT_THAT(Results.Unused, ElementsAre(B));
252 TEST_F(AnalyzeTest, PrivateUsedInPublic) {
253 // Check that umbrella header uses private include.
254 Inputs.Code = R"cpp(#include "private.h")cpp";
255 Inputs.ExtraFiles["private.h"] =
256 guard("// IWYU pragma: private, include \"public.h\"");
257 Inputs.FileName = "public.h";
258 TestAST AST(Inputs);
259 EXPECT_FALSE(PP.Includes.all().empty());
260 auto Results = analyze({}, {}, PP.Includes, &PI, AST.sourceManager(),
261 AST.preprocessor().getHeaderSearchInfo());
262 EXPECT_THAT(Results.Unused, testing::IsEmpty());
265 TEST_F(AnalyzeTest, NoCrashWhenUnresolved) {
266 // Check that umbrella header uses private include.
267 Inputs.Code = R"cpp(#include "not_found.h")cpp";
268 Inputs.ErrorOK = true;
269 TestAST AST(Inputs);
270 EXPECT_FALSE(PP.Includes.all().empty());
271 auto Results = analyze({}, {}, PP.Includes, &PI, AST.sourceManager(),
272 AST.preprocessor().getHeaderSearchInfo());
273 EXPECT_THAT(Results.Unused, testing::IsEmpty());
276 TEST(FixIncludes, Basic) {
277 llvm::StringRef Code = R"cpp(#include "d.h"
278 #include "a.h"
279 #include "b.h"
280 #include <c.h>
281 )cpp";
283 Includes Inc;
284 Include I;
285 I.Spelled = "a.h";
286 I.Line = 2;
287 Inc.add(I);
288 I.Spelled = "b.h";
289 I.Line = 3;
290 Inc.add(I);
291 I.Spelled = "c.h";
292 I.Line = 4;
293 I.Angled = true;
294 Inc.add(I);
296 AnalysisResults Results;
297 Results.Missing.push_back("\"aa.h\"");
298 Results.Missing.push_back("\"ab.h\"");
299 Results.Missing.push_back("<e.h>");
300 Results.Unused.push_back(Inc.atLine(3));
301 Results.Unused.push_back(Inc.atLine(4));
303 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
304 R"cpp(#include "d.h"
305 #include "a.h"
306 #include "aa.h"
307 #include "ab.h"
308 #include <e.h>
309 )cpp");
311 Results = {};
312 Results.Missing.push_back("\"d.h\"");
313 Code = R"cpp(#include "a.h")cpp";
314 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
315 R"cpp(#include "d.h"
316 #include "a.h")cpp");
319 MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
320 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
321 return ExpanedFileID == FileID && ExpandedOffset == Offset;
323 MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
324 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
325 return SpelledFileID == FileID && SpelledOffset == Offset;
327 TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {
328 // Each test is expected to have a single expected ref of `target` symbol
329 // (or have none).
330 // The location in the reported ref is a macro location. $expand points to
331 // the macro location, and $spell points to the spelled location.
332 struct {
333 llvm::StringRef Header;
334 llvm::StringRef Main;
335 } TestCases[] = {
336 // Tests for decl references.
338 /*Header=*/"int target();",
339 R"cpp(
340 #define CALL_FUNC $spell^target()
342 int b = $expand^CALL_FUNC;
343 )cpp",
345 {/*Header=*/R"cpp(
346 int target();
347 #define CALL_FUNC target()
348 )cpp",
349 // No ref of `target` being reported, as it is not spelled in main file.
350 "int a = CALL_FUNC;"},
352 /*Header=*/R"cpp(
353 int target();
354 #define PLUS_ONE(X) X() + 1
355 )cpp",
356 R"cpp(
357 int a = $expand^PLUS_ONE($spell^target);
358 )cpp",
361 /*Header=*/R"cpp(
362 int target();
363 #define PLUS_ONE(X) X() + 1
364 )cpp",
365 R"cpp(
366 int a = $expand^PLUS_ONE($spell^target);
367 )cpp",
369 // Tests for macro references
370 {/*Header=*/"#define target 1",
371 R"cpp(
372 #define USE_target $spell^target
373 int b = $expand^USE_target;
374 )cpp"},
375 {/*Header=*/R"cpp(
376 #define target 1
377 #define USE_target target
378 )cpp",
379 // No ref of `target` being reported, it is not spelled in main file.
380 R"cpp(
381 int a = USE_target;
382 )cpp"},
385 for (const auto &T : TestCases) {
386 llvm::Annotations Main(T.Main);
387 TestInputs Inputs(Main.code());
388 Inputs.ExtraFiles["header.h"] = guard(T.Header);
389 RecordedPP Recorded;
390 Inputs.MakeAction = [&]() {
391 struct RecordAction : public SyntaxOnlyAction {
392 RecordedPP &Out;
393 RecordAction(RecordedPP &Out) : Out(Out) {}
394 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
395 auto &PP = CI.getPreprocessor();
396 PP.addPPCallbacks(Out.record(PP));
397 return true;
400 return std::make_unique<RecordAction>(Recorded);
402 Inputs.ExtraArgs.push_back("-include");
403 Inputs.ExtraArgs.push_back("header.h");
404 TestAST AST(Inputs);
405 llvm::SmallVector<Decl *> TopLevelDecls;
406 for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
407 TopLevelDecls.emplace_back(D);
408 auto &SM = AST.sourceManager();
410 SourceLocation RefLoc;
411 walkUsed(TopLevelDecls, Recorded.MacroReferences,
412 /*PragmaIncludes=*/nullptr, SM,
413 [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
414 if (!Ref.RefLocation.isMacroID())
415 return;
416 if (llvm::to_string(Ref.Target) == "target") {
417 ASSERT_TRUE(RefLoc.isInvalid())
418 << "Expected only one 'target' ref loc per testcase";
419 RefLoc = Ref.RefLocation;
422 FileID MainFID = SM.getMainFileID();
423 if (RefLoc.isValid()) {
424 EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),
425 spelledAt(MainFID, Main.point("spell"), &SM)))
426 << T.Main.str();
427 } else {
428 EXPECT_THAT(Main.points(), testing::IsEmpty());
433 struct Tag {
434 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
435 return OS << "Anon Tag";
438 TEST(Hints, Ordering) {
439 auto Hinted = [](Hints Hints) {
440 return clang::include_cleaner::Hinted<Tag>({}, Hints);
442 EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));
443 EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));
444 EXPECT_LT(Hinted(Hints::PublicHeader), Hinted(Hints::PreferredHeader));
445 EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PublicHeader),
446 Hinted(Hints::PreferredHeader));
449 // Test ast traversal & redecl selection end-to-end for templates, as explicit
450 // instantiations/specializations are not redecls of the primary template. We
451 // need to make sure we're selecting the right ones.
452 TEST_F(WalkUsedTest, TemplateDecls) {
453 llvm::Annotations Code(R"cpp(
454 #include "fwd.h"
455 #include "def.h"
456 #include "partial.h"
457 template <> struct $exp_spec^Foo<char> {};
458 template struct $exp^Foo<int>;
459 $full^Foo<int> x;
460 $implicit^Foo<bool> y;
461 $partial^Foo<int*> z;
462 )cpp");
463 Inputs.Code = Code.code();
464 Inputs.ExtraFiles["fwd.h"] = guard("template<typename> struct Foo;");
465 Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");
466 Inputs.ExtraFiles["partial.h"] =
467 guard("template<typename T> struct Foo<T*> {};");
468 TestAST AST(Inputs);
469 auto &SM = AST.sourceManager();
470 const auto *Fwd = SM.getFileManager().getFile("fwd.h").get();
471 const auto *Def = SM.getFileManager().getFile("def.h").get();
472 const auto *Partial = SM.getFileManager().getFile("partial.h").get();
474 EXPECT_THAT(
475 offsetToProviders(AST, SM),
476 AllOf(Contains(
477 Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),
478 Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),
479 Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),
480 Contains(
481 Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),
482 Contains(
483 Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));
486 } // namespace
487 } // namespace clang::include_cleaner