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