Skip to content

Commit 16619e7

Browse files
committed
[JSON] Facility to track position within an object and report errors.
This error model should be rich enough for most applications. It comprises: - a name for the root object, so the user knows what we're parsing - a path from the root object to the JSON node most associated with the error - a local error message This can be presented as an llvm::Error e.g. "expected string at ConfigFile.credentials[0].username" It's designed to be cheap: Paths are a linked list of lightweight objects on the stack. No heap allocations unless errors are encountered. A subsequent commit will make use of this in the JSON-to-object translation facilities: fromJSON and ObjectMapper. However it's independent of these and can be used for e.g. validation alone. Another subsequent commit will support showing the error in its context within the parsed value. Differential Revision: https://reviews.llvm.org/D88103
1 parent 8f2c31f commit 16619e7

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

llvm/include/llvm/Support/JSON.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,75 @@ inline bool Object::erase(StringRef K) {
557557
return M.erase(ObjectKey(K));
558558
}
559559

560+
/// A "cursor" marking a position within a Value.
561+
/// The Value is a tree, and this is the path from the root to the current node.
562+
/// This is used to associate errors with particular subobjects.
563+
class Path {
564+
public:
565+
class Root;
566+
567+
/// Records that the value at the current path is invalid.
568+
/// Message is e.g. "expected number" and becomes part of the final error.
569+
/// This overwrites any previously written error message in the root.
570+
void report(llvm::StringLiteral Message);
571+
572+
/// The root may be treated as a Path.
573+
Path(Root &R) : Parent(nullptr), Seg(&R) {}
574+
/// Derives a path for an array element: this[Index]
575+
Path index(unsigned Index) const { return Path(this, Segment(Index)); }
576+
/// Derives a path for an object field: this.Field
577+
Path field(StringRef Field) const { return Path(this, Segment(Field)); }
578+
579+
private:
580+
/// One element in a JSON path: an object field (.foo) or array index [27].
581+
/// Exception: the root Path encodes a pointer to the Path::Root.
582+
class Segment {
583+
uintptr_t Pointer;
584+
unsigned Offset;
585+
586+
public:
587+
Segment() = default;
588+
Segment(Root *R) : Pointer(reinterpret_cast<uintptr_t>(R)) {}
589+
Segment(llvm::StringRef Field)
590+
: Pointer(reinterpret_cast<uintptr_t>(Field.data())),
591+
Offset(static_cast<unsigned>(Field.size())) {}
592+
Segment(unsigned Index) : Pointer(0), Offset(Index) {}
593+
594+
bool isField() const { return Pointer != 0; }
595+
StringRef field() const {
596+
return StringRef(reinterpret_cast<const char *>(Pointer), Offset);
597+
}
598+
unsigned index() const { return Offset; }
599+
Root *root() const { return reinterpret_cast<Root *>(Pointer); }
600+
};
601+
602+
const Path *Parent;
603+
Segment Seg;
604+
605+
Path(const Path *Parent, Segment S) : Parent(Parent), Seg(S) {}
606+
};
607+
608+
/// The root is the trivial Path to the root value.
609+
/// It also stores the latest reported error and the path where it occurred.
610+
class Path::Root {
611+
llvm::StringRef Name;
612+
llvm::StringLiteral ErrorMessage;
613+
std::vector<Path::Segment> ErrorPath; // Only valid in error state. Reversed.
614+
615+
friend void Path::report(llvm::StringLiteral Message);
616+
617+
public:
618+
Root(llvm::StringRef Name = "") : Name(Name), ErrorMessage("") {}
619+
// No copy/move allowed as there are incoming pointers.
620+
Root(Root &&) = delete;
621+
Root &operator=(Root &&) = delete;
622+
Root(const Root &) = delete;
623+
Root &operator=(const Root &) = delete;
624+
625+
/// Returns the last error reported, or else a generic error.
626+
Error getError() const;
627+
};
628+
560629
// Standard deserializers are provided for primitive types.
561630
// See comments on Value.
562631
inline bool fromJSON(const Value &E, std::string &Out) {

llvm/lib/Support/JSON.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
//===---------------------------------------------------------------------===//
88

99
#include "llvm/Support/JSON.h"
10+
#include "llvm/ADT/STLExtras.h"
1011
#include "llvm/Support/ConvertUTF.h"
12+
#include "llvm/Support/Error.h"
1113
#include "llvm/Support/Format.h"
1214
#include "llvm/Support/raw_ostream.h"
1315
#include <cctype>
@@ -199,6 +201,40 @@ bool operator==(const Value &L, const Value &R) {
199201
llvm_unreachable("Unknown value kind");
200202
}
201203

204+
void Path::report(llvm::StringLiteral Msg) {
205+
// Walk up to the root context, and count the number of segments.
206+
unsigned Count = 0;
207+
const Path *P;
208+
for (P = this; P->Parent != nullptr; P = P->Parent)
209+
++Count;
210+
Path::Root *R = P->Seg.root();
211+
// Fill in the error message and copy the path (in reverse order).
212+
R->ErrorMessage = Msg;
213+
R->ErrorPath.resize(Count);
214+
auto It = R->ErrorPath.begin();
215+
for (P = this; P->Parent != nullptr; P = P->Parent)
216+
*It++ = P->Seg;
217+
}
218+
219+
Error Path::Root::getError() const {
220+
std::string S;
221+
raw_string_ostream OS(S);
222+
OS << (ErrorMessage.empty() ? "invalid JSON contents" : ErrorMessage);
223+
if (ErrorPath.empty()) {
224+
if (!Name.empty())
225+
OS << " when parsing " << Name;
226+
} else {
227+
OS << " at " << (Name.empty() ? "(root)" : Name);
228+
for (const Path::Segment &S : llvm::reverse(ErrorPath)) {
229+
if (S.isField())
230+
OS << '.' << S.field();
231+
else
232+
OS << '[' << S.index() << ']';
233+
}
234+
}
235+
return createStringError(llvm::inconvertibleErrorCode(), OS.str());
236+
}
237+
202238
namespace {
203239
// Simple recursive-descent JSON parser.
204240
class Parser {

llvm/unittests/Support/JSONTest.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "llvm/Support/JSON.h"
1010
#include "llvm/Support/raw_ostream.h"
11+
#include "llvm/Testing/Support/Error.h"
1112

1213
#include "gmock/gmock.h"
1314
#include "gtest/gtest.h"
@@ -461,6 +462,17 @@ TEST(JSONTest, Stream) {
461462
EXPECT_EQ(Pretty, StreamStuff(2));
462463
}
463464

465+
TEST(JSONTest, Path) {
466+
Path::Root R("foo");
467+
Path P = R, A = P.field("a"), B = P.field("b");
468+
A.index(1).field("c").index(2).report("boom");
469+
EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("boom at foo.a[1].c[2]"));
470+
B.field("d").field("e").report("bam");
471+
EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("bam at foo.b.d.e"));
472+
P.report("oh no");
473+
EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("oh no when parsing foo"));
474+
}
475+
464476
} // namespace
465477
} // namespace json
466478
} // namespace llvm

0 commit comments

Comments
 (0)