#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include "llvm/ADT/StringRef.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" #include "revng/ADT/KeyedObjectContainer.h" #include "revng/ADT/STLExtras.h" #include "revng/ADT/ZipMapIterator.h" #include "revng/Support/Assert.h" #include "revng/Support/Error.h" #include "revng/TupleTree/DiffError.h" #include "revng/TupleTree/TupleLikeTraits.h" #include "revng/TupleTree/TupleTree.h" #include "revng/TupleTree/TupleTreePath.h" #include "revng/TupleTree/Visits.h" template concept HasValueType = requires(T &&) { typename T::value_type; }; template concept HasPushBack = HasValueType && requires(T &&C, const typename T::value_type &V) { { C.push_back(V) }; }; template concept HasInsertOrAssign = HasValueType && requires(T &&C, const typename T::value_type &V) { { C.insert_or_assign(V) }; }; template void addToContainer(C &Container, const typename C::value_type &Value) { Container.push_back(Value); } template void addToContainer(C &Container, const typename C::value_type &Value) { Container.insert_or_assign(Value); } template struct TupleTreeEntries {}; template using AllowedTupleTreeTypes = typename TupleTreeEntries::Types; template concept TupleTreeRootLike = StrictSpecializationOf, std::variant>; namespace revng::detail { template struct CheckTypeIsCorrect { const AllowedTupleTreeTypes *Alternatives = nullptr; bool IsCorrect = false; template void visitTupleElement() { using tuple_element = typename std::tuple_element::type; visit(); } template requires(std::is_enum_v) void visitPolymorphicElement(KindType) { visitTupleElement(); } template void visitContainerElement(KeyT Key) {} template void visit() { check(); } template void visit() { check(); } template void check() { IsCorrect = std::holds_alternative(*Alternatives); } }; template llvm::Error checkTypeIsCorrect(const TupleTreePath &Path, const AllowedTupleTreeTypes &Content) { CheckTypeIsCorrect Checker{ &Content }; auto Result = callByPath(Checker, Path); revng_assert(Result == true); if (not Checker.IsCorrect) return revng::createError("Type check has failed"); return llvm::Error::success(); } } // namespace revng::detail template struct Change { public: using Variant = AllowedTupleTreeTypes; using EntryType = std::optional; public: TupleTreePath Path; EntryType Old = std::nullopt; EntryType New = std::nullopt; public: Change() = default; using TupleTreeType = T; explicit Change(TupleTreePath Path, EntryType Old, EntryType New) : Path(std::move(Path)), Old(std::move(Old)), New(std::move(New)) {} public: static Change createRemoval(TupleTreePath Path, Variant Old) { revng_check(not revng::detail::checkTypeIsCorrect(Path, Old)); return Change(std::move(Path), std::move(Old), std::nullopt); } static Change createAddition(TupleTreePath Path, Variant New) { revng_check(not revng::detail::checkTypeIsCorrect(Path, New)); return Change(std::move(Path), std::nullopt, std::move(New)); } static Change createChange(TupleTreePath Path, Variant Old, Variant New) { revng_check(not revng::detail::checkTypeIsCorrect(Path, New)); revng_check(not revng::detail::checkTypeIsCorrect(Path, Old)); return Change(std::move(Path), std::move(Old), std::move(New)); } }; template struct TupleTreeDiff { public: static llvm::Expected> fromString(llvm::StringRef Input) { return ::fromString>(Input); } public: using Change = Change; std::vector Changes; // TODO: invalidated instances public: TupleTreeDiff invert() const { TupleTreeDiff Result = *this; for (Change &C : Result.Changes) { std::swap(C.Old, C.New); } return Result; } public: template void add(const TupleTreePath &Path, ToAdd What) { Changes.push_back(Change::createAddition(Path, What)); } template void remove(const TupleTreePath &Path, ToRemove What) { Changes.push_back(Change::createRemoval(Path, What)); } template void change(const TupleTreePath &Path, const ToChange &From, const ToChange &To) { Changes.push_back(Change::createChange(Path, From, To)); } public: void dump(llvm::raw_ostream &OutputStream) const; void dump() const { llvm::raw_os_ostream OutputStream(dbg); dump(OutputStream); } llvm::Error apply(TupleTree &M) const; }; template T> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, T &Info) { IO.mapOptional("Changes", Info.Changes); } }; template T, typename X> struct llvm::yaml::SequenceElementTraits { static const bool flow = false; }; namespace detail { template struct MapDiffVisitor { llvm::yaml::IO *Io; AllowedTupleTreeTypes *Change = nullptr; const char *MappingName; template void visitTupleElement() { using tuple_element = typename std::tuple_element::type; visit(); } template requires(std::is_enum_v) void visitPolymorphicElement(KindType) { visitTupleElement(); } template void visitContainerElement(KeyT Key) {} template void visit() { dump(); } template void visit() { dump(); } template void dump() { if (Io->outputting()) { T &Unwrapped = std::get(*Change); if constexpr (SpecializationOf) if (Unwrapped.isEmpty()) return; Io->mapRequired(MappingName, Unwrapped); } else { T Content; Io->mapRequired(MappingName, Content); *Change = std::move(Content); } } }; } // namespace detail template T> struct llvm::yaml::MappingTraits { using EntryType = typename T::EntryType; using Model = typename T::TupleTreeType; static void writeEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { if (not Entry.has_value()) return; ::detail::MapDiffVisitor Visitor{ &IO, &*Entry, Name }; bool Result = callByPath(Visitor, Info.Path); if (not Result) { const auto InfoPathString = pathAsString(Info.Path); if (InfoPathString) IO.setError("Could not write diff, path: " + InfoPathString.value()); else IO.setError("Could not write diff"); } } static void readEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { const auto &Keys = IO.keys(); if (llvm::find(Keys, Name) == Keys.end()) return; Entry.emplace(); revng_assert(Entry.has_value()); ::detail::MapDiffVisitor Visitor{ &IO, &*Entry, Name }; bool Result = callByPath(Visitor, Info.Path); if (not Result) { const auto InfoPathString = pathAsString(Info.Path); if (InfoPathString) IO.setError("Could not parse diff, path: " + InfoPathString.value()); else IO.setError("Could not parse diff"); } } static void mapSingleEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { if (IO.outputting()) writeEntry(IO, Info, Name, Entry); else readEntry(IO, Info, Name, Entry); } static void mapping(IO &IO, T &Info) { if (IO.outputting()) { std::string SerializedPath = *pathAsString(Info.Path); IO.mapRequired("Path", SerializedPath); } else { std::string SerializedPath; IO.mapRequired("Path", SerializedPath); auto MaybePath = stringAsPath(SerializedPath); if (!MaybePath.has_value()) { Info.Path = TupleTreePath(); } else { Info.Path = std::move(*MaybePath); } } mapSingleEntry(IO, Info, "Remove", Info.Old); mapSingleEntry(IO, Info, "Add", Info.New); } }; // // diff // namespace tupletreediff::detail { template struct Diff { TupleTreePath Stack; TupleTreeDiff Result; TupleTreeDiff diff(const M &LHS, const M &RHS) { diffImpl(LHS, RHS); return Result; } private: template void diffTuple(const T &LHS, const T &RHS) { if constexpr (I < std::tuple_size_v) { Stack.push_back(size_t(I)); diffImpl(get(LHS), get(RHS)); Stack.pop_back(); // Recur diffTuple(LHS, RHS); } } template T> void diffImpl(const T &LHS, const T &RHS) { if (LHS.isEmpty() || RHS.isEmpty()) { if (LHS != RHS) Result.change(Stack, LHS, RHS); } else { LHS.upcast([&](auto &LHSUpcasted) { RHS.upcast([&](auto &RHSUpcasted) { using LHSType = std::remove_cvref_t; using RHSType = std::remove_cvref_t; if constexpr (std::is_same_v) { Stack.push_back(LHSUpcasted.Kind()); diffImpl(LHSUpcasted, RHSUpcasted); Stack.pop_back(); } else { Result.change(Stack, LHS, RHS); } }); }); } } template void diffImpl(const T &LHS, const T &RHS) { diffTuple(LHS, RHS); } template void diffImpl(const T &LHS, const T &RHS) { for (auto &&[LHSElement, RHSElement] : zipmap_range(LHS, RHS)) { if (LHSElement == nullptr) { // Added Result.add(Stack, *RHSElement); } else if (RHSElement == nullptr) { // Removed Result.remove(Stack, *LHSElement); } else { // Identical using value_type = typename T::value_type; Stack.push_back(KeyedObjectTraits::key(*LHSElement)); diffImpl(*LHSElement, *RHSElement); Stack.pop_back(); } } } template void diffImpl(const T &LHS, const T &RHS) { if (LHS != RHS) { Result.change(Stack, LHS, RHS); } } }; } // namespace tupletreediff::detail template TupleTreeDiff diff(const M &LHS, const M &RHS) { return tupletreediff::detail::Diff().diff(LHS, RHS); } // // TupleTreeDiff::dump // template inline void TupleTreeDiff::dump(llvm::raw_ostream &OutputStream) const { serialize(OutputStream, *this); } // // TupleTreeDiff::apply // namespace tupletreediff::detail { template struct ApplyDiffVisitor { public: using Change = typename TupleTreeDiff::Change; const Change *C = nullptr; size_t ChangeIndex; revng::DiffError *EL; private: void generateError(const llvm::StringRef Reason, revng::DiffLocation::KindType Kind) { std::string Description = "Error in applying diff"; if (!Reason.empty()) Description += ": " + Reason.str(); std::optional StringPath = pathAsString(C->Path); if (StringPath != std::nullopt) Description += " on Path " + *StringPath; EL->addReason(std::move(Description), revng::DiffLocation(ChangeIndex, Kind)); } public: template void visitTupleElement(K &Element) { visit(Element); } template requires(std::is_enum_v) void visitPolymorphicElement(KindType, K &Element) { visit(Element); } template void visitContainerElement(KeyT, K &Element) { visit(Element); } template void visit(S &M) { // This visitor handles subtree additions/deletions. Here we either have a // New or Old key to add/remove. if (C->Old == std::nullopt && C->New == std::nullopt) { generateError("Both 'Remove' and 'Add' are not present", revng::DiffLocation::KindType::All); return; } if (C->Old != std::nullopt && C->New != std::nullopt) { generateError("Both 'Remove' and 'Add' are not present", revng::DiffLocation::KindType::All); return; } using value_type = typename S::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); size_t OldSize = M.size(); if (C->Old != std::nullopt) { key_type Key = KOT::key(std::get(*C->Old)); auto End = M.end(); auto CompareKeys = [Key](value_type &V) { return KOT::key(V) == Key; }; auto FirstToDelete = std::remove_if(M.begin(), End, CompareKeys); M.erase(FirstToDelete, End); if (OldSize - 1 != M.size()) generateError("Subtree removal failed", revng::DiffLocation::KindType::Old); } else if (C->New != std::nullopt) { // TODO: assert not there already addToContainer(M, std::get(*C->New)); if (OldSize + 1 != M.size()) generateError("Subtree addition failed", revng::DiffLocation::KindType::New); } else { generateError("Arrived at an impossible branch", revng::DiffLocation::KindType::Path); } } template void visit(S &M) { // This visitor handles key changes, so both Old and New are present, unless // one (or even both) of them is an unset polymorphic pointer. // Ensure the tree matches `Old` if (not C->Old.has_value()) { if constexpr (SpecializationOf) { if (M != nullptr) { generateError("`Remove` does not match the contents of the pointer " "within the Tree", revng::DiffLocation::KindType::Old); return; } } else { generateError("Missing `Remove` key", revng::DiffLocation::KindType::Old); return; } } else if (std::get(*C->Old) != M) { generateError("`Remove` does not match the contents of the Tuple Tree", revng::DiffLocation::KindType::Old); return; } // Replace the contents with `New` if (C->New.has_value()) M = std::get(*C->New); else if constexpr (SpecializationOf) M = S::empty(); else generateError("Missing 'Add' key", revng::DiffLocation::KindType::New); } }; } // namespace tupletreediff::detail template inline llvm::Error TupleTreeDiff::apply(TupleTree &M) const { using namespace revng; bool CachingEnabled = M.isReferenceCachingEnabled(); M.disableReferenceCaching(); auto Error = std::make_unique(); size_t Index = 0; for (const Change &C : Changes) { if (C.Path.size() == 0) { Error->addReason("Could not deserialize path", DiffLocation(Index, DiffLocation::KindType::Path)); continue; } tupletreediff::detail::ApplyDiffVisitor ADV{ &C, Index, Error.get() }; if (not callByPath(ADV, C.Path, *M)) { Error->addReason("Path not present: " + pathAsString(C.Path).value_or("(unavailable)"), DiffLocation(Index, DiffLocation::KindType::Path)); } Index++; } M.initializeReferences(); if (CachingEnabled) M.enableReferenceCaching(); return revng::DiffError::makeError(std::move(Error)); }