| 1 | //===--- FindHeadersTest.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 "AnalysisInternal.h" |
| 10 | #include "TypesInternal.h" |
| 11 | #include "clang-include-cleaner/Analysis.h" |
| 12 | #include "clang-include-cleaner/Record.h" |
| 13 | #include "clang-include-cleaner/Types.h" |
| 14 | #include "clang/AST/Expr.h" |
| 15 | #include "clang/AST/RecursiveASTVisitor.h" |
| 16 | #include "clang/Basic/FileEntry.h" |
| 17 | #include "clang/Basic/FileManager.h" |
| 18 | #include "clang/Basic/LLVM.h" |
| 19 | #include "clang/Frontend/FrontendActions.h" |
| 20 | #include "clang/Testing/TestAST.h" |
| 21 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
| 22 | #include "llvm/ADT/SmallVector.h" |
| 23 | #include "llvm/ADT/StringRef.h" |
| 24 | #include "gmock/gmock.h" |
| 25 | #include "gtest/gtest.h" |
| 26 | #include <cassert> |
| 27 | #include <memory> |
| 28 | |
| 29 | namespace clang::include_cleaner { |
| 30 | namespace { |
| 31 | using testing::ElementsAre; |
| 32 | using testing::UnorderedElementsAre; |
| 33 | |
| 34 | std::string guard(llvm::StringRef Code) { |
| 35 | return "#pragma once\n" + Code.str(); |
| 36 | } |
| 37 | |
| 38 | class : public testing::Test { |
| 39 | protected: |
| 40 | TestInputs ; |
| 41 | PragmaIncludes ; |
| 42 | std::unique_ptr<TestAST> ; |
| 43 | () { |
| 44 | Inputs.MakeAction = [this] { |
| 45 | struct Hook : public SyntaxOnlyAction { |
| 46 | public: |
| 47 | Hook(PragmaIncludes *Out) : Out(Out) {} |
| 48 | bool BeginSourceFileAction(clang::CompilerInstance &CI) override { |
| 49 | Out->record(CI); |
| 50 | return true; |
| 51 | } |
| 52 | |
| 53 | PragmaIncludes *Out; |
| 54 | }; |
| 55 | return std::make_unique<Hook>(args: &PI); |
| 56 | }; |
| 57 | } |
| 58 | void () { AST = std::make_unique<TestAST>(args&: Inputs); } |
| 59 | |
| 60 | llvm::SmallVector<Hinted<Header>> (llvm::StringRef FileName) { |
| 61 | return include_cleaner::findHeaders( |
| 62 | Loc: AST->sourceManager().translateFileLineCol( |
| 63 | SourceFile: *AST->fileManager().getOptionalFileRef(Filename: FileName), |
| 64 | /*Line=*/1, /*Col=*/1), |
| 65 | SM: AST->sourceManager(), PI: &PI); |
| 66 | } |
| 67 | FileEntryRef (llvm::StringRef FileName) { |
| 68 | return *AST->fileManager().getOptionalFileRef(Filename: FileName); |
| 69 | }; |
| 70 | }; |
| 71 | |
| 72 | TEST_F(FindHeadersTest, IWYUPrivateToPublic) { |
| 73 | Inputs.Code = R"cpp( |
| 74 | #include "private.h" |
| 75 | )cpp" ; |
| 76 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 77 | // IWYU pragma: private, include "path/public.h" |
| 78 | )cpp" ); |
| 79 | buildAST(); |
| 80 | EXPECT_THAT(findHeaders("private.h" ), |
| 81 | UnorderedElementsAre(physicalHeader("private.h" ), |
| 82 | Header("\"path/public.h\"" ))); |
| 83 | } |
| 84 | |
| 85 | TEST_F(FindHeadersTest, IWYUExport) { |
| 86 | Inputs.Code = R"cpp( |
| 87 | #include "exporter.h" |
| 88 | )cpp" ; |
| 89 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 90 | #include "exported1.h" // IWYU pragma: export |
| 91 | |
| 92 | // IWYU pragma: begin_exports |
| 93 | #include "exported2.h" |
| 94 | // IWYU pragma: end_exports |
| 95 | |
| 96 | #include "normal.h" |
| 97 | )cpp" ); |
| 98 | Inputs.ExtraFiles["exported1.h" ] = guard(Code: "" ); |
| 99 | Inputs.ExtraFiles["exported2.h" ] = guard(Code: "" ); |
| 100 | Inputs.ExtraFiles["normal.h" ] = guard(Code: "" ); |
| 101 | |
| 102 | buildAST(); |
| 103 | EXPECT_THAT(findHeaders("exported1.h" ), |
| 104 | UnorderedElementsAre(physicalHeader("exported1.h" ), |
| 105 | physicalHeader("exporter.h" ))); |
| 106 | EXPECT_THAT(findHeaders("exported2.h" ), |
| 107 | UnorderedElementsAre(physicalHeader("exported2.h" ), |
| 108 | physicalHeader("exporter.h" ))); |
| 109 | EXPECT_THAT(findHeaders("normal.h" ), |
| 110 | UnorderedElementsAre(physicalHeader("normal.h" ))); |
| 111 | EXPECT_THAT(findHeaders("exporter.h" ), |
| 112 | UnorderedElementsAre(physicalHeader("exporter.h" ))); |
| 113 | } |
| 114 | |
| 115 | TEST_F(FindHeadersTest, IWYUExportForStandardHeaders) { |
| 116 | Inputs.Code = R"cpp( |
| 117 | #include "exporter.h" |
| 118 | )cpp" ; |
| 119 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 120 | #include <string> // IWYU pragma: export |
| 121 | )cpp" ); |
| 122 | Inputs.ExtraFiles["string" ] = guard(Code: "" ); |
| 123 | Inputs.ExtraArgs.push_back(x: "-isystem." ); |
| 124 | buildAST(); |
| 125 | tooling::stdlib::Symbol StdString = |
| 126 | *tooling::stdlib::Symbol::named(Scope: "std::" , Name: "string" ); |
| 127 | EXPECT_THAT( |
| 128 | include_cleaner::findHeaders(StdString, AST->sourceManager(), &PI), |
| 129 | UnorderedElementsAre(physicalHeader("exporter.h" ), StdString.header())); |
| 130 | } |
| 131 | |
| 132 | TEST_F(FindHeadersTest, SelfContained) { |
| 133 | Inputs.Code = R"cpp( |
| 134 | #include "header.h" |
| 135 | )cpp" ; |
| 136 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
| 137 | #include "fragment.inc" |
| 138 | )cpp" ); |
| 139 | Inputs.ExtraFiles["fragment.inc" ] = "" ; |
| 140 | buildAST(); |
| 141 | EXPECT_THAT(findHeaders("fragment.inc" ), |
| 142 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
| 143 | physicalHeader("header.h" ))); |
| 144 | } |
| 145 | |
| 146 | TEST_F(FindHeadersTest, NonSelfContainedTraversePrivate) { |
| 147 | Inputs.Code = R"cpp( |
| 148 | #include "header.h" |
| 149 | )cpp" ; |
| 150 | Inputs.ExtraFiles["header.h" ] = guard(Code: R"cpp( |
| 151 | #include "fragment.inc" |
| 152 | )cpp" ); |
| 153 | Inputs.ExtraFiles["fragment.inc" ] = R"cpp( |
| 154 | // IWYU pragma: private, include "public.h" |
| 155 | )cpp" ; |
| 156 | |
| 157 | buildAST(); |
| 158 | // There is a IWYU private mapping in the non self-contained header, verify |
| 159 | // that we don't emit its includer. |
| 160 | EXPECT_THAT(findHeaders("fragment.inc" ), |
| 161 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
| 162 | Header("\"public.h\"" ))); |
| 163 | } |
| 164 | |
| 165 | TEST_F(FindHeadersTest, NonSelfContainedTraverseExporter) { |
| 166 | Inputs.Code = R"cpp( |
| 167 | #include "exporter.h" |
| 168 | )cpp" ; |
| 169 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 170 | #include "exported.h" // IWYU pragma: export |
| 171 | )cpp" ); |
| 172 | Inputs.ExtraFiles["exported.h" ] = guard(Code: R"cpp( |
| 173 | #include "fragment.inc" |
| 174 | )cpp" ); |
| 175 | Inputs.ExtraFiles["fragment.inc" ] = "" ; |
| 176 | buildAST(); |
| 177 | // Verify that we emit exporters for each header on the path. |
| 178 | EXPECT_THAT(findHeaders("fragment.inc" ), |
| 179 | UnorderedElementsAre(physicalHeader("fragment.inc" ), |
| 180 | physicalHeader("exported.h" ), |
| 181 | physicalHeader("exporter.h" ))); |
| 182 | } |
| 183 | |
| 184 | TEST_F(FindHeadersTest, TargetIsExpandedFromMacroInHeader) { |
| 185 | struct CustomVisitor : RecursiveASTVisitor<CustomVisitor> { |
| 186 | const Decl *Out = nullptr; |
| 187 | bool VisitNamedDecl(const NamedDecl *ND) { |
| 188 | if (ND->getName() == "FLAG_foo" || ND->getName() == "Foo" ) { |
| 189 | EXPECT_TRUE(Out == nullptr); |
| 190 | Out = ND; |
| 191 | } |
| 192 | return true; |
| 193 | } |
| 194 | }; |
| 195 | |
| 196 | struct { |
| 197 | llvm::StringRef ; |
| 198 | llvm::StringRef ; |
| 199 | } TestCases[] = { |
| 200 | {/*MacroHeader=*/R"cpp( |
| 201 | #define DEFINE_CLASS(name) class name {}; |
| 202 | )cpp" , |
| 203 | /*DeclareHeader=*/R"cpp( |
| 204 | #include "macro.h" |
| 205 | DEFINE_CLASS(Foo) |
| 206 | )cpp" }, |
| 207 | {/*MacroHeader=*/R"cpp( |
| 208 | #define DEFINE_Foo class Foo {}; |
| 209 | )cpp" , |
| 210 | /*DeclareHeader=*/R"cpp( |
| 211 | #include "macro.h" |
| 212 | DEFINE_Foo |
| 213 | )cpp" }, |
| 214 | {/*MacroHeader=*/R"cpp( |
| 215 | #define DECLARE_FLAGS(name) extern int FLAG_##name |
| 216 | )cpp" , |
| 217 | /*DeclareHeader=*/R"cpp( |
| 218 | #include "macro.h" |
| 219 | DECLARE_FLAGS(foo); |
| 220 | )cpp" }, |
| 221 | }; |
| 222 | |
| 223 | for (const auto &T : TestCases) { |
| 224 | Inputs.Code = R"cpp(#include "declare.h")cpp" ; |
| 225 | Inputs.ExtraFiles["declare.h" ] = guard(Code: T.DeclareHeader); |
| 226 | Inputs.ExtraFiles["macro.h" ] = guard(Code: T.MacroHeader); |
| 227 | buildAST(); |
| 228 | |
| 229 | CustomVisitor Visitor; |
| 230 | Visitor.TraverseDecl(AST->context().getTranslationUnitDecl()); |
| 231 | |
| 232 | auto = clang::include_cleaner::findHeaders( |
| 233 | Loc: Visitor.Out->getLocation(), SM: AST->sourceManager(), |
| 234 | /*PragmaIncludes=*/PI: nullptr); |
| 235 | EXPECT_THAT(Headers, UnorderedElementsAre(physicalHeader("declare.h" ))); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | MATCHER_P2(, , , "" ) { |
| 240 | return std::tie(arg.Hint, arg) == std::tie(Hint, Header); |
| 241 | } |
| 242 | |
| 243 | TEST_F(FindHeadersTest, PublicHeaderHint) { |
| 244 | Inputs.Code = R"cpp( |
| 245 | #include "public.h" |
| 246 | )cpp" ; |
| 247 | Inputs.ExtraFiles["public.h" ] = guard(Code: R"cpp( |
| 248 | #include "private.h" |
| 249 | #include "private.inc" |
| 250 | )cpp" ); |
| 251 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 252 | // IWYU pragma: private |
| 253 | )cpp" ); |
| 254 | Inputs.ExtraFiles["private.inc" ] = "" ; |
| 255 | buildAST(); |
| 256 | // Non self-contained files and headers marked with IWYU private pragma |
| 257 | // shouldn't have PublicHeader hint. |
| 258 | EXPECT_THAT( |
| 259 | findHeaders("private.inc" ), |
| 260 | UnorderedElementsAre( |
| 261 | HintedHeader(physicalHeader("private.inc" ), Hints::OriginHeader), |
| 262 | HintedHeader(physicalHeader("public.h" ), Hints::PublicHeader))); |
| 263 | EXPECT_THAT(findHeaders("private.h" ), |
| 264 | UnorderedElementsAre(HintedHeader(physicalHeader("private.h" ), |
| 265 | Hints::OriginHeader))); |
| 266 | } |
| 267 | |
| 268 | TEST_F(FindHeadersTest, PreferredHeaderHint) { |
| 269 | Inputs.Code = R"cpp( |
| 270 | #include "private.h" |
| 271 | )cpp" ; |
| 272 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 273 | // IWYU pragma: private, include "public.h" |
| 274 | )cpp" ); |
| 275 | buildAST(); |
| 276 | // Headers explicitly marked should've preferred signal. |
| 277 | EXPECT_THAT( |
| 278 | findHeaders("private.h" ), |
| 279 | UnorderedElementsAre( |
| 280 | HintedHeader(physicalHeader("private.h" ), Hints::OriginHeader), |
| 281 | HintedHeader(Header("\"public.h\"" ), |
| 282 | Hints::PreferredHeader | Hints::PublicHeader))); |
| 283 | } |
| 284 | |
| 285 | class : public FindHeadersTest { |
| 286 | protected: |
| 287 | llvm::SmallVector<Header> (llvm::StringRef Name) { |
| 288 | struct Visitor : public RecursiveASTVisitor<Visitor> { |
| 289 | const NamedDecl *Out = nullptr; |
| 290 | llvm::StringRef Name; |
| 291 | Visitor(llvm::StringRef Name) : Name(Name) {} |
| 292 | bool VisitNamedDecl(const NamedDecl *ND) { |
| 293 | if (auto *TD = ND->getDescribedTemplate()) |
| 294 | ND = TD; |
| 295 | |
| 296 | if (ND->getName() == Name) { |
| 297 | EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl()) |
| 298 | << "Found multiple matches for " << Name << "." ; |
| 299 | Out = cast<NamedDecl>(ND->getCanonicalDecl()); |
| 300 | } |
| 301 | return true; |
| 302 | } |
| 303 | }; |
| 304 | Visitor V(Name); |
| 305 | V.TraverseDecl(AST->context().getTranslationUnitDecl()); |
| 306 | if (!V.Out) |
| 307 | ADD_FAILURE() << "Couldn't find any decls named " << Name << "." ; |
| 308 | assert(V.Out); |
| 309 | return headersForSymbol(*V.Out, AST->preprocessor(), &PI); |
| 310 | } |
| 311 | llvm::SmallVector<Header> () { return headersFor(Name: "foo" ); } |
| 312 | }; |
| 313 | |
| 314 | TEST_F(HeadersForSymbolTest, Deduplicates) { |
| 315 | Inputs.Code = R"cpp( |
| 316 | #include "foo.h" |
| 317 | )cpp" ; |
| 318 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
| 319 | // IWYU pragma: private, include "foo.h" |
| 320 | void foo(); |
| 321 | void foo(); |
| 322 | )cpp" ); |
| 323 | buildAST(); |
| 324 | EXPECT_THAT( |
| 325 | headersForFoo(), |
| 326 | UnorderedElementsAre(physicalHeader("foo.h" ), |
| 327 | // FIXME: de-duplicate across different kinds. |
| 328 | Header("\"foo.h\"" ))); |
| 329 | } |
| 330 | |
| 331 | TEST_F(HeadersForSymbolTest, RankByName) { |
| 332 | Inputs.Code = R"cpp( |
| 333 | #include "fox.h" |
| 334 | #include "bar.h" |
| 335 | )cpp" ; |
| 336 | Inputs.ExtraFiles["fox.h" ] = guard(Code: R"cpp( |
| 337 | void foo(); |
| 338 | )cpp" ); |
| 339 | Inputs.ExtraFiles["bar.h" ] = guard(Code: R"cpp( |
| 340 | void foo(); |
| 341 | )cpp" ); |
| 342 | buildAST(); |
| 343 | EXPECT_THAT(headersForFoo(), |
| 344 | ElementsAre(physicalHeader("bar.h" ), physicalHeader("fox.h" ))); |
| 345 | } |
| 346 | |
| 347 | TEST_F(HeadersForSymbolTest, Ranking) { |
| 348 | // Sorting is done over (public, complete, canonical, origin)-tuple. |
| 349 | Inputs.Code = R"cpp( |
| 350 | #include "private.h" |
| 351 | #include "public.h" |
| 352 | #include "public_complete.h" |
| 353 | #include "exporter.h" |
| 354 | )cpp" ; |
| 355 | Inputs.ExtraFiles["public.h" ] = guard(Code: R"cpp( |
| 356 | struct foo; |
| 357 | )cpp" ); |
| 358 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 359 | // IWYU pragma: private, include "canonical.h" |
| 360 | struct foo; |
| 361 | )cpp" ); |
| 362 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 363 | #include "private.h" // IWYU pragma: export |
| 364 | )cpp" ); |
| 365 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: "struct foo {};" ); |
| 366 | buildAST(); |
| 367 | EXPECT_THAT(headersForFoo(), |
| 368 | ElementsAre(physicalHeader("public_complete.h" ), |
| 369 | Header("\"canonical.h\"" ), physicalHeader("public.h" ), |
| 370 | physicalHeader("exporter.h" ), |
| 371 | physicalHeader("private.h" ))); |
| 372 | } |
| 373 | |
| 374 | TEST_F(HeadersForSymbolTest, PreferPublicOverComplete) { |
| 375 | Inputs.Code = R"cpp( |
| 376 | #include "complete_private.h" |
| 377 | #include "public.h" |
| 378 | )cpp" ; |
| 379 | Inputs.ExtraFiles["complete_private.h" ] = guard(Code: R"cpp( |
| 380 | // IWYU pragma: private |
| 381 | struct foo {}; |
| 382 | )cpp" ); |
| 383 | Inputs.ExtraFiles["public.h" ] = guard(Code: "struct foo;" ); |
| 384 | buildAST(); |
| 385 | EXPECT_THAT(headersForFoo(), |
| 386 | ElementsAre(physicalHeader("public.h" ), |
| 387 | physicalHeader("complete_private.h" ))); |
| 388 | } |
| 389 | |
| 390 | TEST_F(HeadersForSymbolTest, PreferNameMatch) { |
| 391 | Inputs.Code = R"cpp( |
| 392 | #include "public_complete.h" |
| 393 | #include "test/foo.fwd.h" |
| 394 | )cpp" ; |
| 395 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: "struct foo {};" ); |
| 396 | Inputs.ExtraFiles["test/foo.fwd.h" ] = guard(Code: "struct foo;" ); |
| 397 | buildAST(); |
| 398 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public_complete.h" ), |
| 399 | physicalHeader("test/foo.fwd.h" ))); |
| 400 | } |
| 401 | |
| 402 | TEST_F(HeadersForSymbolTest, MainFile) { |
| 403 | Inputs.Code = R"cpp( |
| 404 | #include "public_complete.h" |
| 405 | struct foo; |
| 406 | )cpp" ; |
| 407 | Inputs.ExtraFiles["public_complete.h" ] = guard(Code: R"cpp( |
| 408 | struct foo {}; |
| 409 | )cpp" ); |
| 410 | buildAST(); |
| 411 | auto &SM = AST->sourceManager(); |
| 412 | // FIXME: Symbols provided by main file should be treated specially. |
| 413 | EXPECT_THAT( |
| 414 | headersForFoo(), |
| 415 | ElementsAre(physicalHeader("public_complete.h" ), |
| 416 | Header(*SM.getFileEntryRefForID(SM.getMainFileID())))); |
| 417 | } |
| 418 | |
| 419 | TEST_F(HeadersForSymbolTest, PreferExporterOfPrivate) { |
| 420 | Inputs.Code = R"cpp( |
| 421 | #include "private.h" |
| 422 | #include "exporter.h" |
| 423 | )cpp" ; |
| 424 | Inputs.ExtraFiles["private.h" ] = guard(Code: R"cpp( |
| 425 | // IWYU pragma: private |
| 426 | struct foo {}; |
| 427 | )cpp" ); |
| 428 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 429 | #include "private.h" // IWYU pragma: export |
| 430 | )cpp" ); |
| 431 | buildAST(); |
| 432 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h" ), |
| 433 | physicalHeader("private.h" ))); |
| 434 | } |
| 435 | |
| 436 | TEST_F(HeadersForSymbolTest, ExporterIsDownRanked) { |
| 437 | Inputs.Code = R"cpp( |
| 438 | #include "exporter.h" |
| 439 | #include "zoo.h" |
| 440 | )cpp" ; |
| 441 | // Deliberately named as zoo to make sure it doesn't get name-match boost and |
| 442 | // also gets lexicographically bigger order than "exporter". |
| 443 | Inputs.ExtraFiles["zoo.h" ] = guard(Code: R"cpp( |
| 444 | struct foo {}; |
| 445 | )cpp" ); |
| 446 | Inputs.ExtraFiles["exporter.h" ] = guard(Code: R"cpp( |
| 447 | #include "zoo.h" // IWYU pragma: export |
| 448 | )cpp" ); |
| 449 | buildAST(); |
| 450 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h" ), |
| 451 | physicalHeader("exporter.h" ))); |
| 452 | } |
| 453 | |
| 454 | TEST_F(HeadersForSymbolTest, PreferPublicOverNameMatchOnPrivate) { |
| 455 | Inputs.Code = R"cpp( |
| 456 | #include "foo.h" |
| 457 | )cpp" ; |
| 458 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
| 459 | // IWYU pragma: private, include "public.h" |
| 460 | struct foo {}; |
| 461 | )cpp" ); |
| 462 | buildAST(); |
| 463 | EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"" )), |
| 464 | physicalHeader("foo.h" ))); |
| 465 | } |
| 466 | |
| 467 | TEST_F(HeadersForSymbolTest, PublicOverPrivateWithoutUmbrella) { |
| 468 | Inputs.Code = R"cpp( |
| 469 | #include "bar.h" |
| 470 | #include "foo.h" |
| 471 | )cpp" ; |
| 472 | Inputs.ExtraFiles["bar.h" ] = |
| 473 | guard(Code: R"cpp(#include "foo.h" // IWYU pragma: export)cpp" ); |
| 474 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
| 475 | // IWYU pragma: private |
| 476 | struct foo {}; |
| 477 | )cpp" ); |
| 478 | buildAST(); |
| 479 | EXPECT_THAT(headersForFoo(), |
| 480 | ElementsAre(physicalHeader("bar.h" ), physicalHeader("foo.h" ))); |
| 481 | } |
| 482 | |
| 483 | TEST_F(HeadersForSymbolTest, IWYUTransitiveExport) { |
| 484 | Inputs.Code = R"cpp( |
| 485 | #include "export1.h" |
| 486 | )cpp" ; |
| 487 | Inputs.ExtraFiles["export1.h" ] = guard(Code: R"cpp( |
| 488 | #include "export2.h" // IWYU pragma: export |
| 489 | )cpp" ); |
| 490 | Inputs.ExtraFiles["export2.h" ] = guard(Code: R"cpp( |
| 491 | #include "foo.h" // IWYU pragma: export |
| 492 | )cpp" ); |
| 493 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
| 494 | struct foo {}; |
| 495 | )cpp" ); |
| 496 | buildAST(); |
| 497 | EXPECT_THAT(headersForFoo(), |
| 498 | ElementsAre(physicalHeader("foo.h" ), physicalHeader("export1.h" ), |
| 499 | physicalHeader("export2.h" ))); |
| 500 | } |
| 501 | |
| 502 | TEST_F(HeadersForSymbolTest, IWYUTransitiveExportWithPrivate) { |
| 503 | Inputs.Code = R"cpp( |
| 504 | #include "export1.h" |
| 505 | void bar() { foo();} |
| 506 | )cpp" ; |
| 507 | Inputs.ExtraFiles["export1.h" ] = guard(Code: R"cpp( |
| 508 | // IWYU pragma: private, include "public1.h" |
| 509 | #include "export2.h" // IWYU pragma: export |
| 510 | void foo(); |
| 511 | )cpp" ); |
| 512 | Inputs.ExtraFiles["export2.h" ] = guard(Code: R"cpp( |
| 513 | // IWYU pragma: private, include "public2.h" |
| 514 | #include "export3.h" // IWYU pragma: export |
| 515 | )cpp" ); |
| 516 | Inputs.ExtraFiles["export3.h" ] = guard(Code: R"cpp( |
| 517 | // IWYU pragma: private, include "public3.h" |
| 518 | #include "foo.h" // IWYU pragma: export |
| 519 | )cpp" ); |
| 520 | Inputs.ExtraFiles["foo.h" ] = guard(Code: R"cpp( |
| 521 | void foo(); |
| 522 | )cpp" ); |
| 523 | buildAST(); |
| 524 | EXPECT_THAT(headersForFoo(), |
| 525 | ElementsAre(physicalHeader("foo.h" ), |
| 526 | Header(StringRef("\"public1.h\"" )), |
| 527 | physicalHeader("export1.h" ), |
| 528 | physicalHeader("export2.h" ), |
| 529 | physicalHeader("export3.h" ))); |
| 530 | } |
| 531 | |
| 532 | TEST_F(HeadersForSymbolTest, AmbiguousStdSymbols) { |
| 533 | struct { |
| 534 | llvm::StringRef Code; |
| 535 | llvm::StringRef Name; |
| 536 | |
| 537 | llvm::StringRef ; |
| 538 | } TestCases[] = { |
| 539 | { |
| 540 | .Code: R"cpp( |
| 541 | namespace std { |
| 542 | template <typename InputIt, typename OutputIt> |
| 543 | constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest); |
| 544 | })cpp" , |
| 545 | .Name: "move" , |
| 546 | .ExpectedHeader: "<algorithm>" , |
| 547 | }, |
| 548 | { |
| 549 | .Code: R"cpp( |
| 550 | namespace std { |
| 551 | template<class ExecutionPolicy, class ForwardIt1, class ForwardIt2> |
| 552 | ForwardIt2 move(ExecutionPolicy&& policy, |
| 553 | ForwardIt1 first, ForwardIt1 last, ForwardIt2 d_first); |
| 554 | })cpp" , |
| 555 | .Name: "move" , |
| 556 | .ExpectedHeader: "<algorithm>" , |
| 557 | }, |
| 558 | { |
| 559 | .Code: R"cpp( |
| 560 | namespace std { |
| 561 | template<typename T> constexpr T move(T&& t) noexcept; |
| 562 | })cpp" , |
| 563 | .Name: "move" , |
| 564 | .ExpectedHeader: "<utility>" , |
| 565 | }, |
| 566 | { |
| 567 | .Code: R"cpp( |
| 568 | namespace std { |
| 569 | template<class ForwardIt, class T> |
| 570 | ForwardIt remove(ForwardIt first, ForwardIt last, const T& value); |
| 571 | })cpp" , |
| 572 | .Name: "remove" , |
| 573 | .ExpectedHeader: "<algorithm>" , |
| 574 | }, |
| 575 | { |
| 576 | .Code: "namespace std { int remove(const char*); }" , |
| 577 | .Name: "remove" , |
| 578 | .ExpectedHeader: "<cstdio>" , |
| 579 | }, |
| 580 | }; |
| 581 | |
| 582 | for (const auto &T : TestCases) { |
| 583 | Inputs.Code = T.Code; |
| 584 | buildAST(); |
| 585 | EXPECT_THAT(headersFor(T.Name), |
| 586 | UnorderedElementsAre( |
| 587 | Header(*tooling::stdlib::Header::named(T.ExpectedHeader)))); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | TEST_F(HeadersForSymbolTest, AmbiguousStdSymbolsUsingShadow) { |
| 592 | Inputs.Code = R"cpp( |
| 593 | void remove(char*); |
| 594 | namespace std { using ::remove; } |
| 595 | |
| 596 | void k() { |
| 597 | std::remove("abc"); |
| 598 | } |
| 599 | )cpp" ; |
| 600 | buildAST(); |
| 601 | |
| 602 | // Find the DeclRefExpr in the std::remove("abc") function call. |
| 603 | struct Visitor : public RecursiveASTVisitor<Visitor> { |
| 604 | const DeclRefExpr *Out = nullptr; |
| 605 | bool VisitDeclRefExpr(const DeclRefExpr *DRE) { |
| 606 | EXPECT_TRUE(Out == nullptr) << "Found multiple DeclRefExpr!" ; |
| 607 | Out = DRE; |
| 608 | return true; |
| 609 | } |
| 610 | }; |
| 611 | Visitor V; |
| 612 | V.TraverseDecl(AST->context().getTranslationUnitDecl()); |
| 613 | ASSERT_TRUE(V.Out) << "Couldn't find a DeclRefExpr!" ; |
| 614 | EXPECT_THAT( |
| 615 | headersForSymbol(*(V.Out->getFoundDecl()), AST->preprocessor(), &PI), |
| 616 | UnorderedElementsAre( |
| 617 | Header(*tooling::stdlib::Header::named("<cstdio>" )))); |
| 618 | } |
| 619 | |
| 620 | TEST_F(HeadersForSymbolTest, StandardHeaders) { |
| 621 | Inputs.Code = R"cpp( |
| 622 | #include "stdlib_internal.h" |
| 623 | void assert(); |
| 624 | void foo() { assert(); } |
| 625 | )cpp" ; |
| 626 | Inputs.ExtraFiles["stdlib_internal.h" ] = "void assert();" ; |
| 627 | buildAST(); |
| 628 | EXPECT_THAT( |
| 629 | headersFor("assert" ), |
| 630 | // Respect the ordering from the stdlib mapping. |
| 631 | // FIXME: Report physical locations too, stdlib_internal.h and main-file |
| 632 | // should also be candidates. But they should be down-ranked compared to |
| 633 | // stdlib providers. |
| 634 | UnorderedElementsAre(tooling::stdlib::Header::named("<cassert>" ), |
| 635 | tooling::stdlib::Header::named("<assert.h>" ))); |
| 636 | } |
| 637 | |
| 638 | TEST_F(HeadersForSymbolTest, StdlibLangForMacros) { |
| 639 | Inputs.Code = R"cpp( |
| 640 | #define EOF 0 |
| 641 | void foo() { EOF; } |
| 642 | )cpp" ; |
| 643 | { |
| 644 | buildAST(); |
| 645 | const Macro Eof{.Name: AST->preprocessor().getIdentifierInfo(Name: "EOF" ), .Definition: {}}; |
| 646 | EXPECT_THAT( |
| 647 | headersForSymbol(Eof, AST->preprocessor(), nullptr), |
| 648 | UnorderedElementsAre(tooling::stdlib::Header::named("<cstdio>" ), |
| 649 | tooling::stdlib::Header::named("<stdio.h>" ))); |
| 650 | } |
| 651 | |
| 652 | { |
| 653 | Inputs.ExtraArgs.push_back(x: "-xc" ); |
| 654 | buildAST(); |
| 655 | const Macro Eof{.Name: AST->preprocessor().getIdentifierInfo(Name: "EOF" ), .Definition: {}}; |
| 656 | EXPECT_THAT(headersForSymbol(Eof, AST->preprocessor(), nullptr), |
| 657 | UnorderedElementsAre(tooling::stdlib::Header::named( |
| 658 | "<stdio.h>" , tooling::stdlib::Lang::C))); |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | TEST_F(HeadersForSymbolTest, ExporterNoNameMatch) { |
| 663 | Inputs.Code = R"cpp( |
| 664 | #include "exporter/foo.h" |
| 665 | #include "foo_public.h" |
| 666 | )cpp" ; |
| 667 | Inputs.ExtraArgs.emplace_back(args: "-I." ); |
| 668 | // Deliberately named as foo_public to make sure it doesn't get name-match |
| 669 | // boost and also gets lexicographically bigger order than "exporter/foo.h". |
| 670 | Inputs.ExtraFiles["foo_public.h" ] = guard(Code: R"cpp( |
| 671 | struct foo {}; |
| 672 | )cpp" ); |
| 673 | Inputs.ExtraFiles["exporter/foo.h" ] = guard(Code: R"cpp( |
| 674 | #include "foo_public.h" // IWYU pragma: export |
| 675 | )cpp" ); |
| 676 | buildAST(); |
| 677 | EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("foo_public.h" ), |
| 678 | physicalHeader("exporter/foo.h" ))); |
| 679 | } |
| 680 | |
| 681 | } // namespace |
| 682 | } // namespace clang::include_cleaner |
| 683 | |