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 @@ -118,6 +118,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/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -895,7 +895,7 @@ // It's safe to pass in the TU, as dumpAST() does not // deserialize the preamble. auto Node = DynTypedNode::create( - *Inputs->AST.getASTContext().getTranslationUnitDecl()); + *Inputs->AST.getASTContext().getTranslationUnitDecl()); return CB(dumpAST(Node, Inputs->AST.getTokens(), Inputs->AST.getASTContext())); } diff --git a/clang-tools-extra/clangd/FS.h b/clang-tools-extra/clangd/FS.h --- a/clang-tools-extra/clangd/FS.h +++ b/clang-tools-extra/clangd/FS.h @@ -74,6 +74,10 @@ /// filtering everything we get over LSP, CDB, etc. Path removeDots(PathRef File); +/// Get a virtual root directory for the filesystem depending on the OS +/// This is useful when a plausible absolute path name is needed. +const llvm::StringLiteral virtualRoot(); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FS.cpp b/clang-tools-extra/clangd/FS.cpp --- a/clang-tools-extra/clangd/FS.cpp +++ b/clang-tools-extra/clangd/FS.cpp @@ -117,5 +117,15 @@ return CanonPath.str().str(); } +const llvm::StringLiteral virtualRoot() { +#ifdef _WIN32 + return "C:\\virtual\\"; +#else + // This path must exist on the current file system. The only place we can + // safely assume to exist is "/". + return "/"; +#endif +} + } // namespace clangd } // namespace clang 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,55 @@ +//===--- 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 +// +//===----------------------------------------------------------------------===// +// Clangd indexer for the C++ standard library. +// +// The index only contains symbols that are part of the translation unit. So +// if your translation unit does not yet #include , you do not get +// auto completion for std::string. However we expect that many users would +// like to use the the standard library anyway, so we could index that by +// default an offer e.g. code completion without requiring #includes. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_STDLIB_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_STDLIB_H + +#include "FS.h" +#include "index/Index.h" +#include "index/MemIndex.h" +#include "support/ThreadsafeFS.h" +#include "clang/AST/Expr.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace clangd { + +// Enumeration of supported Standard Library versions. +// FIXME: support muiltiple languages (e.g. C and C++) and versions (e.g. 11, +// 14, 17) of the standard library. +// FIXME: add heuristic to detect this version somehow (magically). +enum class StandardLibrarVariant { CXX14 = 0 }; + +/// Generate a index of the standard library index for a given variant of +/// the standard library. This index can be included if the translation unit +/// does not (yet) contain any standard library headers. +std::unique_ptr indexStandardLibrary(const ThreadsafeFS &TFS); + +/// Index the given umbrella header file using the standard library from the +/// given file system. +std::unique_ptr +indexUmbrellaHeaders(llvm::StringRef HeaderSources, const ThreadsafeFS &TFS, + const StandardLibrarVariant &LibraryVariant); + +/// generate header containing #includes for all standard library headers +llvm::StringRef +generateUmbrellaHeaders(const StandardLibrarVariant &LibraryVariant); + +} // 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,150 @@ +//===-- 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 "FS.h" +#include "SymbolCollector.h" +#include "dex/Dex.h" +#include "index/Index.h" +#include "index/IndexAction.h" +#include "index/Ref.h" +#include "index/Serialization.h" +#include "support/Logger.h" +#include "support/ThreadsafeFS.h" +#include "support/Trace.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace clang { +namespace clangd { + +std::unique_ptr indexStandardLibrary(const ThreadsafeFS &TFS) { + // FIXME: this should be replace with some heuristic to detect the current + // language variant. + auto Variant = StandardLibrarVariant::CXX14; + return indexUmbrellaHeaders(generateUmbrellaHeaders(Variant), TFS, Variant); +} +// build umbrelle header for the the standard library. +// FIXME: consider different library versions, based on `LibraryVersion` +llvm::StringRef +generateUmbrellaHeaders(const StandardLibrarVariant &LibraryVariant) { + + // bild the string only once and cache the results in `*Once`. + static std::string *Once = [] { + std::vector Headers; + std::string *Result = new std::string; + +#define SYMBOL(Name, NameSpace, Header) Headers.push_back(#Header); +#include "StdSymbolMap.inc" +#undef SYMBOL + + // deduplication of the headers + llvm::sort(Headers.begin(), Headers.end()); + auto Last = std::unique(Headers.begin(), Headers.end()); + for (auto Header = Headers.begin(); Header != Last; ++Header) { + *Result += "#include " + Header->str() + "\n"; + } + return Result; + }(); + return llvm::StringRef(*Once); +} + +std::unique_ptr +indexUmbrellaHeaders(llvm::StringRef HeaderSources, const ThreadsafeFS &TFS, + const StandardLibrarVariant &LibraryVariant) { + trace::Span Tracer("StandardLibraryIndex"); + ParseInputs Inputs; + Inputs.TFS = &TFS; + llvm::SmallString<64> UmbrellaHeaderFilename = virtualRoot(); + llvm::sys::path::append(UmbrellaHeaderFilename, "UmbrellaHeader.h"); + + // TODO: can we get a real compile command from somewhere? + Inputs.CompileCommand.Directory = virtualRoot().str(); + Inputs.CompileCommand.Filename = UmbrellaHeaderFilename.c_str(); + Inputs.CompileCommand.CommandLine.push_back("clang++"); + + // FIXME: add flags more language variants + if (LibraryVariant == StandardLibrarVariant::CXX14) { + Inputs.CompileCommand.CommandLine.push_back("-xc++-header"); + Inputs.CompileCommand.CommandLine.push_back("-std=c++14"); + } else { + elog("Unsupported language variant. Not indexing standard library."); + return nullptr; + } + + Inputs.CompileCommand.CommandLine.push_back(Inputs.CompileCommand.Filename); + + IgnoreDiagnostics IgnoreDiags; + + auto CI = buildCompilerInvocation(Inputs, IgnoreDiags); + if (!CI) { + elog("Standard Library Index: Couldn't build compiler invocation"); + return nullptr; + } + + auto Buffer = llvm::MemoryBuffer::getMemBuffer( + HeaderSources, Inputs.CompileCommand.Filename); + assert(Buffer && Buffer->getBufferSize() > 0); + auto Clang = prepareCompilerInstance( + std::move(CI), /*Preamble=*/nullptr, std::move(Buffer), + TFS.view(/*CWD=*/llvm::None), IgnoreDiags); + if (!Clang) { + elog("tandard Library Index: Couldn't build compiler instance"); + return nullptr; + } + SymbolCollector::Options IndexOpts; + IndexFileIn IndexSlabs; + + // we only care about the symbols, so not storing the other attributes + auto Action = createStaticIndexingAction( + IndexOpts, [&](SymbolSlab S) { IndexSlabs.Symbols = std::move(S); }, + nullptr, nullptr, nullptr); + + const FrontendInputFile &Input = Clang->getFrontendOpts().Inputs.front(); + if (!Action->BeginSourceFile(*Clang, Input)) { + elog("Standard Library Index:BeginSourceFile() failed"); + return nullptr; + } + + if (llvm::Error Err = Action->Execute()) { + elog(toString(std::move(Err)).c_str()); + return nullptr; + } + + Action->EndSourceFile(); + + assert(IndexSlabs.Symbols); + log("Indexed standard library: {0} ({1} symbols)", + Inputs.CompileCommand.Filename, IndexSlabs.Symbols->size()); + + SPAN_ATTACH(Tracer, "symbols", int(IndexSlabs.Symbols->size())); + + bool HadErrors = Clang->hasDiagnostics() && + Clang->getDiagnostics().hasUncompilableErrorOccurred(); + if (HadErrors) { + log("Errors when generating the standard library index, index may be " + "incomplete"); + } + + // FIXME: filter Symbols to only include those in our list, rather than + // all the private implementation cruft. (I expect cruft is the + // majority by weight) + + auto Index = dex::Dex::build(std::move(IndexSlabs.Symbols.getValue()), + RefSlab(), RelationSlab()); + 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 @@ -79,6 +79,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,61 @@ +//===-- 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 "FuzzyMatch.h" +#include "TestFS.h" +#include "TestIndex.h" +#include "index/StdLib.h" +#include "support/Logger.h" +#include "llvm/Support/Error.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +using namespace testing; + +namespace clang { +namespace clangd { + +/// check that the generated header sources contains some usual standard library +/// headers +TEST(StdLibIndexTests, generateUmbrellaHeader) { + MockFS FS; + auto UmbrellaHeader = + generateUmbrellaHeaders(StandardLibrarVariant::CXX14).str(); + + EXPECT_THAT(UmbrellaHeader, HasSubstr("#include ")); + EXPECT_THAT(UmbrellaHeader, HasSubstr("#include ")); + EXPECT_THAT(UmbrellaHeader, HasSubstr("#include ")); +} + +/// build the index and check if it contains the right symbols +TEST(StdLibIndexTests, buildIndex) { + MockFS FS; + // 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 = + indexUmbrellaHeaders(HeaderMock, FS, StandardLibrarVariant::CXX14); + ASSERT_TRUE(Index != nullptr); + + FuzzyFindRequest Req; + Req.AnyScope = true; + EXPECT_THAT(match(*Index, Req), + UnorderedElementsAre(llvm::StringRef("myfunc"), + llvm::StringRef("otherfunc"))); +} + +// TODO: add tests for indexStandardLibrary() +// TODO: test with different library versions + +} // namespace clangd +} // namespace clang