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