258 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
//===-- CallHierarchyTests.cpp  ---------------------------*- C++ -*-------===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
#include "Annotations.h"
 | 
						|
#include "Compiler.h"
 | 
						|
#include "Matchers.h"
 | 
						|
#include "ParsedAST.h"
 | 
						|
#include "SyncAPI.h"
 | 
						|
#include "TestFS.h"
 | 
						|
#include "TestTU.h"
 | 
						|
#include "TestWorkspace.h"
 | 
						|
#include "XRefs.h"
 | 
						|
#include "index/FileIndex.h"
 | 
						|
#include "index/SymbolCollector.h"
 | 
						|
#include "clang/AST/DeclCXX.h"
 | 
						|
#include "clang/AST/DeclTemplate.h"
 | 
						|
#include "clang/Index/IndexingAction.h"
 | 
						|
#include "llvm/Support/Path.h"
 | 
						|
#include "llvm/Support/ScopedPrinter.h"
 | 
						|
#include "gmock/gmock.h"
 | 
						|
#include "gtest/gtest.h"
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace clangd {
 | 
						|
namespace {
 | 
						|
 | 
						|
using ::testing::AllOf;
 | 
						|
using ::testing::ElementsAre;
 | 
						|
using ::testing::Field;
 | 
						|
using ::testing::IsEmpty;
 | 
						|
using ::testing::Matcher;
 | 
						|
using ::testing::UnorderedElementsAre;
 | 
						|
 | 
						|
// Helpers for matching call hierarchy data structures.
 | 
						|
MATCHER_P(WithName, N, "") { return arg.name == N; }
 | 
						|
MATCHER_P(WithSelectionRange, R, "") { return arg.selectionRange == R; }
 | 
						|
 | 
						|
template <class ItemMatcher>
 | 
						|
::testing::Matcher<CallHierarchyIncomingCall> From(ItemMatcher M) {
 | 
						|
  return Field(&CallHierarchyIncomingCall::from, M);
 | 
						|
}
 | 
						|
template <class... RangeMatchers>
 | 
						|
::testing::Matcher<CallHierarchyIncomingCall> FromRanges(RangeMatchers... M) {
 | 
						|
  return Field(&CallHierarchyIncomingCall::fromRanges,
 | 
						|
               UnorderedElementsAre(M...));
 | 
						|
}
 | 
						|
 | 
						|
TEST(CallHierarchy, IncomingOneFile) {
 | 
						|
  Annotations Source(R"cpp(
 | 
						|
    void call^ee(int);
 | 
						|
    void caller1() {
 | 
						|
      $Callee[[callee]](42);
 | 
						|
    }
 | 
						|
    void caller2() {
 | 
						|
      $Caller1A[[caller1]]();
 | 
						|
      $Caller1B[[caller1]]();
 | 
						|
    }
 | 
						|
    void caller3() {
 | 
						|
      $Caller1C[[caller1]]();
 | 
						|
      $Caller2[[caller2]]();
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
  TestTU TU = TestTU::withCode(Source.code());
 | 
						|
  auto AST = TU.build();
 | 
						|
  auto Index = TU.index();
 | 
						|
 | 
						|
  std::vector<CallHierarchyItem> Items =
 | 
						|
      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
 | 
						|
  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
 | 
						|
  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
 | 
						|
  ASSERT_THAT(IncomingLevel1,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller1")),
 | 
						|
                                FromRanges(Source.range("Callee")))));
 | 
						|
 | 
						|
  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
 | 
						|
  ASSERT_THAT(IncomingLevel2,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller2")),
 | 
						|
                                FromRanges(Source.range("Caller1A"),
 | 
						|
                                           Source.range("Caller1B"))),
 | 
						|
                          AllOf(From(WithName("caller3")),
 | 
						|
                                FromRanges(Source.range("Caller1C")))));
 | 
						|
 | 
						|
  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
 | 
						|
  ASSERT_THAT(IncomingLevel3,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller3")),
 | 
						|
                                FromRanges(Source.range("Caller2")))));
 | 
						|
 | 
						|
  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
 | 
						|
  EXPECT_THAT(IncomingLevel4, IsEmpty());
 | 
						|
}
 | 
						|
 | 
						|
