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 "TestTU.h"
12 #include "llvm/Testing/Support/SupportHelpers.h"
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15 
16 namespace clang {
17 namespace clangd {
18 namespace {
19 
20 using ::testing::ElementsAre;
21 using ::testing::IsEmpty;
22 using ::testing::UnorderedElementsAre;
23 
24 std::string guard(llvm::StringRef Code) {
25   return "#pragma once\n" + Code.str();
26 }
27 
28 TEST(IncludeCleaner, ReferencedLocations) {
29   struct TestCase {
30     std::string HeaderCode;
31     std::string MainCode;
32   };
33   TestCase Cases[] = {
34       // DeclRefExpr
35       {
36           "int ^x();",
37           "int y = x();",
38       },
39       // RecordDecl
40       {
41           "class ^X;",
42           "X *y;",
43       },
44       // When definition is available, we don't need to mark forward
45       // declarations as used.
46       {
47           "class ^X {}; class X;",
48           "X *y;",
49       },
50       // We already have forward declaration in the main file, the other
51       // non-definition declarations are not needed.
52       {
53           "class ^X {}; class X;",
54           "class X; X *y;",
55       },
56       // Nested class definition can occur outside of the parent class
57       // definition. Bar declaration should be visible to its definition but
58       // it will always be because we will mark Foo definition as used.
59       {
60           "class ^Foo { class Bar; };",
61           "class Foo::Bar {};",
62       },
63       // TypedefType and UsingDecls
64       {
65           "using ^Integer = int;",
66           "Integer x;",
67       },
68       {
69           "namespace ns { void ^foo(); void ^foo() {} }",
70           "using ns::foo;",
71       },
72       {
73           "namespace ns { void foo(); void foo() {}; }",
74           "using namespace ns;",
75       },
76       {
77           "struct ^A {}; using B = A; using ^C = B;",
78           "C a;",
79       },
80       {
81           "typedef bool ^Y; template <typename T> struct ^X {};",
82           "X<Y> x;",
83       },
84       {
85           "struct Foo; struct ^Foo{}; typedef Foo ^Bar;",
86           "Bar b;",
87       },
88       // MemberExpr
89       {
90           "struct ^X{int ^a;}; X ^foo();",
91           "int y = foo().a;",
92       },
93       // Expr (type is traversed)
94       {
95           "class ^X{}; X ^foo();",
96           "auto bar() { return foo(); }",
97       },
98       // Redecls
99       {
100           "void ^foo(); void ^foo() {} void ^foo();",
101           "void bar() { foo(); }",
102       },
103       // Constructor
104       {
105           "struct ^X { ^X(int) {} int ^foo(); };",
106           "auto x = X(42); auto y = x.foo();",
107       },
108       // Function
109       {
110           "void ^foo();",
111           "void foo() {}",
112       },
113       {
114           "void foo() {}",
115           "void foo();",
116       },
117       {
118           "inline void ^foo() {}",
119           "void bar() { foo(); }",
120       },
121       {
122           "int ^foo(char); int ^foo(float);",
123           "template<class T> int x = foo(T{});",
124       },
125       // Static function
126       {
127           "struct ^X { static bool ^foo(); }; bool X::^foo() {}",
128           "auto b = X::foo();",
129       },
130       // TemplateRecordDecl
131       {
132           "template <typename> class ^X;",
133           "X<int> *y;",
134       },
135       // Type name not spelled out in code
136       {
137           "class ^X{}; X ^getX();",
138           "auto x = getX();",
139       },
140       // Enums
141       {
142           "enum ^Color { ^Red = 42, Green = 9000};",
143           "int MyColor = Red;",
144       },
145       {
146           "struct ^X { enum ^Language { ^CXX = 42, Python = 9000}; };",
147           "int Lang = X::CXX;",
148       },
149       // Macros
150       {
151           "#define ^CONSTANT 42",
152           "int Foo = CONSTANT;",
153       },
154       {
155           "#define ^FOO x",
156           "#define BAR FOO",
157       },
158       {
159           "#define INNER 42\n"
160           "#define ^OUTER INNER",
161           "int answer = OUTER;",
162       },
163       {
164           "#define ^ANSWER 42\n"
165           "#define ^SQUARE(X) X * X",
166           "int sq = SQUARE(ANSWER);",
167       },
168       {
169           "#define ^FOO\n"
170           "#define ^BAR",
171           "#if 0\n"
172           "#if FOO\n"
173           "BAR\n"
174           "#endif\n"
175           "#endif",
176       },
177       // Misc
178       {
179           "enum class ^Color : int;",
180           "enum class Color : int {};",
181       },
182       {
183           "enum class Color : int {};",
184           "enum class Color : int;",
185       },
186       {
187           "enum class ^Color;",
188           "Color c;",
189       },
190       {
191           "enum class ^Color : int;",
192           "Color c;",
193       },
194       {
195           "enum class ^Color : char;",
196           "Color *c;",
197       },
198       {
199           "enum class ^Color : char {};",
200           "Color *c;",
201       },
202       {
203           // When a type is resolved via a using declaration, the
204           // UsingShadowDecl is not referenced in the AST.
205           // Compare to TypedefType, or DeclRefExpr::getFoundDecl().
206           //                                 ^
207           "namespace ns { class ^X; }; using ns::X;",
208           "X *y;",
209       }};
210   for (const TestCase &T : Cases) {
211     TestTU TU;
212     TU.Code = T.MainCode;
213     Annotations Header(T.HeaderCode);
214     TU.HeaderCode = Header.code().str();
215     auto AST = TU.build();
216 
217     std::vector<Position> Points;
218     for (const auto &Loc : findReferencedLocations(AST)) {
219       if (AST.getSourceManager().getBufferName(Loc).endswith(
220               TU.HeaderFilename)) {
221         Points.push_back(offsetToPosition(
222             TU.HeaderCode, AST.getSourceManager().getFileOffset(Loc)));
223       }
224     }
225     llvm::sort(Points);
226 
227     EXPECT_EQ(Points, Header.points()) << T.HeaderCode << "\n---\n"
228                                        << T.MainCode;
229   }
230 }
231 
232 TEST(IncludeCleaner, GetUnusedHeaders) {
233   llvm::StringLiteral MainFile = R"cpp(
234     #include "a.h"
235     #include "b.h"
236     #include "dir/c.h"
237     #include "dir/unused.h"
238     #include "unguarded.h"
239     #include "unused.h"
240     #include <system_header.h>
241     void foo() {
242       a();
243       b();
244       c();
245     })cpp";
246   // Build expected ast with symbols coming from headers.
247   TestTU TU;
248   TU.Filename = "foo.cpp";
249   TU.AdditionalFiles["foo.h"] = guard("void foo();");
250   TU.AdditionalFiles["a.h"] = guard("void a();");
251   TU.AdditionalFiles["b.h"] = guard("void b();");
252   TU.AdditionalFiles["dir/c.h"] = guard("void c();");
253   TU.AdditionalFiles["unused.h"] = guard("void unused();");
254   TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
255   TU.AdditionalFiles["system/system_header.h"] = guard("");
256   TU.AdditionalFiles["unguarded.h"] = "";
257   TU.ExtraArgs.push_back("-I" + testPath("dir"));
258   TU.ExtraArgs.push_back("-isystem" + testPath("system"));
259   TU.Code = MainFile.str();
260   ParsedAST AST = TU.build();
261   std::vector<std::string> UnusedIncludes;
262   for (const auto &Include : computeUnusedIncludes(AST))
263     UnusedIncludes.push_back(Include->Written);
264   EXPECT_THAT(UnusedIncludes,
265               UnorderedElementsAre("\"unused.h\"", "\"dir/unused.h\""));
266 }
267 
268 TEST(IncludeCleaner, VirtualBuffers) {
269   TestTU TU;
270   TU.Code = R"cpp(
271     #include "macros.h"
272 
273     using flags::FLAGS_FOO;
274 
275     // CLI will come from a define, __llvm__ is a built-in. In both cases, they
276     // come from non-existent files.
277     int y = CLI + __llvm__;
278 
279     int concat(a, b) = 42;
280     )cpp";
281   // The pasting operator in combination with DEFINE_FLAG will create
282   // ScratchBuffer with `flags::FLAGS_FOO` that will have FileID but not
283   // FileEntry.
284   TU.AdditionalFiles["macros.h"] = R"cpp(
285     #ifndef MACROS_H
286     #define MACROS_H
287 
288     #define DEFINE_FLAG(X) \
289     namespace flags { \
290     int FLAGS_##X; \
291     } \
292 
293     DEFINE_FLAG(FOO)
294 
295     #define ab x
296     #define concat(x, y) x##y
297 
298     #endif // MACROS_H
299     )cpp";
300   TU.ExtraArgs = {"-DCLI=42"};
301   ParsedAST AST = TU.build();
302   auto &SM = AST.getSourceManager();
303   auto &Includes = AST.getIncludeStructure();
304 
305   auto ReferencedFiles =
306       findReferencedFiles(findReferencedLocations(AST), Includes, SM);
307   llvm::StringSet<> ReferencedFileNames;
308   for (FileID FID : ReferencedFiles)
309     ReferencedFileNames.insert(
310         SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
311   // Note we deduped the names as _number_ of <built-in>s is uninteresting.
312   EXPECT_THAT(ReferencedFileNames.keys(),
313               UnorderedElementsAre("<built-in>", "<scratch space>",
314                                    testPath("macros.h")));
315 
316   // Should not crash due to FileIDs that are not headers.
317   auto ReferencedHeaders = translateToHeaderIDs(ReferencedFiles, Includes, SM);
318   std::vector<llvm::StringRef> ReferencedHeaderNames;
319   for (IncludeStructure::HeaderID HID : ReferencedHeaders)
320     ReferencedHeaderNames.push_back(Includes.getRealPath(HID));
321   // Non-header files are gone at this point.
322   EXPECT_THAT(ReferencedHeaderNames, ElementsAre(testPath("macros.h")));
323 
324   // Sanity check.
325   EXPECT_THAT(getUnused(AST, ReferencedHeaders), IsEmpty());
326 }
327 
328 TEST(IncludeCleaner, DistinctUnguardedInclusions) {
329   TestTU TU;
330   TU.Code = R"cpp(
331     #include "bar.h"
332     #include "foo.h"
333 
334     int LocalFoo = foo::Variable;
335     )cpp";
336   TU.AdditionalFiles["foo.h"] = R"cpp(
337     #pragma once
338     namespace foo {
339     #include "unguarded.h"
340     }
341     )cpp";
342   TU.AdditionalFiles["bar.h"] = R"cpp(
343     #pragma once
344     namespace bar {
345     #include "unguarded.h"
346     }
347     )cpp";
348   TU.AdditionalFiles["unguarded.h"] = R"cpp(
349     constexpr int Variable = 42;
350     )cpp";
351 
352   ParsedAST AST = TU.build();
353 
354   auto ReferencedFiles =
355       findReferencedFiles(findReferencedLocations(AST),
356                           AST.getIncludeStructure(), AST.getSourceManager());
357   llvm::StringSet<> ReferencedFileNames;
358   auto &SM = AST.getSourceManager();
359   for (FileID FID : ReferencedFiles)
360     ReferencedFileNames.insert(
361         SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
362   // Note that we have uplifted the referenced files from non self-contained
363   // headers to header-guarded ones.
364   EXPECT_THAT(ReferencedFileNames.keys(),
365               UnorderedElementsAre(testPath("foo.h")));
366 }
367 
368 TEST(IncludeCleaner, NonSelfContainedHeaders) {
369   TestTU TU;
370   TU.Code = R"cpp(
371     #include "foo.h"
372 
373     int LocalFoo = Variable;
374     )cpp";
375   TU.AdditionalFiles["foo.h"] = R"cpp(
376     #pragma once
377     #include "indirection.h"
378     )cpp";
379   TU.AdditionalFiles["indirection.h"] = R"cpp(
380     #include "unguarded.h"
381     )cpp";
382   TU.AdditionalFiles["unguarded.h"] = R"cpp(
383     constexpr int Variable = 42;
384     )cpp";
385 
386   ParsedAST AST = TU.build();
387 
388   auto ReferencedFiles =
389       findReferencedFiles(findReferencedLocations(AST),
390                           AST.getIncludeStructure(), AST.getSourceManager());
391   llvm::StringSet<> ReferencedFileNames;
392   auto &SM = AST.getSourceManager();
393   for (FileID FID : ReferencedFiles)
394     ReferencedFileNames.insert(
395         SM.getPresumedLoc(SM.getLocForStartOfFile(FID)).getFilename());
396   // Note that we have uplifted the referenced files from non self-contained
397   // headers to header-guarded ones.
398   EXPECT_THAT(ReferencedFileNames.keys(),
399               UnorderedElementsAre(testPath("foo.h")));
400 }
401 
402 TEST(IncludeCleaner, IWYUPragmas) {
403   TestTU TU;
404   TU.Code = R"cpp(
405     #include "behind_keep.h" // IWYU pragma: keep
406     )cpp";
407   TU.AdditionalFiles["behind_keep.h"] = guard("");
408   ParsedAST AST = TU.build();
409 
410   auto ReferencedFiles =
411       findReferencedFiles(findReferencedLocations(AST),
412                           AST.getIncludeStructure(), AST.getSourceManager());
413   EXPECT_TRUE(ReferencedFiles.empty());
414   EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
415 }
416 
417 } // namespace
418 } // namespace clangd
419 } // namespace clang
420