diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -110,6 +110,7 @@ index/Ref.cpp index/Relation.cpp index/Serialization.cpp + index/StdLib.cpp index/Symbol.cpp index/SymbolCollector.cpp index/SymbolID.cpp diff --git a/clang-tools-extra/clangd/index/StdLib.h b/clang-tools-extra/clangd/index/StdLib.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/index/StdLib.h @@ -0,0 +1,42 @@ +//===--- StdLib.h ------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_STDLIB_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_STDLIB_H + +#include "index/Index.h" +#include "index/MemIndex.h" +#include "support/ThreadsafeFS.h" +#include + +namespace clang { +namespace clangd { + +class StandardLibraryIndex { + // TODO: do we really need a class? Or would plain functions be good enough? + // TODO: do we really need to make verything public just for unittesting? +public: + /* virtual file name for indexing */ + static const std::string StdLibHeaderFileName; + + /* generate the index */ + Expected> + indexHeaders(std::string HeaderSources); + + /* generate header containing #includes for all standard library headers */ + std::string generateIncludeHeader(); + + /* build a virtual filesystem with the file to be indexed */ + llvm::IntrusiveRefCntPtr + buildFilesystem(std::string HeaderSources); +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_STDLIB_H \ No newline at end of file diff --git a/clang-tools-extra/clangd/index/StdLib.cpp b/clang-tools-extra/clangd/index/StdLib.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/index/StdLib.cpp @@ -0,0 +1,152 @@ +//===-- StdLib.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 "StdLib.h" +#include +#include +#include +#include + +#include "Compiler.h" +#include "Headers.h" +#include "SymbolCollector.h" +#include "index/IndexAction.h" +#include "index/Serialization.h" +#include "support/Logger.h" +#include "support/ThreadsafeFS.h" +#include "support/Trace.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/VirtualFileSystem.h" + +namespace clang { +namespace clangd { + +const std::string + StandardLibraryIndex::StdLibHeaderFileName("/stdlibheaders.cpp"); + +std::string StandardLibraryIndex::generateIncludeHeader() { + std::string Includes; + // gather all the known STL headers in a set for depulication + std::set Headers; + +#define SYMBOL(Name, NameSpace, Header) Headers.insert(#Header); +#include "StdSymbolMap.inc" +#undef SYMBOL + + for (auto Header : Headers) { + Includes += "#include " + Header + "\n"; + } + return Includes; +} +namespace { +/* Wrapper around llvm::vfs::InMemoryFileSystem */ +class MemTFS : public ThreadsafeFS { + +private: + const llvm::IntrusiveRefCntPtr FS; + +public: + MemTFS(llvm::IntrusiveRefCntPtr const FS) + : FS(FS){}; + + llvm::IntrusiveRefCntPtr viewImpl() const override { + return FS; + } +}; +} // namespace + +llvm::IntrusiveRefCntPtr +StandardLibraryIndex::buildFilesystem(std::string HeaderSources) { + auto FS = llvm::makeIntrusiveRefCnt(); + auto Now = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + FS->addFile(StandardLibraryIndex::StdLibHeaderFileName, Now, + llvm::MemoryBuffer::getMemBufferCopy(HeaderSources, + StdLibHeaderFileName)); + return FS; +} + +Expected> +StandardLibraryIndex::indexHeaders(std::string HeaderSources) { + ParseInputs Inputs; + auto FS = buildFilesystem(HeaderSources); + auto TFS = MemTFS(FS); + Inputs.TFS = &TFS; + // TODO: can we get a real compile command from somewhere? + Inputs.CompileCommand = tooling::CompileCommand(); + Inputs.CompileCommand.Directory = "/"; + Inputs.CompileCommand.Filename = StdLibHeaderFileName; + Inputs.CompileCommand.Output = StdLibHeaderFileName + ".o"; + Inputs.CompileCommand.CommandLine.push_back("clang++"); + Inputs.CompileCommand.CommandLine.push_back(Inputs.CompileCommand.Filename); + Inputs.CompileCommand.CommandLine.push_back("-o"); + Inputs.CompileCommand.CommandLine.push_back(Inputs.CompileCommand.Output); + + IgnoreDiagnostics IgnoreDiags; + + auto CI = buildCompilerInvocation(Inputs, IgnoreDiags); + if (!CI) + return error("Couldn't build compiler invocation"); + + auto Buffer = FS->getBufferForFile(StdLibHeaderFileName); + if (!Buffer) + return error("Could not read file from InMemoryFileSystem"); + auto Clang = prepareCompilerInstance(std::move(CI), /*Preamble=*/nullptr, + std::move(*Buffer), FS, IgnoreDiags); + if (!Clang) + return error("Couldn't build compiler instance"); + + SymbolCollector::Options IndexOpts; + IndexFileIn IndexSlabs; + auto Action = createStaticIndexingAction( + IndexOpts, [&](SymbolSlab S) { IndexSlabs.Symbols = std::move(S); }, + [&](RefSlab R) { IndexSlabs.Refs = std::move(R); }, + [&](RelationSlab R) { IndexSlabs.Relations = std::move(R); }, + [&](IncludeGraph IG) { IndexSlabs.Sources = std::move(IG); }); + + // We're going to run clang here, and it could potentially crash. + // We could use CrashRecoveryContext to try to make indexing crashes nonfatal, + // but the leaky "recovery" is pretty scary too in a long-running process. + // If crashes are a real problem, maybe we should fork a child process. + + const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front(); + if (!Action->BeginSourceFile(*Clang, Input)) + return error("BeginSourceFile() failed"); + if (llvm::Error Err = Action->Execute()) + return std::move(Err); + + Action->EndSourceFile(); + + IndexSlabs.Cmd = Inputs.CompileCommand; + assert(IndexSlabs.Symbols && IndexSlabs.Refs && IndexSlabs.Sources && + "Symbols, Refs and Sources must be set."); + + log("Indexed {0} ({1} symbols, {2} refs, {3} files)", + Inputs.CompileCommand.Filename, IndexSlabs.Symbols->size(), + IndexSlabs.Refs->numRefs(), IndexSlabs.Sources->size()); + + trace::Span Tracer("StandardLibraryIndex"); + SPAN_ATTACH(Tracer, "symbols", int(IndexSlabs.Symbols->size())); + SPAN_ATTACH(Tracer, "refs", int(IndexSlabs.Refs->numRefs())); + SPAN_ATTACH(Tracer, "sources", int(IndexSlabs.Sources->size())); + + bool HadErrors = Clang->hasDiagnostics() && + Clang->getDiagnostics().hasUncompilableErrorOccurred(); + if (HadErrors) { + log("Failed to compile standard librarx index, index may be incomplete"); + } + + auto Index = MemIndex::build(std::move(IndexSlabs.Symbols.getValue()), + std::move(IndexSlabs.Refs.getValue()), + std::move(IndexSlabs.Relations.getValue())); + + return Index; +} +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -88,6 +88,7 @@ SemanticSelectionTests.cpp SerializationTests.cpp SourceCodeTests.cpp + StdLibIndexTests.cpp SymbolCollectorTests.cpp SymbolInfoTests.cpp SyncAPI.cpp diff --git a/clang-tools-extra/clangd/unittests/StdLibIndexTests.cpp b/clang-tools-extra/clangd/unittests/StdLibIndexTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/StdLibIndexTests.cpp @@ -0,0 +1,63 @@ +//===-- StdLibIndexTests.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 "index/StdLib.h" +#include "support/Logger.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace testing; + +namespace clang { +namespace clangd { + +/// check that the generated header sources contains some usual standard library +/// headers +TEST(StdLibIndexTests, generateIncludeHeader) { + auto Sli = StandardLibraryIndex(); + auto Includes = Sli.generateIncludeHeader(); + + EXPECT_THAT(Includes, HasSubstr("#include ")); + EXPECT_THAT(Includes, HasSubstr("#include ")); + EXPECT_THAT(Includes, HasSubstr("#include ")); +} + +/// test building the virtual file system +TEST(StdLibIndexTests, buildFilesystem) { + auto Sli = StandardLibraryIndex(); + auto FS = Sli.buildFilesystem(Sli.generateIncludeHeader()); + auto Buffer = + FS->getBufferForFile(StandardLibraryIndex::StdLibHeaderFileName); + EXPECT_THAT((bool)Buffer.getError(), IsFalse()); + EXPECT_THAT(Buffer.get()->getBuffer(), HasSubstr("#include ")); +} + +/// build the index and check if it contains the right symbols +TEST(StdLibIndexTests, buildIndex) { + auto Sli = StandardLibraryIndex(); + // TODO: maybe find a way to use a local libcxx for testing if that is + // available on the machine + std::string HeaderMock = R"CPP( + int myfunc(int a); + bool otherfunc(int a, int b); + )CPP"; + auto Index = Sli.indexHeaders(HeaderMock); + EXPECT_THAT((bool)Index, IsTrue()) << llvm::toString(Index.takeError()); + FuzzyFindRequest Req; + Req.Query = "myfunc"; + Req.AnyScope = true; + Req.Limit = 100; + std::vector Matches; + Index.get()->fuzzyFind( + Req, [&](const Symbol &Sym) { Matches.push_back(Sym.Name.str()); }); + EXPECT_THAT(Matches.size(), Eq((size_t)1)); + EXPECT_THAT(Matches, Contains("myfunc")); +} + +} // namespace clangd +} // namespace clang