TEST(CallHierarchy, MainFileOnlyRef) {
 | 
						|
  // In addition to testing that we store refs to main-file only symbols,
 | 
						|
  // this tests that anonymous namespaces do not interfere with the
 | 
						|
  // symbol re-identification process in callHierarchyItemToSymbo().
 | 
						|
  Annotations Source(R"cpp(
 | 
						|
    void call^ee(int);
 | 
						|
    namespace {
 | 
						|
      void caller1() {
 | 
						|
        $Callee[[callee]](42);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    void caller2() {
 | 
						|
      $Caller1[[caller1]]();
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
  TestTU TU = TestTU::withCode(Source.code());
 | 
						|
  auto AST = TU.build();
 | 
						|
  auto Index = TU.index();
 | 
						|
 | 
						|
  std::vector<CallHierarchyItem> Items =
 | 
						|
      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
 | 
						|
  ASSERT_THAT(Items, ElementsAre(WithName("callee")));
 | 
						|
  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
 | 
						|
  ASSERT_THAT(IncomingLevel1,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller1")),
 | 
						|
                                FromRanges(Source.range("Callee")))));
 | 
						|
 | 
						|
  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
 | 
						|
  EXPECT_THAT(IncomingLevel2,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller2")),
 | 
						|
                                FromRanges(Source.range("Caller1")))));
 | 
						|
}
 | 
						|
 | 
						|
