diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h --- a/llvm/include/llvm/Support/JSON.h +++ b/llvm/include/llvm/Support/JSON.h @@ -557,6 +557,75 @@ return M.erase(ObjectKey(K)); } +/// A "cursor" marking a position within a Value. +/// The Value is a tree, and this is the path from the root to the current node. +/// This is used to associate errors with particular subobjects. +class Path { +public: + class Root; + + /// Records that the value at the current path is invalid. + /// Message is e.g. "expected number" and becomes part of the final error. + /// This overwrites any previously written error message in the root. + void report(llvm::StringLiteral Message); + + /// The root may be treated as a Path. + Path(Root &R) : Parent(nullptr), Seg(&R) {} + /// Derives a path for an array element: this[Index] + Path index(unsigned Index) const { return Path(this, Segment(Index)); } + /// Derives a path for an object field: this.Field + Path field(StringRef Field) const { return Path(this, Segment(Field)); } + +private: + /// One element in a JSON path: an object field (.foo) or array index [27]. + /// Exception: the root Path encodes a pointer to the Path::Root. + class Segment { + uintptr_t Pointer; + unsigned Offset; + + public: + Segment() = default; + Segment(Root *R) : Pointer(reinterpret_cast(R)) {} + Segment(llvm::StringRef Field) + : Pointer(reinterpret_cast(Field.data())), + Offset(static_cast(Field.size())) {} + Segment(unsigned Index) : Pointer(0), Offset(Index) {} + + bool isField() const { return Pointer != 0; } + StringRef field() const { + return StringRef(reinterpret_cast(Pointer), Offset); + } + unsigned index() const { return Offset; } + Root *root() const { return reinterpret_cast(Pointer); } + }; + + const Path *Parent; + Segment Seg; + + Path(const Path *Parent, Segment S) : Parent(Parent), Seg(S) {} +}; + +/// The root is the trivial Path to the root value. +/// It also stores the latest reported error and the path where it occurred. +class Path::Root { + llvm::StringRef Name; + llvm::StringLiteral ErrorMessage; + std::vector ErrorPath; // Only valid in error state. Reversed. + + friend void Path::report(llvm::StringLiteral Message); + +public: + Root(llvm::StringRef Name = "") : Name(Name), ErrorMessage("") {} + // No copy/move allowed as there are incoming pointers. + Root(Root &&) = delete; + Root &operator=(Root &&) = delete; + Root(const Root &) = delete; + Root &operator=(const Root &) = delete; + + /// Returns the last error reported, or else a generic error. + Error getError() const; +}; + // Standard deserializers are provided for primitive types. // See comments on Value. inline bool fromJSON(const Value &E, std::string &Out) { diff --git a/llvm/lib/Support/JSON.cpp b/llvm/lib/Support/JSON.cpp --- a/llvm/lib/Support/JSON.cpp +++ b/llvm/lib/Support/JSON.cpp @@ -7,7 +7,9 @@ //===---------------------------------------------------------------------===// #include "llvm/Support/JSON.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include @@ -199,6 +201,40 @@ llvm_unreachable("Unknown value kind"); } +void Path::report(llvm::StringLiteral Msg) { + // Walk up to the root context, and count the number of segments. + unsigned Count = 0; + const Path *P; + for (P = this; P->Parent != nullptr; P = P->Parent) + ++Count; + Path::Root *R = P->Seg.root(); + // Fill in the error message and copy the path (in reverse order). + R->ErrorMessage = Msg; + R->ErrorPath.resize(Count); + auto It = R->ErrorPath.begin(); + for (P = this; P->Parent != nullptr; P = P->Parent) + *It++ = P->Seg; +} + +Error Path::Root::getError() const { + std::string S; + raw_string_ostream OS(S); + OS << (ErrorMessage.empty() ? "invalid JSON contents" : ErrorMessage); + if (ErrorPath.empty()) { + if (!Name.empty()) + OS << " when parsing " << Name; + } else { + OS << " at " << (Name.empty() ? "(root)" : Name); + for (const Path::Segment &S : llvm::reverse(ErrorPath)) { + if (S.isField()) + OS << '.' << S.field(); + else + OS << '[' << S.index() << ']'; + } + } + return createStringError(llvm::inconvertibleErrorCode(), OS.str()); +} + namespace { // Simple recursive-descent JSON parser. class Parser { diff --git a/llvm/unittests/Support/JSONTest.cpp b/llvm/unittests/Support/JSONTest.cpp --- a/llvm/unittests/Support/JSONTest.cpp +++ b/llvm/unittests/Support/JSONTest.cpp @@ -8,6 +8,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -461,6 +462,17 @@ EXPECT_EQ(Pretty, StreamStuff(2)); } +TEST(JSONTest, Path) { + Path::Root R("foo"); + Path P = R, A = P.field("a"), B = P.field("b"); + A.index(1).field("c").index(2).report("boom"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("boom at foo.a[1].c[2]")); + B.field("d").field("e").report("bam"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("bam at foo.b.d.e")); + P.report("oh no"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("oh no when parsing foo")); +} + } // namespace } // namespace json } // namespace llvm