1 //===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===//
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 "Annotations.h"
10 #include "IncludeCleaner.h"
11 #include "SourceCode.h"
12 #include "TestFS.h"
13 #include "TestTU.h"
14 #include "llvm/ADT/ScopeExit.h"
15 #include "llvm/Testing/Support/SupportHelpers.h"
16 #include "gmock/gmock.h"
17 #include "gtest/gtest.h"
18
19 namespace clang {
20 namespace clangd {
21 namespace {
22
23 using ::testing::ElementsAre;
24 using ::testing::ElementsAreArray;
25 using ::testing::IsEmpty;
26 using ::testing::Pointee;
27 using ::testing::UnorderedElementsAre;
28
guard(llvm::StringRef Code)29 std::string guard(llvm::StringRef Code) {
30 return "#pragma once\n" + Code.str();
31 }
32
TEST(IncludeCleaner,ReferencedLocations)33 TEST(IncludeCleaner, ReferencedLocations) {
34 struct TestCase {
35 std::string HeaderCode;
36 std::string MainCode;
37 };
38 TestCase Cases[] = {
39 // DeclRefExpr
40 {
41 "int ^x();",
42 "int y = x();",
43 },
44 // RecordDecl
45 {
46 "class ^X;",
47 "X *y;",
48 },
49 // When definition is available, we don't need to mark forward
50 // declarations as used.
51 {
52 "class ^X {}; class X;",
53 "X *y;",
54 },
55 // We already have forward declaration in the main file, the other
56 // non-definition declarations are not needed.
57 {
58 "class ^X {}; class X;",
59 "class X; X *y;",
60 },
61 // Nested class definition can occur outside of the parent class
62 // definition. Bar declaration should be visible to its definition but
63 // it will always be because we will mark Foo definition as used.
64 {
65 "class ^Foo { class Bar; };",
66 "class Foo::Bar {};",
67 },
68 // TypedefType and UsingDecls
69 {
70 "using ^Integer = int;",
71 "Integer x;",
72 },
73 {
74 "namespace ns { void ^foo(); void ^foo() {} }",
75 "using ns::foo;",
76 },
77 {
78 "namespace ns { void foo(); void foo() {}; }",
79 "using namespace ns;",
80 },
81 {
82 // Refs from UsingTypeLoc and implicit constructor!
83 "struct ^A {}; using B = A; using ^C = B;",
84 "C a;",
85 },
86 {"namespace ns { template<typename T> class A {}; } using ns::^A;",
87 "A<int>* a;"},
88 {"namespace ns { template<typename T> class A {}; } using ns::^A;",
89 R"cpp(
90 template <template <typename> class T> class X {};
91 X<A> x;
92 )cpp"},
93 {R"cpp(
94 namespace ns { template<typename T> class A {}; }
95 namespace absl {using ns::^A;}
96 )cpp",
97 R"cpp(
98 template <template <typename> class T> class X {};
99 X<absl::A> x;
100 )cpp"},
101 {R"cpp(
102 namespace ns { template<typename T> struct ^A { ^A(T); }; }
103 using ns::^A;
104 )cpp",
105 "A CATD(123);"},
106 {
107 "typedef bool ^Y; template <typename T> struct ^X {};",
108 "X<Y> x;",
109 },
110 {
111 // https://github.com/clangd/clangd/issues/1036
112 R"cpp(
113 struct ^Base { void ^base(); };
114 template <int> struct ^Derived : Base {};
115 )cpp",
116 R"cpp(
117 class Holder {
118 void foo() { Member.base(); }
119 Derived<0> Member;
120 };
121 )cpp",
122 },
123 {
124 "struct Foo; struct ^Foo{}; typedef Foo ^Bar;",
125 "Bar b;",
126 },
127 {
128 "namespace ns { class X; }; using ns::^X;",
129 "X *y;",
130 },
131 // MemberExpr
132 {
133 "struct ^X{int ^a;}; X ^foo();",
134 "int y = foo().a;",
135 },
136 // Expr (type is traversed)
137 {
138 "class ^X{}; X ^foo();",
139 "auto bar() { return foo(); }",
140 },
141 // Redecls
142 {
143 "void ^foo(); void ^foo() {} void ^foo();",
144 "void bar() { foo(); }",
145 },
146 // Constructor
147 {
148 "struct ^X { ^X(int) {} int ^foo(); };",
149 "auto x = X(42); auto y = x.foo();",
150 },
151 // Function
152 {
153 "void ^foo();",
154 "void foo() {}",
155 },
156 {
157 "void foo() {}",
158 "void foo();",
159 },
160 {
161 "inline void ^foo() {}",
162 "void bar() { foo(); }",
163 },
164 {
165 "int ^foo(char); int ^foo(float);",
166 "template<class T> int x = foo(T{});",
167 },
168 // Static function
169 {
170 "struct ^X { static bool ^foo(); }; bool X::^foo() {}",
171 "auto b = X::foo();",
172 },
173 // TemplateRecordDecl
174 {
175 "template <typename> class ^X;",
176 "X<int> *y;",
177 },
178 // Type name not spelled out in code
179 {
180 "class ^X{}; X ^getX();",
181 "auto x = getX();",
182 },
183 // Enums
184 {
185 "enum ^Color { ^Red = 42, Green = 9000};",
186 "int MyColor = Red;",
187 },
188 {
189 "struct ^X { enum ^Language { ^CXX = 42, Python = 9000}; };",
190 "int Lang = X::CXX;",
191 },
192 // Macros
193 {
194 "#define ^CONSTANT 42",
195 "int Foo = CONSTANT;",
196 },
197 {
198 "#define ^FOO x",
199 "#define BAR FOO",
200 },
201 {
202 "#define INNER 42\n"
203 "#define ^OUTER INNER",
204 "int answer = OUTER;",
205 },
206 {
207 "#define ^ANSWER 42\n"
208 "#define ^SQUARE(X) X * X",
209 "int sq = SQUARE(ANSWER);",
210 },
211 {
212 "#define ^FOO\n"
213 "#define ^BAR",
214 "#if 0\n"
215 "#if FOO\n"
216 "BAR\n"
217 "#endif\n"
218 "#endif",
219 },
220 // Misc
221 {
222 "enum class ^Color : int;",
223 "enum class Color : int {};",
224 },
225 {
226 "enum class Color : int {};",
227 "enum class Color : int;",
228 },
229 {
230 "enum class ^Color;",
231 "Color c;",
232 },
233 {
234 "enum class ^Color : int;",
235 "Color c;",
236 },
237 {
238 "enum class ^Color : char;",
239 "Color *c;",
240 },
241 {
242 "enum class ^Color : char {};",
243 "Color *c;",
244 },
245 };
246 for (const TestCase &T : Cases) {
247 TestTU TU;
248 TU.Code = T.MainCode;
249 Annotations Header(T.HeaderCode);
250 TU.HeaderCode = Header.code().str();
251 TU.ExtraArgs.push_back("-std=c++17");
252 auto AST = TU.build();
253
254 std::vector<Position> Points;
255 for (const auto &Loc : findReferencedLocations(AST).User) {
256 if (AST.getSourceManager().getBufferName(Loc).endswith(
257 TU.HeaderFilename)) {
258 Points.push_back(offsetToPosition(
259 TU.HeaderCode, AST.getSourceManager().getFileOffset(Loc)));
260 }
261 }
262 llvm::sort(Points);
263
264 EXPECT_EQ(Points, Header.points()) << T.HeaderCode << "\n---\n"
265 << T.MainCode;
266 }
267 }
268
TEST(IncludeCleaner,Stdlib)269 TEST(IncludeCleaner, Stdlib) {
270 // Smoke tests only for finding used symbols/headers.
271 // Details of Decl -> stdlib::Symbol -> stdlib::Headers mapping tested there.
272 auto TU = TestTU::withHeaderCode(R"cpp(
273 namespace std { class error_code {}; }
274 class error_code {};
275 namespace nonstd { class error_code {}; }
276 )cpp");
277 struct {
278 llvm::StringRef Code;
279 std::vector<llvm::StringRef> Symbols;
280 std::vector<llvm::StringRef> Headers;
281 } Tests[] = {
282 {"std::error_code x;", {"std::error_code"}, {"<system_error>"}},
283 {"error_code x;", {}, {}},
284 {"nonstd::error_code x;", {}, {}},
285 };
286
287 for (const auto &Test : Tests) {
288 TU.Code = Test.Code.str();
289 ParsedAST AST = TU.build();
290 std::vector<tooling::stdlib::Symbol> WantSyms;
291 for (const auto &SymName : Test.Symbols) {
292 auto QName = splitQualifiedName(SymName);
293 auto Sym = tooling::stdlib::Symbol::named(QName.first, QName.second);
294 EXPECT_TRUE(Sym) << SymName;
295 WantSyms.push_back(*Sym);
296 }
297 std::vector<tooling::stdlib::Header> WantHeaders;
298 for (const auto &HeaderName : Test.Headers) {
299 auto Header = tooling::stdlib::Header::named(HeaderName);
300 EXPECT_TRUE(Header) << HeaderName;
301 WantHeaders.push_back(*Header);
302 }
303
304 ReferencedLocations Locs = findReferencedLocations(AST);
305 EXPECT_THAT(Locs.Stdlib, ElementsAreArray(WantSyms));
306 ReferencedFiles Files =
307 findReferencedFiles(Locs, AST.getIncludeStructure(),
308 AST.getCanonicalIncludes(), AST.getSourceManager());
309 EXPECT_THAT(Files.Stdlib, ElementsAreArray(WantHeaders));
310 }
311 }
312
313 MATCHER_P(writtenInclusion, Written, "") {
314 if (arg.Written != Written)
315 *result_listener << arg.Written;
316 return arg.Written == Written;
317 }
318
TEST(IncludeCleaner,StdlibUnused)319 TEST(IncludeCleaner, StdlibUnused) {
320 setIncludeCleanerAnalyzesStdlib(true);
321 auto Cleanup =
322 llvm::make_scope_exit([] { setIncludeCleanerAnalyzesStdlib(false); });
323
324 auto TU = TestTU::withCode(R"cpp(
325 #include <list>
326 #include <queue>
327 std::list<int> x;
328 )cpp");
329 // Layout of std library impl is not relevant.
330 TU.AdditionalFiles["bits"] = R"cpp(
331 #pragma once
332 namespace std {
333 template <typename> class list {};
334 template <typename> class queue {};
335 }
336 )cpp";
337 TU.AdditionalFiles["list"] = "#include <bits>";
338 TU.AdditionalFiles["queue"] = "#include <bits>";
339 TU.ExtraArgs = {"-isystem", testRoot()};
340 auto AST = TU.build();
341
342 auto Unused = computeUnusedIncludes(AST);
343 EXPECT_THAT(Unused, ElementsAre(Pointee(writtenInclusion("<queue>"))));
344 }
345
TEST(IncludeCleaner,GetUnusedHeaders)346 TEST(IncludeCleaner, GetUnusedHeaders) {
347 llvm::StringLiteral MainFile = R"cpp(
348 #include "a.h"
349 #include "b.h"
350 #include "dir/c.h"
351 #include "dir/unused.h"
352 #include "unguarded.h"
353 #include "unused.h"
354 #include <system_header.h>
355 void foo() {
356 a();
357 b();
358 c();
359 })cpp";
360 // Build expected ast with symbols coming from headers.
361 TestTU TU;
362 TU.Filename = "foo.cpp";
363 TU.AdditionalFiles["foo.h"] = guard("void foo();");
364 TU.AdditionalFiles["a.h"] = guard("void a();");
365 TU.AdditionalFiles["b.h"] = guard("void b();");
366 TU.AdditionalFiles["dir/c.h"] = guard("void c();");
367 TU.AdditionalFiles["unused.h"] = guard("void unused();");
368 TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
369 TU.AdditionalFiles["system/system_header.h"] = guard("");
370 TU.AdditionalFiles["unguarded.h"] = "";
371 TU.ExtraArgs.push_back("-I" + testPath("dir"));
372 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
373 TU.Code = MainFile.str();
374 ParsedAST AST = TU.build();
375 std::vector<std::string> UnusedIncludes;
376 for (const auto &Include : computeUnusedIncludes(AST))
377 UnusedIncludes.push_back(Include->Written);
378 EXPECT_THAT(UnusedIncludes,
379 UnorderedElementsAre("\"unused.h\"", "\"dir/unused.h\""));
380 }
381
TEST(IncludeCleaner,VirtualBuffers)382 TEST(IncludeCleaner, VirtualBuffers) {
383 TestTU TU;
384 TU.Code = R"cpp(
385 #include "macros.h"
386
387 using flags::FLAGS_FOO;
388
389 // CLI will come from a define, __cplusplus is a built-in. In both cases, they
390 // come from non-existent files.
391 int y = CLI + __cplusplus;
392
393 int concat(a, b) = 42;
394 )cpp";
395 // The pasting operator in combination with DEFINE_FLAG will create
396 // ScratchBuffer with `flags::FLAGS_FOO` that will have FileID but not
397 // FileEntry.
398 TU.AdditionalFiles["macros.h"] = R"cpp(
399 #ifndef MACROS_H
400 #define MACROS_H
401
402 #define DEFINE_FLAG(X) \
403 namespace flags { \
404 int FLAGS_##X; \
405 } \
406
407 DEFINE_FLAG(FOO)
408
409 #define ab x
410 #define concat(x, y) x##y
411
412 #endif // MACROS_H
413 )cpp";
414 TU.ExtraArgs = {"-DCLI=42"};
415 ParsedAST AST = TU.build();
416 auto &SM = AST.getSourceManager();
417 auto &Includes = AST.getIncludeStructure();
418
419 auto ReferencedFiles = findReferencedFiles(
420 findReferencedLocations(AST), Includes, AST.getCanonicalIncludes(), SM);
421 llvm::StringSet<> ReferencedFileNames;
422 for (FileID FID : ReferencedFiles.User)
423 ReferencedFileNames.insert(
424 SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
425 // Note we deduped the names as _number_ of <built-in>s is uninteresting.
426 EXPECT_THAT(ReferencedFileNames.keys(),
427 UnorderedElementsAre("<built-in>", "<scratch space>",
428 testPath("macros.h")));
429
430 // Should not crash due to FileIDs that are not headers.
431 auto ReferencedHeaders = translateToHeaderIDs(ReferencedFiles, Includes, SM);
432 std::vector<llvm::StringRef> ReferencedHeaderNames;
433 for (IncludeStructure::HeaderID HID : ReferencedHeaders)
434 ReferencedHeaderNames.push_back(Includes.getRealPath(HID));
435 // Non-header files are gone at this point.
436 EXPECT_THAT(ReferencedHeaderNames, ElementsAre(testPath("macros.h")));
437
438 // Sanity check.
439 EXPECT_THAT(
440 getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas),
441 IsEmpty());
442 }
443
TEST(IncludeCleaner,DistinctUnguardedInclusions)444 TEST(IncludeCleaner, DistinctUnguardedInclusions) {
445 TestTU TU;
446 TU.Code = R"cpp(
447 #include "bar.h"
448 #include "foo.h"
449
450 int LocalFoo = foo::Variable;
451 )cpp";
452 TU.AdditionalFiles["foo.h"] = R"cpp(
453 #pragma once
454 namespace foo {
455 #include "unguarded.h"
456 }
457 )cpp";
458 TU.AdditionalFiles["bar.h"] = R"cpp(
459 #pragma once
460 namespace bar {
461 #include "unguarded.h"
462 }
463 )cpp";
464 TU.AdditionalFiles["unguarded.h"] = R"cpp(
465 constexpr int Variable = 42;
466 )cpp";
467
468 ParsedAST AST = TU.build();
469
470 auto ReferencedFiles = findReferencedFiles(
471 findReferencedLocations(AST), AST.getIncludeStructure(),
472 AST.getCanonicalIncludes(), AST.getSourceManager());
473 llvm::StringSet<> ReferencedFileNames;
474 auto &SM = AST.getSourceManager();
475 for (FileID FID : ReferencedFiles.User)
476 ReferencedFileNames.insert(
477 SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
478 // Note that we have uplifted the referenced files from non self-contained
479 // headers to header-guarded ones.
480 EXPECT_THAT(ReferencedFileNames.keys(),
481 UnorderedElementsAre(testPath("foo.h")));
482 }
483
TEST(IncludeCleaner,NonSelfContainedHeaders)484 TEST(IncludeCleaner, NonSelfContainedHeaders) {
485 TestTU TU;
486 TU.Code = R"cpp(
487 #include "foo.h"
488
489 int LocalFoo = Variable;
490 )cpp";
491 TU.AdditionalFiles["foo.h"] = R"cpp(
492 #pragma once
493 #include "indirection.h"
494 )cpp";
495 TU.AdditionalFiles["indirection.h"] = R"cpp(
496 #include "unguarded.h"
497 )cpp";
498 TU.AdditionalFiles["unguarded.h"] = R"cpp(
499 constexpr int Variable = 42;
500 )cpp";
501
502 ParsedAST AST = TU.build();
503
504 auto ReferencedFiles = findReferencedFiles(
505 findReferencedLocations(AST), AST.getIncludeStructure(),
506 AST.getCanonicalIncludes(), AST.getSourceManager());
507 llvm::StringSet<> ReferencedFileNames;
508 auto &SM = AST.getSourceManager();
509 for (FileID FID : ReferencedFiles.User)
510 ReferencedFileNames.insert(
511 SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
512 // Note that we have uplifted the referenced files from non self-contained
513 // headers to header-guarded ones.
514 EXPECT_THAT(ReferencedFileNames.keys(),
515 UnorderedElementsAre(testPath("foo.h")));
516 }
517
TEST(IncludeCleaner,IWYUPragmas)518 TEST(IncludeCleaner, IWYUPragmas) {
519 TestTU TU;
520 TU.Code = R"cpp(
521 #include "behind_keep.h" // IWYU pragma: keep
522 #include "exported.h" // IWYU pragma: export
523 #include "public.h"
524
525 void bar() { foo(); }
526 )cpp";
527 TU.AdditionalFiles["behind_keep.h"] = guard("");
528 TU.AdditionalFiles["exported.h"] = guard("");
529 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
530 TU.AdditionalFiles["private.h"] = guard(R"cpp(
531 // IWYU pragma: private, include "public.h"
532 void foo() {}
533 )cpp");
534 ParsedAST AST = TU.build();
535
536 auto ReferencedFiles = findReferencedFiles(
537 findReferencedLocations(AST), AST.getIncludeStructure(),
538 AST.getCanonicalIncludes(), AST.getSourceManager());
539 EXPECT_EQ(ReferencedFiles.SpelledUmbrellas.size(), 1u);
540 EXPECT_EQ(ReferencedFiles.SpelledUmbrellas.begin()->getKey(), "\"public.h\"");
541 EXPECT_EQ(ReferencedFiles.User.size(), 2u);
542 EXPECT_TRUE(
543 ReferencedFiles.User.contains(AST.getSourceManager().getMainFileID()));
544 EXPECT_TRUE(
545 ReferencedFiles.User.contains(AST.getSourceManager().getMainFileID()));
546 EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
547 EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
548 }
549
TEST(IncludeCleaner,RecursiveInclusion)550 TEST(IncludeCleaner, RecursiveInclusion) {
551 TestTU TU;
552 TU.Code = R"cpp(
553 #include "foo.h"
554
555 void baz() {
556 foo();
557 }
558 )cpp";
559 TU.AdditionalFiles["foo.h"] = R"cpp(
560 #ifndef FOO_H
561 #define FOO_H
562
563 void foo() {}
564
565 #include "bar.h"
566
567 #endif
568 )cpp";
569 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
570 #include "foo.h"
571 )cpp");
572 ParsedAST AST = TU.build();
573
574 auto ReferencedFiles = findReferencedFiles(
575 findReferencedLocations(AST), AST.getIncludeStructure(),
576 AST.getCanonicalIncludes(), AST.getSourceManager());
577 EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
578 EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
579 }
580
TEST(IncludeCleaner,IWYUPragmaExport)581 TEST(IncludeCleaner, IWYUPragmaExport) {
582 TestTU TU;
583 TU.Code = R"cpp(
584 #include "foo.h"
585 )cpp";
586 TU.AdditionalFiles["foo.h"] = R"cpp(
587 #ifndef FOO_H
588 #define FOO_H
589
590 #include "bar.h" // IWYU pragma: export
591
592 #endif
593 )cpp";
594 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
595 void bar() {}
596 )cpp");
597 ParsedAST AST = TU.build();
598
599 auto ReferencedFiles = findReferencedFiles(
600 findReferencedLocations(AST), AST.getIncludeStructure(),
601 AST.getCanonicalIncludes(), AST.getSourceManager());
602 EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
603 // FIXME: This is not correct: foo.h is unused but is not diagnosed as such
604 // because we ignore headers with IWYU export pragmas for now.
605 EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
606 }
607
608 } // namespace
609 } // namespace clangd
610 } // namespace clang
611