//===-- YAMLSerialization.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 // //===----------------------------------------------------------------------===// // // A YAML index file is a sequence of tagged entries. // Each entry either encodes a Symbol or the list of references to a symbol // (a "ref bundle"). // //===----------------------------------------------------------------------===// #include "Index.h" #include "Relation.h" #include "Serialization.h" #include "SymbolLocation.h" #include "SymbolOrigin.h" #include "dex/Dex.h" #include "support/Logger.h" #include "support/Trace.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Symbol::IncludeHeaderWithReferences) LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Ref) namespace { using RefBundle = std::pair>; // This is a pale imitation of std::variant struct VariantEntry { 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. struct YPosition { uint32_t Line; 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; using clang::clangd::RelationKind; using clang::clangd::Symbol; using clang::clangd::SymbolID; using clang::clangd::SymbolLocation; using clang::clangd::SymbolOrigin; using clang::index::SymbolInfo; 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 { NormalizedSymbolID(IO &) {} NormalizedSymbolID(IO &, const SymbolID &ID) { llvm::raw_string_ostream OS(HexString); OS << ID; } SymbolID denormalize(IO &I) { auto ID = SymbolID::fromStr(HexString); if (!ID) { I.setError(llvm::toString(ID.takeError())); return SymbolID(); } return *ID; } std::string HexString; }; struct NormalizedSymbolFlag { NormalizedSymbolFlag(IO &) {} NormalizedSymbolFlag(IO &, Symbol::SymbolFlag F) { Flag = static_cast(F); } Symbol::SymbolFlag denormalize(IO &) { return static_cast(Flag); } uint8_t Flag = 0; }; struct NormalizedSymbolOrigin { NormalizedSymbolOrigin(IO &) {} NormalizedSymbolOrigin(IO &, SymbolOrigin O) { Origin = static_cast(O); } SymbolOrigin denormalize(IO &) { return static_cast(Origin); } uint8_t Origin = 0; }; template <> struct MappingTraits { static void mapping(IO &IO, YPosition &Value) { IO.mapRequired("Line", Value.Line); IO.mapRequired("Column", Value.Column); } }; struct NormalizedPosition { using Position = clang::clangd::SymbolLocation::Position; NormalizedPosition(IO &) {} NormalizedPosition(IO &, const Position &Pos) { P.Line = Pos.line(); P.Column = Pos.column(); } Position denormalize(IO &) { Position Pos; Pos.setLine(P.Line); Pos.setColumn(P.Column); return Pos; } YPosition P; }; struct NormalizedFileURI { NormalizedFileURI(IO &) {} NormalizedFileURI(IO &, const char *FileURI) { URI = FileURI; } const char *denormalize(IO &IO) { assert(IO.getContext() && "Expecting an UniqueStringSaver to allocate data"); return static_cast(IO.getContext()) ->save(URI) .data(); } std::string URI; }; template <> struct MappingTraits { static void mapping(IO &IO, SymbolLocation &Value) { MappingNormalization NFile(IO, Value.FileURI); IO.mapRequired("FileURI", NFile->URI); MappingNormalization NStart( IO, Value.Start); IO.mapRequired("Start", NStart->P); MappingNormalization NEnd( IO, Value.End); IO.mapRequired("End", NEnd->P); } }; template <> struct MappingTraits { static void mapping(IO &io, SymbolInfo &SymInfo) { // FIXME: expose other fields? io.mapRequired("Kind", SymInfo.Kind); io.mapRequired("Lang", SymInfo.Lang); } }; template <> struct MappingTraits { static void mapping(IO &io, clang::clangd::Symbol::IncludeHeaderWithReferences &Inc) { io.mapRequired("Header", Inc.IncludeHeader); io.mapRequired("References", Inc.References); } }; template <> struct MappingTraits { static void mapping(IO &IO, Symbol &Sym) { MappingNormalization NSymbolID(IO, Sym.ID); MappingNormalization NSymbolFlag( IO, Sym.Flags); MappingNormalization NSymbolOrigin( IO, Sym.Origin); IO.mapRequired("ID", NSymbolID->HexString); IO.mapRequired("Name", Sym.Name); IO.mapRequired("Scope", Sym.Scope); IO.mapRequired("SymInfo", Sym.SymInfo); IO.mapOptional("CanonicalDeclaration", Sym.CanonicalDeclaration, SymbolLocation()); IO.mapOptional("Definition", Sym.Definition, SymbolLocation()); IO.mapOptional("References", Sym.References, 0u); IO.mapOptional("Origin", NSymbolOrigin->Origin); IO.mapOptional("Flags", NSymbolFlag->Flag); IO.mapOptional("Signature", Sym.Signature); IO.mapOptional("TemplateSpecializationArgs", Sym.TemplateSpecializationArgs); IO.mapOptional("CompletionSnippetSuffix", Sym.CompletionSnippetSuffix); IO.mapOptional("Documentation", Sym.Documentation); IO.mapOptional("ReturnType", Sym.ReturnType); IO.mapOptional("Type", Sym.Type); IO.mapOptional("IncludeHeaders", Sym.IncludeHeaders); } }; template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, SymbolLanguage &Value) { IO.enumCase(Value, "C", SymbolLanguage::C); IO.enumCase(Value, "Cpp", SymbolLanguage::CXX); IO.enumCase(Value, "ObjC", SymbolLanguage::ObjC); IO.enumCase(Value, "Swift", SymbolLanguage::Swift); } }; template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, SymbolKind &Value) { #define DEFINE_ENUM(name) IO.enumCase(Value, #name, SymbolKind::name) DEFINE_ENUM(Unknown); DEFINE_ENUM(Function); DEFINE_ENUM(Module); DEFINE_ENUM(Namespace); DEFINE_ENUM(NamespaceAlias); DEFINE_ENUM(Macro); DEFINE_ENUM(Enum); DEFINE_ENUM(Struct); DEFINE_ENUM(Class); DEFINE_ENUM(Protocol); DEFINE_ENUM(Extension); DEFINE_ENUM(Union); DEFINE_ENUM(TypeAlias); DEFINE_ENUM(Function); DEFINE_ENUM(Variable); DEFINE_ENUM(Field); DEFINE_ENUM(EnumConstant); DEFINE_ENUM(InstanceMethod); DEFINE_ENUM(ClassMethod); DEFINE_ENUM(StaticMethod); DEFINE_ENUM(InstanceProperty); DEFINE_ENUM(ClassProperty); DEFINE_ENUM(StaticProperty); DEFINE_ENUM(Constructor); DEFINE_ENUM(Destructor); DEFINE_ENUM(ConversionFunction); DEFINE_ENUM(Parameter); DEFINE_ENUM(Using); #undef DEFINE_ENUM } }; template <> struct MappingTraits { static void mapping(IO &IO, RefBundle &Refs) { MappingNormalization NSymbolID(IO, Refs.first); IO.mapRequired("ID", NSymbolID->HexString); IO.mapRequired("References", Refs.second); } }; struct NormalizedRefKind { NormalizedRefKind(IO &) {} NormalizedRefKind(IO &, RefKind O) { Kind = static_cast(O); } RefKind denormalize(IO &) { return static_cast(Kind); } uint8_t Kind = 0; }; template <> struct MappingTraits { static void mapping(IO &IO, Ref &R) { MappingNormalization NKind(IO, R.Kind); IO.mapRequired("Kind", NKind->Kind); IO.mapRequired("Location", R.Location); } }; struct NormalizedSymbolRole { NormalizedSymbolRole(IO &) {} NormalizedSymbolRole(IO &IO, RelationKind R) { Kind = static_cast(R); } RelationKind denormalize(IO &IO) { return static_cast(Kind); } uint8_t Kind = 0; }; template <> struct MappingTraits { static void mapping(IO &IO, SymbolID &ID) { MappingNormalization NSymbolID(IO, ID); IO.mapRequired("ID", NSymbolID->HexString); } }; template <> struct MappingTraits { static void mapping(IO &IO, Relation &Relation) { MappingNormalization NRole( IO, Relation.Predicate); IO.mapRequired("Subject", Relation.Subject); IO.mapRequired("Predicate", NRole->Kind); IO.mapRequired("Object", Relation.Object); } }; 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())) { if (!IO.outputting()) Variant.Symbol.emplace(); MappingTraits::mapping(IO, *Variant.Symbol); } else if (IO.mapTag("!Refs", Variant.Refs.hasValue())) { if (!IO.outputting()) Variant.Refs.emplace(); MappingTraits::mapping(IO, *Variant.Refs); } else if (IO.mapTag("!Relations", Variant.Relation.hasValue())) { 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)); } } }; } // namespace yaml } // namespace llvm namespace clang { namespace clangd { void writeYAML(const IndexFileOut &O, llvm::raw_ostream &OS) { llvm::yaml::Output Yout(OS); for (const auto &Sym : *O.Symbols) { VariantEntry Entry; Entry.Symbol = Sym; Yout << Entry; } if (O.Refs) for (auto &Sym : *O.Refs) { VariantEntry Entry; Entry.Refs = Sym; Yout << Entry; } if (O.Relations) for (auto &R : *O.Relations) { VariantEntry Entry; 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) { SymbolSlab::Builder Symbols; RefSlab::Builder Refs; RelationSlab::Builder Relations; llvm::BumpPtrAllocator 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; yamlize(Yin, Variant, true, Ctx); if (Yin.error()) return llvm::errorCodeToError(Yin.error()); if (Variant.Symbol) Symbols.insert(*Variant.Symbol); if (Variant.Refs) for (const auto &Ref : Variant.Refs->second) 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(); } IndexFileIn Result; 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); } std::string toYAML(const Symbol &S) { std::string Buf; { llvm::raw_string_ostream OS(Buf); llvm::yaml::Output Yout(OS); Symbol Sym = S; // copy: Yout<< requires mutability. Yout << Sym; } return Buf; } std::string toYAML(const std::pair> &Data) { RefBundle Refs = {Data.first, Data.second}; std::string Buf; { llvm::raw_string_ostream OS(Buf); llvm::yaml::Output Yout(OS); Yout << Refs; } return Buf; } std::string toYAML(const Relation &R) { std::string Buf; { llvm::raw_string_ostream OS(Buf); llvm::yaml::Output Yout(OS); Relation Rel = R; // copy: Yout<< requires mutability. Yout << Rel; } return Buf; } std::string toYAML(const Ref &R) { std::string Buf; { llvm::raw_string_ostream OS(Buf); llvm::yaml::Output Yout(OS); Ref Reference = R; // copy: Yout<< requires mutability. Yout << Reference; } return Buf; } llvm::Expected symbolFromYAML(StringRef YAML, llvm::UniqueStringSaver *Strings) { clangd::Symbol Deserialized; llvm::yaml::Input YAMLInput(YAML, Strings); if (YAMLInput.error()) return error("Unable to deserialize Symbol from YAML: {0}", YAML); YAMLInput >> Deserialized; return Deserialized; } llvm::Expected refFromYAML(StringRef YAML, llvm::UniqueStringSaver *Strings) { clangd::Ref Deserialized; llvm::yaml::Input YAMLInput(YAML, Strings); if (YAMLInput.error()) return error("Unable to deserialize Symbol from YAML: {0}", YAML); YAMLInput >> Deserialized; return Deserialized; } } // namespace clangd } // namespace clang