| 1 | //===-- SymbolCollectorTests.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 "TestFS.h" |
| 11 | #include "TestTU.h" |
| 12 | #include "URI.h" |
| 13 | #include "clang-include-cleaner/Record.h" |
| 14 | #include "index/SymbolCollector.h" |
| 15 | #include "clang/Basic/FileManager.h" |
| 16 | #include "clang/Basic/FileSystemOptions.h" |
| 17 | #include "clang/Basic/SourceLocation.h" |
| 18 | #include "clang/Frontend/CompilerInstance.h" |
| 19 | #include "clang/Index/IndexingAction.h" |
| 20 | #include "clang/Index/IndexingOptions.h" |
| 21 | #include "clang/Tooling/Tooling.h" |
| 22 | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
| 23 | #include "llvm/ADT/StringRef.h" |
| 24 | #include "llvm/Support/MemoryBuffer.h" |
| 25 | #include "llvm/Support/VirtualFileSystem.h" |
| 26 | #include "gmock/gmock-matchers.h" |
| 27 | #include "gmock/gmock.h" |
| 28 | #include "gtest/gtest.h" |
| 29 | |
| 30 | #include <memory> |
| 31 | #include <optional> |
| 32 | #include <string> |
| 33 | #include <utility> |
| 34 | |
| 35 | namespace clang { |
| 36 | namespace clangd { |
| 37 | namespace { |
| 38 | |
| 39 | using ::testing::_; |
| 40 | using ::testing::AllOf; |
| 41 | using ::testing::Contains; |
| 42 | using ::testing::Each; |
| 43 | using ::testing::ElementsAre; |
| 44 | using ::testing::Field; |
| 45 | using ::testing::IsEmpty; |
| 46 | using ::testing::Not; |
| 47 | using ::testing::Pair; |
| 48 | using ::testing::UnorderedElementsAre; |
| 49 | using ::testing::UnorderedElementsAreArray; |
| 50 | |
| 51 | // GMock helpers for matching Symbol. |
| 52 | MATCHER_P(labeled, Label, "" ) { |
| 53 | return (arg.Name + arg.Signature).str() == Label; |
| 54 | } |
| 55 | MATCHER_P(returnType, D, "" ) { return arg.ReturnType == D; } |
| 56 | MATCHER_P(doc, D, "" ) { return arg.Documentation == D; } |
| 57 | MATCHER_P(snippet, S, "" ) { |
| 58 | return (arg.Name + arg.CompletionSnippetSuffix).str() == S; |
| 59 | } |
| 60 | MATCHER_P(qName, Name, "" ) { return (arg.Scope + arg.Name).str() == Name; } |
| 61 | MATCHER_P(hasName, Name, "" ) { return arg.Name == Name; } |
| 62 | MATCHER_P(templateArgs, TemplArgs, "" ) { |
| 63 | return arg.TemplateSpecializationArgs == TemplArgs; |
| 64 | } |
| 65 | MATCHER_P(hasKind, Kind, "" ) { return arg.SymInfo.Kind == Kind; } |
| 66 | MATCHER_P(declURI, P, "" ) { |
| 67 | return StringRef(arg.CanonicalDeclaration.FileURI) == P; |
| 68 | } |
| 69 | MATCHER_P(defURI, P, "" ) { return StringRef(arg.Definition.FileURI) == P; } |
| 70 | MATCHER(, "" ) { return !arg.IncludeHeaders.empty(); } |
| 71 | MATCHER_P(, , "" ) { |
| 72 | return (arg.IncludeHeaders.size() == 1) && |
| 73 | (arg.IncludeHeaders.begin()->IncludeHeader == P); |
| 74 | } |
| 75 | MATCHER_P2(, , , "" ) { |
| 76 | return (arg.IncludeHeader == includeHeader) && (arg.References == References); |
| 77 | } |
| 78 | bool rangesMatch(const SymbolLocation &Loc, const Range &R) { |
| 79 | return std::make_tuple(args: Loc.Start.line(), args: Loc.Start.column(), args: Loc.End.line(), |
| 80 | args: Loc.End.column()) == |
| 81 | std::make_tuple(args: R.start.line, args: R.start.character, args: R.end.line, |
| 82 | args: R.end.character); |
| 83 | } |
| 84 | MATCHER_P(declRange, Pos, "" ) { |
| 85 | return rangesMatch(arg.CanonicalDeclaration, Pos); |
| 86 | } |
| 87 | MATCHER_P(defRange, Pos, "" ) { return rangesMatch(arg.Definition, Pos); } |
| 88 | MATCHER_P(refCount, R, "" ) { return int(arg.References) == R; } |
| 89 | MATCHER_P(forCodeCompletion, IsIndexedForCodeCompletion, "" ) { |
| 90 | return static_cast<bool>(arg.Flags & Symbol::IndexedForCodeCompletion) == |
| 91 | IsIndexedForCodeCompletion; |
| 92 | } |
| 93 | MATCHER(deprecated, "" ) { return arg.Flags & Symbol::Deprecated; } |
| 94 | MATCHER(implementationDetail, "" ) { |
| 95 | return arg.Flags & Symbol::ImplementationDetail; |
| 96 | } |
| 97 | MATCHER(visibleOutsideFile, "" ) { |
| 98 | return static_cast<bool>(arg.Flags & Symbol::VisibleOutsideFile); |
| 99 | } |
| 100 | MATCHER(refRange, "" ) { |
| 101 | const Ref &Pos = ::testing::get<0>(arg); |
| 102 | const Range &Range = ::testing::get<1>(arg); |
| 103 | return rangesMatch(Loc: Pos.Location, R: Range); |
| 104 | } |
| 105 | MATCHER_P2(OverriddenBy, Subject, Object, "" ) { |
| 106 | return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; |
| 107 | } |
| 108 | MATCHER(isSpelled, "" ) { |
| 109 | return static_cast<bool>(arg.Kind & RefKind::Spelled); |
| 110 | } |
| 111 | ::testing::Matcher<const std::vector<Ref> &> |
| 112 | haveRanges(const std::vector<Range> Ranges) { |
| 113 | return ::testing::UnorderedPointwise(tuple2_matcher: refRange(), rhs_container: Ranges); |
| 114 | } |
| 115 | |
| 116 | class ShouldCollectSymbolTest : public ::testing::Test { |
| 117 | public: |
| 118 | void build(llvm::StringRef , llvm::StringRef Code = "" ) { |
| 119 | File.HeaderFilename = HeaderName; |
| 120 | File.Filename = FileName; |
| 121 | File.HeaderCode = std::string(HeaderCode); |
| 122 | File.Code = std::string(Code); |
| 123 | AST = File.build(); |
| 124 | } |
| 125 | |
| 126 | // build() must have been called. |
| 127 | bool shouldCollect(llvm::StringRef Name, bool Qualified = true) { |
| 128 | assert(AST); |
| 129 | const NamedDecl &ND = |
| 130 | Qualified ? findDecl(AST&: *AST, QName: Name) : findUnqualifiedDecl(AST&: *AST, Name); |
| 131 | const SourceManager &SM = AST->getSourceManager(); |
| 132 | bool MainFile = isInsideMainFile(ND.getBeginLoc(), SM); |
| 133 | return SymbolCollector::shouldCollectSymbol( |
| 134 | ND, ASTCtx: AST->getASTContext(), Opts: SymbolCollector::Options(), IsMainFileSymbol: MainFile); |
| 135 | } |
| 136 | |
| 137 | protected: |
| 138 | std::string = "f.h" ; |
| 139 | std::string FileName = "f.cpp" ; |
| 140 | TestTU File; |
| 141 | std::optional<ParsedAST> AST; // Initialized after build. |
| 142 | }; |
| 143 | |
| 144 | TEST_F(ShouldCollectSymbolTest, ShouldCollectSymbol) { |
| 145 | build(HeaderCode: R"( |
| 146 | namespace nx { |
| 147 | class X{}; |
| 148 | auto f() { int Local; } // auto ensures function body is parsed. |
| 149 | struct { int x; } var; |
| 150 | } |
| 151 | )" , |
| 152 | Code: R"( |
| 153 | class InMain {}; |
| 154 | namespace { class InAnonymous {}; } |
| 155 | static void g(); |
| 156 | )" ); |
| 157 | auto AST = File.build(); |
| 158 | EXPECT_TRUE(shouldCollect("nx" )); |
| 159 | EXPECT_TRUE(shouldCollect("nx::X" )); |
| 160 | EXPECT_TRUE(shouldCollect("nx::f" )); |
| 161 | EXPECT_TRUE(shouldCollect("InMain" )); |
| 162 | EXPECT_TRUE(shouldCollect("InAnonymous" , /*Qualified=*/false)); |
| 163 | EXPECT_TRUE(shouldCollect("g" )); |
| 164 | |
| 165 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
| 166 | } |
| 167 | |
| 168 | TEST_F(ShouldCollectSymbolTest, CollectLocalClassesAndVirtualMethods) { |
| 169 | build(HeaderCode: R"( |
| 170 | namespace nx { |
| 171 | auto f() { |
| 172 | int Local; |
| 173 | auto LocalLambda = [&](){ |
| 174 | Local++; |
| 175 | class ClassInLambda{}; |
| 176 | return Local; |
| 177 | }; |
| 178 | } // auto ensures function body is parsed. |
| 179 | auto foo() { |
| 180 | class LocalBase { |
| 181 | virtual void LocalVirtual(); |
| 182 | void LocalConcrete(); |
| 183 | int BaseMember; |
| 184 | }; |
| 185 | } |
| 186 | } // namespace nx |
| 187 | )" , |
| 188 | Code: "" ); |
| 189 | auto AST = File.build(); |
| 190 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
| 191 | EXPECT_TRUE(shouldCollect("ClassInLambda" , /*Qualified=*/false)); |
| 192 | EXPECT_TRUE(shouldCollect("LocalBase" , /*Qualified=*/false)); |
| 193 | EXPECT_TRUE(shouldCollect("LocalVirtual" , /*Qualified=*/false)); |
| 194 | EXPECT_TRUE(shouldCollect("LocalConcrete" , /*Qualified=*/false)); |
| 195 | EXPECT_FALSE(shouldCollect("BaseMember" , /*Qualified=*/false)); |
| 196 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
| 197 | } |
| 198 | |
| 199 | TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) { |
| 200 | HeaderName = "f.proto.h" ; |
| 201 | build( |
| 202 | HeaderCode: R"(// Generated by the protocol buffer compiler. DO NOT EDIT! |
| 203 | namespace nx { |
| 204 | enum Outer_Enum : int { |
| 205 | Outer_Enum_KIND1, |
| 206 | Outer_Enum_Kind_2, |
| 207 | }; |
| 208 | bool Outer_Enum_IsValid(int); |
| 209 | |
| 210 | class Outer_Inner {}; |
| 211 | class Outer { |
| 212 | using Inner = Outer_Inner; |
| 213 | using Enum = Outer_Enum; |
| 214 | static constexpr Enum KIND1 = Outer_Enum_KIND1; |
| 215 | static constexpr Enum Kind_2 = Outer_Enum_Kind_2; |
| 216 | static bool Enum_IsValid(int); |
| 217 | int &x(); |
| 218 | void set_x(); |
| 219 | void _internal_set_x(); |
| 220 | |
| 221 | int &Outer_y(); |
| 222 | }; |
| 223 | enum Foo { |
| 224 | FOO_VAL1, |
| 225 | Foo_VAL2, |
| 226 | }; |
| 227 | bool Foo_IsValid(int); |
| 228 | })" ); |
| 229 | |
| 230 | // Make sure all the mangled names for Outer::Enum is discarded. |
| 231 | EXPECT_FALSE(shouldCollect("nx::Outer_Enum" )); |
| 232 | EXPECT_FALSE(shouldCollect("nx::Outer_Enum_KIND1" )); |
| 233 | EXPECT_FALSE(shouldCollect("nx::Outer_Enum_Kind_2" )); |
| 234 | EXPECT_FALSE(shouldCollect("nx::Outer_Enum_IsValid" )); |
| 235 | // But nested aliases are preserved. |
| 236 | EXPECT_TRUE(shouldCollect("nx::Outer::Enum" )); |
| 237 | EXPECT_TRUE(shouldCollect("nx::Outer::KIND1" )); |
| 238 | EXPECT_TRUE(shouldCollect("nx::Outer::Kind_2" )); |
| 239 | EXPECT_TRUE(shouldCollect("nx::Outer::Enum_IsValid" )); |
| 240 | |
| 241 | // Check for Outer::Inner. |
| 242 | EXPECT_FALSE(shouldCollect("nx::Outer_Inner" )); |
| 243 | EXPECT_TRUE(shouldCollect("nx::Outer" )); |
| 244 | EXPECT_TRUE(shouldCollect("nx::Outer::Inner" )); |
| 245 | |
| 246 | // Make sure field related information is preserved, unless it's explicitly |
| 247 | // marked with `_internal_`. |
| 248 | EXPECT_TRUE(shouldCollect("nx::Outer::x" )); |
| 249 | EXPECT_TRUE(shouldCollect("nx::Outer::set_x" )); |
| 250 | EXPECT_FALSE(shouldCollect("nx::Outer::_internal_set_x" )); |
| 251 | EXPECT_TRUE(shouldCollect("nx::Outer::Outer_y" )); |
| 252 | |
| 253 | // Handling of a top-level enum |
| 254 | EXPECT_TRUE(shouldCollect("nx::Foo::FOO_VAL1" )); |
| 255 | EXPECT_TRUE(shouldCollect("nx::FOO_VAL1" )); |
| 256 | EXPECT_TRUE(shouldCollect("nx::Foo_IsValid" )); |
| 257 | // Our heuristic goes wrong here, if the user has a nested name that starts |
| 258 | // with parent's name. |
| 259 | EXPECT_FALSE(shouldCollect("nx::Foo::Foo_VAL2" )); |
| 260 | EXPECT_FALSE(shouldCollect("nx::Foo_VAL2" )); |
| 261 | } |
| 262 | |
| 263 | TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) { |
| 264 | HeaderName = "f.proto.h" ; |
| 265 | build(HeaderCode: R"( |
| 266 | namespace nx { |
| 267 | class Top_Level {}; |
| 268 | enum Kind { |
| 269 | Kind_Fine |
| 270 | }; |
| 271 | } |
| 272 | )" ); |
| 273 | EXPECT_TRUE(shouldCollect("nx::Top_Level" )); |
| 274 | EXPECT_TRUE(shouldCollect("nx::Kind_Fine" )); |
| 275 | } |
| 276 | |
| 277 | class SymbolIndexActionFactory : public tooling::FrontendActionFactory { |
| 278 | public: |
| 279 | SymbolIndexActionFactory(SymbolCollector::Options COpts) |
| 280 | : COpts(std::move(COpts)) {} |
| 281 | |
| 282 | std::unique_ptr<FrontendAction> create() override { |
| 283 | class IndexAction : public ASTFrontendAction { |
| 284 | public: |
| 285 | IndexAction(std::shared_ptr<index::IndexDataConsumer> DataConsumer, |
| 286 | const index::IndexingOptions &Opts, |
| 287 | std::shared_ptr<include_cleaner::PragmaIncludes> PI) |
| 288 | : DataConsumer(std::move(DataConsumer)), Opts(Opts), |
| 289 | PI(std::move(PI)) {} |
| 290 | |
| 291 | std::unique_ptr<ASTConsumer> |
| 292 | CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { |
| 293 | PI->record(CI); |
| 294 | return createIndexingASTConsumer(DataConsumer, Opts, |
| 295 | PP: CI.getPreprocessorPtr()); |
| 296 | } |
| 297 | |
| 298 | bool BeginInvocation(CompilerInstance &CI) override { |
| 299 | // Make the compiler parse all comments. |
| 300 | CI.getLangOpts().CommentOpts.ParseAllComments = true; |
| 301 | return true; |
| 302 | } |
| 303 | |
| 304 | private: |
| 305 | std::shared_ptr<index::IndexDataConsumer> DataConsumer; |
| 306 | index::IndexingOptions Opts; |
| 307 | std::shared_ptr<include_cleaner::PragmaIncludes> PI; |
| 308 | }; |
| 309 | index::IndexingOptions IndexOpts; |
| 310 | IndexOpts.SystemSymbolFilter = |
| 311 | index::IndexingOptions::SystemSymbolFilterKind::All; |
| 312 | IndexOpts.IndexFunctionLocals = true; |
| 313 | std::shared_ptr<include_cleaner::PragmaIncludes> PI = |
| 314 | std::make_shared<include_cleaner::PragmaIncludes>(); |
| 315 | COpts.PragmaIncludes = PI.get(); |
| 316 | Collector = std::make_shared<SymbolCollector>(args&: COpts); |
| 317 | return std::make_unique<IndexAction>(args&: Collector, args: std::move(IndexOpts), |
| 318 | args: std::move(PI)); |
| 319 | } |
| 320 | |
| 321 | std::shared_ptr<SymbolCollector> Collector; |
| 322 | SymbolCollector::Options COpts; |
| 323 | }; |
| 324 | |
| 325 | class SymbolCollectorTest : public ::testing::Test { |
| 326 | public: |
| 327 | SymbolCollectorTest() |
| 328 | : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), |
| 329 | TestHeaderName(testPath(File: "symbol.h" )), |
| 330 | TestFileName(testPath(File: "symbol.cc" )) { |
| 331 | TestHeaderURI = URI::create(AbsolutePath: TestHeaderName).toString(); |
| 332 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
| 333 | } |
| 334 | |
| 335 | // Note that unlike TestTU, no automatic header guard is added. |
| 336 | // HeaderCode should start with #pragma once to be treated as modular. |
| 337 | bool runSymbolCollector(llvm::StringRef , llvm::StringRef MainCode, |
| 338 | const std::vector<std::string> & = {}) { |
| 339 | llvm::IntrusiveRefCntPtr<FileManager> Files( |
| 340 | new FileManager(FileSystemOptions(), InMemoryFileSystem)); |
| 341 | |
| 342 | auto Factory = std::make_unique<SymbolIndexActionFactory>(args&: CollectorOpts); |
| 343 | |
| 344 | std::vector<std::string> Args = {"symbol_collector" , "-fsyntax-only" , |
| 345 | "-xc++" , "-include" , TestHeaderName}; |
| 346 | Args.insert(position: Args.end(), first: ExtraArgs.begin(), last: ExtraArgs.end()); |
| 347 | // This allows to override the "-xc++" with something else, i.e. |
| 348 | // -xobjective-c++. |
| 349 | Args.push_back(x: TestFileName); |
| 350 | |
| 351 | tooling::ToolInvocation Invocation( |
| 352 | Args, Factory->create(), Files.get(), |
| 353 | std::make_shared<PCHContainerOperations>()); |
| 354 | |
| 355 | // Multiple calls to runSymbolCollector with different contents will fail |
| 356 | // to update the filesystem! Why are we sharing one across tests, anyway? |
| 357 | EXPECT_TRUE(InMemoryFileSystem->addFile( |
| 358 | TestHeaderName, 0, llvm::MemoryBuffer::getMemBuffer(HeaderCode))); |
| 359 | EXPECT_TRUE(InMemoryFileSystem->addFile( |
| 360 | TestFileName, 0, llvm::MemoryBuffer::getMemBuffer(MainCode))); |
| 361 | Invocation.run(); |
| 362 | Symbols = Factory->Collector->takeSymbols(); |
| 363 | Refs = Factory->Collector->takeRefs(); |
| 364 | Relations = Factory->Collector->takeRelations(); |
| 365 | return true; |
| 366 | } |
| 367 | |
| 368 | protected: |
| 369 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; |
| 370 | std::string ; |
| 371 | std::string ; |
| 372 | std::string TestFileName; |
| 373 | std::string TestFileURI; |
| 374 | SymbolSlab Symbols; |
| 375 | RefSlab Refs; |
| 376 | RelationSlab Relations; |
| 377 | SymbolCollector::Options CollectorOpts; |
| 378 | }; |
| 379 | |
| 380 | TEST_F(SymbolCollectorTest, CollectSymbols) { |
| 381 | const std::string = R"( |
| 382 | class Foo { |
| 383 | Foo() {} |
| 384 | Foo(int a) {} |
| 385 | void f(); |
| 386 | friend void f1(); |
| 387 | friend class Friend; |
| 388 | Foo& operator=(const Foo&); |
| 389 | ~Foo(); |
| 390 | class Nested { |
| 391 | void f(); |
| 392 | }; |
| 393 | }; |
| 394 | class Friend { |
| 395 | }; |
| 396 | |
| 397 | void f1(); |
| 398 | inline void f2() {} |
| 399 | static const int KInt = 2; |
| 400 | const char* kStr = "123"; |
| 401 | |
| 402 | namespace { |
| 403 | void ff() {} // ignore |
| 404 | } |
| 405 | |
| 406 | void f1() { |
| 407 | auto LocalLambda = [&](){ |
| 408 | class ClassInLambda{}; |
| 409 | }; |
| 410 | } |
| 411 | |
| 412 | namespace foo { |
| 413 | // Type alias |
| 414 | typedef int int32; |
| 415 | using int32_t = int32; |
| 416 | |
| 417 | // Variable |
| 418 | int v1; |
| 419 | |
| 420 | // Namespace |
| 421 | namespace bar { |
| 422 | int v2; |
| 423 | } |
| 424 | // Namespace alias |
| 425 | namespace baz = bar; |
| 426 | |
| 427 | using bar::v2; |
| 428 | } // namespace foo |
| 429 | )" ; |
| 430 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 431 | EXPECT_THAT(Symbols, |
| 432 | UnorderedElementsAreArray( |
| 433 | {AllOf(qName("Foo" ), forCodeCompletion(true)), |
| 434 | AllOf(qName("Foo::Foo" ), forCodeCompletion(false)), |
| 435 | AllOf(qName("Foo::Foo" ), forCodeCompletion(false)), |
| 436 | AllOf(qName("Foo::f" ), forCodeCompletion(false)), |
| 437 | AllOf(qName("Foo::~Foo" ), forCodeCompletion(false)), |
| 438 | AllOf(qName("Foo::operator=" ), forCodeCompletion(false)), |
| 439 | AllOf(qName("Foo::Nested" ), forCodeCompletion(false)), |
| 440 | AllOf(qName("Foo::Nested::f" ), forCodeCompletion(false)), |
| 441 | AllOf(qName("ClassInLambda" ), forCodeCompletion(false)), |
| 442 | AllOf(qName("Friend" ), forCodeCompletion(true)), |
| 443 | AllOf(qName("f1" ), forCodeCompletion(true)), |
| 444 | AllOf(qName("f2" ), forCodeCompletion(true)), |
| 445 | AllOf(qName("KInt" ), forCodeCompletion(true)), |
| 446 | AllOf(qName("kStr" ), forCodeCompletion(true)), |
| 447 | AllOf(qName("foo" ), forCodeCompletion(true)), |
| 448 | AllOf(qName("foo::bar" ), forCodeCompletion(true)), |
| 449 | AllOf(qName("foo::int32" ), forCodeCompletion(true)), |
| 450 | AllOf(qName("foo::int32_t" ), forCodeCompletion(true)), |
| 451 | AllOf(qName("foo::v1" ), forCodeCompletion(true)), |
| 452 | AllOf(qName("foo::bar::v2" ), forCodeCompletion(true)), |
| 453 | AllOf(qName("foo::v2" ), forCodeCompletion(true)), |
| 454 | AllOf(qName("foo::baz" ), forCodeCompletion(true))})); |
| 455 | } |
| 456 | |
| 457 | TEST_F(SymbolCollectorTest, FileLocal) { |
| 458 | const std::string = R"( |
| 459 | class Foo {}; |
| 460 | namespace { |
| 461 | class Ignored {}; |
| 462 | } |
| 463 | void bar(); |
| 464 | )" ; |
| 465 | const std::string Main = R"( |
| 466 | class ForwardDecl; |
| 467 | void bar() {} |
| 468 | static void a(); |
| 469 | class B {}; |
| 470 | namespace { |
| 471 | void c(); |
| 472 | } |
| 473 | )" ; |
| 474 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 475 | EXPECT_THAT(Symbols, |
| 476 | UnorderedElementsAre( |
| 477 | AllOf(qName("Foo" ), visibleOutsideFile()), |
| 478 | AllOf(qName("bar" ), visibleOutsideFile()), |
| 479 | AllOf(qName("a" ), Not(visibleOutsideFile())), |
| 480 | AllOf(qName("B" ), Not(visibleOutsideFile())), |
| 481 | AllOf(qName("c" ), Not(visibleOutsideFile())), |
| 482 | // FIXME: ForwardDecl likely *is* visible outside. |
| 483 | AllOf(qName("ForwardDecl" ), Not(visibleOutsideFile())))); |
| 484 | } |
| 485 | |
| 486 | TEST_F(SymbolCollectorTest, Template) { |
| 487 | Annotations (R"( |
| 488 | // Primary template and explicit specialization are indexed, instantiation |
| 489 | // is not. |
| 490 | template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;}; |
| 491 | template <> struct $specdecl[[Tmpl]]<int, bool> {}; |
| 492 | template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {}; |
| 493 | extern template struct Tmpl<float, bool>; |
| 494 | template struct Tmpl<double, bool>; |
| 495 | )" ); |
| 496 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 497 | EXPECT_THAT(Symbols, |
| 498 | UnorderedElementsAre( |
| 499 | AllOf(qName("Tmpl" ), declRange(Header.range()), |
| 500 | forCodeCompletion(true)), |
| 501 | AllOf(qName("Tmpl" ), declRange(Header.range("specdecl" )), |
| 502 | forCodeCompletion(false)), |
| 503 | AllOf(qName("Tmpl" ), declRange(Header.range("partspecdecl" )), |
| 504 | forCodeCompletion(false)), |
| 505 | AllOf(qName("Tmpl::x" ), declRange(Header.range("xdecl" )), |
| 506 | forCodeCompletion(false)))); |
| 507 | } |
| 508 | |
| 509 | TEST_F(SymbolCollectorTest, templateArgs) { |
| 510 | Annotations (R"( |
| 511 | template <class X> class $barclasstemp[[Bar]] {}; |
| 512 | template <class T, class U, template<typename> class Z, int Q> |
| 513 | struct [[Tmpl]] { T $xdecl[[x]] = 0; }; |
| 514 | |
| 515 | // template-template, non-type and type full spec |
| 516 | template <> struct $specdecl[[Tmpl]]<int, bool, Bar, 3> {}; |
| 517 | |
| 518 | // template-template, non-type and type partial spec |
| 519 | template <class U, int T> struct $partspecdecl[[Tmpl]]<bool, U, Bar, T> {}; |
| 520 | // instantiation |
| 521 | extern template struct Tmpl<float, bool, Bar, 8>; |
| 522 | // instantiation |
| 523 | template struct Tmpl<double, bool, Bar, 2>; |
| 524 | |
| 525 | template <typename ...> class $fooclasstemp[[Foo]] {}; |
| 526 | // parameter-packs full spec |
| 527 | template<> class $parampack[[Foo]]<Bar<int>, int, double> {}; |
| 528 | // parameter-packs partial spec |
| 529 | template<class T> class $parampackpartial[[Foo]]<T, T> {}; |
| 530 | |
| 531 | template <int ...> class $bazclasstemp[[Baz]] {}; |
| 532 | // non-type parameter-packs full spec |
| 533 | template<> class $parampacknontype[[Baz]]<3, 5, 8> {}; |
| 534 | // non-type parameter-packs partial spec |
| 535 | template<int T> class $parampacknontypepartial[[Baz]]<T, T> {}; |
| 536 | |
| 537 | template <template <class> class ...> class $fozclasstemp[[Foz]] {}; |
| 538 | // template-template parameter-packs full spec |
| 539 | template<> class $parampacktempltempl[[Foz]]<Bar, Bar> {}; |
| 540 | // template-template parameter-packs partial spec |
| 541 | template<template <class> class T> |
| 542 | class $parampacktempltemplpartial[[Foz]]<T, T> {}; |
| 543 | )" ); |
| 544 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 545 | EXPECT_THAT( |
| 546 | Symbols, |
| 547 | AllOf( |
| 548 | Contains(AllOf(qName("Tmpl" ), templateArgs("<int, bool, Bar, 3>" ), |
| 549 | declRange(Header.range("specdecl" )), |
| 550 | forCodeCompletion(false))), |
| 551 | Contains(AllOf(qName("Tmpl" ), templateArgs("<bool, U, Bar, T>" ), |
| 552 | declRange(Header.range("partspecdecl" )), |
| 553 | forCodeCompletion(false))), |
| 554 | Contains(AllOf(qName("Foo" ), templateArgs("<Bar<int>, int, double>" ), |
| 555 | declRange(Header.range("parampack" )), |
| 556 | forCodeCompletion(false))), |
| 557 | Contains(AllOf(qName("Foo" ), templateArgs("<T, T>" ), |
| 558 | declRange(Header.range("parampackpartial" )), |
| 559 | forCodeCompletion(false))), |
| 560 | Contains(AllOf(qName("Baz" ), templateArgs("<3, 5, 8>" ), |
| 561 | declRange(Header.range("parampacknontype" )), |
| 562 | forCodeCompletion(false))), |
| 563 | Contains(AllOf(qName("Baz" ), templateArgs("<T, T>" ), |
| 564 | declRange(Header.range("parampacknontypepartial" )), |
| 565 | forCodeCompletion(false))), |
| 566 | Contains(AllOf(qName("Foz" ), templateArgs("<Bar, Bar>" ), |
| 567 | declRange(Header.range("parampacktempltempl" )), |
| 568 | forCodeCompletion(false))), |
| 569 | Contains(AllOf(qName("Foz" ), templateArgs("<T, T>" ), |
| 570 | declRange(Header.range("parampacktempltemplpartial" )), |
| 571 | forCodeCompletion(false))))); |
| 572 | } |
| 573 | |
| 574 | TEST_F(SymbolCollectorTest, ObjCRefs) { |
| 575 | Annotations (R"( |
| 576 | @interface Person |
| 577 | - (void)$talk[[talk]]; |
| 578 | - (void)$say[[say]]:(id)something; |
| 579 | @end |
| 580 | @interface Person (Category) |
| 581 | - (void)categoryMethod; |
| 582 | - (void)multiArg:(id)a method:(id)b; |
| 583 | @end |
| 584 | )" ); |
| 585 | Annotations Main(R"( |
| 586 | @implementation Person |
| 587 | - (void)$talk[[talk]] {} |
| 588 | - (void)$say[[say]]:(id)something {} |
| 589 | @end |
| 590 | |
| 591 | void fff(Person *p) { |
| 592 | [p $talk[[talk]]]; |
| 593 | [p $say[[say]]:0]; |
| 594 | [p categoryMethod]; |
| 595 | [p multiArg:0 method:0]; |
| 596 | } |
| 597 | )" ); |
| 598 | CollectorOpts.RefFilter = RefKind::All; |
| 599 | CollectorOpts.CollectMainFileRefs = true; |
| 600 | TestFileName = testPath(File: "test.m" ); |
| 601 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
| 602 | ExtraArgs: {"-fblocks" , "-xobjective-c++" , "-Wno-objc-root-class" }); |
| 603 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk" ).ID, |
| 604 | haveRanges(Main.ranges("talk" ))))); |
| 605 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:" ).ID, |
| 606 | haveRanges(Main.ranges("say" ))))); |
| 607 | EXPECT_THAT(Refs, |
| 608 | Contains(Pair(findSymbol(Symbols, "Person::categoryMethod" ).ID, |
| 609 | ElementsAre(isSpelled())))); |
| 610 | EXPECT_THAT(Refs, |
| 611 | Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:" ).ID, |
| 612 | ElementsAre(isSpelled())))); |
| 613 | } |
| 614 | |
| 615 | TEST_F(SymbolCollectorTest, ObjCSymbols) { |
| 616 | const std::string = R"( |
| 617 | @interface Person |
| 618 | - (void)someMethodName:(void*)name1 lastName:(void*)lName; |
| 619 | @end |
| 620 | |
| 621 | @implementation Person |
| 622 | - (void)someMethodName:(void*)name1 lastName:(void*)lName{ |
| 623 | int foo; |
| 624 | ^(int param){ int bar; }; |
| 625 | } |
| 626 | @end |
| 627 | |
| 628 | @interface Person (MyCategory) |
| 629 | - (void)someMethodName2:(void*)name2; |
| 630 | @end |
| 631 | |
| 632 | @implementation Person (MyCategory) |
| 633 | - (void)someMethodName2:(void*)name2 { |
| 634 | int foo2; |
| 635 | } |
| 636 | @end |
| 637 | |
| 638 | @protocol MyProtocol |
| 639 | - (void)someMethodName3:(void*)name3; |
| 640 | @end |
| 641 | )" ; |
| 642 | TestFileName = testPath(File: "test.m" ); |
| 643 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , ExtraArgs: {"-fblocks" , "-xobjective-c++" }); |
| 644 | EXPECT_THAT(Symbols, |
| 645 | UnorderedElementsAre( |
| 646 | qName("Person" ), qName("Person::someMethodName:lastName:" ), |
| 647 | AllOf(qName("MyCategory" ), forCodeCompletion(false)), |
| 648 | qName("Person::someMethodName2:" ), qName("MyProtocol" ), |
| 649 | qName("MyProtocol::someMethodName3:" ))); |
| 650 | } |
| 651 | |
| 652 | TEST_F(SymbolCollectorTest, ObjCPropertyImpl) { |
| 653 | const std::string = R"( |
| 654 | @interface Container |
| 655 | @property(nonatomic) int magic; |
| 656 | @end |
| 657 | |
| 658 | @implementation Container |
| 659 | @end |
| 660 | )" ; |
| 661 | TestFileName = testPath(File: "test.m" ); |
| 662 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , ExtraArgs: {"-xobjective-c++" }); |
| 663 | EXPECT_THAT(Symbols, Contains(qName("Container" ))); |
| 664 | EXPECT_THAT(Symbols, Contains(qName("Container::magic" ))); |
| 665 | // FIXME: Results also contain Container::_magic on some platforms. |
| 666 | // Figure out why it's platform-dependent. |
| 667 | } |
| 668 | |
| 669 | TEST_F(SymbolCollectorTest, ObjCLocations) { |
| 670 | Annotations (R"( |
| 671 | // Declared in header, defined in main. |
| 672 | @interface $dogdecl[[Dog]] |
| 673 | @end |
| 674 | @interface $fluffydecl[[Dog]] (Fluffy) |
| 675 | @end |
| 676 | )" ); |
| 677 | Annotations Main(R"( |
| 678 | @interface Dog () |
| 679 | @end |
| 680 | @implementation $dogdef[[Dog]] |
| 681 | @end |
| 682 | @implementation $fluffydef[[Dog]] (Fluffy) |
| 683 | @end |
| 684 | // Category with no declaration (only implementation). |
| 685 | @implementation $ruff[[Dog]] (Ruff) |
| 686 | @end |
| 687 | // Implicitly defined interface. |
| 688 | @implementation $catdog[[CatDog]] |
| 689 | @end |
| 690 | )" ); |
| 691 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
| 692 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
| 693 | EXPECT_THAT(Symbols, |
| 694 | UnorderedElementsAre( |
| 695 | AllOf(qName("Dog" ), declRange(Header.range("dogdecl" )), |
| 696 | defRange(Main.range("dogdef" ))), |
| 697 | AllOf(qName("Fluffy" ), declRange(Header.range("fluffydecl" )), |
| 698 | defRange(Main.range("fluffydef" ))), |
| 699 | AllOf(qName("CatDog" ), declRange(Main.range("catdog" )), |
| 700 | defRange(Main.range("catdog" ))), |
| 701 | AllOf(qName("Ruff" ), declRange(Main.range("ruff" )), |
| 702 | defRange(Main.range("ruff" ))))); |
| 703 | } |
| 704 | |
| 705 | TEST_F(SymbolCollectorTest, ObjCForwardDecls) { |
| 706 | Annotations (R"( |
| 707 | // Forward declared in header, declared and defined in main. |
| 708 | @protocol Barker; |
| 709 | @class Dog; |
| 710 | // Never fully declared so Clang latches onto this decl. |
| 711 | @class $catdogdecl[[CatDog]]; |
| 712 | )" ); |
| 713 | Annotations Main(R"( |
| 714 | @protocol $barkerdecl[[Barker]] |
| 715 | - (void)woof; |
| 716 | @end |
| 717 | @interface $dogdecl[[Dog]]<Barker> |
| 718 | - (void)woof; |
| 719 | @end |
| 720 | @implementation $dogdef[[Dog]] |
| 721 | - (void)woof {} |
| 722 | @end |
| 723 | @implementation $catdogdef[[CatDog]] |
| 724 | @end |
| 725 | )" ); |
| 726 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
| 727 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
| 728 | EXPECT_THAT(Symbols, |
| 729 | UnorderedElementsAre( |
| 730 | AllOf(qName("CatDog" ), declRange(Header.range("catdogdecl" )), |
| 731 | defRange(Main.range("catdogdef" ))), |
| 732 | AllOf(qName("Dog" ), declRange(Main.range("dogdecl" )), |
| 733 | defRange(Main.range("dogdef" ))), |
| 734 | AllOf(qName("Barker" ), declRange(Main.range("barkerdecl" ))), |
| 735 | qName("Barker::woof" ), qName("Dog::woof" ))); |
| 736 | } |
| 737 | |
| 738 | TEST_F(SymbolCollectorTest, ObjCClassExtensions) { |
| 739 | Annotations (R"( |
| 740 | @interface $catdecl[[Cat]] |
| 741 | @end |
| 742 | )" ); |
| 743 | Annotations Main(R"( |
| 744 | @interface Cat () |
| 745 | - (void)meow; |
| 746 | @end |
| 747 | @interface Cat () |
| 748 | - (void)pur; |
| 749 | @end |
| 750 | )" ); |
| 751 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
| 752 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
| 753 | EXPECT_THAT(Symbols, |
| 754 | UnorderedElementsAre( |
| 755 | AllOf(qName("Cat" ), declRange(Header.range("catdecl" ))), |
| 756 | qName("Cat::meow" ), qName("Cat::pur" ))); |
| 757 | } |
| 758 | |
| 759 | TEST_F(SymbolCollectorTest, ObjCFrameworkIncludeHeader) { |
| 760 | CollectorOpts.CollectIncludePath = true; |
| 761 | auto FrameworksPath = testPath(File: "Frameworks/" ); |
| 762 | std::string = R"( |
| 763 | __attribute((objc_root_class)) |
| 764 | @interface NSObject |
| 765 | @end |
| 766 | )" ; |
| 767 | InMemoryFileSystem->addFile( |
| 768 | Path: testPath(File: "Frameworks/Foundation.framework/Headers/NSObject.h" ), ModificationTime: 0, |
| 769 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: FrameworkHeader)); |
| 770 | std::string = R"( |
| 771 | #import <Foundation/NSObject.h> |
| 772 | |
| 773 | @interface PrivateClass : NSObject |
| 774 | @end |
| 775 | )" ; |
| 776 | InMemoryFileSystem->addFile( |
| 777 | Path: testPath( |
| 778 | File: "Frameworks/Foundation.framework/PrivateHeaders/NSObject+Private.h" ), |
| 779 | ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: PrivateFrameworkHeader)); |
| 780 | |
| 781 | std::string = R"( |
| 782 | #import <Foundation/NSObject+Private.h> |
| 783 | #import <Foundation/NSObject.h> |
| 784 | |
| 785 | @interface Container : NSObject |
| 786 | @end |
| 787 | )" ; |
| 788 | std::string Main = "" ; |
| 789 | TestFileName = testPath(File: "test.m" ); |
| 790 | runSymbolCollector(HeaderCode: Header, MainCode: Main, ExtraArgs: {"-F" , FrameworksPath, "-xobjective-c++" }); |
| 791 | EXPECT_THAT( |
| 792 | Symbols, |
| 793 | UnorderedElementsAre( |
| 794 | AllOf(qName("NSObject" ), includeHeader("<Foundation/NSObject.h>" )), |
| 795 | AllOf(qName("PrivateClass" ), |
| 796 | includeHeader("<Foundation/NSObject+Private.h>" )), |
| 797 | AllOf(qName("Container" )))); |
| 798 | |
| 799 | // After adding the umbrella headers, we should use that spelling instead. |
| 800 | std::string = R"( |
| 801 | #import <Foundation/NSObject.h> |
| 802 | )" ; |
| 803 | InMemoryFileSystem->addFile( |
| 804 | Path: testPath(File: "Frameworks/Foundation.framework/Headers/Foundation.h" ), ModificationTime: 0, |
| 805 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: UmbrellaHeader)); |
| 806 | std::string = R"( |
| 807 | #import <Foundation/NSObject+Private.h> |
| 808 | )" ; |
| 809 | InMemoryFileSystem->addFile( |
| 810 | Path: testPath(File: "Frameworks/Foundation.framework/PrivateHeaders/" |
| 811 | "Foundation_Private.h" ), |
| 812 | ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: PrivateUmbrellaHeader)); |
| 813 | runSymbolCollector(HeaderCode: Header, MainCode: Main, ExtraArgs: {"-F" , FrameworksPath, "-xobjective-c++" }); |
| 814 | EXPECT_THAT( |
| 815 | Symbols, |
| 816 | UnorderedElementsAre( |
| 817 | AllOf(qName("NSObject" ), includeHeader("<Foundation/Foundation.h>" )), |
| 818 | AllOf(qName("PrivateClass" ), |
| 819 | includeHeader("<Foundation/Foundation_Private.h>" )), |
| 820 | AllOf(qName("Container" )))); |
| 821 | |
| 822 | runSymbolCollector(HeaderCode: Header, MainCode: Main, |
| 823 | ExtraArgs: {"-iframework" , FrameworksPath, "-xobjective-c++" }); |
| 824 | EXPECT_THAT( |
| 825 | Symbols, |
| 826 | UnorderedElementsAre( |
| 827 | AllOf(qName("NSObject" ), includeHeader("<Foundation/Foundation.h>" )), |
| 828 | AllOf(qName("PrivateClass" ), |
| 829 | includeHeader("<Foundation/Foundation_Private.h>" )), |
| 830 | AllOf(qName("Container" )))); |
| 831 | } |
| 832 | |
| 833 | TEST_F(SymbolCollectorTest, Locations) { |
| 834 | Annotations (R"cpp( |
| 835 | // Declared in header, defined in main. |
| 836 | extern int $xdecl[[X]]; |
| 837 | class $clsdecl[[Cls]]; |
| 838 | void $printdecl[[print]](); |
| 839 | |
| 840 | // Declared in header, defined nowhere. |
| 841 | extern int $zdecl[[Z]]; |
| 842 | |
| 843 | void $foodecl[[fo\ |
| 844 | o]](); |
| 845 | )cpp" ); |
| 846 | Annotations Main(R"cpp( |
| 847 | int $xdef[[X]] = 42; |
| 848 | class $clsdef[[Cls]] {}; |
| 849 | void $printdef[[print]]() {} |
| 850 | |
| 851 | // Declared/defined in main only. |
| 852 | int $ydecl[[Y]]; |
| 853 | )cpp" ); |
| 854 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
| 855 | EXPECT_THAT(Symbols, |
| 856 | UnorderedElementsAre( |
| 857 | AllOf(qName("X" ), declRange(Header.range("xdecl" )), |
| 858 | defRange(Main.range("xdef" ))), |
| 859 | AllOf(qName("Cls" ), declRange(Header.range("clsdecl" )), |
| 860 | defRange(Main.range("clsdef" ))), |
| 861 | AllOf(qName("print" ), declRange(Header.range("printdecl" )), |
| 862 | defRange(Main.range("printdef" ))), |
| 863 | AllOf(qName("Z" ), declRange(Header.range("zdecl" ))), |
| 864 | AllOf(qName("foo" ), declRange(Header.range("foodecl" ))), |
| 865 | AllOf(qName("Y" ), declRange(Main.range("ydecl" ))))); |
| 866 | } |
| 867 | |
| 868 | TEST_F(SymbolCollectorTest, Refs) { |
| 869 | Annotations (R"( |
| 870 | #define MACRO(X) (X + 1) |
| 871 | class Foo { |
| 872 | public: |
| 873 | Foo() {} |
| 874 | Foo(int); |
| 875 | }; |
| 876 | class Bar; |
| 877 | void func(); |
| 878 | |
| 879 | namespace NS {} // namespace ref is ignored |
| 880 | )" ); |
| 881 | Annotations Main(R"( |
| 882 | class $bar[[Bar]] {}; |
| 883 | |
| 884 | void $func[[func]](); |
| 885 | |
| 886 | void fff() { |
| 887 | $foo[[Foo]] foo; |
| 888 | $bar[[Bar]] bar; |
| 889 | $func[[func]](); |
| 890 | int abc = 0; |
| 891 | $foo[[Foo]] foo2 = abc; |
| 892 | abc = $macro[[MACRO]](1); |
| 893 | } |
| 894 | )" ); |
| 895 | Annotations SymbolsOnlyInMainCode(R"( |
| 896 | #define FUNC(X) (X+1) |
| 897 | int a; |
| 898 | void b() {} |
| 899 | static const int c = FUNC(1); |
| 900 | class d {}; |
| 901 | )" ); |
| 902 | CollectorOpts.RefFilter = RefKind::All; |
| 903 | CollectorOpts.CollectMacro = true; |
| 904 | runSymbolCollector(HeaderCode: Header.code(), |
| 905 | MainCode: (Main.code() + SymbolsOnlyInMainCode.code()).str()); |
| 906 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 907 | haveRanges(Main.ranges("foo" ))))); |
| 908 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Bar" ).ID, |
| 909 | haveRanges(Main.ranges("bar" ))))); |
| 910 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "func" ).ID, |
| 911 | haveRanges(Main.ranges("func" ))))); |
| 912 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "NS" ).ID, _)))); |
| 913 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO" ).ID, |
| 914 | haveRanges(Main.ranges("macro" ))))); |
| 915 | // - (a, b) externally visible and should have refs. |
| 916 | // - (c, FUNC) externally invisible and had no refs collected. |
| 917 | auto MainSymbols = |
| 918 | TestTU::withHeaderCode(HeaderCode: SymbolsOnlyInMainCode.code()).headerSymbols(); |
| 919 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "a" ).ID, _))); |
| 920 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "b" ).ID, _))); |
| 921 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "c" ).ID, _)))); |
| 922 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "FUNC" ).ID, _)))); |
| 923 | |
| 924 | // Run the collector again with CollectMainFileRefs = true. |
| 925 | // We need to recreate InMemoryFileSystem because runSymbolCollector() |
| 926 | // calls MemoryBuffer::getMemBuffer(), which makes the buffers unusable |
| 927 | // after runSymbolCollector() exits. |
| 928 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem(); |
| 929 | CollectorOpts.CollectMainFileRefs = true; |
| 930 | runSymbolCollector(HeaderCode: Header.code(), |
| 931 | MainCode: (Main.code() + SymbolsOnlyInMainCode.code()).str()); |
| 932 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "a" ).ID, _))); |
| 933 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "b" ).ID, _))); |
| 934 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "c" ).ID, _))); |
| 935 | // However, references to main-file macros are not collected. |
| 936 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "FUNC" ).ID, _)))); |
| 937 | } |
| 938 | |
| 939 | TEST_F(SymbolCollectorTest, RefContainers) { |
| 940 | Annotations Code(R"cpp( |
| 941 | int $toplevel1[[f1]](int); |
| 942 | void f2() { |
| 943 | (void) $ref1a[[f1]](1); |
| 944 | auto fptr = &$ref1b[[f1]]; |
| 945 | } |
| 946 | int $toplevel2[[v1]] = $ref2[[f1]](2); |
| 947 | void f3(int arg = $ref3[[f1]](3)); |
| 948 | struct S1 { |
| 949 | int $classscope1[[member1]] = $ref4[[f1]](4); |
| 950 | int $classscope2[[member2]] = 42; |
| 951 | }; |
| 952 | constexpr int f4(int x) { return x + 1; } |
| 953 | template <int I = $ref5[[f4]](0)> struct S2 {}; |
| 954 | S2<$ref6[[f4]](0)> v2; |
| 955 | S2<$ref7a[[f4]](0)> f5(S2<$ref7b[[f4]](0)>); |
| 956 | namespace N { |
| 957 | void $namespacescope1[[f6]](); |
| 958 | int $namespacescope2[[v3]]; |
| 959 | } |
| 960 | )cpp" ); |
| 961 | CollectorOpts.RefFilter = RefKind::All; |
| 962 | CollectorOpts.CollectMainFileRefs = true; |
| 963 | runSymbolCollector(HeaderCode: "" , MainCode: Code.code()); |
| 964 | auto FindRefWithRange = [&](Range R) -> std::optional<Ref> { |
| 965 | for (auto &Entry : Refs) { |
| 966 | for (auto &Ref : Entry.second) { |
| 967 | if (rangesMatch(Loc: Ref.Location, R)) |
| 968 | return Ref; |
| 969 | } |
| 970 | } |
| 971 | return std::nullopt; |
| 972 | }; |
| 973 | auto Container = [&](llvm::StringRef RangeName) { |
| 974 | auto Ref = FindRefWithRange(Code.range(Name: RangeName)); |
| 975 | EXPECT_TRUE(bool(Ref)); |
| 976 | return Ref->Container; |
| 977 | }; |
| 978 | EXPECT_EQ(Container("ref1a" ), |
| 979 | findSymbol(Symbols, "f2" ).ID); // function body (call) |
| 980 | EXPECT_EQ(Container("ref1b" ), |
| 981 | findSymbol(Symbols, "f2" ).ID); // function body (address-of) |
| 982 | EXPECT_EQ(Container("ref2" ), |
| 983 | findSymbol(Symbols, "v1" ).ID); // variable initializer |
| 984 | EXPECT_EQ(Container("ref3" ), |
| 985 | findSymbol(Symbols, "f3" ).ID); // function parameter default value |
| 986 | EXPECT_EQ(Container("ref4" ), |
| 987 | findSymbol(Symbols, "S1::member1" ).ID); // member initializer |
| 988 | EXPECT_EQ(Container("ref5" ), |
| 989 | findSymbol(Symbols, "S2" ).ID); // template parameter default value |
| 990 | EXPECT_EQ(Container("ref6" ), |
| 991 | findSymbol(Symbols, "v2" ).ID); // type of variable |
| 992 | EXPECT_EQ(Container("ref7a" ), |
| 993 | findSymbol(Symbols, "f5" ).ID); // return type of function |
| 994 | EXPECT_EQ(Container("ref7b" ), |
| 995 | findSymbol(Symbols, "f5" ).ID); // parameter type of function |
| 996 | |
| 997 | EXPECT_FALSE(Container("classscope1" ).isNull()); |
| 998 | EXPECT_FALSE(Container("namespacescope1" ).isNull()); |
| 999 | |
| 1000 | EXPECT_EQ(Container("toplevel1" ), Container("toplevel2" )); |
| 1001 | EXPECT_EQ(Container("classscope1" ), Container("classscope2" )); |
| 1002 | EXPECT_EQ(Container("namespacescope1" ), Container("namespacescope2" )); |
| 1003 | |
| 1004 | EXPECT_NE(Container("toplevel1" ), Container("namespacescope1" )); |
| 1005 | EXPECT_NE(Container("toplevel1" ), Container("classscope1" )); |
| 1006 | EXPECT_NE(Container("classscope1" ), Container("namespacescope1" )); |
| 1007 | } |
| 1008 | |
| 1009 | TEST_F(SymbolCollectorTest, MacroRefInHeader) { |
| 1010 | Annotations (R"( |
| 1011 | #define $foo[[FOO]](X) (X + 1) |
| 1012 | #define $bar[[BAR]](X) (X + 2) |
| 1013 | |
| 1014 | // Macro defined multiple times. |
| 1015 | #define $ud1[[UD]] 1 |
| 1016 | int ud_1 = $ud1[[UD]]; |
| 1017 | #undef UD |
| 1018 | |
| 1019 | #define $ud2[[UD]] 2 |
| 1020 | int ud_2 = $ud2[[UD]]; |
| 1021 | #undef UD |
| 1022 | |
| 1023 | // Macros from token concatenations not included. |
| 1024 | #define $concat[[CONCAT]](X) X##A() |
| 1025 | #define $prepend[[PREPEND]](X) MACRO##X() |
| 1026 | #define $macroa[[MACROA]]() 123 |
| 1027 | int B = $concat[[CONCAT]](MACRO); |
| 1028 | int D = $prepend[[PREPEND]](A); |
| 1029 | |
| 1030 | void fff() { |
| 1031 | int abc = $foo[[FOO]](1) + $bar[[BAR]]($foo[[FOO]](1)); |
| 1032 | } |
| 1033 | )" ); |
| 1034 | CollectorOpts.RefFilter = RefKind::All; |
| 1035 | CollectorOpts.RefsInHeaders = true; |
| 1036 | // Need this to get the SymbolID for macros for tests. |
| 1037 | CollectorOpts.CollectMacro = true; |
| 1038 | |
| 1039 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
| 1040 | |
| 1041 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "FOO" ).ID, |
| 1042 | haveRanges(Header.ranges("foo" ))))); |
| 1043 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "BAR" ).ID, |
| 1044 | haveRanges(Header.ranges("bar" ))))); |
| 1045 | // No unique ID for multiple symbols named UD. Check for ranges only. |
| 1046 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud1" ))))); |
| 1047 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud2" ))))); |
| 1048 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "CONCAT" ).ID, |
| 1049 | haveRanges(Header.ranges("concat" ))))); |
| 1050 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PREPEND" ).ID, |
| 1051 | haveRanges(Header.ranges("prepend" ))))); |
| 1052 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACROA" ).ID, |
| 1053 | haveRanges(Header.ranges("macroa" ))))); |
| 1054 | } |
| 1055 | |
| 1056 | TEST_F(SymbolCollectorTest, MacroRefWithoutCollectingSymbol) { |
| 1057 | Annotations (R"( |
| 1058 | #define $foo[[FOO]](X) (X + 1) |
| 1059 | int abc = $foo[[FOO]](1); |
| 1060 | )" ); |
| 1061 | CollectorOpts.RefFilter = RefKind::All; |
| 1062 | CollectorOpts.RefsInHeaders = true; |
| 1063 | CollectorOpts.CollectMacro = false; |
| 1064 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
| 1065 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("foo" ))))); |
| 1066 | } |
| 1067 | |
| 1068 | TEST_F(SymbolCollectorTest, MacrosWithRefFilter) { |
| 1069 | Annotations ("#define $macro[[MACRO]](X) (X + 1)" ); |
| 1070 | Annotations Main("void foo() { int x = $macro[[MACRO]](1); }" ); |
| 1071 | CollectorOpts.RefFilter = RefKind::Unknown; |
| 1072 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
| 1073 | EXPECT_THAT(Refs, IsEmpty()); |
| 1074 | } |
| 1075 | |
| 1076 | TEST_F(SymbolCollectorTest, SpelledReferences) { |
| 1077 | struct { |
| 1078 | llvm::StringRef ; |
| 1079 | llvm::StringRef Main; |
| 1080 | llvm::StringRef TargetSymbolName; |
| 1081 | } TestCases[] = { |
| 1082 | { |
| 1083 | .Header: R"cpp( |
| 1084 | struct Foo; |
| 1085 | #define MACRO Foo |
| 1086 | )cpp" , |
| 1087 | .Main: R"cpp( |
| 1088 | struct $spelled[[Foo]] { |
| 1089 | $spelled[[Foo]](); |
| 1090 | ~$spelled[[Foo]](); |
| 1091 | }; |
| 1092 | $spelled[[Foo]] Variable1; |
| 1093 | $implicit[[MACRO]] Variable2; |
| 1094 | )cpp" , |
| 1095 | .TargetSymbolName: "Foo" , |
| 1096 | }, |
| 1097 | { |
| 1098 | .Header: R"cpp( |
| 1099 | class Foo { |
| 1100 | public: |
| 1101 | Foo() = default; |
| 1102 | }; |
| 1103 | )cpp" , |
| 1104 | .Main: R"cpp( |
| 1105 | void f() { Foo $implicit[[f]]; f = $spelled[[Foo]]();} |
| 1106 | )cpp" , |
| 1107 | .TargetSymbolName: "Foo::Foo" /// constructor. |
| 1108 | }, |
| 1109 | { // Unclean identifiers |
| 1110 | .Header: R"cpp( |
| 1111 | struct Foo {}; |
| 1112 | )cpp" , |
| 1113 | .Main: R"cpp( |
| 1114 | $spelled[[Fo\ |
| 1115 | o]] f{}; |
| 1116 | )cpp" , |
| 1117 | .TargetSymbolName: "Foo" , |
| 1118 | }, |
| 1119 | }; |
| 1120 | CollectorOpts.RefFilter = RefKind::All; |
| 1121 | CollectorOpts.RefsInHeaders = false; |
| 1122 | for (const auto& T : TestCases) { |
| 1123 | SCOPED_TRACE(T.Header + "\n---\n" + T.Main); |
| 1124 | Annotations (T.Header); |
| 1125 | Annotations Main(T.Main); |
| 1126 | // Reset the file system. |
| 1127 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
| 1128 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
| 1129 | |
| 1130 | const auto SpelledRanges = Main.ranges(Name: "spelled" ); |
| 1131 | const auto ImplicitRanges = Main.ranges(Name: "implicit" ); |
| 1132 | RefSlab::Builder SpelledSlabBuilder, ImplicitSlabBuilder; |
| 1133 | const auto TargetID = findSymbol(Symbols, QName: T.TargetSymbolName).ID; |
| 1134 | for (const auto &SymbolAndRefs : Refs) { |
| 1135 | const auto ID = SymbolAndRefs.first; |
| 1136 | if (ID != TargetID) |
| 1137 | continue; |
| 1138 | for (const auto &Ref : SymbolAndRefs.second) |
| 1139 | if ((Ref.Kind & RefKind::Spelled) != RefKind::Unknown) |
| 1140 | SpelledSlabBuilder.insert(ID, S: Ref); |
| 1141 | else |
| 1142 | ImplicitSlabBuilder.insert(ID, S: Ref); |
| 1143 | } |
| 1144 | const auto SpelledRefs = std::move(SpelledSlabBuilder).build(), |
| 1145 | ImplicitRefs = std::move(ImplicitSlabBuilder).build(); |
| 1146 | EXPECT_EQ(SpelledRanges.empty(), SpelledRefs.empty()); |
| 1147 | EXPECT_EQ(ImplicitRanges.empty(), ImplicitRefs.empty()); |
| 1148 | if (!SpelledRanges.empty()) |
| 1149 | EXPECT_THAT(SpelledRefs, |
| 1150 | Contains(Pair(TargetID, haveRanges(SpelledRanges)))); |
| 1151 | if (!ImplicitRanges.empty()) |
| 1152 | EXPECT_THAT(ImplicitRefs, |
| 1153 | Contains(Pair(TargetID, haveRanges(ImplicitRanges)))); |
| 1154 | } |
| 1155 | } |
| 1156 | |
| 1157 | TEST_F(SymbolCollectorTest, NameReferences) { |
| 1158 | CollectorOpts.RefFilter = RefKind::All; |
| 1159 | CollectorOpts.RefsInHeaders = true; |
| 1160 | Annotations (R"( |
| 1161 | class [[Foo]] { |
| 1162 | public: |
| 1163 | [[Foo]]() {} |
| 1164 | ~[[Foo]]() {} |
| 1165 | }; |
| 1166 | )" ); |
| 1167 | CollectorOpts.RefFilter = RefKind::All; |
| 1168 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
| 1169 | // When we find references for class Foo, we expect to see all |
| 1170 | // constructor/destructor references. |
| 1171 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1172 | haveRanges(Header.ranges())))); |
| 1173 | } |
| 1174 | |
| 1175 | TEST_F(SymbolCollectorTest, RefsOnMacros) { |
| 1176 | // Refs collected from SymbolCollector behave in the same way as |
| 1177 | // AST-based xrefs. |
| 1178 | CollectorOpts.RefFilter = RefKind::All; |
| 1179 | CollectorOpts.RefsInHeaders = true; |
| 1180 | Annotations (R"( |
| 1181 | #define TYPE(X) X |
| 1182 | #define FOO Foo |
| 1183 | #define CAT(X, Y) X##Y |
| 1184 | class [[Foo]] {}; |
| 1185 | void test() { |
| 1186 | TYPE([[Foo]]) foo; |
| 1187 | [[FOO]] foo2; |
| 1188 | TYPE(TYPE([[Foo]])) foo3; |
| 1189 | [[CAT]](Fo, o) foo4; |
| 1190 | } |
| 1191 | )" ); |
| 1192 | CollectorOpts.RefFilter = RefKind::All; |
| 1193 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
| 1194 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1195 | haveRanges(Header.ranges())))); |
| 1196 | } |
| 1197 | |
| 1198 | TEST_F(SymbolCollectorTest, HeaderAsMainFile) { |
| 1199 | CollectorOpts.RefFilter = RefKind::All; |
| 1200 | Annotations (R"( |
| 1201 | class $Foo[[Foo]] {}; |
| 1202 | |
| 1203 | void $Func[[Func]]() { |
| 1204 | $Foo[[Foo]] fo; |
| 1205 | } |
| 1206 | )" ); |
| 1207 | // We should collect refs to main-file symbols in all cases: |
| 1208 | |
| 1209 | // 1. The main file is normal .cpp file. |
| 1210 | TestFileName = testPath(File: "foo.cpp" ); |
| 1211 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code()); |
| 1212 | EXPECT_THAT(Refs, |
| 1213 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1214 | haveRanges(Header.ranges("Foo" ))), |
| 1215 | Pair(findSymbol(Symbols, "Func" ).ID, |
| 1216 | haveRanges(Header.ranges("Func" ))))); |
| 1217 | |
| 1218 | // 2. Run the .h file as main file. |
| 1219 | TestFileName = testPath(File: "foo.h" ); |
| 1220 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code(), |
| 1221 | /*ExtraArgs=*/{"-xobjective-c++-header" }); |
| 1222 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), qName("Func" ))); |
| 1223 | EXPECT_THAT(Refs, |
| 1224 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1225 | haveRanges(Header.ranges("Foo" ))), |
| 1226 | Pair(findSymbol(Symbols, "Func" ).ID, |
| 1227 | haveRanges(Header.ranges("Func" ))))); |
| 1228 | |
| 1229 | // 3. Run the .hh file as main file (without "-x c++-header"). |
| 1230 | TestFileName = testPath(File: "foo.hh" ); |
| 1231 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code()); |
| 1232 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), qName("Func" ))); |
| 1233 | EXPECT_THAT(Refs, |
| 1234 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1235 | haveRanges(Header.ranges("Foo" ))), |
| 1236 | Pair(findSymbol(Symbols, "Func" ).ID, |
| 1237 | haveRanges(Header.ranges("Func" ))))); |
| 1238 | } |
| 1239 | |
| 1240 | TEST_F(SymbolCollectorTest, RefsInHeaders) { |
| 1241 | CollectorOpts.RefFilter = RefKind::All; |
| 1242 | CollectorOpts.RefsInHeaders = true; |
| 1243 | CollectorOpts.CollectMacro = true; |
| 1244 | Annotations (R"( |
| 1245 | #define $macro[[MACRO]](x) (x+1) |
| 1246 | class $foo[[Foo]] {}; |
| 1247 | )" ); |
| 1248 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
| 1249 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
| 1250 | haveRanges(Header.ranges("foo" ))))); |
| 1251 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO" ).ID, |
| 1252 | haveRanges(Header.ranges("macro" ))))); |
| 1253 | } |
| 1254 | |
| 1255 | TEST_F(SymbolCollectorTest, BaseOfRelations) { |
| 1256 | std::string = R"( |
| 1257 | class Base {}; |
| 1258 | class Derived : public Base {}; |
| 1259 | )" ; |
| 1260 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1261 | const Symbol &Base = findSymbol(Symbols, QName: "Base" ); |
| 1262 | const Symbol &Derived = findSymbol(Symbols, QName: "Derived" ); |
| 1263 | EXPECT_THAT(Relations, |
| 1264 | Contains(Relation{Base.ID, RelationKind::BaseOf, Derived.ID})); |
| 1265 | } |
| 1266 | |
| 1267 | TEST_F(SymbolCollectorTest, OverrideRelationsSimpleInheritance) { |
| 1268 | std::string = R"cpp( |
| 1269 | class A { |
| 1270 | virtual void foo(); |
| 1271 | }; |
| 1272 | class B : public A { |
| 1273 | void foo() override; // A::foo |
| 1274 | virtual void bar(); |
| 1275 | }; |
| 1276 | class C : public B { |
| 1277 | void bar() override; // B::bar |
| 1278 | }; |
| 1279 | class D: public C { |
| 1280 | void foo() override; // B::foo |
| 1281 | void bar() override; // C::bar |
| 1282 | }; |
| 1283 | )cpp" ; |
| 1284 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1285 | const Symbol &AFoo = findSymbol(Symbols, QName: "A::foo" ); |
| 1286 | const Symbol &BFoo = findSymbol(Symbols, QName: "B::foo" ); |
| 1287 | const Symbol &DFoo = findSymbol(Symbols, QName: "D::foo" ); |
| 1288 | |
| 1289 | const Symbol &BBar = findSymbol(Symbols, QName: "B::bar" ); |
| 1290 | const Symbol &CBar = findSymbol(Symbols, QName: "C::bar" ); |
| 1291 | const Symbol &DBar = findSymbol(Symbols, QName: "D::bar" ); |
| 1292 | |
| 1293 | std::vector<Relation> Result; |
| 1294 | for (const Relation &R : Relations) |
| 1295 | if (R.Predicate == RelationKind::OverriddenBy) |
| 1296 | Result.push_back(x: R); |
| 1297 | EXPECT_THAT(Result, UnorderedElementsAre( |
| 1298 | OverriddenBy(AFoo, BFoo), OverriddenBy(BBar, CBar), |
| 1299 | OverriddenBy(BFoo, DFoo), OverriddenBy(CBar, DBar))); |
| 1300 | } |
| 1301 | |
| 1302 | TEST_F(SymbolCollectorTest, OverrideRelationsMultipleInheritance) { |
| 1303 | std::string = R"cpp( |
| 1304 | class A { |
| 1305 | virtual void foo(); |
| 1306 | }; |
| 1307 | class B { |
| 1308 | virtual void bar(); |
| 1309 | }; |
| 1310 | class C : public B { |
| 1311 | void bar() override; // B::bar |
| 1312 | virtual void baz(); |
| 1313 | } |
| 1314 | class D : public A, C { |
| 1315 | void foo() override; // A::foo |
| 1316 | void bar() override; // C::bar |
| 1317 | void baz() override; // C::baz |
| 1318 | }; |
| 1319 | )cpp" ; |
| 1320 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1321 | const Symbol &AFoo = findSymbol(Symbols, QName: "A::foo" ); |
| 1322 | const Symbol &BBar = findSymbol(Symbols, QName: "B::bar" ); |
| 1323 | const Symbol &CBar = findSymbol(Symbols, QName: "C::bar" ); |
| 1324 | const Symbol &CBaz = findSymbol(Symbols, QName: "C::baz" ); |
| 1325 | const Symbol &DFoo = findSymbol(Symbols, QName: "D::foo" ); |
| 1326 | const Symbol &DBar = findSymbol(Symbols, QName: "D::bar" ); |
| 1327 | const Symbol &DBaz = findSymbol(Symbols, QName: "D::baz" ); |
| 1328 | |
| 1329 | std::vector<Relation> Result; |
| 1330 | for (const Relation &R : Relations) |
| 1331 | if (R.Predicate == RelationKind::OverriddenBy) |
| 1332 | Result.push_back(x: R); |
| 1333 | EXPECT_THAT(Result, UnorderedElementsAre( |
| 1334 | OverriddenBy(BBar, CBar), OverriddenBy(AFoo, DFoo), |
| 1335 | OverriddenBy(CBar, DBar), OverriddenBy(CBaz, DBaz))); |
| 1336 | } |
| 1337 | |
| 1338 | TEST_F(SymbolCollectorTest, ObjCOverrideRelationsSimpleInheritance) { |
| 1339 | std::string = R"cpp( |
| 1340 | @interface A |
| 1341 | - (void)foo; |
| 1342 | @end |
| 1343 | @interface B : A |
| 1344 | - (void)foo; // A::foo |
| 1345 | - (void)bar; |
| 1346 | @end |
| 1347 | @interface C : B |
| 1348 | - (void)bar; // B::bar |
| 1349 | @end |
| 1350 | @interface D : C |
| 1351 | - (void)foo; // B::foo |
| 1352 | - (void)bar; // C::bar |
| 1353 | @end |
| 1354 | )cpp" ; |
| 1355 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , |
| 1356 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
| 1357 | const Symbol &AFoo = findSymbol(Symbols, QName: "A::foo" ); |
| 1358 | const Symbol &BFoo = findSymbol(Symbols, QName: "B::foo" ); |
| 1359 | const Symbol &DFoo = findSymbol(Symbols, QName: "D::foo" ); |
| 1360 | |
| 1361 | const Symbol &BBar = findSymbol(Symbols, QName: "B::bar" ); |
| 1362 | const Symbol &CBar = findSymbol(Symbols, QName: "C::bar" ); |
| 1363 | const Symbol &DBar = findSymbol(Symbols, QName: "D::bar" ); |
| 1364 | |
| 1365 | std::vector<Relation> Result; |
| 1366 | for (const Relation &R : Relations) |
| 1367 | if (R.Predicate == RelationKind::OverriddenBy) |
| 1368 | Result.push_back(x: R); |
| 1369 | EXPECT_THAT(Result, UnorderedElementsAre( |
| 1370 | OverriddenBy(AFoo, BFoo), OverriddenBy(BBar, CBar), |
| 1371 | OverriddenBy(BFoo, DFoo), OverriddenBy(CBar, DBar))); |
| 1372 | } |
| 1373 | |
| 1374 | TEST_F(SymbolCollectorTest, CountReferences) { |
| 1375 | const std::string = R"( |
| 1376 | class W; |
| 1377 | class X {}; |
| 1378 | class Y; |
| 1379 | class Z {}; // not used anywhere |
| 1380 | Y* y = nullptr; // used in header doesn't count |
| 1381 | #define GLOBAL_Z(name) Z name; |
| 1382 | )" ; |
| 1383 | const std::string Main = R"( |
| 1384 | W* w = nullptr; |
| 1385 | W* w2 = nullptr; // only one usage counts |
| 1386 | X x(); |
| 1387 | class V; |
| 1388 | class Y{}; // definition doesn't count as a reference |
| 1389 | V* v = nullptr; |
| 1390 | GLOBAL_Z(z); // Not a reference to Z, we don't spell the type. |
| 1391 | )" ; |
| 1392 | CollectorOpts.CountReferences = true; |
| 1393 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 1394 | EXPECT_THAT( |
| 1395 | Symbols, |
| 1396 | UnorderedElementsAreArray( |
| 1397 | {AllOf(qName("W" ), refCount(1)), AllOf(qName("X" ), refCount(1)), |
| 1398 | AllOf(qName("Y" ), refCount(0)), AllOf(qName("Z" ), refCount(0)), |
| 1399 | AllOf(qName("y" ), refCount(0)), AllOf(qName("z" ), refCount(0)), |
| 1400 | AllOf(qName("x" ), refCount(0)), AllOf(qName("w" ), refCount(0)), |
| 1401 | AllOf(qName("w2" ), refCount(0)), AllOf(qName("V" ), refCount(1)), |
| 1402 | AllOf(qName("v" ), refCount(0))})); |
| 1403 | } |
| 1404 | |
| 1405 | TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { |
| 1406 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
| 1407 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1408 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
| 1409 | } |
| 1410 | |
| 1411 | TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { |
| 1412 | TestHeaderName = "x.h" ; |
| 1413 | TestFileName = "x.cpp" ; |
| 1414 | TestHeaderURI = URI::create(AbsolutePath: testPath(File: TestHeaderName)).toString(); |
| 1415 | CollectorOpts.FallbackDir = testRoot(); |
| 1416 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
| 1417 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1418 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
| 1419 | } |
| 1420 | |
| 1421 | TEST_F(SymbolCollectorTest, UnittestURIScheme) { |
| 1422 | // Use test URI scheme from URITests.cpp |
| 1423 | TestHeaderName = testPath(File: "x.h" ); |
| 1424 | TestFileName = testPath(File: "x.cpp" ); |
| 1425 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
| 1426 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1427 | AllOf(qName("Foo" ), declURI("unittest:///x.h" )))); |
| 1428 | } |
| 1429 | |
| 1430 | TEST_F(SymbolCollectorTest, IncludeEnums) { |
| 1431 | const std::string = R"( |
| 1432 | enum { |
| 1433 | Red |
| 1434 | }; |
| 1435 | enum Color { |
| 1436 | Green |
| 1437 | }; |
| 1438 | enum class Color2 { |
| 1439 | Yellow |
| 1440 | }; |
| 1441 | namespace ns { |
| 1442 | enum { |
| 1443 | Black |
| 1444 | }; |
| 1445 | } |
| 1446 | class Color3 { |
| 1447 | enum { |
| 1448 | Blue |
| 1449 | }; |
| 1450 | }; |
| 1451 | )" ; |
| 1452 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1453 | EXPECT_THAT(Symbols, |
| 1454 | UnorderedElementsAre( |
| 1455 | AllOf(qName("Red" ), forCodeCompletion(true)), |
| 1456 | AllOf(qName("Color" ), forCodeCompletion(true)), |
| 1457 | AllOf(qName("Green" ), forCodeCompletion(true)), |
| 1458 | AllOf(qName("Color2" ), forCodeCompletion(true)), |
| 1459 | AllOf(qName("Color2::Yellow" ), forCodeCompletion(true)), |
| 1460 | AllOf(qName("ns" ), forCodeCompletion(true)), |
| 1461 | AllOf(qName("ns::Black" ), forCodeCompletion(true)), |
| 1462 | AllOf(qName("Color3" ), forCodeCompletion(true)), |
| 1463 | AllOf(qName("Color3::Blue" ), forCodeCompletion(true)))); |
| 1464 | } |
| 1465 | |
| 1466 | TEST_F(SymbolCollectorTest, NamelessSymbols) { |
| 1467 | const std::string = R"( |
| 1468 | struct { |
| 1469 | int a; |
| 1470 | } Foo; |
| 1471 | )" ; |
| 1472 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1473 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), |
| 1474 | qName("(anonymous struct)::a" ))); |
| 1475 | } |
| 1476 | |
| 1477 | TEST_F(SymbolCollectorTest, SymbolFormedFromRegisteredSchemeFromMacro) { |
| 1478 | |
| 1479 | Annotations (R"( |
| 1480 | #define FF(name) \ |
| 1481 | class name##_Test {}; |
| 1482 | |
| 1483 | $expansion[[FF]](abc); |
| 1484 | |
| 1485 | #define FF2() \ |
| 1486 | class $spelling[[Test]] {}; |
| 1487 | |
| 1488 | FF2(); |
| 1489 | )" ); |
| 1490 | |
| 1491 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 1492 | EXPECT_THAT(Symbols, |
| 1493 | UnorderedElementsAre( |
| 1494 | AllOf(qName("abc_Test" ), declRange(Header.range("expansion" )), |
| 1495 | declURI(TestHeaderURI)), |
| 1496 | AllOf(qName("Test" ), declRange(Header.range("spelling" )), |
| 1497 | declURI(TestHeaderURI)))); |
| 1498 | } |
| 1499 | |
| 1500 | TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { |
| 1501 | Annotations (R"( |
| 1502 | #ifdef NAME |
| 1503 | class $expansion[[NAME]] {}; |
| 1504 | #endif |
| 1505 | )" ); |
| 1506 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" , /*ExtraArgs=*/{"-DNAME=name" }); |
| 1507 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
| 1508 | qName("name" ), declRange(Header.range("expansion" )), |
| 1509 | declURI(TestHeaderURI)))); |
| 1510 | } |
| 1511 | |
| 1512 | TEST_F(SymbolCollectorTest, SymbolsInMainFile) { |
| 1513 | const std::string Main = R"( |
| 1514 | class Foo {}; |
| 1515 | void f1(); |
| 1516 | inline void f2() {} |
| 1517 | |
| 1518 | namespace { |
| 1519 | void ff() {} |
| 1520 | } |
| 1521 | namespace foo { |
| 1522 | namespace { |
| 1523 | class Bar {}; |
| 1524 | } |
| 1525 | } |
| 1526 | void main_f() {} |
| 1527 | void f1() {} |
| 1528 | )" ; |
| 1529 | runSymbolCollector(/*Header=*/HeaderCode: "" , MainCode: Main); |
| 1530 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1531 | qName("Foo" ), qName("f1" ), qName("f2" ), qName("ff" ), |
| 1532 | qName("foo" ), qName("foo::Bar" ), qName("main_f" ))); |
| 1533 | } |
| 1534 | |
| 1535 | TEST_F(SymbolCollectorTest, Documentation) { |
| 1536 | const std::string = R"( |
| 1537 | // doc Foo |
| 1538 | class Foo { |
| 1539 | // doc f |
| 1540 | int f(); |
| 1541 | }; |
| 1542 | )" ; |
| 1543 | CollectorOpts.StoreAllDocumentation = false; |
| 1544 | runSymbolCollector(HeaderCode: Header, /* Main */ MainCode: "" ); |
| 1545 | EXPECT_THAT(Symbols, |
| 1546 | UnorderedElementsAre( |
| 1547 | AllOf(qName("Foo" ), doc("doc Foo" ), forCodeCompletion(true)), |
| 1548 | AllOf(qName("Foo::f" ), doc("" ), returnType("" ), |
| 1549 | forCodeCompletion(false)))); |
| 1550 | |
| 1551 | CollectorOpts.StoreAllDocumentation = true; |
| 1552 | runSymbolCollector(HeaderCode: Header, /* Main */ MainCode: "" ); |
| 1553 | EXPECT_THAT(Symbols, |
| 1554 | UnorderedElementsAre( |
| 1555 | AllOf(qName("Foo" ), doc("doc Foo" ), forCodeCompletion(true)), |
| 1556 | AllOf(qName("Foo::f" ), doc("doc f" ), returnType("" ), |
| 1557 | forCodeCompletion(false)))); |
| 1558 | } |
| 1559 | |
| 1560 | TEST_F(SymbolCollectorTest, DocumentationInMain) { |
| 1561 | const std::string = R"( |
| 1562 | // doc Foo |
| 1563 | class Foo { |
| 1564 | void f(); |
| 1565 | }; |
| 1566 | )" ; |
| 1567 | const std::string Main = R"( |
| 1568 | // doc f |
| 1569 | void Foo::f() {} |
| 1570 | )" ; |
| 1571 | CollectorOpts.StoreAllDocumentation = true; |
| 1572 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 1573 | EXPECT_THAT(Symbols, |
| 1574 | UnorderedElementsAre( |
| 1575 | AllOf(qName("Foo" ), doc("doc Foo" ), forCodeCompletion(true)), |
| 1576 | AllOf(qName("Foo::f" ), doc("doc f" ), returnType("" ), |
| 1577 | forCodeCompletion(false)))); |
| 1578 | } |
| 1579 | |
| 1580 | TEST_F(SymbolCollectorTest, DocumentationAtDeclThenDef) { |
| 1581 | const std::string = R"( |
| 1582 | class Foo { |
| 1583 | // doc f decl |
| 1584 | void f(); |
| 1585 | }; |
| 1586 | )" ; |
| 1587 | const std::string Main = R"( |
| 1588 | // doc f def |
| 1589 | void Foo::f() {} |
| 1590 | )" ; |
| 1591 | CollectorOpts.StoreAllDocumentation = true; |
| 1592 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 1593 | EXPECT_THAT(Symbols, |
| 1594 | UnorderedElementsAre(AllOf(qName("Foo" )), |
| 1595 | AllOf(qName("Foo::f" ), doc("doc f decl" )))); |
| 1596 | } |
| 1597 | |
| 1598 | TEST_F(SymbolCollectorTest, DocumentationAtDefThenDecl) { |
| 1599 | const std::string = R"( |
| 1600 | // doc f def |
| 1601 | void f() {} |
| 1602 | |
| 1603 | // doc f decl |
| 1604 | void f(); |
| 1605 | )" ; |
| 1606 | CollectorOpts.StoreAllDocumentation = true; |
| 1607 | runSymbolCollector(HeaderCode: Header, MainCode: "" /*Main*/); |
| 1608 | EXPECT_THAT(Symbols, |
| 1609 | UnorderedElementsAre(AllOf(qName("f" ), doc("doc f def" )))); |
| 1610 | } |
| 1611 | |
| 1612 | TEST_F(SymbolCollectorTest, ClassMembers) { |
| 1613 | const std::string = R"( |
| 1614 | class Foo { |
| 1615 | void f() {} |
| 1616 | void g(); |
| 1617 | static void sf() {} |
| 1618 | static void ssf(); |
| 1619 | static int x; |
| 1620 | }; |
| 1621 | )" ; |
| 1622 | const std::string Main = R"( |
| 1623 | void Foo::g() {} |
| 1624 | void Foo::ssf() {} |
| 1625 | )" ; |
| 1626 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 1627 | EXPECT_THAT( |
| 1628 | Symbols, |
| 1629 | UnorderedElementsAre( |
| 1630 | qName("Foo" ), |
| 1631 | AllOf(qName("Foo::f" ), returnType("" ), forCodeCompletion(false)), |
| 1632 | AllOf(qName("Foo::g" ), returnType("" ), forCodeCompletion(false)), |
| 1633 | AllOf(qName("Foo::sf" ), returnType("" ), forCodeCompletion(false)), |
| 1634 | AllOf(qName("Foo::ssf" ), returnType("" ), forCodeCompletion(false)), |
| 1635 | AllOf(qName("Foo::x" ), returnType("" ), forCodeCompletion(false)))); |
| 1636 | } |
| 1637 | |
| 1638 | TEST_F(SymbolCollectorTest, Scopes) { |
| 1639 | const std::string = R"( |
| 1640 | namespace na { |
| 1641 | class Foo {}; |
| 1642 | namespace nb { |
| 1643 | class Bar {}; |
| 1644 | } |
| 1645 | } |
| 1646 | )" ; |
| 1647 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1648 | EXPECT_THAT(Symbols, |
| 1649 | UnorderedElementsAre(qName("na" ), qName("na::nb" ), |
| 1650 | qName("na::Foo" ), qName("na::nb::Bar" ))); |
| 1651 | } |
| 1652 | |
| 1653 | TEST_F(SymbolCollectorTest, ExternC) { |
| 1654 | const std::string = R"( |
| 1655 | extern "C" { class Foo {}; } |
| 1656 | namespace na { |
| 1657 | extern "C" { class Bar {}; } |
| 1658 | } |
| 1659 | )" ; |
| 1660 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1661 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("na" ), qName("Foo" ), |
| 1662 | qName("na::Bar" ))); |
| 1663 | } |
| 1664 | |
| 1665 | TEST_F(SymbolCollectorTest, SkipInlineNamespace) { |
| 1666 | const std::string = R"( |
| 1667 | namespace na { |
| 1668 | inline namespace nb { |
| 1669 | class Foo {}; |
| 1670 | } |
| 1671 | } |
| 1672 | namespace na { |
| 1673 | // This is still inlined. |
| 1674 | namespace nb { |
| 1675 | class Bar {}; |
| 1676 | } |
| 1677 | } |
| 1678 | )" ; |
| 1679 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1680 | EXPECT_THAT(Symbols, |
| 1681 | UnorderedElementsAre(qName("na" ), qName("na::nb" ), |
| 1682 | qName("na::Foo" ), qName("na::Bar" ))); |
| 1683 | } |
| 1684 | |
| 1685 | TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { |
| 1686 | const std::string = R"( |
| 1687 | namespace nx { |
| 1688 | /// Foo comment. |
| 1689 | int ff(int x, double y) { return 0; } |
| 1690 | } |
| 1691 | )" ; |
| 1692 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1693 | EXPECT_THAT( |
| 1694 | Symbols, |
| 1695 | UnorderedElementsAre( |
| 1696 | qName("nx" ), AllOf(qName("nx::ff" ), labeled("ff(int x, double y)" ), |
| 1697 | returnType("int" ), doc("Foo comment." )))); |
| 1698 | } |
| 1699 | |
| 1700 | TEST_F(SymbolCollectorTest, snippet) { |
| 1701 | const std::string = R"( |
| 1702 | namespace nx { |
| 1703 | void f() {} |
| 1704 | int ff(int x, double y) { return 0; } |
| 1705 | } |
| 1706 | )" ; |
| 1707 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1708 | EXPECT_THAT(Symbols, |
| 1709 | UnorderedElementsAre( |
| 1710 | qName("nx" ), |
| 1711 | AllOf(qName("nx::f" ), labeled("f()" ), snippet("f()" )), |
| 1712 | AllOf(qName("nx::ff" ), labeled("ff(int x, double y)" ), |
| 1713 | snippet("ff(${1:int x}, ${2:double y})" )))); |
| 1714 | } |
| 1715 | |
| 1716 | TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { |
| 1717 | CollectorOpts.CollectIncludePath = true; |
| 1718 | runSymbolCollector(HeaderCode: "#pragma once\nclass Foo {};" , /*Main=*/MainCode: "" ); |
| 1719 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1720 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
| 1721 | EXPECT_THAT(Symbols.begin()->IncludeHeaders, |
| 1722 | UnorderedElementsAre(IncludeHeaderWithRef(TestHeaderURI, 1u))); |
| 1723 | } |
| 1724 | |
| 1725 | TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { |
| 1726 | CollectorOpts.CollectIncludePath = true; |
| 1727 | runSymbolCollector( |
| 1728 | HeaderCode: R"cpp( |
| 1729 | namespace std { |
| 1730 | class string {}; |
| 1731 | // Move overloads have special handling. |
| 1732 | template <typename _T> T&& move(_T&& __value); |
| 1733 | template <typename _I, typename _O> _O move(_I, _I, _O); |
| 1734 | template <typename _T, typename _O, typename _I> _O move( |
| 1735 | _T&&, _O, _O, _I); |
| 1736 | } |
| 1737 | )cpp" , |
| 1738 | /*Main=*/MainCode: "" ); |
| 1739 | EXPECT_THAT( |
| 1740 | Symbols, |
| 1741 | UnorderedElementsAre( |
| 1742 | qName("std" ), |
| 1743 | AllOf(qName("std::string" ), declURI(TestHeaderURI), |
| 1744 | includeHeader("<string>" )), |
| 1745 | // Parameter names are demangled. |
| 1746 | AllOf(labeled("move(T &&value)" ), includeHeader("<utility>" )), |
| 1747 | AllOf(labeled("move(I, I, O)" ), includeHeader("<algorithm>" )), |
| 1748 | AllOf(labeled("move(T &&, O, O, I)" ), includeHeader("<algorithm>" )))); |
| 1749 | } |
| 1750 | |
| 1751 | TEST_F(SymbolCollectorTest, IWYUPragma) { |
| 1752 | CollectorOpts.CollectIncludePath = true; |
| 1753 | const std::string = R"( |
| 1754 | // IWYU pragma: private, include the/good/header.h |
| 1755 | class Foo {}; |
| 1756 | )" ; |
| 1757 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1758 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1759 | AllOf(qName("Foo" ), declURI(TestHeaderURI), |
| 1760 | includeHeader("\"the/good/header.h\"" )))); |
| 1761 | } |
| 1762 | |
| 1763 | TEST_F(SymbolCollectorTest, IWYUPragmaWithDoubleQuotes) { |
| 1764 | CollectorOpts.CollectIncludePath = true; |
| 1765 | const std::string = R"( |
| 1766 | // IWYU pragma: private, include "the/good/header.h" |
| 1767 | class Foo {}; |
| 1768 | )" ; |
| 1769 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
| 1770 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1771 | AllOf(qName("Foo" ), declURI(TestHeaderURI), |
| 1772 | includeHeader("\"the/good/header.h\"" )))); |
| 1773 | } |
| 1774 | |
| 1775 | TEST_F(SymbolCollectorTest, IWYUPragmaExport) { |
| 1776 | CollectorOpts.CollectIncludePath = true; |
| 1777 | const std::string = R"cpp(#pragma once |
| 1778 | #include "exporter.h" |
| 1779 | )cpp" ; |
| 1780 | auto ExporterFile = testPath(File: "exporter.h" ); |
| 1781 | InMemoryFileSystem->addFile( |
| 1782 | Path: ExporterFile, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: R"cpp(#pragma once |
| 1783 | #include "private.h" // IWYU pragma: export |
| 1784 | )cpp" )); |
| 1785 | auto PrivateFile = testPath(File: "private.h" ); |
| 1786 | InMemoryFileSystem->addFile( |
| 1787 | Path: PrivateFile, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class Foo {};" )); |
| 1788 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , |
| 1789 | /*ExtraArgs=*/{"-I" , testRoot()}); |
| 1790 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
| 1791 | qName("Foo" ), |
| 1792 | includeHeader(URI::create(ExporterFile).toString()), |
| 1793 | declURI(URI::create(PrivateFile).toString())))); |
| 1794 | } |
| 1795 | |
| 1796 | TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { |
| 1797 | CollectorOpts.CollectIncludePath = true; |
| 1798 | // To make this case as hard as possible, we won't tell clang main is a |
| 1799 | // header. No extension, no -x c++-header. |
| 1800 | TestFileName = testPath(File: "no_ext_main" ); |
| 1801 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
| 1802 | auto IncFile = testPath(File: "test.inc" ); |
| 1803 | auto IncURI = URI::create(AbsolutePath: IncFile).toString(); |
| 1804 | InMemoryFileSystem->addFile(Path: IncFile, ModificationTime: 0, |
| 1805 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class X {};" )); |
| 1806 | runSymbolCollector(HeaderCode: "" , MainCode: R"cpp( |
| 1807 | // Can't use #pragma once in a main file clang doesn't think is a header. |
| 1808 | #ifndef MAIN_H_ |
| 1809 | #define MAIN_H_ |
| 1810 | #include "test.inc" |
| 1811 | #endif |
| 1812 | )cpp" , |
| 1813 | /*ExtraArgs=*/{"-I" , testRoot()}); |
| 1814 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), declURI(IncURI), |
| 1815 | includeHeader(TestFileURI)))); |
| 1816 | } |
| 1817 | |
| 1818 | TEST_F(SymbolCollectorTest, IncFileInNonHeader) { |
| 1819 | CollectorOpts.CollectIncludePath = true; |
| 1820 | TestFileName = testPath(File: "main.cc" ); |
| 1821 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
| 1822 | auto IncFile = testPath(File: "test.inc" ); |
| 1823 | auto IncURI = URI::create(AbsolutePath: IncFile).toString(); |
| 1824 | InMemoryFileSystem->addFile(Path: IncFile, ModificationTime: 0, |
| 1825 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class X {};" )); |
| 1826 | runSymbolCollector(HeaderCode: "" , MainCode: R"cpp( |
| 1827 | #include "test.inc" |
| 1828 | )cpp" , |
| 1829 | /*ExtraArgs=*/{"-I" , testRoot()}); |
| 1830 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), declURI(IncURI), |
| 1831 | Not(includeHeader())))); |
| 1832 | } |
| 1833 | |
| 1834 | // Features that depend on header-guards are fragile. Header guards are only |
| 1835 | // recognized when the file ends, so we have to defer checking for them. |
| 1836 | TEST_F(SymbolCollectorTest, HeaderGuardDetected) { |
| 1837 | CollectorOpts.CollectIncludePath = true; |
| 1838 | CollectorOpts.CollectMacro = true; |
| 1839 | runSymbolCollector(HeaderCode: R"cpp( |
| 1840 | #ifndef HEADER_GUARD_ |
| 1841 | #define HEADER_GUARD_ |
| 1842 | |
| 1843 | // Symbols are seen before the header guard is complete. |
| 1844 | #define MACRO |
| 1845 | int decl(); |
| 1846 | |
| 1847 | #endif // Header guard is recognized here. |
| 1848 | )cpp" , |
| 1849 | MainCode: "" ); |
| 1850 | EXPECT_THAT(Symbols, Not(Contains(qName("HEADER_GUARD_" )))); |
| 1851 | EXPECT_THAT(Symbols, Each(includeHeader())); |
| 1852 | } |
| 1853 | |
| 1854 | TEST_F(SymbolCollectorTest, NonModularHeader) { |
| 1855 | auto TU = TestTU::withHeaderCode(HeaderCode: "int x();" ); |
| 1856 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(includeHeader())); |
| 1857 | |
| 1858 | // Files missing include guards aren't eligible for insertion. |
| 1859 | TU.ImplicitHeaderGuard = false; |
| 1860 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); |
| 1861 | |
| 1862 | // We recognize some patterns of trying to prevent insertion. |
| 1863 | TU = TestTU::withHeaderCode(HeaderCode: R"cpp( |
| 1864 | #ifndef SECRET |
| 1865 | #error "This file isn't safe to include directly" |
| 1866 | #endif |
| 1867 | int x(); |
| 1868 | )cpp" ); |
| 1869 | TU.ExtraArgs.push_back(x: "-DSECRET" ); // *we're* able to include it. |
| 1870 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); |
| 1871 | } |
| 1872 | |
| 1873 | TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { |
| 1874 | CollectorOpts.CollectIncludePath = true; |
| 1875 | Annotations (R"( |
| 1876 | #pragma once |
| 1877 | // Forward declarations of TagDecls. |
| 1878 | class C; |
| 1879 | struct S; |
| 1880 | union U; |
| 1881 | |
| 1882 | // Canonical declarations. |
| 1883 | class $cdecl[[C]] {}; |
| 1884 | struct $sdecl[[S]] {}; |
| 1885 | union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; |
| 1886 | )" ); |
| 1887 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 1888 | EXPECT_THAT( |
| 1889 | Symbols, |
| 1890 | UnorderedElementsAre( |
| 1891 | AllOf(qName("C" ), declURI(TestHeaderURI), |
| 1892 | declRange(Header.range("cdecl" )), includeHeader(TestHeaderURI), |
| 1893 | defURI(TestHeaderURI), defRange(Header.range("cdecl" ))), |
| 1894 | AllOf(qName("S" ), declURI(TestHeaderURI), |
| 1895 | declRange(Header.range("sdecl" )), includeHeader(TestHeaderURI), |
| 1896 | defURI(TestHeaderURI), defRange(Header.range("sdecl" ))), |
| 1897 | AllOf(qName("U" ), declURI(TestHeaderURI), |
| 1898 | declRange(Header.range("udecl" )), includeHeader(TestHeaderURI), |
| 1899 | defURI(TestHeaderURI), defRange(Header.range("udecl" ))), |
| 1900 | AllOf(qName("U::x" ), declURI(TestHeaderURI), |
| 1901 | declRange(Header.range("xdecl" )), defURI(TestHeaderURI), |
| 1902 | defRange(Header.range("xdecl" ))), |
| 1903 | AllOf(qName("U::y" ), declURI(TestHeaderURI), |
| 1904 | declRange(Header.range("ydecl" )), defURI(TestHeaderURI), |
| 1905 | defRange(Header.range("ydecl" ))))); |
| 1906 | } |
| 1907 | |
| 1908 | TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { |
| 1909 | CollectorOpts.CollectIncludePath = true; |
| 1910 | runSymbolCollector(/*Header=*/HeaderCode: "#pragma once\nclass X;" , |
| 1911 | /*Main=*/MainCode: "class X {};" ); |
| 1912 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
| 1913 | qName("X" ), declURI(TestHeaderURI), |
| 1914 | includeHeader(TestHeaderURI), defURI(TestFileURI)))); |
| 1915 | } |
| 1916 | |
| 1917 | TEST_F(SymbolCollectorTest, UTF16Character) { |
| 1918 | // ö is 2-bytes. |
| 1919 | Annotations (/*Header=*/"class [[pörk]] {};" ); |
| 1920 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 1921 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1922 | AllOf(qName("pörk" ), declRange(Header.range())))); |
| 1923 | } |
| 1924 | |
| 1925 | TEST_F(SymbolCollectorTest, DoNotIndexSymbolsInFriendDecl) { |
| 1926 | Annotations (R"( |
| 1927 | namespace nx { |
| 1928 | class $z[[Z]] {}; |
| 1929 | class X { |
| 1930 | friend class Y; |
| 1931 | friend class Z; |
| 1932 | friend void foo(); |
| 1933 | friend void $bar[[bar]]() {} |
| 1934 | }; |
| 1935 | class $y[[Y]] {}; |
| 1936 | void $foo[[foo]](); |
| 1937 | } |
| 1938 | )" ); |
| 1939 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
| 1940 | |
| 1941 | EXPECT_THAT(Symbols, |
| 1942 | UnorderedElementsAre( |
| 1943 | qName("nx" ), qName("nx::X" ), |
| 1944 | AllOf(qName("nx::Y" ), declRange(Header.range("y" ))), |
| 1945 | AllOf(qName("nx::Z" ), declRange(Header.range("z" ))), |
| 1946 | AllOf(qName("nx::foo" ), declRange(Header.range("foo" ))), |
| 1947 | AllOf(qName("nx::bar" ), declRange(Header.range("bar" ))))); |
| 1948 | } |
| 1949 | |
| 1950 | TEST_F(SymbolCollectorTest, ReferencesInFriendDecl) { |
| 1951 | const std::string = R"( |
| 1952 | class X; |
| 1953 | class Y; |
| 1954 | )" ; |
| 1955 | const std::string Main = R"( |
| 1956 | class C { |
| 1957 | friend ::X; |
| 1958 | friend class Y; |
| 1959 | }; |
| 1960 | )" ; |
| 1961 | CollectorOpts.CountReferences = true; |
| 1962 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
| 1963 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), refCount(1)), |
| 1964 | AllOf(qName("Y" ), refCount(1)), |
| 1965 | AllOf(qName("C" ), refCount(0)))); |
| 1966 | } |
| 1967 | |
| 1968 | TEST_F(SymbolCollectorTest, Origin) { |
| 1969 | CollectorOpts.Origin = SymbolOrigin::Static; |
| 1970 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
| 1971 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1972 | Field(&Symbol::Origin, SymbolOrigin::Static))); |
| 1973 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
| 1974 | CollectorOpts.CollectMacro = true; |
| 1975 | runSymbolCollector(HeaderCode: "#define FOO" , /*Main=*/MainCode: "" ); |
| 1976 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 1977 | Field(&Symbol::Origin, SymbolOrigin::Static))); |
| 1978 | } |
| 1979 | |
| 1980 | TEST_F(SymbolCollectorTest, CollectMacros) { |
| 1981 | CollectorOpts.CollectIncludePath = true; |
| 1982 | Annotations (R"( |
| 1983 | #pragma once |
| 1984 | #define X 1 |
| 1985 | #define $mac[[MAC]](x) int x |
| 1986 | #define $used[[USED]](y) float y; |
| 1987 | |
| 1988 | MAC(p); |
| 1989 | )" ); |
| 1990 | |
| 1991 | Annotations Main(R"( |
| 1992 | #define $main[[MAIN]] 1 |
| 1993 | USED(t); |
| 1994 | )" ); |
| 1995 | CollectorOpts.CountReferences = true; |
| 1996 | CollectorOpts.CollectMacro = true; |
| 1997 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
| 1998 | EXPECT_THAT( |
| 1999 | Symbols, |
| 2000 | UnorderedElementsAre( |
| 2001 | qName("p" ), qName("t" ), |
| 2002 | AllOf(qName("X" ), declURI(TestHeaderURI), |
| 2003 | includeHeader(TestHeaderURI)), |
| 2004 | AllOf(labeled("MAC(x)" ), refCount(0), |
| 2005 | |
| 2006 | declRange(Header.range("mac" )), visibleOutsideFile()), |
| 2007 | AllOf(labeled("USED(y)" ), refCount(1), |
| 2008 | declRange(Header.range("used" )), visibleOutsideFile()), |
| 2009 | AllOf(labeled("MAIN" ), refCount(0), declRange(Main.range("main" )), |
| 2010 | Not(visibleOutsideFile())))); |
| 2011 | } |
| 2012 | |
| 2013 | TEST_F(SymbolCollectorTest, DeprecatedSymbols) { |
| 2014 | const std::string = R"( |
| 2015 | void TestClangc() __attribute__((deprecated("", ""))); |
| 2016 | void TestClangd(); |
| 2017 | )" ; |
| 2018 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
| 2019 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
| 2020 | AllOf(qName("TestClangc" ), deprecated()), |
| 2021 | AllOf(qName("TestClangd" ), Not(deprecated())))); |
| 2022 | } |
| 2023 | |
| 2024 | TEST_F(SymbolCollectorTest, implementationDetail) { |
| 2025 | const std::string = R"( |
| 2026 | #define DECL_NAME(x, y) x##_##y##_Decl |
| 2027 | #define DECL(x, y) class DECL_NAME(x, y) {}; |
| 2028 | DECL(X, Y); // X_Y_Decl |
| 2029 | |
| 2030 | class Public {}; |
| 2031 | )" ; |
| 2032 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
| 2033 | EXPECT_THAT(Symbols, |
| 2034 | UnorderedElementsAre( |
| 2035 | AllOf(qName("X_Y_Decl" ), implementationDetail()), |
| 2036 | AllOf(qName("Public" ), Not(implementationDetail())))); |
| 2037 | } |
| 2038 | |
| 2039 | TEST_F(SymbolCollectorTest, UsingDecl) { |
| 2040 | const char * = R"( |
| 2041 | void foo(); |
| 2042 | namespace std { |
| 2043 | using ::foo; |
| 2044 | })" ; |
| 2045 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
| 2046 | EXPECT_THAT(Symbols, Contains(qName("std::foo" ))); |
| 2047 | } |
| 2048 | |
| 2049 | TEST_F(SymbolCollectorTest, CBuiltins) { |
| 2050 | // In C, printf in stdio.h is a redecl of an implicit builtin. |
| 2051 | const char * = R"( |
| 2052 | extern int printf(const char*, ...); |
| 2053 | )" ; |
| 2054 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" , ExtraArgs: {"-xc" }); |
| 2055 | EXPECT_THAT(Symbols, Contains(qName("printf" ))); |
| 2056 | } |
| 2057 | |
| 2058 | TEST_F(SymbolCollectorTest, InvalidSourceLoc) { |
| 2059 | const char * = R"( |
| 2060 | void operator delete(void*) |
| 2061 | __attribute__((__externally_visible__));)" ; |
| 2062 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
| 2063 | EXPECT_THAT(Symbols, Contains(qName("operator delete" ))); |
| 2064 | } |
| 2065 | |
| 2066 | TEST_F(SymbolCollectorTest, BadUTF8) { |
| 2067 | // Extracted from boost/spirit/home/support/char_encoding/iso8859_1.hpp |
| 2068 | // This looks like UTF-8 and fools clang, but has high-ISO-8859-1 comments. |
| 2069 | const char * = "int PUNCT = 0;\n" |
| 2070 | "/* \xa1 */ int types[] = { /* \xa1 */PUNCT };" ; |
| 2071 | CollectorOpts.RefFilter = RefKind::All; |
| 2072 | CollectorOpts.RefsInHeaders = true; |
| 2073 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
| 2074 | EXPECT_THAT(Symbols, Contains(AllOf(qName("types" ), doc("\xef\xbf\xbd " )))); |
| 2075 | EXPECT_THAT(Symbols, Contains(qName("PUNCT" ))); |
| 2076 | // Reference is stored, although offset within line is not reliable. |
| 2077 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PUNCT" ).ID, _))); |
| 2078 | } |
| 2079 | |
| 2080 | TEST_F(SymbolCollectorTest, MacrosInHeaders) { |
| 2081 | CollectorOpts.CollectMacro = true; |
| 2082 | TestFileName = testPath(File: "test.h" ); |
| 2083 | runSymbolCollector(HeaderCode: "" , MainCode: "#define X" ); |
| 2084 | EXPECT_THAT(Symbols, |
| 2085 | UnorderedElementsAre(AllOf(qName("X" ), forCodeCompletion(true)))); |
| 2086 | } |
| 2087 | |
| 2088 | // Regression test for a crash-bug we used to have. |
| 2089 | TEST_F(SymbolCollectorTest, UndefOfModuleMacro) { |
| 2090 | auto TU = TestTU::withCode(Code: R"cpp(#include "bar.h")cpp" ); |
| 2091 | TU.AdditionalFiles["bar.h" ] = R"cpp( |
| 2092 | #include "foo.h" |
| 2093 | #undef X |
| 2094 | )cpp" ; |
| 2095 | TU.AdditionalFiles["foo.h" ] = "#define X 1" ; |
| 2096 | TU.AdditionalFiles["module.modulemap" ] = R"cpp( |
| 2097 | module foo { |
| 2098 | header "foo.h" |
| 2099 | export * |
| 2100 | } |
| 2101 | )cpp" ; |
| 2102 | TU.ExtraArgs.push_back(x: "-fmodules" ); |
| 2103 | TU.ExtraArgs.push_back(x: "-fmodule-map-file=" + testPath(File: "module.modulemap" )); |
| 2104 | TU.OverlayRealFileSystemForModules = true; |
| 2105 | |
| 2106 | TU.build(); |
| 2107 | // We mostly care about not crashing, but verify that we didn't insert garbage |
| 2108 | // about X too. |
| 2109 | EXPECT_THAT(TU.headerSymbols(), Not(Contains(qName("X" )))); |
| 2110 | } |
| 2111 | |
| 2112 | TEST_F(SymbolCollectorTest, NoCrashOnObjCMethodCStyleParam) { |
| 2113 | auto TU = TestTU::withCode(Code: R"objc( |
| 2114 | @interface Foo |
| 2115 | - (void)fun:(bool)foo, bool bar; |
| 2116 | @end |
| 2117 | )objc" ); |
| 2118 | TU.ExtraArgs.push_back(x: "-xobjective-c++" ); |
| 2119 | |
| 2120 | TU.build(); |
| 2121 | // We mostly care about not crashing. |
| 2122 | EXPECT_THAT(TU.headerSymbols(), |
| 2123 | UnorderedElementsAre(qName("Foo" ), qName("Foo::fun:" ))); |
| 2124 | } |
| 2125 | |
| 2126 | TEST_F(SymbolCollectorTest, Reserved) { |
| 2127 | const char * = R"cpp( |
| 2128 | #pragma once |
| 2129 | void __foo(); |
| 2130 | namespace _X { int secret; } |
| 2131 | )cpp" ; |
| 2132 | |
| 2133 | CollectorOpts.CollectReserved = true; |
| 2134 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
| 2135 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo" ), qName("_X" ), |
| 2136 | qName("_X::secret" ))); |
| 2137 | |
| 2138 | CollectorOpts.CollectReserved = false; |
| 2139 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
| 2140 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo" ), qName("_X" ), |
| 2141 | qName("_X::secret" ))); |
| 2142 | |
| 2143 | // Ugly: for some reason we reuse the test filesystem across tests. |
| 2144 | // You can't overwrite the same filename with new content! |
| 2145 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
| 2146 | runSymbolCollector(HeaderCode: "#pragma GCC system_header\n" + std::string(Header), MainCode: "" ); |
| 2147 | EXPECT_THAT(Symbols, IsEmpty()); |
| 2148 | } |
| 2149 | |
| 2150 | TEST_F(SymbolCollectorTest, ReservedSymbolInIntrinsicHeader) { |
| 2151 | const char * = R"cpp( |
| 2152 | #pragma once |
| 2153 | void __foo(); |
| 2154 | )cpp" ; |
| 2155 | |
| 2156 | TestHeaderName = "xintrin.h" ; |
| 2157 | TestHeaderURI = URI::create(AbsolutePath: testPath(File: TestHeaderName)).toString(); |
| 2158 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
| 2159 | CollectorOpts.FallbackDir = testRoot(); |
| 2160 | runSymbolCollector(HeaderCode: "#pragma GCC system_header\n" + std::string(Header), MainCode: "" ); |
| 2161 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo" ))); |
| 2162 | } |
| 2163 | |
| 2164 | TEST_F(SymbolCollectorTest, Concepts) { |
| 2165 | const char * = R"cpp( |
| 2166 | template <class T> |
| 2167 | concept A = sizeof(T) <= 8; |
| 2168 | )cpp" ; |
| 2169 | runSymbolCollector(HeaderCode: "" , MainCode: Header, ExtraArgs: {"-std=c++20" }); |
| 2170 | EXPECT_THAT(Symbols, |
| 2171 | UnorderedElementsAre(AllOf( |
| 2172 | qName("A" ), hasKind(clang::index::SymbolKind::Concept)))); |
| 2173 | } |
| 2174 | |
| 2175 | TEST_F(SymbolCollectorTest, IncludeHeaderForwardDecls) { |
| 2176 | CollectorOpts.CollectIncludePath = true; |
| 2177 | const std::string = R"cpp(#pragma once |
| 2178 | struct Foo; |
| 2179 | #include "full.h" |
| 2180 | )cpp" ; |
| 2181 | auto FullFile = testPath(File: "full.h" ); |
| 2182 | InMemoryFileSystem->addFile(Path: FullFile, ModificationTime: 0, |
| 2183 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: R"cpp( |
| 2184 | #pragma once |
| 2185 | struct Foo {};)cpp" )); |
| 2186 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , |
| 2187 | /*ExtraArgs=*/{"-I" , testRoot()}); |
| 2188 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
| 2189 | qName("Foo" ), |
| 2190 | includeHeader(URI::create(FullFile).toString())))) |
| 2191 | << *Symbols.begin(); |
| 2192 | } |
| 2193 | } // namespace |
| 2194 | } // namespace clangd |
| 2195 | } // namespace clang |
| 2196 | |