diff options
author | Sam McCall <sam.mccall@gmail.com> | 2020-09-24 00:28:52 +0200 |
---|---|---|
committer | Sam McCall <sam.mccall@gmail.com> | 2020-09-24 00:34:11 +0200 |
commit | 38de1c33a8374bb16abfb024a973d851c170bafc (patch) | |
tree | 955f614f49deb9e40614f199f62a5f79b4f2318a /llvm/lib/Support/JSON.cpp | |
parent | 111aa4e36614d9a056cf5040d4d7bbfddeb9ebb2 (diff) | |
download | llvm-38de1c33a8374bb16abfb024a973d851c170bafc.zip llvm-38de1c33a8374bb16abfb024a973d851c170bafc.tar.gz llvm-38de1c33a8374bb16abfb024a973d851c170bafc.tar.bz2 |
[JSON] Display errors associated with Paths in context
When an error occurs processing a JSON object, seeing the actual
surrounding data helps. Dumping just the node where the problem
was identified can be too much or too little information.
printErrorContext() shows the error message in its context, as a comment.
JSON values along the path to the broken place are shown in some detail,
the rest of the document is elided. For example:
```
{
"credentials": [
{
"username": /* error: expected string */ 42,
"password": "secret"
},
{ ... }
]
"backups": { ... }
}
```
Differential Revision: https://reviews.llvm.org/D88103
Diffstat (limited to 'llvm/lib/Support/JSON.cpp')
-rw-r--r-- | llvm/lib/Support/JSON.cpp | 138 |
1 files changed, 127 insertions, 11 deletions
diff --git a/llvm/lib/Support/JSON.cpp b/llvm/lib/Support/JSON.cpp index f7c51a4..0672ddd 100644 --- a/llvm/lib/Support/JSON.cpp +++ b/llvm/lib/Support/JSON.cpp @@ -236,6 +236,133 @@ Error Path::Root::getError() const { } namespace { + +std::vector<const Object::value_type *> sortedElements(const Object &O) { + std::vector<const Object::value_type *> Elements; + for (const auto &E : O) + Elements.push_back(&E); + llvm::sort(Elements, + [](const Object::value_type *L, const Object::value_type *R) { + return L->first < R->first; + }); + return Elements; +} + +// Prints a one-line version of a value that isn't our main focus. +// We interleave writes to OS and JOS, exploiting the lack of extra buffering. +// This is OK as we own the implementation. +// FIXME: once we have a "write custom serialized value" API, use it here. +void abbreviate(const Value &V, OStream &JOS, raw_ostream &OS) { + switch (V.kind()) { + case Value::Array: + JOS.array([&] { + if (!V.getAsArray()->empty()) + OS << " ... "; + }); + break; + case Value::Object: + JOS.object([&] { + if (!V.getAsObject()->empty()) + OS << " ... "; + }); + break; + case Value::String: { + llvm::StringRef S = *V.getAsString(); + if (S.size() < 40) { + JOS.value(V); + } else { + std::string Truncated = fixUTF8(S.take_front(37)); + Truncated.append("..."); + JOS.value(Truncated); + } + break; + } + default: + JOS.value(V); + } +} + +// Prints a semi-expanded version of a value that is our main focus. +// Array/Object entries are printed, but not recursively as they may be huge. +void abbreviateChildren(const Value &V, OStream &JOS, raw_ostream &OS) { + switch (V.kind()) { + case Value::Array: + JOS.array([&] { + for (const auto &V : *V.getAsArray()) + abbreviate(V, JOS, OS); + }); + break; + case Value::Object: + JOS.object([&] { + for (const auto *KV : sortedElements(*V.getAsObject())) { + JOS.attributeBegin(KV->first); + abbreviate(KV->second, JOS, OS); + JOS.attributeEnd(); + } + }); + break; + default: + JOS.value(V); + } +} + +} // namespace + +void Path::Root::printErrorContext(const Value &R, raw_ostream &OS) const { + OStream JOS(OS, /*IndentSize=*/2); + // PrintValue recurses down the path, printing the ancestors of our target. + // Siblings of nodes along the path are printed with abbreviate(), and the + // target itself is printed with the somewhat richer abbreviateChildren(). + // 'Recurse' is the lambda itself, to allow recursive calls. + auto PrintValue = [&](const Value &V, ArrayRef<Segment> Path, auto &Recurse) { + // Print the target node itself, with the error as a comment. + // Also used if we can't follow our path, e.g. it names a field that + // *should* exist but doesn't. + auto HighlightCurrent = [&] { + std::string Comment = "error: "; + Comment.append(ErrorMessage.data(), ErrorMessage.size()); + JOS.comment(Comment); + abbreviateChildren(V, JOS, OS); + }; + if (Path.empty()) // We reached our target. + return HighlightCurrent(); + const Segment &S = Path.back(); // Path is in reverse order. + if (S.isField()) { + // Current node is an object, path names a field. + llvm::StringRef FieldName = S.field(); + const Object *O = V.getAsObject(); + if (!O || !O->get(FieldName)) + return HighlightCurrent(); + JOS.object([&] { + for (const auto *KV : sortedElements(*O)) { + JOS.attributeBegin(KV->first); + if (FieldName.equals(KV->first)) + Recurse(KV->second, Path.drop_back(), Recurse); + else + abbreviate(KV->second, JOS, OS); + JOS.attributeEnd(); + } + }); + } else { + // Current node is an array, path names an element. + const Array *A = V.getAsArray(); + if (!A || S.index() >= A->size()) + return HighlightCurrent(); + JOS.array([&] { + unsigned Current = 0; + for (const auto &V : *A) { + if (Current++ == S.index()) + Recurse(V, Path.drop_back(), Recurse); + else + abbreviate(V, JOS, OS); + } + }); + } + }; + PrintValue(R, ErrorPath, PrintValue); +} + +namespace { // Simple recursive-descent JSON parser. class Parser { public: @@ -555,17 +682,6 @@ Expected<Value> parse(StringRef JSON) { } char ParseError::ID = 0; -static std::vector<const Object::value_type *> sortedElements(const Object &O) { - std::vector<const Object::value_type *> Elements; - for (const auto &E : O) - Elements.push_back(&E); - llvm::sort(Elements, - [](const Object::value_type *L, const Object::value_type *R) { - return L->first < R->first; - }); - return Elements; -} - bool isUTF8(llvm::StringRef S, size_t *ErrOffset) { // Fast-path for ASCII, which is valid UTF-8. if (LLVM_LIKELY(isASCII(S))) |