TEST(CallHierarchy, IncomingQualified) {
 | 
						|
  Annotations Source(R"cpp(
 | 
						|
    namespace ns {
 | 
						|
    struct Waldo {
 | 
						|
      void find();
 | 
						|
    };
 | 
						|
    void Waldo::find() {}
 | 
						|
    void caller1(Waldo &W) {
 | 
						|
      W.$Caller1[[f^ind]]();
 | 
						|
    }
 | 
						|
    void caller2(Waldo &W) {
 | 
						|
      W.$Caller2[[find]]();
 | 
						|
    }
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
  TestTU TU = TestTU::withCode(Source.code());
 | 
						|
  auto AST = TU.build();
 | 
						|
  auto Index = TU.index();
 | 
						|
 | 
						|
  std::vector<CallHierarchyItem> Items =
 | 
						|
      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
 | 
						|
  ASSERT_THAT(Items, ElementsAre(WithName("Waldo::find")));
 | 
						|
  auto Incoming = incomingCalls(Items[0], Index.get());
 | 
						|
  EXPECT_THAT(Incoming,
 | 
						|
              ElementsAre(AllOf(From(WithName("caller1")),
 | 
						|
                                FromRanges(Source.range("Caller1"))),
 | 
						|
                          AllOf(From(WithName("caller2")),
 | 
						|
                                FromRanges(Source.range("Caller2")))));
 | 
						|
}
 | 
						|
 | 
						|
TEST(CallHierarchy, IncomingMultiFile) {
 | 
						|
  // The test uses a .hh suffix for header files to get clang
 | 
						|
  // to parse them in C++ mode. .h files are parsed in C mode
 | 
						|
  // by default, which causes problems because e.g. symbol
 | 
						|
  // USRs are different in C mode (do not include function signatures).
 | 
						|
 | 
						|
  Annotations CalleeH(R"cpp(
 | 
						|
    void calle^e(int);
 | 
						|
  )cpp");
 | 
						|
  Annotations CalleeC(R"cpp(
 | 
						|
    #include "callee.hh"
 | 
						|
    void calle^e(int) {}
 | 
						|
  )cpp");
 | 
						|
  Annotations Caller1H(R"cpp(
 | 
						|
    void caller1();
 | 
						|
  )cpp");
 | 
						|
  Annotations Caller1C(R"cpp(
 | 
						|
    #include "callee.hh"
 | 
						|
    #include "caller1.hh"
 | 
						|
    void caller1() {
 | 
						|
      [[calle^e]](42);
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
  Annotations Caller2H(R"cpp(
 | 
						|
    void caller2();
 | 
						|
  )cpp");
 | 
						|
  Annotations Caller2C(R"cpp(
 | 
						|
    #include "caller1.hh"
 | 
						|
    #include "caller2.hh"
 | 
						|
    void caller2() {
 | 
						|
      $A[[caller1]]();
 | 
						|
      $B[[caller1]]();
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
  Annotations Caller3C(R"cpp(
 | 
						|
    #include "caller1.hh"
 | 
						|
    #include "caller2.hh"
 | 
						|
    void caller3() {
 | 
						|
      $Caller1[[caller1]]();
 | 
						|
      $Caller2[[caller2]]();
 | 
						|
    }
 | 
						|
  )cpp");
 | 
						|
 | 
						|
  TestWorkspace Workspace;
 | 
						|
  Workspace.addSource("callee.hh", CalleeH.code());
 | 
						|
  Workspace.addSource("caller1.hh", Caller1H.code());
 | 
						|
  Workspace.addSource("caller2.hh", Caller2H.code());
 | 
						|
  Workspace.addMainFile("callee.cc", CalleeC.code());
 | 
						|
  Workspace.addMainFile("caller1.cc", Caller1C.code());
 | 
						|
  Workspace.addMainFile("caller2.cc", Caller2C.code());
 | 
						|
  Workspace.addMainFile("caller3.cc", Caller3C.code());
 | 
						|
 | 
						|
  auto Index = Workspace.index();
 | 
						|
 | 
						|
  auto CheckCallHierarchy = [&](ParsedAST &AST, Position Pos, PathRef TUPath) {
 | 
						|
    std::vector<CallHierarchyItem> Items =
 | 
						|
        prepareCallHierarchy(AST, Pos, TUPath);
 | 
						|
    ASSERT_THAT(Items, ElementsAre(WithName("callee")));
 | 
						|
    auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
 | 
						|
    ASSERT_THAT(IncomingLevel1,
 | 
						|
                ElementsAre(AllOf(From(WithName("caller1")),
 | 
						|
                                  FromRanges(Caller1C.range()))));
 | 
						|
 | 
						|
    auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
 | 
						|
    ASSERT_THAT(
 | 
						|
        IncomingLevel2,
 | 
						|
        ElementsAre(AllOf(From(WithName("caller2")),
 | 
						|
                          FromRanges(Caller2C.range("A"), Caller2C.range("B"))),
 | 
						|
                    AllOf(From(WithName("caller3")),
 | 
						|
                          FromRanges(Caller3C.range("Caller1")))));
 | 
						|
 | 
						|
    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
 | 
						|
    ASSERT_THAT(IncomingLevel3,
 | 
						|
                ElementsAre(AllOf(From(WithName("caller3")),
 | 
						|
                                  FromRanges(Caller3C.range("Caller2")))));
 | 
						|
 | 
						|
    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
 | 
						|
    EXPECT_THAT(IncomingLevel4, IsEmpty());
 | 
						|
  };
 | 
						|
 | 
						|
  // Check that invoking from a call site works.
 | 
						|
  auto AST = Workspace.openFile("caller1.cc");
 | 
						|
  ASSERT_TRUE(bool(AST));
 | 
						|
  CheckCallHierarchy(*AST, Caller1C.point(), testPath("caller1.cc"));
 | 
						|
 | 
						|
  // Check that invoking from the declaration site works.
 | 
						|
  AST = Workspace.openFile("callee.hh");
 | 
						|
  ASSERT_TRUE(bool(AST));
 | 
						|
  CheckCallHierarchy(*AST, CalleeH.point(), testPath("callee.hh"));
 | 
						|
 | 
						|
  // Check that invoking from the definition site works.
 | 
						|
  AST = Workspace.openFile("callee.cc");
 | 
						|
  ASSERT_TRUE(bool(AST));
 | 
						|
  CheckCallHierarchy(*AST, CalleeC.point(), testPath("callee.cc"));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
} // namespace clangd
 | 
						|
} // namespace clang
 |