Index: clang-tools-extra/clangd/index/YAMLSerialization.cpp =================================================================== --- clang-tools-extra/clangd/index/YAMLSerialization.cpp +++ clang-tools-extra/clangd/index/YAMLSerialization.cpp @@ -41,6 +41,8 @@ llvm::Optional Symbol; llvm::Optional Refs; llvm::Optional Relation; + llvm::Optional Source; + llvm::Optional Cmd; }; // A class helps YAML to serialize the 32-bit encoded position (Line&Column), // as YAMLIO can't directly map bitfields. @@ -49,10 +51,16 @@ uint32_t Column; }; +// avoid ODR violation of specialization for non-owned CompileCommand +struct CompileCommandYAML : clang::tooling::CompileCommand {}; + } // namespace namespace llvm { namespace yaml { +using clang::clangd::FileDigest; +using clang::clangd::IncludeGraph; +using clang::clangd::IncludeGraphNode; using clang::clangd::Ref; using clang::clangd::RefKind; using clang::clangd::Relation; @@ -65,6 +73,7 @@ using clang::index::SymbolKind; using clang::index::SymbolLanguage; using clang::index::SymbolRole; +using clang::tooling::CompileCommand; // Helper to (de)serialize the SymbolID. We serialize it as a hex string. struct NormalizedSymbolID { @@ -308,6 +317,59 @@ } }; +struct NormalizedSourceFlag { + NormalizedSourceFlag(IO &) {} + NormalizedSourceFlag(IO &, IncludeGraphNode::SourceFlag O) { + Flag = static_cast(O); + } + + IncludeGraphNode::SourceFlag denormalize(IO &) { + return static_cast(Flag); + } + + uint8_t Flag = 0; +}; + +struct NormalizedFileDigest { + NormalizedFileDigest(IO &) {} + NormalizedFileDigest(IO &, const FileDigest &Digest) { + HexString = llvm::toHex(Digest); + } + + FileDigest denormalize(IO &I) { + FileDigest Digest; + if (HexString.size() == Digest.size() * 2 && + llvm::all_of(HexString, llvm::isHexDigit)) { + memcpy(Digest.data(), llvm::fromHex(HexString).data(), Digest.size()); + } else { + I.setError(std::string("Bad hex file digest: ") + HexString); + } + return Digest; + } + + std::string HexString; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, IncludeGraphNode &Node) { + IO.mapRequired("URI", Node.URI); + MappingNormalization + NSourceFlag(IO, Node.Flags); + IO.mapRequired("Flags", NSourceFlag->Flag); + MappingNormalization NDigest(IO, + Node.Digest); + IO.mapRequired("Digest", NDigest->HexString); + IO.mapRequired("DirectIncludes", Node.DirectIncludes); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, CompileCommandYAML &Cmd) { + IO.mapRequired("Directory", Cmd.Directory); + IO.mapRequired("CommandLine", Cmd.CommandLine); + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, VariantEntry &Variant) { if (IO.mapTag("!Symbol", Variant.Symbol.hasValue())) { @@ -322,6 +384,15 @@ if (!IO.outputting()) Variant.Relation.emplace(); MappingTraits::mapping(IO, *Variant.Relation); + } else if (IO.mapTag("!Source", Variant.Source.hasValue())) { + if (!IO.outputting()) + Variant.Source.emplace(); + MappingTraits::mapping(IO, *Variant.Source); + } else if (IO.mapTag("!Cmd", Variant.Cmd.hasValue())) { + if (!IO.outputting()) + Variant.Cmd.emplace(); + MappingTraits::mapping( + IO, static_cast(*Variant.Cmd)); } } }; @@ -351,6 +422,18 @@ Entry.Relation = R; Yout << Entry; } + if (O.Sources) { + for (const auto &Source : *O.Sources) { + VariantEntry Entry; + Entry.Source = Source.getValue(); + Yout << Entry; + } + } + if (O.Cmd) { + VariantEntry Entry; + Entry.Cmd = *O.Cmd; + Yout << Entry; + } } llvm::Expected readYAML(llvm::StringRef Data) { @@ -361,6 +444,8 @@ Arena; // store the underlying data of Position::FileURI. llvm::UniqueStringSaver Strings(Arena); llvm::yaml::Input Yin(Data, &Strings); + IncludeGraph Sources; + llvm::Optional Cmd; while (Yin.setCurrentDocument()) { llvm::yaml::EmptyContext Ctx; VariantEntry Variant; @@ -375,6 +460,17 @@ Refs.insert(Variant.Refs->first, Ref); if (Variant.Relation) Relations.insert(*Variant.Relation); + if (Variant.Source) { + auto &IGN = Variant.Source.getValue(); + auto Entry = Sources.try_emplace(IGN.URI).first; + Entry->getValue() = std::move(IGN); + // Fixup refs to refer to map keys which will live on + Entry->getValue().URI = Entry->getKey(); + for (auto &Include : Entry->getValue().DirectIncludes) + Include = Sources.try_emplace(Include).first->getKey(); + } + if (Variant.Cmd) + Cmd = *Variant.Cmd; Yin.nextDocument(); } @@ -382,6 +478,9 @@ Result.Symbols.emplace(std::move(Symbols).build()); Result.Refs.emplace(std::move(Refs).build()); Result.Relations.emplace(std::move(Relations).build()); + if (Sources.size()) + Result.Sources = std::move(Sources); + Result.Cmd = std::move(Cmd); return std::move(Result); } Index: clang-tools-extra/clangd/unittests/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/unittests/CMakeLists.txt +++ clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -82,6 +82,7 @@ TweakTesting.cpp URITests.cpp XRefsTests.cpp + YAMLTests.cpp $ ) Index: clang-tools-extra/clangd/unittests/YAMLTests.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/unittests/YAMLTests.cpp @@ -0,0 +1,244 @@ +//===-- YAMLTests.cpp - YAML container unit tests -------------------------===// +// +// 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/Serialization.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +std::string YAMLData = + R"|(--- !Symbol +ID: 13C6B898A37B5E70 +Name: changedFilesTask +Scope: 'clang::clangd::BackgroundIndex::' +SymInfo: + Kind: InstanceMethod + Lang: Cpp +CanonicalDeclaration: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.h' + Start: + Line: 197 + Column: 2 + End: + Line: 197 + Column: 18 +Definition: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 166 + Column: 39 + End: + Line: 166 + Column: 55 +Origin: 4 +Flags: 8 +Signature: '' +TemplateSpecializationArgs: '' +CompletionSnippetSuffix: '' +Documentation: '' +ReturnType: '' +Type: '' +... +--- !Refs +ID: FA89F28EEE2EDA4F +References: + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 408 + Column: 40 + End: + Line: 408 + Column: 42 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 409 + Column: 13 + End: + Line: 409 + Column: 15 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 412 + Column: 30 + End: + Line: 412 + Column: 32 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 415 + Column: 8 + End: + Line: 415 + Column: 10 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 428 + Column: 24 + End: + Line: 428 + Column: 26 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 429 + Column: 24 + End: + Line: 429 + Column: 26 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 460 + Column: 18 + End: + Line: 460 + Column: 20 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 461 + Column: 64 + End: + Line: 461 + Column: 66 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 463 + Column: 24 + End: + Line: 463 + Column: 26 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 464 + Column: 67 + End: + Line: 464 + Column: 69 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 467 + Column: 18 + End: + Line: 467 + Column: 20 + - Kind: 4 + Location: + FileURI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' + Start: + Line: 468 + Column: 66 + End: + Line: 468 + Column: 68 +... +--- !Source +URI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.cpp' +Flags: 1 +Digest: EED8F5EAF25C453C +DirectIncludes: + - 'file:///llvm-project/clang-tools-extra/clangd/index/Background.h' +... +--- !Source +URI: 'file:///llvm-project/clang-tools-extra/clangd/index/Background.h' +Flags: 0 +Digest: '0000000000000000' +DirectIncludes: [] +... +--- !Cmd +Directory: '/llvm-project/clang-tools-extra/clangd/index/Background.cpp' +CommandLine: + - '/usr/bin/c++' + - '--driver-mode=g++' + - '-Wall' + - '-Wextra' + - '-std=c++14' + - '-o CMakeFiles/obj.clangDaemon.dir/index/Background.cpp' + - '-c' + - '/home/mnauw/src/cppobject/tools/cppgir.cpp' +... +)|"; + +llvm::Expected readWriteYAML(const std::string &Input) { + auto IndexIn = clang::clangd::readIndexFile(Input); + if (!IndexIn) + return IndexIn.takeError(); + + clang::clangd::IndexFileOut IndexOut(IndexIn.get()); + IndexOut.Format = clang::clangd::IndexFileFormat::YAML; + + std::string Output; + llvm::raw_string_ostream OS(Output); + OS << IndexOut; + OS.flush(); + return Output; +} + +TEST(YAMLTest, File) { + auto IndexIn = clang::clangd::readIndexFile(YAMLData); + ASSERT_TRUE(bool(IndexIn)) << IndexIn.takeError(); + ASSERT_TRUE(IndexIn->Symbols); + EXPECT_EQ(size_t(1), IndexIn->Symbols->size()); + ASSERT_TRUE(IndexIn->Refs); + EXPECT_EQ(size_t(1), IndexIn->Refs->size()); + ASSERT_TRUE(IndexIn->Cmd); + EXPECT_EQ(size_t(8), IndexIn->Cmd->CommandLine.size()); + ASSERT_TRUE(IndexIn->Sources); + EXPECT_EQ(size_t(2), IndexIn->Sources->size()); + + // Moreover, as the input is 'normalized', it should match output again. + // Such a check definitely covers a lot, but might be overly sensitive? + auto Output1 = readWriteYAML(YAMLData); + ASSERT_TRUE(bool(Output1)) << Output1.takeError(); + EXPECT_EQ(Output1.get(), YAMLData); + + // Output should not have dropped bits + // That was checked above already but let's do it this way as well. + auto IndexOut = clang::clangd::readIndexFile(Output1.get()); + ASSERT_TRUE(bool(IndexOut)) << IndexOut.takeError(); + ASSERT_TRUE(IndexOut->Symbols); + EXPECT_EQ(IndexIn->Symbols->size(), IndexOut->Symbols->size()); + ASSERT_TRUE(IndexOut->Refs); + EXPECT_EQ(IndexIn->Refs->size(), IndexOut->Refs->size()); + ASSERT_TRUE(IndexOut->Cmd); + EXPECT_EQ(IndexIn->Cmd->CommandLine.size(), + IndexOut->Cmd->CommandLine.size()); + ASSERT_TRUE(IndexOut->Sources); + EXPECT_EQ(IndexIn->Sources->size(), IndexOut->Sources->size()); + + // Another round-trip should also match + auto Output2 = readWriteYAML(Output1.get()); + ASSERT_TRUE(bool(Output2)) << Output2.takeError(); + EXPECT_EQ(Output1.get(), Output2.get()); +} + +} // namespace +} // namespace clangd +} // namespace clang