| 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 | //===----------------------------------------------------------------------===// |
| 8 | |
| 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/AST/DeclBase.h" |
| 16 | #include "clang/Basic/FileManager.h" |
| 17 | #include "clang/Basic/IdentifierTable.h" |
| 18 | #include "clang/Basic/SourceLocation.h" |
| 19 | #include "clang/Basic/SourceManager.h" |
| 20 | #include "clang/Format/Format.h" |
| 21 | #include "clang/Frontend/FrontendActions.h" |
| 22 | #include "clang/Testing/TestAST.h" |
| 23 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
| 24 | #include "llvm/ADT/ArrayRef.h" |
| 25 | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
| 26 | #include "llvm/ADT/SmallVector.h" |
| 27 | #include "llvm/ADT/StringRef.h" |
| 28 | #include "llvm/Support/Error.h" |
| 29 | #include "llvm/Support/MemoryBuffer.h" |
| 30 | #include "llvm/Support/ScopedPrinter.h" |
| 31 | #include "llvm/Support/VirtualFileSystem.h" |
| 32 | #include "llvm/Testing/Annotations/Annotations.h" |
| 33 | #include "gmock/gmock.h" |
| 34 | #include "gtest/gtest.h" |
| 35 | #include <cstddef> |
| 36 | #include <map> |
| 37 | #include <memory> |
| 38 | #include <string> |
| 39 | #include <vector> |
| 40 | |
| 41 | namespace clang::include_cleaner { |
| 42 | namespace { |
| 43 | using testing::_; |
| 44 | using testing::AllOf; |
| 45 | using testing::Contains; |
| 46 | using testing::ElementsAre; |
| 47 | using testing::Pair; |
| 48 | using testing::UnorderedElementsAre; |
| 49 | |
| 50 | std::string guard(llvm::StringRef Code) { |
| 51 | return "#pragma once\n" + Code.str(); |
| 52 | } |
| 53 | |
| 54 | class WalkUsedTest : public testing::Test { |
| 55 | protected: |
| 56 | TestInputs Inputs; |
| 57 | PragmaIncludes PI; |
| 58 | WalkUsedTest() { |
| 59 | Inputs.MakeAction = [this] { |
| 60 | struct Hook : public SyntaxOnlyAction { |
| 61 | public: |
| 62 | Hook(PragmaIncludes *Out) : Out(Out) {} |
| 63 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
| 64 | Out->record(CI); |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | PragmaIncludes *Out; |
| 69 | }; |
| 70 | return std::make_unique<Hook>(args: &PI); |
| 71 | }; |
| 72 | } |
| 73 | |
| 74 | std::multimap<size_t, std::vector<Header>> |
| 75 | offsetToProviders(TestAST &AST, |
| 76 | llvm::ArrayRef<SymbolReference> MacroRefs = {}) { |
| 77 | const auto &SM = AST.sourceManager(); |
| 78 | llvm::SmallVector<Decl *> TopLevelDecls; |
| 79 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { |
| 80 | if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation()))) |
| 81 | continue; |
| 82 | TopLevelDecls.emplace_back(D); |
| 83 | } |
| 84 | std::multimap<size_t, std::vector<Header>> OffsetToProviders; |
| 85 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs, PI: &PI, PP: AST.preprocessor(), |
| 86 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) { |
| 87 | auto [FID, Offset] = SM.getDecomposedLoc(Loc: Ref.RefLocation); |
| 88 | if (FID != SM.getMainFileID()) |
| 89 | ADD_FAILURE() << "Reference outside of the main file!" ; |
| 90 | OffsetToProviders.emplace(args&: Offset, args: Providers.vec()); |
| 91 | }); |
| 92 | return OffsetToProviders; |
| 93 | } |
| 94 | }; |
| 95 | |
| 96 | TEST_F(WalkUsedTest, Basic) { |
| 97 | llvm::Annotations Code(R"cpp( |
| 98 | #include "header.h" |
| 99 | #include "private.h" |
| 100 | |
| 101 | // No reference reported for the Parameter "p". |
| 102 | void $bar^bar($private^Private p) { |
| 103 | $foo^foo(); |
| 104 | std::$vector^vector $vconstructor^$v^v; |
| 105 | $builtin^__builtin_popcount(1); |
| 106 | std::$move^move(3); |
| 107 | } |
| 108 | )cpp" ); |
| 109 | Inputs.Code = Code.code(); |
| 110 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
| 111 | void foo(); |
| 112 | namespace std { class vector {}; int&& move(int&&); } |
| 113 | )cpp" ); |
| 114 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 115 | // IWYU pragma: private, include "path/public.h" |
| 116 | class Private {}; |
| 117 | )cpp" ); |
| 118 | |
| 119 | TestAST AST(Inputs); |
| 120 | auto &SM = AST.sourceManager(); |
| 121 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header.h" )); |
| 122 | auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef(Filename: "private.h" )); |
| 123 | auto PublicFile = Header("\"path/public.h\"" ); |
| 124 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
| 125 | auto VectorSTL = Header(*tooling::stdlib::Header::named(Name: "<vector>" )); |
| 126 | auto UtilitySTL = Header(*tooling::stdlib::Header::named(Name: "<utility>" )); |
| 127 | EXPECT_THAT( |
| 128 | offsetToProviders(AST), |
| 129 | UnorderedElementsAre( |
| 130 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)), |
| 131 | Pair(Code.point("private" ), |
| 132 | UnorderedElementsAre(PublicFile, PrivateFile)), |
| 133 | Pair(Code.point("foo" ), UnorderedElementsAre(HeaderFile)), |
| 134 | Pair(Code.point("vector" ), UnorderedElementsAre(VectorSTL)), |
| 135 | Pair(Code.point("vconstructor" ), UnorderedElementsAre(VectorSTL)), |
| 136 | Pair(Code.point("v" ), UnorderedElementsAre(MainFile)), |
| 137 | Pair(Code.point("builtin" ), testing::IsEmpty()), |
| 138 | Pair(Code.point("move" ), UnorderedElementsAre(UtilitySTL)))); |
| 139 | } |
| 140 | |
| 141 | TEST_F(WalkUsedTest, MultipleProviders) { |
| 142 | llvm::Annotations Code(R"cpp( |
| 143 | #include "header1.h" |
| 144 | #include "header2.h" |
| 145 | void foo(); |
| 146 | |
| 147 | void bar() { |
| 148 | $foo^foo(); |
| 149 | } |
| 150 | )cpp" ); |
| 151 | Inputs.Code = Code.code(); |
| 152 | Inputs.ExtraFiles["header1.h" ] = guard(Code: R"cpp( |
| 153 | void foo(); |
| 154 | )cpp" ); |
| 155 | Inputs.ExtraFiles["header2.h" ] = guard(Code: R"cpp( |
| 156 | void foo(); |
| 157 | )cpp" ); |
| 158 | |
| 159 | TestAST AST(Inputs); |
| 160 | auto &SM = AST.sourceManager(); |
| 161 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header1.h" )); |
| 162 | auto = Header(*AST.fileManager().getOptionalFileRef(Filename: "header2.h" )); |
| 163 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
| 164 | EXPECT_THAT( |
| 165 | offsetToProviders(AST), |
| 166 | Contains(Pair(Code.point("foo" ), |
| 167 | UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile)))); |
| 168 | } |
| 169 | |
| 170 | TEST_F(WalkUsedTest, MacroRefs) { |
| 171 | llvm::Annotations Code(R"cpp( |
| 172 | #include "hdr.h" |
| 173 | int $3^x = $1^ANSWER; |
| 174 | int $4^y = $2^ANSWER; |
| 175 | )cpp" ); |
| 176 | llvm::Annotations Hdr(guard(Code: "#define ^ANSWER 42" )); |
| 177 | Inputs.Code = Code.code(); |
| 178 | Inputs.ExtraFiles["hdr.h" ] = Hdr.code(); |
| 179 | TestAST AST(Inputs); |
| 180 | auto &SM = AST.sourceManager(); |
| 181 | auto &PP = AST.preprocessor(); |
| 182 | auto HdrFile = *SM.getFileManager().getOptionalFileRef(Filename: "hdr.h" ); |
| 183 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
| 184 | |
| 185 | auto HdrID = SM.translateFile(SourceFile: HdrFile); |
| 186 | |
| 187 | Symbol Answer1 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
| 188 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
| 189 | Symbol Answer2 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER" ), |
| 190 | .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())}; |
| 191 | EXPECT_THAT( |
| 192 | offsetToProviders( |
| 193 | AST, |
| 194 | {SymbolReference{ |
| 195 | Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1" )), |
| 196 | RefType::Explicit}, |
| 197 | SymbolReference{ |
| 198 | Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2" )), |
| 199 | RefType::Explicit}}), |
| 200 | UnorderedElementsAre( |
| 201 | Pair(Code.point("1" ), UnorderedElementsAre(HdrFile)), |
| 202 | Pair(Code.point("2" ), UnorderedElementsAre(HdrFile)), |
| 203 | Pair(Code.point("3" ), UnorderedElementsAre(MainFile)), |
| 204 | Pair(Code.point("4" ), UnorderedElementsAre(MainFile)))); |
| 205 | } |
| 206 | |
| 207 | class AnalyzeTest : public testing::Test { |
| 208 | protected: |
| 209 | TestInputs Inputs; |
| 210 | PragmaIncludes PI; |
| 211 | RecordedPP PP; |
| 212 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> = nullptr; |
| 213 | |
| 214 | AnalyzeTest() { |
| 215 | Inputs.MakeAction = [this] { |
| 216 | struct Hook : public SyntaxOnlyAction { |
| 217 | public: |
| 218 | Hook(RecordedPP &PP, PragmaIncludes &PI, |
| 219 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ) |
| 220 | : PP(PP), PI(PI), ExtraFS(std::move(ExtraFS)) {} |
| 221 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
| 222 | CI.getPreprocessor().addPPCallbacks(C: PP.record(PP: CI.getPreprocessor())); |
| 223 | PI.record(CI); |
| 224 | return true; |
| 225 | } |
| 226 | |
| 227 | bool BeginInvocation(CompilerInstance &CI) override { |
| 228 | if (!ExtraFS) |
| 229 | return true; |
| 230 | auto OverlayFS = |
| 231 | llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>( |
| 232 | A: CI.getFileManager().getVirtualFileSystemPtr()); |
| 233 | OverlayFS->pushOverlay(FS: ExtraFS); |
| 234 | CI.getFileManager().setVirtualFileSystem(std::move(OverlayFS)); |
| 235 | return true; |
| 236 | } |
| 237 | |
| 238 | RecordedPP &PP; |
| 239 | PragmaIncludes &PI; |
| 240 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ; |
| 241 | }; |
| 242 | return std::make_unique<Hook>(args&: PP, args&: PI, args&: ExtraFS); |
| 243 | }; |
| 244 | } |
| 245 | }; |
| 246 | |
| 247 | TEST_F(AnalyzeTest, Basic) { |
| 248 | Inputs.Code = R"cpp( |
| 249 | #include "a.h" |
| 250 | #include "b.h" |
| 251 | #include "keep.h" // IWYU pragma: keep |
| 252 | |
| 253 | int x = a + c; |
| 254 | )cpp" ; |
| 255 | Inputs.ExtraFiles["a.h" ] = guard(Code: "int a;" ); |
| 256 | Inputs.ExtraFiles["b.h" ] = guard(Code: R"cpp( |
| 257 | #include "c.h" |
| 258 | int b; |
| 259 | )cpp" ); |
| 260 | Inputs.ExtraFiles["c.h" ] = guard(Code: "int c;" ); |
| 261 | Inputs.ExtraFiles["keep.h" ] = guard(Code: "" ); |
| 262 | TestAST AST(Inputs); |
| 263 | auto Decls = AST.context().getTranslationUnitDecl()->decls(); |
| 264 | auto Results = |
| 265 | analyze(ASTRoots: std::vector<Decl *>{Decls.begin(), Decls.end()}, |
| 266 | MacroRefs: PP.MacroReferences, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 267 | auto = llvm::cantFail( |
| 268 | ValOrErr: AST.context().getSourceManager().getFileManager().getFileRef(Filename: "c.h" )); |
| 269 | |
| 270 | const Include *B = PP.Includes.atLine(OneBasedIndex: 3); |
| 271 | ASSERT_EQ(B->Spelled, "b.h" ); |
| 272 | EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"" , Header(CHeader)))); |
| 273 | EXPECT_THAT(Results.Unused, ElementsAre(B)); |
| 274 | } |
| 275 | |
| 276 | TEST_F(AnalyzeTest, PrivateUsedInPublic) { |
| 277 | // Check that umbrella header uses private include. |
| 278 | Inputs.Code = R"cpp(#include "private.h")cpp" ; |
| 279 | Inputs.ExtraFiles["private.h" ] = |
| 280 | guard(Code: "// IWYU pragma: private, include \"public.h\"" ); |
| 281 | Inputs.FileName = "public.h" ; |
| 282 | TestAST AST(Inputs); |
| 283 | EXPECT_FALSE(PP.Includes.all().empty()); |
| 284 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 285 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
| 286 | } |
| 287 | |
| 288 | TEST_F(AnalyzeTest, NoCrashWhenUnresolved) { |
| 289 | // Check that umbrella header uses private include. |
| 290 | Inputs.Code = R"cpp(#include "not_found.h")cpp" ; |
| 291 | Inputs.ErrorOK = true; |
| 292 | TestAST AST(Inputs); |
| 293 | EXPECT_FALSE(PP.Includes.all().empty()); |
| 294 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 295 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
| 296 | } |
| 297 | |
| 298 | TEST_F(AnalyzeTest, ResourceDirIsIgnored) { |
| 299 | Inputs.ExtraArgs.push_back(x: "-resource-dir" ); |
| 300 | Inputs.ExtraArgs.push_back(x: "resources" ); |
| 301 | Inputs.ExtraArgs.push_back(x: "-internal-isystem" ); |
| 302 | Inputs.ExtraArgs.push_back(x: "resources/include" ); |
| 303 | Inputs.Code = R"cpp( |
| 304 | #include <amintrin.h> |
| 305 | #include <imintrin.h> |
| 306 | void baz() { |
| 307 | bar(); |
| 308 | } |
| 309 | )cpp" ; |
| 310 | Inputs.ExtraFiles["resources/include/amintrin.h" ] = guard(Code: "" ); |
| 311 | Inputs.ExtraFiles["resources/include/emintrin.h" ] = guard(Code: R"cpp( |
| 312 | void bar(); |
| 313 | )cpp" ); |
| 314 | Inputs.ExtraFiles["resources/include/imintrin.h" ] = guard(Code: R"cpp( |
| 315 | #include <emintrin.h> |
| 316 | )cpp" ); |
| 317 | TestAST AST(Inputs); |
| 318 | auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 319 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
| 320 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
| 321 | } |
| 322 | |
| 323 | TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) { |
| 324 | Inputs.ExtraArgs.push_back(x: "-Ifoo" ); |
| 325 | Inputs.ExtraArgs.push_back(x: "-Ifoo_inner" ); |
| 326 | // `foo` is declared in foo_inner/foo.h, but there's no way to spell it |
| 327 | // directly. Make sure we don't generate unusued/missing include findings in |
| 328 | // such cases. |
| 329 | Inputs.Code = R"cpp( |
| 330 | #include <foo.h> |
| 331 | void baz() { |
| 332 | foo(); |
| 333 | } |
| 334 | )cpp" ; |
| 335 | Inputs.ExtraFiles["foo/foo.h" ] = guard(Code: "#include_next <foo.h>" ); |
| 336 | Inputs.ExtraFiles["foo_inner/foo.h" ] = guard(Code: R"cpp( |
| 337 | void foo(); |
| 338 | )cpp" ); |
| 339 | TestAST AST(Inputs); |
| 340 | std::vector<Decl *> DeclsInTU; |
| 341 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
| 342 | DeclsInTU.push_back(D); |
| 343 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 344 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
| 345 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
| 346 | } |
| 347 | |
| 348 | TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) { |
| 349 | llvm::Annotations Code(R"cpp( |
| 350 | #include "header.h" |
| 351 | void $bar^bar() { |
| 352 | $foo^foo(); |
| 353 | } |
| 354 | )cpp" ); |
| 355 | Inputs.Code = Code.code(); |
| 356 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
| 357 | ExtraFS->addFile(Path: "content_for/0" , /*ModificationTime=*/{}, |
| 358 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
| 359 | #include "inner.h" |
| 360 | )cpp" ))); |
| 361 | ExtraFS->addSymbolicLink(NewLink: "header.h" , Target: "content_for/0" , |
| 362 | /*ModificationTime=*/{}); |
| 363 | ExtraFS->addFile(Path: "content_for/1" , /*ModificationTime=*/{}, |
| 364 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
| 365 | void foo(); |
| 366 | )cpp" ))); |
| 367 | ExtraFS->addSymbolicLink(NewLink: "inner.h" , Target: "content_for/1" , |
| 368 | /*ModificationTime=*/{}); |
| 369 | |
| 370 | TestAST AST(Inputs); |
| 371 | std::vector<Decl *> DeclsInTU; |
| 372 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
| 373 | DeclsInTU.push_back(D); |
| 374 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 375 | // Check that we're spelling header using the symlink, and not underlying |
| 376 | // path. |
| 377 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
| 378 | // header.h should be unused. |
| 379 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
| 380 | |
| 381 | { |
| 382 | // Make sure filtering is also applied to symlink, not underlying file. |
| 383 | auto = [](llvm::StringRef Path) { return Path == "inner.h" ; }; |
| 384 | Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(), |
| 385 | HeaderFilter); |
| 386 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
| 387 | // header.h should be unused. |
| 388 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
| 389 | } |
| 390 | { |
| 391 | auto = [](llvm::StringRef Path) { return Path == "header.h" ; }; |
| 392 | Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(), |
| 393 | HeaderFilter); |
| 394 | // header.h should be ignored now. |
| 395 | EXPECT_THAT(Results.Unused, Not(testing::IsEmpty())); |
| 396 | EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"" , _))); |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | // Make sure that the references to implicit operator new/delete are reported as |
| 401 | // ambigious. |
| 402 | TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotMissing) { |
| 403 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
| 404 | ExtraFS->addFile(Path: "header.h" , |
| 405 | /*ModificationTime=*/{}, |
| 406 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
| 407 | void* operator new(decltype(sizeof(int))); |
| 408 | )cpp" ))); |
| 409 | ExtraFS->addFile(Path: "wrapper.h" , |
| 410 | /*ModificationTime=*/{}, |
| 411 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
| 412 | #include "header.h" |
| 413 | )cpp" ))); |
| 414 | |
| 415 | Inputs.Code = R"cpp( |
| 416 | #include "wrapper.h" |
| 417 | void bar() { |
| 418 | operator new(3); |
| 419 | })cpp" ; |
| 420 | TestAST AST(Inputs); |
| 421 | std::vector<Decl *> DeclsInTU; |
| 422 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
| 423 | DeclsInTU.push_back(D); |
| 424 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 425 | EXPECT_THAT(Results.Missing, testing::IsEmpty()); |
| 426 | } |
| 427 | |
| 428 | TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) { |
| 429 | ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
| 430 | ExtraFS->addFile(Path: "header.h" , |
| 431 | /*ModificationTime=*/{}, |
| 432 | Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp( |
| 433 | void* operator new(decltype(sizeof(int))); |
| 434 | )cpp" ))); |
| 435 | |
| 436 | Inputs.Code = R"cpp( |
| 437 | #include "header.h" |
| 438 | void bar() { |
| 439 | operator new(3); |
| 440 | })cpp" ; |
| 441 | TestAST AST(Inputs); |
| 442 | std::vector<Decl *> DeclsInTU; |
| 443 | for (auto *D : AST.context().getTranslationUnitDecl()->decls()) |
| 444 | DeclsInTU.push_back(D); |
| 445 | auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor()); |
| 446 | EXPECT_THAT(Results.Unused, testing::IsEmpty()); |
| 447 | } |
| 448 | |
| 449 | TEST(FixIncludes, Basic) { |
| 450 | llvm::StringRef Code = R"cpp(#include "d.h" |
| 451 | #include "a.h" |
| 452 | #include "b.h" |
| 453 | #include <c.h> |
| 454 | )cpp" ; |
| 455 | |
| 456 | Includes Inc; |
| 457 | Include I; |
| 458 | I.Spelled = "a.h" ; |
| 459 | I.Line = 2; |
| 460 | Inc.add(I); |
| 461 | I.Spelled = "b.h" ; |
| 462 | I.Line = 3; |
| 463 | Inc.add(I); |
| 464 | I.Spelled = "c.h" ; |
| 465 | I.Line = 4; |
| 466 | I.Angled = true; |
| 467 | Inc.add(I); |
| 468 | |
| 469 | AnalysisResults Results; |
| 470 | Results.Missing.emplace_back(args: "\"aa.h\"" , args: Header("" )); |
| 471 | Results.Missing.emplace_back(args: "\"ab.h\"" , args: Header("" )); |
| 472 | Results.Missing.emplace_back(args: "<e.h>" , args: Header("" )); |
| 473 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 3)); |
| 474 | Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 4)); |
| 475 | |
| 476 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
| 477 | R"cpp(#include "d.h" |
| 478 | #include "a.h" |
| 479 | #include "aa.h" |
| 480 | #include "ab.h" |
| 481 | #include <e.h> |
| 482 | )cpp" ); |
| 483 | |
| 484 | Results = {}; |
| 485 | Results.Missing.emplace_back(args: "\"d.h\"" , args: Header("" )); |
| 486 | Code = R"cpp(#include "a.h")cpp" ; |
| 487 | EXPECT_EQ(fixIncludes(Results, "d.cc" , Code, format::getLLVMStyle()), |
| 488 | R"cpp(#include "d.h" |
| 489 | #include "a.h")cpp" ); |
| 490 | } |
| 491 | |
| 492 | MATCHER_P3(expandedAt, FileID, Offset, SM, "" ) { |
| 493 | auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg); |
| 494 | return ExpanedFileID == FileID && ExpandedOffset == Offset; |
| 495 | } |
| 496 | MATCHER_P3(spelledAt, FileID, Offset, SM, "" ) { |
| 497 | auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg); |
| 498 | return SpelledFileID == FileID && SpelledOffset == Offset; |
| 499 | } |
| 500 | TEST(WalkUsed, FilterRefsNotSpelledInMainFile) { |
| 501 | // Each test is expected to have a single expected ref of `target` symbol |
| 502 | // (or have none). |
| 503 | // The location in the reported ref is a macro location. $expand points to |
| 504 | // the macro location, and $spell points to the spelled location. |
| 505 | struct { |
| 506 | llvm::StringRef ; |
| 507 | llvm::StringRef Main; |
| 508 | } TestCases[] = { |
| 509 | // Tests for decl references. |
| 510 | { |
| 511 | /*Header=*/"int target();" , |
| 512 | .Main: R"cpp( |
| 513 | #define CALL_FUNC $spell^target() |
| 514 | |
| 515 | int b = $expand^CALL_FUNC; |
| 516 | )cpp" , |
| 517 | }, |
| 518 | {/*Header=*/R"cpp( |
| 519 | int target(); |
| 520 | #define CALL_FUNC target() |
| 521 | )cpp" , |
| 522 | // No ref of `target` being reported, as it is not spelled in main file. |
| 523 | .Main: "int a = CALL_FUNC;" }, |
| 524 | { |
| 525 | /*Header=*/R"cpp( |
| 526 | int target(); |
| 527 | #define PLUS_ONE(X) X() + 1 |
| 528 | )cpp" , |
| 529 | .Main: R"cpp( |
| 530 | int a = $expand^PLUS_ONE($spell^target); |
| 531 | )cpp" , |
| 532 | }, |
| 533 | { |
| 534 | /*Header=*/R"cpp( |
| 535 | int target(); |
| 536 | #define PLUS_ONE(X) X() + 1 |
| 537 | )cpp" , |
| 538 | .Main: R"cpp( |
| 539 | int a = $expand^PLUS_ONE($spell^target); |
| 540 | )cpp" , |
| 541 | }, |
| 542 | // Tests for macro references |
| 543 | {/*Header=*/"#define target 1" , |
| 544 | .Main: R"cpp( |
| 545 | #define USE_target $spell^target |
| 546 | int b = $expand^USE_target; |
| 547 | )cpp" }, |
| 548 | {/*Header=*/R"cpp( |
| 549 | #define target 1 |
| 550 | #define USE_target target |
| 551 | )cpp" , |
| 552 | // No ref of `target` being reported, it is not spelled in main file. |
| 553 | .Main: R"cpp( |
| 554 | int a = USE_target; |
| 555 | )cpp" }, |
| 556 | }; |
| 557 | |
| 558 | for (const auto &T : TestCases) { |
| 559 | llvm::Annotations Main(T.Main); |
| 560 | TestInputs Inputs(Main.code()); |
| 561 | Inputs.ExtraFiles["header.h" ] = guard(Code: T.Header); |
| 562 | RecordedPP Recorded; |
| 563 | Inputs.MakeAction = [&]() { |
| 564 | struct RecordAction : public SyntaxOnlyAction { |
| 565 | RecordedPP &Out; |
| 566 | RecordAction(RecordedPP &Out) : Out(Out) {} |
| 567 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
| 568 | auto &PP = CI.getPreprocessor(); |
| 569 | PP.addPPCallbacks(C: Out.record(PP)); |
| 570 | return true; |
| 571 | } |
| 572 | }; |
| 573 | return std::make_unique<RecordAction>(args&: Recorded); |
| 574 | }; |
| 575 | Inputs.ExtraArgs.push_back(x: "-include" ); |
| 576 | Inputs.ExtraArgs.push_back(x: "header.h" ); |
| 577 | TestAST AST(Inputs); |
| 578 | llvm::SmallVector<Decl *> TopLevelDecls; |
| 579 | for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) |
| 580 | TopLevelDecls.emplace_back(D); |
| 581 | auto &SM = AST.sourceManager(); |
| 582 | |
| 583 | SourceLocation RefLoc; |
| 584 | walkUsed(ASTRoots: TopLevelDecls, MacroRefs: Recorded.MacroReferences, |
| 585 | /*PragmaIncludes=*/PI: nullptr, PP: AST.preprocessor(), |
| 586 | CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) { |
| 587 | if (!Ref.RefLocation.isMacroID()) |
| 588 | return; |
| 589 | if (llvm::to_string(Value: Ref.Target) == "target" ) { |
| 590 | ASSERT_TRUE(RefLoc.isInvalid()) |
| 591 | << "Expected only one 'target' ref loc per testcase" ; |
| 592 | RefLoc = Ref.RefLocation; |
| 593 | } |
| 594 | }); |
| 595 | FileID MainFID = SM.getMainFileID(); |
| 596 | if (RefLoc.isValid()) { |
| 597 | EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand" ), &SM), |
| 598 | spelledAt(MainFID, Main.point("spell" ), &SM))) |
| 599 | << T.Main.str(); |
| 600 | } else { |
| 601 | EXPECT_THAT(Main.points(), testing::IsEmpty()); |
| 602 | } |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | struct Tag { |
| 607 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) { |
| 608 | return OS << "Anon Tag" ; |
| 609 | } |
| 610 | }; |
| 611 | TEST(Hints, Ordering) { |
| 612 | auto Hinted = [](Hints Hints) { |
| 613 | return clang::include_cleaner::Hinted<Tag>({}, Hints); |
| 614 | }; |
| 615 | EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol)); |
| 616 | EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader)); |
| 617 | EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader)); |
| 618 | EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader), |
| 619 | Hinted(Hints::PublicHeader)); |
| 620 | } |
| 621 | |
| 622 | // Test ast traversal & redecl selection end-to-end for templates, as explicit |
| 623 | // instantiations/specializations are not redecls of the primary template. We |
| 624 | // need to make sure we're selecting the right ones. |
| 625 | TEST_F(WalkUsedTest, TemplateDecls) { |
| 626 | llvm::Annotations Code(R"cpp( |
| 627 | #include "fwd.h" |
| 628 | #include "def.h" |
| 629 | #include "partial.h" |
| 630 | template <> struct $exp_spec^Foo<char> {}; |
| 631 | template struct $exp^Foo<int>; |
| 632 | $full^Foo<int> x; |
| 633 | $implicit^Foo<bool> y; |
| 634 | $partial^Foo<int*> z; |
| 635 | )cpp" ); |
| 636 | Inputs.Code = Code.code(); |
| 637 | Inputs.ExtraFiles["fwd.h" ] = guard(Code: "template<typename> struct Foo;" ); |
| 638 | Inputs.ExtraFiles["def.h" ] = guard(Code: "template<typename> struct Foo {};" ); |
| 639 | Inputs.ExtraFiles["partial.h" ] = |
| 640 | guard(Code: "template<typename T> struct Foo<T*> {};" ); |
| 641 | TestAST AST(Inputs); |
| 642 | auto &SM = AST.sourceManager(); |
| 643 | auto Fwd = *SM.getFileManager().getOptionalFileRef(Filename: "fwd.h" ); |
| 644 | auto Def = *SM.getFileManager().getOptionalFileRef(Filename: "def.h" ); |
| 645 | auto Partial = *SM.getFileManager().getOptionalFileRef(Filename: "partial.h" ); |
| 646 | |
| 647 | EXPECT_THAT( |
| 648 | offsetToProviders(AST), |
| 649 | AllOf(Contains( |
| 650 | Pair(Code.point("exp_spec" ), UnorderedElementsAre(Fwd, Def))), |
| 651 | Contains(Pair(Code.point("exp" ), UnorderedElementsAre(Fwd, Def))), |
| 652 | Contains(Pair(Code.point("full" ), UnorderedElementsAre(Fwd, Def))), |
| 653 | Contains( |
| 654 | Pair(Code.point("implicit" ), UnorderedElementsAre(Fwd, Def))), |
| 655 | Contains( |
| 656 | Pair(Code.point("partial" ), UnorderedElementsAre(Partial))))); |
| 657 | } |
| 658 | |
| 659 | TEST_F(WalkUsedTest, IgnoresIdentityMacros) { |
| 660 | llvm::Annotations Code(R"cpp( |
| 661 | #include "header.h" |
| 662 | void $bar^bar() { |
| 663 | $stdin^stdin(); |
| 664 | } |
| 665 | )cpp" ); |
| 666 | Inputs.Code = Code.code(); |
| 667 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
| 668 | #include "inner.h" |
| 669 | void stdin(); |
| 670 | )cpp" ); |
| 671 | Inputs.ExtraFiles["inner.h" ] = guard(Code: R"cpp( |
| 672 | #define stdin stdin |
| 673 | )cpp" ); |
| 674 | |
| 675 | TestAST AST(Inputs); |
| 676 | auto &SM = AST.sourceManager(); |
| 677 | auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID())); |
| 678 | EXPECT_THAT(offsetToProviders(AST), |
| 679 | UnorderedElementsAre( |
| 680 | // FIXME: we should have a reference from stdin to header.h |
| 681 | Pair(Code.point("bar" ), UnorderedElementsAre(MainFile)))); |
| 682 | } |
| 683 | } // namespace |
| 684 | } // namespace clang::include_cleaner |
| 685 | |