json_view: address review of the exception diagnostics
- value_path(): check for the empty stack where the last frame is popped, so that the loop is `for (;;)`; update the frame only after the target check; document that the frame reference is not used after pop_back() or push_back() (and add a test with deep documents to run under a sanitizer) - value_range(): flip the container check to remove a level of nesting - diagnostics(): wrap the whole function in the macro check, and compute the path before the bytes, so that nothing is computed when there is no path; the function stays a template - error_site: separate constructors for the two configurations - site_of(): static_assert that it is called with a view - document why the throw helpers without an error_site have none - pointer.hpp: do not split a comment around the template header - docs: "when materialize() sets them"; list the effects of JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS separately Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasBf4ToHzdcoNEPAJhS
This commit is contained in:
12 files changed
+180
-139
No files matched your search
@@ -70,7 +70,7 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
Pointer path of the value within the document, and with
|
Pointer path of the value within the document, and with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (where
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
[`materialize()`](materialize.md) sets them).
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_it
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
its bytes in the source text (where [`materialize()`](materialize.md) sets them).
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_it
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
its bytes in the source text (where [`materialize()`](materialize.md) sets them).
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) i
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
its bytes in the source text (where [`materialize()`](materialize.md) sets them).
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) i
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
its bytes in the source text (where [`materialize()`](materialize.md) sets them).
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
Pointer path of the value within the document, and with
|
Pointer path of the value within the document, and with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (where
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
[`materialize()`](materialize.md) sets them).
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
|
|
||||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
Pointer path of the value within the document, and with
|
Pointer path of the value within the document, and with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (where
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
[`materialize()`](materialize.md) sets them).
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|||||||
@@ -134,14 +134,21 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
|||||||
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
||||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
||||||
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
||||||
how it reports errors. With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) and
|
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), the exceptions of views name the path
|
`#!cpp basic_json`:
|
||||||
and the bytes of the value as those of `#!cpp basic_json` do; as a view has neither parent pointers nor stored
|
|
||||||
positions, both are found from the document's index when the exception is thrown, which takes time linear in the
|
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
|
||||||
size of the document (only for an exception). One exception has neither: `key()` of an iterator over an array or a
|
value within the document.
|
||||||
primitive value ([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), as a view's
|
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
|
||||||
iterator does not know the value it iterates over. The other macros only affect the values `materialize()` produces,
|
the value in the source text.
|
||||||
through `BasicJsonType`.
|
|
||||||
|
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
|
||||||
|
exception is thrown. This takes time linear in the size of the document. One exception has neither:
|
||||||
|
`key()` of an iterator over an array or a primitive value
|
||||||
|
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
|
||||||
|
not know the value it iterates over.
|
||||||
|
|
||||||
|
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
|
||||||
|
|
||||||
## What is different
|
## What is different
|
||||||
|
|
||||||
|
|||||||
@@ -40,16 +40,11 @@ class error_site
|
|||||||
public:
|
public:
|
||||||
error_site() noexcept = default;
|
error_site() noexcept = default;
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||||
error_site(const document_data* d, const node* n) noexcept
|
error_site(const document_data* d, const node* n) noexcept
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
#endif
|
{}
|
||||||
{
|
|
||||||
static_cast<void>(d);
|
|
||||||
static_cast<void>(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
const document_data* doc() const noexcept
|
const document_data* doc() const noexcept
|
||||||
{
|
{
|
||||||
return m_doc;
|
return m_doc;
|
||||||
@@ -62,6 +57,8 @@ class error_site
|
|||||||
private:
|
private:
|
||||||
const document_data* m_doc = nullptr;
|
const document_data* m_doc = nullptr;
|
||||||
const node* m_node = nullptr;
|
const node* m_node = nullptr;
|
||||||
|
#else
|
||||||
|
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,6 +75,7 @@ struct view_access
|
|||||||
template<typename View>
|
template<typename View>
|
||||||
error_site site_of(const View& v) noexcept
|
error_site site_of(const View& v) noexcept
|
||||||
{
|
{
|
||||||
|
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
|
||||||
return view_access::site(v);
|
return view_access::site(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,16 +113,18 @@ bool value_path(const document_data& d, const node* target, std::vector<std::str
|
|||||||
std::vector<frame> stack;
|
std::vector<frame> stack;
|
||||||
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
||||||
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
||||||
while (!stack.empty())
|
for (;;)
|
||||||
{
|
{
|
||||||
|
// (f is not used after the stack changes: pop_back() below, push_back() at the end)
|
||||||
frame& f = stack.back();
|
frame& f = stack.back();
|
||||||
if (f.pos == f.end)
|
if (f.pos == f.end)
|
||||||
{
|
{
|
||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
if (!tokens.empty())
|
if (stack.empty())
|
||||||
{
|
{
|
||||||
tokens.pop_back();
|
return false;
|
||||||
}
|
}
|
||||||
|
tokens.pop_back();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const node* entry = f.pos;
|
const node* entry = f.pos;
|
||||||
@@ -145,20 +145,19 @@ bool value_path(const document_data& d, const node* target, std::vector<std::str
|
|||||||
token = std::to_string(f.index);
|
token = std::to_string(f.index);
|
||||||
}
|
}
|
||||||
const node* const child = nav::value(entry);
|
const node* const child = nav::value(entry);
|
||||||
f.pos = document_data::after(entry);
|
|
||||||
++f.index;
|
|
||||||
if (child == target)
|
if (child == target)
|
||||||
{
|
{
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
f.pos = document_data::after(entry);
|
||||||
|
++f.index;
|
||||||
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
||||||
{
|
{
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -195,32 +194,31 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
std::size_t pos = std::string::npos;
|
std::size_t pos = std::string::npos;
|
||||||
for (const node* c = n;;)
|
for (const node* c = n;;)
|
||||||
{
|
{
|
||||||
if (is_container(*c))
|
if (!is_container(*c))
|
||||||
{
|
{
|
||||||
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
p.set_scalar(d, *c);
|
||||||
{
|
pos = p.end;
|
||||||
return;
|
break;
|
||||||
}
|
|
||||||
++closing;
|
|
||||||
const node* const first = nav::first(d, c);
|
|
||||||
const node* const last_end = nav::end(d, c);
|
|
||||||
if (first == last_end)
|
|
||||||
{
|
|
||||||
pos = std::size_t{c->off} + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
|
||||||
const node* last = first;
|
|
||||||
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
|
||||||
{
|
|
||||||
last = e;
|
|
||||||
}
|
|
||||||
c = nav::value(last + value_offset);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
p.set_scalar(d, *c);
|
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
||||||
pos = p.end;
|
{
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
++closing;
|
||||||
|
const node* const first = nav::first(d, c);
|
||||||
|
const node* const last_end = nav::end(d, c);
|
||||||
|
if (first == last_end)
|
||||||
|
{
|
||||||
|
pos = std::size_t{c->off} + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
||||||
|
const node* last = first;
|
||||||
|
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
||||||
|
{
|
||||||
|
last = e;
|
||||||
|
}
|
||||||
|
c = nav::value(last + value_offset);
|
||||||
}
|
}
|
||||||
for (; closing != 0 && pos != std::string::npos; --closing)
|
for (; closing != 0 && pos != std::string::npos; --closing)
|
||||||
{
|
{
|
||||||
@@ -245,33 +243,17 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
/// function makes it), it changed GCC's early inlining of unrelated view
|
/// function makes it), it changed GCC's early inlining of unrelated view
|
||||||
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
||||||
/// into itself (2.7 times the instructions).
|
/// into itself (2.7 times the instructions).
|
||||||
|
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||||
template<typename Unused = void>
|
template<typename Unused = void>
|
||||||
std::string diagnostics(const error_site site)
|
std::string diagnostics(const error_site site)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (site.doc() == nullptr || site.value() == nullptr)
|
if (site.doc() == nullptr || site.value() == nullptr)
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const document_data& d = *site.doc();
|
const document_data& d = *site.doc();
|
||||||
const bool edited = d.edits != nullptr;
|
const bool edited = d.edits != nullptr;
|
||||||
std::string bytes;
|
std::string context;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
std::size_t start = 0;
|
|
||||||
std::size_t end = 0;
|
|
||||||
if (edited)
|
|
||||||
{
|
|
||||||
value_range<true>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value_range<false>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
if (start != std::string::npos && end != std::string::npos)
|
|
||||||
{
|
|
||||||
bytes = concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
||||||
@@ -285,15 +267,33 @@ std::string diagnostics(const error_site site)
|
|||||||
path.push_back('/');
|
path.push_back('/');
|
||||||
path += detail::escape(token);
|
path += detail::escape(token);
|
||||||
}
|
}
|
||||||
return concat('(', path, ") ", bytes);
|
context = concat('(', path, ") ");
|
||||||
#else
|
|
||||||
return bytes;
|
|
||||||
#endif
|
#endif
|
||||||
#else
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
static_cast<void>(site);
|
std::size_t start = 0;
|
||||||
return "";
|
std::size_t end = 0;
|
||||||
|
if (edited)
|
||||||
|
{
|
||||||
|
value_range<true>(d, site.value(), start, end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value_range<false>(d, site.value(), start, end);
|
||||||
|
}
|
||||||
|
if (start != std::string::npos && end != std::string::npos)
|
||||||
|
{
|
||||||
|
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename Unused = void>
|
||||||
|
std::string diagnostics(const error_site /*site*/)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// the context of an exception about view @a v (see exception_prefix())
|
/// the context of an exception about view @a v (see exception_prefix())
|
||||||
template<typename View>
|
template<typename View>
|
||||||
@@ -305,6 +305,13 @@ std::string diagnostics_of(const View& v)
|
|||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined. The overloads with an error_site put its context
|
// small enough to be inlined. The overloads with an error_site put its context
|
||||||
// in front of the message.
|
// in front of the message.
|
||||||
|
//
|
||||||
|
// The overloads without one are for exceptions that are not about a value of a
|
||||||
|
// document: limits and invalid input of the editing and image functions, a
|
||||||
|
// discarded view (it has no node), a view of another document (its path would
|
||||||
|
// be one in that document), and those that basic_json creates without a
|
||||||
|
// context too (the errors of json_pointer, invalid UTF-8). The one exception
|
||||||
|
// about a value that has no site is key(), see view_iterator::key().
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
template<typename StringType>
|
|
||||||
/// (without a context, as json_pointer::array_index)
|
/// (without a context, as json_pointer::array_index)
|
||||||
|
template<typename StringType>
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||||
{
|
{
|
||||||
switch (status)
|
switch (status)
|
||||||
|
|||||||
Generated
+69
-62
@@ -4546,16 +4546,11 @@ class error_site
|
|||||||
public:
|
public:
|
||||||
error_site() noexcept = default;
|
error_site() noexcept = default;
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||||
error_site(const document_data* d, const node* n) noexcept
|
error_site(const document_data* d, const node* n) noexcept
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
#endif
|
{}
|
||||||
{
|
|
||||||
static_cast<void>(d);
|
|
||||||
static_cast<void>(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
const document_data* doc() const noexcept
|
const document_data* doc() const noexcept
|
||||||
{
|
{
|
||||||
return m_doc;
|
return m_doc;
|
||||||
@@ -4568,6 +4563,8 @@ class error_site
|
|||||||
private:
|
private:
|
||||||
const document_data* m_doc = nullptr;
|
const document_data* m_doc = nullptr;
|
||||||
const node* m_node = nullptr;
|
const node* m_node = nullptr;
|
||||||
|
#else
|
||||||
|
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4584,6 +4581,7 @@ struct view_access
|
|||||||
template<typename View>
|
template<typename View>
|
||||||
error_site site_of(const View& v) noexcept
|
error_site site_of(const View& v) noexcept
|
||||||
{
|
{
|
||||||
|
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
|
||||||
return view_access::site(v);
|
return view_access::site(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4621,16 +4619,18 @@ bool value_path(const document_data& d, const node* target, std::vector<std::str
|
|||||||
std::vector<frame> stack;
|
std::vector<frame> stack;
|
||||||
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
||||||
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
||||||
while (!stack.empty())
|
for (;;)
|
||||||
{
|
{
|
||||||
|
// (f is not used after the stack changes: pop_back() below, push_back() at the end)
|
||||||
frame& f = stack.back();
|
frame& f = stack.back();
|
||||||
if (f.pos == f.end)
|
if (f.pos == f.end)
|
||||||
{
|
{
|
||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
if (!tokens.empty())
|
if (stack.empty())
|
||||||
{
|
{
|
||||||
tokens.pop_back();
|
return false;
|
||||||
}
|
}
|
||||||
|
tokens.pop_back();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const node* entry = f.pos;
|
const node* entry = f.pos;
|
||||||
@@ -4651,20 +4651,19 @@ bool value_path(const document_data& d, const node* target, std::vector<std::str
|
|||||||
token = std::to_string(f.index);
|
token = std::to_string(f.index);
|
||||||
}
|
}
|
||||||
const node* const child = nav::value(entry);
|
const node* const child = nav::value(entry);
|
||||||
f.pos = document_data::after(entry);
|
|
||||||
++f.index;
|
|
||||||
if (child == target)
|
if (child == target)
|
||||||
{
|
{
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
f.pos = document_data::after(entry);
|
||||||
|
++f.index;
|
||||||
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
||||||
{
|
{
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -4701,32 +4700,31 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
std::size_t pos = std::string::npos;
|
std::size_t pos = std::string::npos;
|
||||||
for (const node* c = n;;)
|
for (const node* c = n;;)
|
||||||
{
|
{
|
||||||
if (is_container(*c))
|
if (!is_container(*c))
|
||||||
{
|
{
|
||||||
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
p.set_scalar(d, *c);
|
||||||
{
|
pos = p.end;
|
||||||
return;
|
break;
|
||||||
}
|
|
||||||
++closing;
|
|
||||||
const node* const first = nav::first(d, c);
|
|
||||||
const node* const last_end = nav::end(d, c);
|
|
||||||
if (first == last_end)
|
|
||||||
{
|
|
||||||
pos = std::size_t{c->off} + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
|
||||||
const node* last = first;
|
|
||||||
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
|
||||||
{
|
|
||||||
last = e;
|
|
||||||
}
|
|
||||||
c = nav::value(last + value_offset);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
p.set_scalar(d, *c);
|
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
||||||
pos = p.end;
|
{
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
++closing;
|
||||||
|
const node* const first = nav::first(d, c);
|
||||||
|
const node* const last_end = nav::end(d, c);
|
||||||
|
if (first == last_end)
|
||||||
|
{
|
||||||
|
pos = std::size_t{c->off} + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
||||||
|
const node* last = first;
|
||||||
|
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
||||||
|
{
|
||||||
|
last = e;
|
||||||
|
}
|
||||||
|
c = nav::value(last + value_offset);
|
||||||
}
|
}
|
||||||
for (; closing != 0 && pos != std::string::npos; --closing)
|
for (; closing != 0 && pos != std::string::npos; --closing)
|
||||||
{
|
{
|
||||||
@@ -4751,33 +4749,17 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
/// function makes it), it changed GCC's early inlining of unrelated view
|
/// function makes it), it changed GCC's early inlining of unrelated view
|
||||||
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
||||||
/// into itself (2.7 times the instructions).
|
/// into itself (2.7 times the instructions).
|
||||||
|
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||||
template<typename Unused = void>
|
template<typename Unused = void>
|
||||||
std::string diagnostics(const error_site site)
|
std::string diagnostics(const error_site site)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (site.doc() == nullptr || site.value() == nullptr)
|
if (site.doc() == nullptr || site.value() == nullptr)
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const document_data& d = *site.doc();
|
const document_data& d = *site.doc();
|
||||||
const bool edited = d.edits != nullptr;
|
const bool edited = d.edits != nullptr;
|
||||||
std::string bytes;
|
std::string context;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
std::size_t start = 0;
|
|
||||||
std::size_t end = 0;
|
|
||||||
if (edited)
|
|
||||||
{
|
|
||||||
value_range<true>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value_range<false>(d, site.value(), start, end);
|
|
||||||
}
|
|
||||||
if (start != std::string::npos && end != std::string::npos)
|
|
||||||
{
|
|
||||||
bytes = concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
||||||
@@ -4791,15 +4773,33 @@ std::string diagnostics(const error_site site)
|
|||||||
path.push_back('/');
|
path.push_back('/');
|
||||||
path += detail::escape(token);
|
path += detail::escape(token);
|
||||||
}
|
}
|
||||||
return concat('(', path, ") ", bytes);
|
context = concat('(', path, ") ");
|
||||||
#else
|
|
||||||
return bytes;
|
|
||||||
#endif
|
#endif
|
||||||
#else
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
static_cast<void>(site);
|
std::size_t start = 0;
|
||||||
return "";
|
std::size_t end = 0;
|
||||||
|
if (edited)
|
||||||
|
{
|
||||||
|
value_range<true>(d, site.value(), start, end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value_range<false>(d, site.value(), start, end);
|
||||||
|
}
|
||||||
|
if (start != std::string::npos && end != std::string::npos)
|
||||||
|
{
|
||||||
|
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename Unused = void>
|
||||||
|
std::string diagnostics(const error_site /*site*/)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// the context of an exception about view @a v (see exception_prefix())
|
/// the context of an exception about view @a v (see exception_prefix())
|
||||||
template<typename View>
|
template<typename View>
|
||||||
@@ -4811,6 +4811,13 @@ std::string diagnostics_of(const View& v)
|
|||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined. The overloads with an error_site put its context
|
// small enough to be inlined. The overloads with an error_site put its context
|
||||||
// in front of the message.
|
// in front of the message.
|
||||||
|
//
|
||||||
|
// The overloads without one are for exceptions that are not about a value of a
|
||||||
|
// document: limits and invalid input of the editing and image functions, a
|
||||||
|
// discarded view (it has no node), a view of another document (its path would
|
||||||
|
// be one in that document), and those that basic_json creates without a
|
||||||
|
// context too (the errors of json_pointer, invalid UTF-8). The one exception
|
||||||
|
// about a value that has no site is key(), see view_iterator::key().
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
@@ -8019,8 +8026,8 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
template<typename StringType>
|
|
||||||
/// (without a context, as json_pointer::array_index)
|
/// (without a context, as json_pointer::array_index)
|
||||||
|
template<typename StringType>
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||||
{
|
{
|
||||||
switch (status)
|
switch (status)
|
||||||
|
|||||||
@@ -512,6 +512,26 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("deep documents with siblings after every nested container")
|
||||||
|
{
|
||||||
|
// the search for the path pushes and pops one frame per level (the
|
||||||
|
// stack reallocates while it grows, and shrinks again at every
|
||||||
|
// sibling): run under a sanitizer, a reference to a popped or moved
|
||||||
|
// frame would show here
|
||||||
|
std::string text = "7";
|
||||||
|
for (int level = 0; level < 40; ++level)
|
||||||
|
{
|
||||||
|
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
||||||
|
}
|
||||||
|
CAPTURE(text)
|
||||||
|
const json j = json::parse(text);
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
check_exceptions(d.root(), j);
|
||||||
|
json_editable_document e = json_editable_document::parse(text);
|
||||||
|
e.set(e.root()[0][0], 9);
|
||||||
|
check_exceptions(e.root(), e.root().materialize());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("examples")
|
SECTION("examples")
|
||||||
{
|
{
|
||||||
const std::string text = R"({"people": [{"name": "a", "age": 1}, {"name": "c", "age": "x"}], "o": {"k": 1}})";
|
const std::string text = R"({"people": [{"name": "a", "age": 1}, {"name": "c", "age": "x"}], "o": {"k": 1}})";
|
||||||
|
|||||||
Reference in new issue
Block a user