mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 11:26:07 +00:00

Summary: This would save us 8 bytes per ref, and buy us ~40MB in total for llvm index (from ~300MB to ~260 MB). The char pointer must be null-terminated, and llvm::StringSaver guarantees it. Reviewers: sammccall Subscribers: ilya-biryukov, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits Differential Revision: https://reviews.llvm.org/D53427 llvm-svn: 346852
385 lines
12 KiB
C++
385 lines
12 KiB
C++
//===-- FileIndexTests.cpp ---------------------------*- C++ -*-----------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "AST.h"
|
|
#include "Annotations.h"
|
|
#include "ClangdUnit.h"
|
|
#include "SyncAPI.h"
|
|
#include "TestFS.h"
|
|
#include "TestTU.h"
|
|
#include "index/FileIndex.h"
|
|
#include "index/Index.h"
|
|
#include "clang/Frontend/CompilerInvocation.h"
|
|
#include "clang/Frontend/PCHContainerOperations.h"
|
|
#include "clang/Frontend/Utils.h"
|
|
#include "clang/Index/IndexSymbol.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
#include "clang/Tooling/CompilationDatabase.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using testing::_;
|
|
using testing::AllOf;
|
|
using testing::Contains;
|
|
using testing::ElementsAre;
|
|
using testing::IsEmpty;
|
|
using testing::Pair;
|
|
using testing::UnorderedElementsAre;
|
|
using namespace llvm;
|
|
|
|
MATCHER_P(RefRange, Range, "") {
|
|
return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(),
|
|
arg.Location.End.line(), arg.Location.End.column()) ==
|
|
std::make_tuple(Range.start.line, Range.start.character,
|
|
Range.end.line, Range.end.character);
|
|
}
|
|
MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; }
|
|
MATCHER_P(DeclURI, U, "") {
|
|
return StringRef(arg.CanonicalDeclaration.FileURI) == U;
|
|
}
|
|
MATCHER_P(DefURI, U, "") { return StringRef(arg.Definition.FileURI) == U; }
|
|
MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; }
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
testing::Matcher<const RefSlab &>
|
|
RefsAre(std::vector<testing::Matcher<Ref>> Matchers) {
|
|
return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers)));
|
|
}
|
|
|
|
Symbol symbol(StringRef ID) {
|
|
Symbol Sym;
|
|
Sym.ID = SymbolID(ID);
|
|
Sym.Name = ID;
|
|
return Sym;
|
|
}
|
|
|
|
std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
|
|
SymbolSlab::Builder Slab;
|
|
for (int i = Begin; i <= End; i++)
|
|
Slab.insert(symbol(std::to_string(i)));
|
|
return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
|
|
}
|
|
|
|
std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) {
|
|
RefSlab::Builder Slab;
|
|
Ref R;
|
|
R.Location.FileURI = Path;
|
|
R.Kind = RefKind::Reference;
|
|
Slab.insert(ID, R);
|
|
return llvm::make_unique<RefSlab>(std::move(Slab).build());
|
|
}
|
|
|
|
TEST(FileSymbolsTest, UpdateAndGet) {
|
|
FileSymbols FS;
|
|
EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty());
|
|
|
|
FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"));
|
|
EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""),
|
|
UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
|
|
EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")),
|
|
RefsAre({FileURI("f1.cc")}));
|
|
}
|
|
|
|
TEST(FileSymbolsTest, Overlap) {
|
|
FileSymbols FS;
|
|
FS.update("f1", numSlab(1, 3), nullptr);
|
|
FS.update("f2", numSlab(3, 5), nullptr);
|
|
for (auto Type : {IndexType::Light, IndexType::Heavy})
|
|
EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""),
|
|
UnorderedElementsAre(QName("1"), QName("2"), QName("3"),
|
|
QName("4"), QName("5")));
|
|
}
|
|
|
|
TEST(FileSymbolsTest, MergeOverlap) {
|
|
FileSymbols FS;
|
|
auto OneSymboSlab = [](Symbol Sym) {
|
|
SymbolSlab::Builder S;
|
|
S.insert(Sym);
|
|
return make_unique<SymbolSlab>(std::move(S).build());
|
|
};
|
|
auto X1 = symbol("x");
|
|
X1.CanonicalDeclaration.FileURI = "file:///x1";
|
|
auto X2 = symbol("x");
|
|
X2.Definition.FileURI = "file:///x2";
|
|
|
|
FS.update("f1", OneSymboSlab(X1), nullptr);
|
|
FS.update("f2", OneSymboSlab(X2), nullptr);
|
|
for (auto Type : {IndexType::Light, IndexType::Heavy})
|
|
EXPECT_THAT(
|
|
runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"),
|
|
UnorderedElementsAre(
|
|
AllOf(QName("x"), DeclURI("file:///x1"), DefURI("file:///x2"))));
|
|
}
|
|
|
|
TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
|
|
FileSymbols FS;
|
|
|
|
SymbolID ID("1");
|
|
FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"));
|
|
|
|
auto Symbols = FS.buildIndex(IndexType::Light);
|
|
EXPECT_THAT(runFuzzyFind(*Symbols, ""),
|
|
UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
|
|
EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
|
|
|
|
FS.update("f1", nullptr, nullptr);
|
|
auto Empty = FS.buildIndex(IndexType::Light);
|
|
EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty());
|
|
EXPECT_THAT(getRefs(*Empty, ID), ElementsAre());
|
|
|
|
EXPECT_THAT(runFuzzyFind(*Symbols, ""),
|
|
UnorderedElementsAre(QName("1"), QName("2"), QName("3")));
|
|
EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")}));
|
|
}
|
|
|
|
// Adds Basename.cpp, which includes Basename.h, which contains Code.
|
|
void update(FileIndex &M, StringRef Basename, StringRef Code) {
|
|
TestTU File;
|
|
File.Filename = (Basename + ".cpp").str();
|
|
File.HeaderFilename = (Basename + ".h").str();
|
|
File.HeaderCode = Code;
|
|
auto AST = File.build();
|
|
M.updatePreamble(File.Filename, AST.getASTContext(),
|
|
AST.getPreprocessorPtr());
|
|
}
|
|
|
|
TEST(FileIndexTest, CustomizedURIScheme) {
|
|
FileIndex M({"unittest"});
|
|
update(M, "f", "class string {};");
|
|
|
|
EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(DeclURI("unittest:///f.h")));
|
|
}
|
|
|
|
TEST(FileIndexTest, IndexAST) {
|
|
FileIndex M;
|
|
update(M, "f1", "namespace ns { void f() {} class X {}; }");
|
|
|
|
FuzzyFindRequest Req;
|
|
Req.Query = "";
|
|
Req.Scopes = {"ns::"};
|
|
EXPECT_THAT(runFuzzyFind(M, Req),
|
|
UnorderedElementsAre(QName("ns::f"), QName("ns::X")));
|
|
}
|
|
|
|
TEST(FileIndexTest, NoLocal) {
|
|
FileIndex M;
|
|
update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }");
|
|
|
|
EXPECT_THAT(
|
|
runFuzzyFind(M, ""),
|
|
UnorderedElementsAre(QName("ns"), QName("ns::f"), QName("ns::X")));
|
|
}
|
|
|
|
TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
|
|
FileIndex M;
|
|
update(M, "f1", "namespace ns { void f() {} class X {}; }");
|
|
update(M, "f2", "namespace ns { void ff() {} class X {}; }");
|
|
|
|
FuzzyFindRequest Req;
|
|
Req.Scopes = {"ns::"};
|
|
EXPECT_THAT(
|
|
runFuzzyFind(M, Req),
|
|
UnorderedElementsAre(QName("ns::f"), QName("ns::X"), QName("ns::ff")));
|
|
}
|
|
|
|
TEST(FileIndexTest, ClassMembers) {
|
|
FileIndex M;
|
|
update(M, "f1", "class X { static int m1; int m2; static void f(); };");
|
|
|
|
EXPECT_THAT(runFuzzyFind(M, ""),
|
|
UnorderedElementsAre(QName("X"), QName("X::m1"), QName("X::m2"),
|
|
QName("X::f")));
|
|
}
|
|
|
|
TEST(FileIndexTest, NoIncludeCollected) {
|
|
FileIndex M;
|
|
update(M, "f", "class string {};");
|
|
|
|
auto Symbols = runFuzzyFind(M, "");
|
|
EXPECT_THAT(Symbols, ElementsAre(_));
|
|
EXPECT_THAT(Symbols.begin()->IncludeHeaders, IsEmpty());
|
|
}
|
|
|
|
TEST(FileIndexTest, TemplateParamsInLabel) {
|
|
auto Source = R"cpp(
|
|
template <class Ty>
|
|
class vector {
|
|
};
|
|
|
|
template <class Ty, class Arg>
|
|
vector<Ty> make_vector(Arg A) {}
|
|
)cpp";
|
|
|
|
FileIndex M;
|
|
update(M, "f", Source);
|
|
|
|
auto Symbols = runFuzzyFind(M, "");
|
|
EXPECT_THAT(Symbols,
|
|
UnorderedElementsAre(QName("vector"), QName("make_vector")));
|
|
auto It = Symbols.begin();
|
|
Symbol Vector = *It++;
|
|
Symbol MakeVector = *It++;
|
|
if (MakeVector.Name == "vector")
|
|
std::swap(MakeVector, Vector);
|
|
|
|
EXPECT_EQ(Vector.Signature, "<class Ty>");
|
|
EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>");
|
|
|
|
EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)");
|
|
EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
|
|
}
|
|
|
|
TEST(FileIndexTest, RebuildWithPreamble) {
|
|
auto FooCpp = testPath("foo.cpp");
|
|
auto FooH = testPath("foo.h");
|
|
// Preparse ParseInputs.
|
|
ParseInputs PI;
|
|
PI.CompileCommand.Directory = testRoot();
|
|
PI.CompileCommand.Filename = FooCpp;
|
|
PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp};
|
|
|
|
StringMap<std::string> Files;
|
|
Files[FooCpp] = "";
|
|
Files[FooH] = R"cpp(
|
|
namespace ns_in_header {
|
|
int func_in_header();
|
|
}
|
|
)cpp";
|
|
PI.FS = buildTestFS(std::move(Files));
|
|
|
|
PI.Contents = R"cpp(
|
|
#include "foo.h"
|
|
namespace ns_in_source {
|
|
int func_in_source();
|
|
}
|
|
)cpp";
|
|
|
|
// Rebuild the file.
|
|
auto CI = buildCompilerInvocation(PI);
|
|
|
|
FileIndex Index;
|
|
bool IndexUpdated = false;
|
|
buildPreamble(
|
|
FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
|
|
std::make_shared<PCHContainerOperations>(), /*StoreInMemory=*/true,
|
|
[&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP) {
|
|
EXPECT_FALSE(IndexUpdated) << "Expected only a single index update";
|
|
IndexUpdated = true;
|
|
Index.updatePreamble(FooCpp, Ctx, std::move(PP));
|
|
});
|
|
ASSERT_TRUE(IndexUpdated);
|
|
|
|
// Check the index contains symbols from the preamble, but not from the main
|
|
// file.
|
|
FuzzyFindRequest Req;
|
|
Req.Query = "";
|
|
Req.Scopes = {"", "ns_in_header::"};
|
|
|
|
EXPECT_THAT(runFuzzyFind(Index, Req),
|
|
UnorderedElementsAre(QName("ns_in_header"),
|
|
QName("ns_in_header::func_in_header")));
|
|
}
|
|
|
|
TEST(FileIndexTest, Refs) {
|
|
const char *HeaderCode = "class Foo {};";
|
|
Annotations MainCode(R"cpp(
|
|
void f() {
|
|
$foo[[Foo]] foo;
|
|
}
|
|
)cpp");
|
|
|
|
auto Foo =
|
|
findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo");
|
|
|
|
RefsRequest Request;
|
|
Request.IDs = {Foo.ID};
|
|
|
|
FileIndex Index(/*URISchemes*/ {"unittest"});
|
|
// Add test.cc
|
|
TestTU Test;
|
|
Test.HeaderCode = HeaderCode;
|
|
Test.Code = MainCode.code();
|
|
Test.Filename = "test.cc";
|
|
auto AST = Test.build();
|
|
Index.updateMain(Test.Filename, AST);
|
|
// Add test2.cc
|
|
TestTU Test2;
|
|
Test2.HeaderCode = HeaderCode;
|
|
Test2.Code = MainCode.code();
|
|
Test2.Filename = "test2.cc";
|
|
AST = Test2.build();
|
|
Index.updateMain(Test2.Filename, AST);
|
|
|
|
EXPECT_THAT(getRefs(Index, Foo.ID),
|
|
RefsAre({AllOf(RefRange(MainCode.range("foo")),
|
|
FileURI("unittest:///test.cc")),
|
|
AllOf(RefRange(MainCode.range("foo")),
|
|
FileURI("unittest:///test2.cc"))}));
|
|
}
|
|
|
|
TEST(FileIndexTest, CollectMacros) {
|
|
FileIndex M;
|
|
update(M, "f", "#define CLANGD 1");
|
|
EXPECT_THAT(runFuzzyFind(M, ""), Contains(QName("CLANGD")));
|
|
}
|
|
|
|
TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
|
|
const std::string Header = R"cpp(
|
|
class Foo {};
|
|
)cpp";
|
|
Annotations Main(R"cpp(
|
|
#include "foo.h"
|
|
void f() {
|
|
[[Foo]] foo;
|
|
}
|
|
)cpp");
|
|
auto MainFile = testPath("foo.cpp");
|
|
auto HeaderFile = testPath("foo.h");
|
|
std::vector<const char*> Cmd = {"clang", "-xc++", MainFile.c_str()};
|
|
// Preparse ParseInputs.
|
|
ParseInputs PI;
|
|
PI.CompileCommand.Directory = testRoot();
|
|
PI.CompileCommand.Filename = MainFile;
|
|
PI.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()};
|
|
PI.Contents = Main.code();
|
|
PI.FS = buildTestFS({{MainFile, Main.code()}, {HeaderFile, Header}});
|
|
|
|
// Prepare preamble.
|
|
auto CI = buildCompilerInvocation(PI);
|
|
auto PreambleData = buildPreamble(
|
|
MainFile,
|
|
*buildCompilerInvocation(PI), /*OldPreamble=*/nullptr,
|
|
tooling::CompileCommand(), PI,
|
|
std::make_shared<PCHContainerOperations>(), /*StoreInMemory=*/true,
|
|
[&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP) {});
|
|
// Build AST for main file with preamble.
|
|
auto AST =
|
|
ParsedAST::build(createInvocationFromCommandLine(Cmd), PreambleData,
|
|
MemoryBuffer::getMemBufferCopy(Main.code()),
|
|
std::make_shared<PCHContainerOperations>(), PI.FS);
|
|
ASSERT_TRUE(AST);
|
|
FileIndex Index;
|
|
Index.updateMain(MainFile, *AST);
|
|
|
|
auto Foo =
|
|
findSymbol(TestTU::withHeaderCode(Header).headerSymbols(), "Foo");
|
|
RefsRequest Request;
|
|
Request.IDs.insert(Foo.ID);
|
|
|
|
// Expect to see references in main file, references in headers are excluded
|
|
// because we only index main AST.
|
|
EXPECT_THAT(getRefs(Index, Foo.ID), RefsAre({RefRange(Main.range())}));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|