Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdd9d576a2 | ||
|
|
ae792a4c95 |
No files matched your search
@@ -65,17 +65,7 @@ the same index, which is now a different value.
|
|||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
1. Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
Strong exception safety: if an exception occurs, the original value stays intact.
|
||||||
2. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
|
|
||||||
value may be partially modified, but it stays a valid array.
|
|
||||||
3. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
|
|
||||||
value may be partially modified, but it stays a valid array.
|
|
||||||
4. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
|
|
||||||
value may be partially modified, but it stays a valid array.
|
|
||||||
5. Basic guarantee: if an exception is thrown while the members are copied (for example, `std::bad_alloc`), the JSON
|
|
||||||
value may be partially modified, but it stays a valid object.
|
|
||||||
|
|
||||||
The exceptions listed below are thrown before the JSON value is changed.
|
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ Note that enabling this macro increases the size of every JSON value by two `std
|
|||||||
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
||||||
these values to be reported in those error messages.
|
these values to be reported in those error messages.
|
||||||
|
|
||||||
|
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
|
||||||
|
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
|
||||||
|
|
||||||
## Default definition
|
## Default definition
|
||||||
|
|
||||||
The default value is `0` (position diagnostics are switched off).
|
The default value is `0` (position diagnostics are switched off).
|
||||||
|
|||||||
@@ -198,18 +198,19 @@ class exception : public std::exception
|
|||||||
// all other parent types (primitives) contribute no token
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
// the root value has no path, but still has a byte range
|
||||||
|
std::string prefix;
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
return "";
|
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||||
|
[](const std::string & a, const std::string & b)
|
||||||
|
{
|
||||||
|
return concat(a, '/', detail::escape(b));
|
||||||
|
});
|
||||||
|
prefix = concat('(', str, ") ");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
return concat(prefix, get_byte_positions(leaf_element));
|
||||||
[](const std::string & a, const std::string & b)
|
|
||||||
{
|
|
||||||
return concat(a, '/', detail::escape(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
|
||||||
#else
|
#else
|
||||||
return get_byte_positions(leaf_element);
|
return get_byte_positions(leaf_element);
|
||||||
#endif
|
#endif
|
||||||
@@ -222,7 +223,8 @@ class exception : public std::exception
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||||
{
|
{
|
||||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||||
|
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||||
{
|
{
|
||||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,8 +234,7 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
|
|
||||||
/// the context basic_json puts in front of the message of an exception about
|
/// the context basic_json puts in front of the message of an exception about
|
||||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||||
/// if there is a path)
|
|
||||||
///
|
///
|
||||||
/// This and the throw helpers that take an error_site are templates, so that
|
/// This and the throw helpers that take an error_site are templates, so that
|
||||||
/// the path and range code is instantiated only where an exception can be
|
/// the path and range code is instantiated only where an exception can be
|
||||||
@@ -256,19 +255,19 @@ std::string diagnostics(const error_site site)
|
|||||||
std::string context;
|
std::string context;
|
||||||
#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);
|
// a value that is not found leaves no tokens, like the root
|
||||||
if (!found || tokens.empty())
|
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
// the root value has no path, but still has a byte range
|
||||||
return context;
|
std::string path;
|
||||||
|
for (const std::string& token : tokens)
|
||||||
|
{
|
||||||
|
path.push_back('/');
|
||||||
|
path += detail::escape(token);
|
||||||
|
}
|
||||||
|
context = concat('(', path, ") ");
|
||||||
}
|
}
|
||||||
std::string path;
|
|
||||||
for (const std::string& token : tokens)
|
|
||||||
{
|
|
||||||
path.push_back('/');
|
|
||||||
path += detail::escape(token);
|
|
||||||
}
|
|
||||||
context = concat('(', path, ") ");
|
|
||||||
#endif
|
#endif
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
std::size_t start = 0;
|
std::size_t start = 0;
|
||||||
|
|||||||
@@ -4914,30 +4914,6 @@ public:
|
|||||||
return {it, res.second};
|
return {it, res.second};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// Sets the parents of a value when it goes out of scope, also when an
|
|
||||||
/// exception is thrown: a container insertion that throws may already
|
|
||||||
/// have added elements, whose parents must be set for the value to stay
|
|
||||||
/// valid (basic exception guarantee). Does nothing without JSON_DIAGNOSTICS.
|
|
||||||
class set_parents_on_exit
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit set_parents_on_exit(basic_json* v) noexcept : m_value(v) {}
|
|
||||||
set_parents_on_exit(const set_parents_on_exit&) = delete;
|
|
||||||
set_parents_on_exit& operator=(const set_parents_on_exit&) = delete;
|
|
||||||
set_parents_on_exit(set_parents_on_exit&&) = delete;
|
|
||||||
set_parents_on_exit& operator=(set_parents_on_exit&&) = delete;
|
|
||||||
|
|
||||||
~set_parents_on_exit()
|
|
||||||
{
|
|
||||||
m_value->set_parents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
basic_json* m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Helper for insertion of an iterator
|
/// Helper for insertion of an iterator
|
||||||
/// @note: This uses std::distance to support GCC 4.8,
|
/// @note: This uses std::distance to support GCC 4.8,
|
||||||
/// see https://github.com/nlohmann/json/pull/1257
|
/// see https://github.com/nlohmann/json/pull/1257
|
||||||
@@ -4947,7 +4923,6 @@ public:
|
|||||||
iterator result(this);
|
iterator result(this);
|
||||||
JSON_ASSERT(m_data.m_value.array != nullptr);
|
JSON_ASSERT(m_data.m_value.array != nullptr);
|
||||||
|
|
||||||
const set_parents_on_exit parents(this);
|
|
||||||
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
|
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
|
||||||
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
|
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
|
||||||
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
|
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
|
||||||
@@ -4956,6 +4931,7 @@ public:
|
|||||||
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
|
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
|
||||||
// but the return value of insert is missing in GCC 4.8, so it is written this way instead.
|
// but the return value of insert is missing in GCC 4.8, so it is written this way instead.
|
||||||
|
|
||||||
|
set_parents();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4975,16 +4951,8 @@ public:
|
|||||||
throw_iterator_from_other_value(this);
|
throw_iterator_from_other_value(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// vector::insert has no effects if copying a single element to the
|
// insert to array and return iterator
|
||||||
// end throws; elsewhere, it may already have shifted the elements, so
|
return insert_iterator(pos, val);
|
||||||
// copy first and insert by move, which cannot throw but by allocating
|
|
||||||
if (pos.m_it.array_iterator == m_data.m_value.array->end())
|
|
||||||
{
|
|
||||||
return insert_iterator(pos, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
basic_json tmp(val);
|
|
||||||
return insert_iterator(pos, std::move(tmp));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief inserts element into array
|
/// @brief inserts element into array
|
||||||
@@ -5116,8 +5084,8 @@ public:
|
|||||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const set_parents_on_exit parents(this);
|
|
||||||
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
||||||
|
set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||||
|
|||||||
Generated
+16
-46
@@ -5695,18 +5695,19 @@ class exception : public std::exception
|
|||||||
// all other parent types (primitives) contribute no token
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
// the root value has no path, but still has a byte range
|
||||||
|
std::string prefix;
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
return "";
|
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||||
|
[](const std::string & a, const std::string & b)
|
||||||
|
{
|
||||||
|
return concat(a, '/', detail::escape(b));
|
||||||
|
});
|
||||||
|
prefix = concat('(', str, ") ");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
return concat(prefix, get_byte_positions(leaf_element));
|
||||||
[](const std::string & a, const std::string & b)
|
|
||||||
{
|
|
||||||
return concat(a, '/', detail::escape(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
|
||||||
#else
|
#else
|
||||||
return get_byte_positions(leaf_element);
|
return get_byte_positions(leaf_element);
|
||||||
#endif
|
#endif
|
||||||
@@ -5719,7 +5720,8 @@ class exception : public std::exception
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||||
{
|
{
|
||||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||||
|
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||||
{
|
{
|
||||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||||
}
|
}
|
||||||
@@ -42706,30 +42708,6 @@ public:
|
|||||||
return {it, res.second};
|
return {it, res.second};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// Sets the parents of a value when it goes out of scope, also when an
|
|
||||||
/// exception is thrown: a container insertion that throws may already
|
|
||||||
/// have added elements, whose parents must be set for the value to stay
|
|
||||||
/// valid (basic exception guarantee). Does nothing without JSON_DIAGNOSTICS.
|
|
||||||
class set_parents_on_exit
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit set_parents_on_exit(basic_json* v) noexcept : m_value(v) {}
|
|
||||||
set_parents_on_exit(const set_parents_on_exit&) = delete;
|
|
||||||
set_parents_on_exit& operator=(const set_parents_on_exit&) = delete;
|
|
||||||
set_parents_on_exit(set_parents_on_exit&&) = delete;
|
|
||||||
set_parents_on_exit& operator=(set_parents_on_exit&&) = delete;
|
|
||||||
|
|
||||||
~set_parents_on_exit()
|
|
||||||
{
|
|
||||||
m_value->set_parents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
basic_json* m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Helper for insertion of an iterator
|
/// Helper for insertion of an iterator
|
||||||
/// @note: This uses std::distance to support GCC 4.8,
|
/// @note: This uses std::distance to support GCC 4.8,
|
||||||
/// see https://github.com/nlohmann/json/pull/1257
|
/// see https://github.com/nlohmann/json/pull/1257
|
||||||
@@ -42739,7 +42717,6 @@ public:
|
|||||||
iterator result(this);
|
iterator result(this);
|
||||||
JSON_ASSERT(m_data.m_value.array != nullptr);
|
JSON_ASSERT(m_data.m_value.array != nullptr);
|
||||||
|
|
||||||
const set_parents_on_exit parents(this);
|
|
||||||
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
|
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
|
||||||
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
|
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
|
||||||
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
|
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
|
||||||
@@ -42748,6 +42725,7 @@ public:
|
|||||||
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
|
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
|
||||||
// but the return value of insert is missing in GCC 4.8, so it is written this way instead.
|
// but the return value of insert is missing in GCC 4.8, so it is written this way instead.
|
||||||
|
|
||||||
|
set_parents();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42767,16 +42745,8 @@ public:
|
|||||||
throw_iterator_from_other_value(this);
|
throw_iterator_from_other_value(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// vector::insert has no effects if copying a single element to the
|
// insert to array and return iterator
|
||||||
// end throws; elsewhere, it may already have shifted the elements, so
|
return insert_iterator(pos, val);
|
||||||
// copy first and insert by move, which cannot throw but by allocating
|
|
||||||
if (pos.m_it.array_iterator == m_data.m_value.array->end())
|
|
||||||
{
|
|
||||||
return insert_iterator(pos, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
basic_json tmp(val);
|
|
||||||
return insert_iterator(pos, std::move(tmp));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief inserts element into array
|
/// @brief inserts element into array
|
||||||
@@ -42908,8 +42878,8 @@ public:
|
|||||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const set_parents_on_exit parents(this);
|
|
||||||
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
||||||
|
set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||||
|
|||||||
Generated
+12
-13
@@ -5076,8 +5076,7 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
|
|
||||||
/// the context basic_json puts in front of the message of an exception about
|
/// the context basic_json puts in front of the message of an exception about
|
||||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
|
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||||
/// if there is a path)
|
|
||||||
///
|
///
|
||||||
/// This and the throw helpers that take an error_site are templates, so that
|
/// This and the throw helpers that take an error_site are templates, so that
|
||||||
/// the path and range code is instantiated only where an exception can be
|
/// the path and range code is instantiated only where an exception can be
|
||||||
@@ -5098,19 +5097,19 @@ std::string diagnostics(const error_site site)
|
|||||||
std::string context;
|
std::string context;
|
||||||
#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);
|
// a value that is not found leaves no tokens, like the root
|
||||||
if (!found || tokens.empty())
|
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens));
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
// the root value has no path, but still has a byte range
|
||||||
return context;
|
std::string path;
|
||||||
|
for (const std::string& token : tokens)
|
||||||
|
{
|
||||||
|
path.push_back('/');
|
||||||
|
path += detail::escape(token);
|
||||||
|
}
|
||||||
|
context = concat('(', path, ") ");
|
||||||
}
|
}
|
||||||
std::string path;
|
|
||||||
for (const std::string& token : tokens)
|
|
||||||
{
|
|
||||||
path.push_back('/');
|
|
||||||
path += detail::escape(token);
|
|
||||||
}
|
|
||||||
context = concat('(', path, ") ");
|
|
||||||
#endif
|
#endif
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
std::size_t start = 0;
|
std::size_t start = 0;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
#if JSON_HAS_RANGES
|
#if JSON_HAS_RANGES
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
@@ -805,222 +804,3 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// the no-exceptions CI job has no std::bad_alloc to catch; with iterator
|
|
||||||
// debugging (MSVC STL debug builds) containers allocate inside noexcept
|
|
||||||
// functions, so failing an allocation there terminates the program
|
|
||||||
#if !defined(JSON_NOEXCEPTION) && !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// whether every child of j names j as its parent (always true without
|
|
||||||
// JSON_DIAGNOSTICS, where no parents are stored)
|
|
||||||
template<class BasicJsonType>
|
|
||||||
bool children_name_their_parent(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
if (j.is_array())
|
|
||||||
{
|
|
||||||
for (const auto& element : *j.m_data.m_value.array)
|
|
||||||
{
|
|
||||||
if (element.m_parent != &j)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (j.is_object())
|
|
||||||
{
|
|
||||||
for (const auto& element : *j.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
if (element.second.m_parent != &j)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static_cast<void>(j);
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class guarantee
|
|
||||||
{
|
|
||||||
basic, // the value stays valid and keeps its type
|
|
||||||
strong // the value is unchanged
|
|
||||||
};
|
|
||||||
|
|
||||||
// Runs op on a fresh value make() with the 0th, 1st, 2nd, ... allocation made
|
|
||||||
// by the library failing in turn, until op completes. Every failed attempt
|
|
||||||
// must have thrown std::bad_alloc and left the value valid, with all parent
|
|
||||||
// pointers intact (copying a value asserts the invariant), and for the strong
|
|
||||||
// guarantee exactly as it was.
|
|
||||||
template<class BasicJsonType>
|
|
||||||
void check_exception_guarantee(guarantee g,
|
|
||||||
const std::function<BasicJsonType()>& make,
|
|
||||||
const std::function<void(BasicJsonType&)>& op)
|
|
||||||
{
|
|
||||||
fail_at_alloc_call = -1;
|
|
||||||
|
|
||||||
bool completed = false;
|
|
||||||
std::size_t failures = 0;
|
|
||||||
for (std::size_t n = 0; !completed; ++n)
|
|
||||||
{
|
|
||||||
CAPTURE(n)
|
|
||||||
REQUIRE(n < 1000);
|
|
||||||
|
|
||||||
BasicJsonType j = make();
|
|
||||||
const std::string before = j.dump();
|
|
||||||
const auto type_before = j.type();
|
|
||||||
|
|
||||||
alloc_call_count = 0;
|
|
||||||
fail_at_alloc_call = static_cast<long>(n);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
op(j);
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
catch (const std::bad_alloc&)
|
|
||||||
{
|
|
||||||
++failures;
|
|
||||||
}
|
|
||||||
fail_at_alloc_call = -1;
|
|
||||||
|
|
||||||
if (!completed)
|
|
||||||
{
|
|
||||||
CHECK((j.type() == type_before));
|
|
||||||
CHECK(children_name_their_parent(j));
|
|
||||||
const BasicJsonType copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is the point, it allocates
|
|
||||||
CHECK(copy.dump() == j.dump());
|
|
||||||
if (g == guarantee::strong)
|
|
||||||
{
|
|
||||||
CHECK(j.dump() == before);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the operation allocates, so at least one attempt must have failed
|
|
||||||
CHECK(failures > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class BasicJsonType>
|
|
||||||
void check_insert_exception_guarantees()
|
|
||||||
{
|
|
||||||
using json_t = BasicJsonType;
|
|
||||||
using make_t = std::function<json_t()>;
|
|
||||||
|
|
||||||
// strings of this length do not fit into the small string buffer, so
|
|
||||||
// copying them allocates
|
|
||||||
const json_t val = std::string(64, 'x');
|
|
||||||
const json_t source = json_t::array({std::string(64, 'p'), std::string(64, 'q'), std::string(64, 'r')});
|
|
||||||
|
|
||||||
for (const bool spare_capacity :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(spare_capacity)
|
|
||||||
|
|
||||||
// [ "aaa...", "bbb...", "ccc..." ], with a spare slot if requested
|
|
||||||
const make_t make = [spare_capacity]()
|
|
||||||
{
|
|
||||||
json_t j = json_t::array({std::string(64, 'a'), std::string(64, 'b'), std::string(64, 'c')});
|
|
||||||
if (spare_capacity)
|
|
||||||
{
|
|
||||||
j.push_back(std::string(64, 'd'));
|
|
||||||
j.erase(3);
|
|
||||||
}
|
|
||||||
return json_t(std::move(j));
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const bool at_end :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(at_end)
|
|
||||||
|
|
||||||
SECTION("insert(pos, const basic_json&)")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(at_end ? j.end() : j.begin() + 1, val);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("insert(pos, basic_json&&)")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(at_end ? j.end() : j.begin() + 1, json_t(val));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("insert(pos, size_type, const basic_json&)")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(at_end ? j.end() : j.begin() + 1, 3, val);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("insert(pos, const_iterator, const_iterator)")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(at_end ? j.end() : j.begin() + 1, source.begin(), source.end());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("insert(pos, initializer_list_t)")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(at_end ? j.end() : j.begin() + 1, {val, val});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("insert(const_iterator, const_iterator) for objects")
|
|
||||||
{
|
|
||||||
const json_t members = {{"m", std::string(64, 'm')}, {"n", std::string(64, 'n')}, {"o", std::string(64, 'o')}};
|
|
||||||
|
|
||||||
SECTION("no member exists yet")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
|
||||||
{
|
|
||||||
return json_t({{"a", 1}});
|
|
||||||
}, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(members.begin(), members.end());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("some members exist already")
|
|
||||||
{
|
|
||||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
|
||||||
{
|
|
||||||
return json_t({{"a", 1}, {"n", std::string(64, 'x')}});
|
|
||||||
}, [&](json_t& j)
|
|
||||||
{
|
|
||||||
j.insert(members.begin(), members.end());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("insert() provides its documented exception guarantees (#5986)")
|
|
||||||
{
|
|
||||||
SECTION("std::map-backed object_t")
|
|
||||||
{
|
|
||||||
check_insert_exception_guarantees<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ordered_map-backed object_t")
|
|
||||||
{
|
|
||||||
check_insert_exception_guarantees<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -193,6 +193,28 @@ TEST_CASE("Better diagnostics with positions")
|
|||||||
check_conversion(300);
|
check_conversion(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("exception about the root value (#5987)")
|
||||||
|
{
|
||||||
|
// the root has no path, but still has a byte range
|
||||||
|
const json root = json::parse("true");
|
||||||
|
const json nested = json::parse("[1, true]");
|
||||||
|
const json constructed = true;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(root.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean", json::type_error);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(root.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean", json::type_error);
|
||||||
|
#endif
|
||||||
|
// a value without positions gets no range and no stray whitespace
|
||||||
|
CHECK_THROWS_WITH_AS(constructed.get<std::string>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("JSON patch add to primitive parent (#4292)")
|
SECTION("JSON patch add to primitive parent (#4292)")
|
||||||
{
|
{
|
||||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||||
@@ -243,3 +265,14 @@ TEST_CASE("values read from a binary format have no positions")
|
|||||||
check_no_positions(j);
|
check_no_positions(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("comparing a value-initialized iterator throws invalid_iterator.212")
|
||||||
|
{
|
||||||
|
// such an iterator has no value to take the byte positions from; the
|
||||||
|
// comparison used to dereference the null pointer instead of throwing
|
||||||
|
json j = {1, 2};
|
||||||
|
json::iterator it{};
|
||||||
|
CHECK_THROWS_WITH_AS(it == j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
CHECK_THROWS_WITH_AS(it < j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
CHECK_THROWS_WITH_AS(j.begin() == it, "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
|
||||||
|
}
|
||||||
@@ -692,7 +692,7 @@ TEST_CASE("json_view builder: fraction digits")
|
|||||||
check_same(number);
|
check_same(number);
|
||||||
check_same("[" + number + "]");
|
check_same("[" + number + "]");
|
||||||
std::string pair;
|
std::string pair;
|
||||||
pair.reserve((number.size() * 2) + 3);
|
pair.reserve(number.size() * 2 + 3);
|
||||||
pair += '[';
|
pair += '[';
|
||||||
pair += number;
|
pair += number;
|
||||||
pair += ',';
|
pair += ',';
|
||||||
|
|||||||
@@ -300,6 +300,27 @@ TEST_CASE("json_view materialize() source positions")
|
|||||||
static_cast<void>(j.at("list").at(1).get<int>());
|
static_cast<void>(j.at("list").at(1).get<int>());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("exception about the root value (#5987)")
|
||||||
|
{
|
||||||
|
const json_document root = json_document::parse("true");
|
||||||
|
const json_document nested = json_document::parse("[1, true]");
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(root.root().get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean");
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(nested.root()[1].get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean");
|
||||||
|
#else
|
||||||
|
CHECK(json_view_test::exception_of_call([&]
|
||||||
|
{
|
||||||
|
static_cast<void>(nested.root()[1].get<std::string>());
|
||||||
|
}) == "[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SECTION("edited documents")
|
SECTION("edited documents")
|
||||||
@@ -521,8 +542,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
std::string text = "7";
|
std::string text = "7";
|
||||||
for (int level = 0; level < 40; ++level)
|
for (int level = 0; level < 40; ++level)
|
||||||
{
|
{
|
||||||
text.insert(0, R"([[1, [2]], )");
|
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
||||||
text += R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
|
||||||
}
|
}
|
||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
@@ -551,12 +571,8 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
expected_zz += "(bytes 12-35) key 'zz' not found";
|
expected_zz += "(bytes 12-35) key 'zz' not found";
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
|
||||||
|
|
||||||
// the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json)
|
// the root has no path, but still its bytes (#5987)
|
||||||
std::string expected_missing = "[json.exception.out_of_range.403] ";
|
const std::string expected_missing = "[json.exception.out_of_range.403] (bytes 0-79) key 'missing' not found";
|
||||||
#if !JSON_DIAGNOSTICS
|
|
||||||
expected_missing += "(bytes 0-79) ";
|
|
||||||
#endif
|
|
||||||
expected_missing += "key 'missing' not found";
|
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user