mirror of
https://github.com/nlohmann/json.git
synced 2026-11-02 02:45:34 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da5272f6c | ||
|
|
b2674a4a2f | ||
|
|
e2423a1857 |
No files matched your search
@@ -24,7 +24,7 @@ jobs:
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
|
||||
@@ -19,7 +19,7 @@ permissions:
|
||||
jobs:
|
||||
save:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
|
||||
@@ -16,7 +16,7 @@ concurrency:
|
||||
jobs:
|
||||
check_docs_links:
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
comment:
|
||||
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
@@ -18,7 +18,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
|
||||
@@ -24,7 +24,7 @@ on:
|
||||
jobs:
|
||||
flawfinder:
|
||||
name: Flawfinder
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
|
||||
@@ -24,7 +24,7 @@ permissions:
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
|
||||
@@ -8,7 +8,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
@@ -18,7 +18,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
ci_test_gcc:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: gcc:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_static_analysis_clang:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_cmake_options:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
fail-on-error: false
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8', '4.9', '5', '6']
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_gcc:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
@@ -269,7 +269,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
@@ -289,7 +289,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -306,7 +306,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_cuda_example:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -383,7 +383,7 @@ jobs:
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
ci_icpc:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||
# Intel's own last officially published image that still includes it.
|
||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||
@@ -402,7 +402,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpc
|
||||
|
||||
ci_icpx:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: intel/oneapi-hpckit:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -416,7 +416,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpx
|
||||
|
||||
ci_nvhpc:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -65,9 +65,7 @@ the same index, which is now a different value.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception occurs, the original value stays intact. This includes an exception thrown
|
||||
while the new elements are copied (for example, `std::bad_alloc`): elements and members that were already added
|
||||
are removed again.
|
||||
Strong exception safety: if an exception occurs, the original value stays intact.
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
||||
@@ -406,6 +406,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
||||
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
||||
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
||||
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
|
||||
|
||||
### Compatible types
|
||||
|
||||
|
||||
@@ -467,11 +467,13 @@ class binary_reader
|
||||
|
||||
const std::size_t element_type_parse_position = chars_read;
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on: they are skipped unless
|
||||
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||
// and those names are not passed on: they are skipped, and only
|
||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
||||
const bool is_object = container_stack.back().is_object;
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
||||
? get_bson_cstr(key, is_object)
|
||||
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -495,7 +497,7 @@ class binary_reader
|
||||
@param[in] store whether the characters are needed; if not (the names of
|
||||
an array's elements), they are skipped and @a result may
|
||||
be left unchanged, and they are not checked against
|
||||
@ref error_handler
|
||||
@ref error_handler (@ref check_bson_cstr checks them)
|
||||
@return `true` if the \\x00-byte indicating the end of the string was
|
||||
encountered before the EOF; false` indicates an unexpected EOF.
|
||||
*/
|
||||
@@ -554,6 +556,42 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip a C-style string that error_handler_t::strict must check
|
||||
|
||||
The names of an array's elements are not passed on. On contiguous input,
|
||||
a well-formed name is checked in place and skipped without being copied.
|
||||
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
||||
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
||||
|
||||
@param[in,out] result an empty string, which may receive the name
|
||||
@return whether the name was read and is well-formed
|
||||
*/
|
||||
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining != 0)
|
||||
{
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
||||
{
|
||||
// consume the string and its \x00-byte, as get_bson_cstr does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
current = 0x00;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: the name is read and checked byte by byte
|
||||
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
||||
{
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
|
||||
@@ -47,6 +47,7 @@ class output_buffer
|
||||
: m_out(sized(out, estimate))
|
||||
, m_pos(&m_out[0])
|
||||
, m_end(m_pos + m_out.size())
|
||||
, m_reserved((std::max)(estimate, m_out.size()))
|
||||
{}
|
||||
|
||||
void finish()
|
||||
@@ -116,11 +117,12 @@ class output_buffer
|
||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||
// (a step does not go beyond the reserved estimate, so that a good
|
||||
// estimate is never copied to a larger allocation; not a clamp: the
|
||||
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||
if (size > m_out.capacity())
|
||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
||||
if (size > m_reserved)
|
||||
{
|
||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||
m_reserved = (std::max)(m_reserved * 2, size);
|
||||
m_out.reserve(m_reserved);
|
||||
}
|
||||
m_out.resize(size);
|
||||
m_pos = &m_out[0] + used;
|
||||
@@ -130,6 +132,9 @@ class output_buffer
|
||||
StringType& m_out;
|
||||
char* m_pos;
|
||||
char* m_end;
|
||||
/// what was last passed to reserve(), a lower bound of the capacity
|
||||
/// (StringType need not have a capacity() member)
|
||||
std::size_t m_reserved;
|
||||
};
|
||||
|
||||
/// The length of the run at s that dump() writes unchanged without
|
||||
|
||||
+24
-296
@@ -4935,277 +4935,23 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/// whether copying a value cannot throw: the types whose copy allocates nothing
|
||||
/// (a mask of the types, tested with one shift)
|
||||
static bool copy_cannot_throw(const basic_json& v) noexcept
|
||||
{
|
||||
constexpr unsigned scalar_types = (1U << static_cast<unsigned>(value_t::null)) |
|
||||
(1U << static_cast<unsigned>(value_t::boolean)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_integer)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_unsigned)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_float)) |
|
||||
(1U << static_cast<unsigned>(value_t::discarded));
|
||||
return ((1U << static_cast<unsigned>(v.m_data.m_type)) & scalar_types) != 0;
|
||||
}
|
||||
|
||||
/// Undoes an array insertion that did not complete: erases the elements
|
||||
/// appended at the end and sets the parents, which the container may have
|
||||
/// left stale when it reallocated. Does nothing on the success path but
|
||||
/// setting the parents.
|
||||
class array_append_rollback
|
||||
{
|
||||
public:
|
||||
array_append_rollback(basic_json* v, std::size_t old_size) noexcept
|
||||
: m_value(v), m_old_size(old_size) {}
|
||||
array_append_rollback(const array_append_rollback&) = delete;
|
||||
array_append_rollback& operator=(const array_append_rollback&) = delete;
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~array_append_rollback()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
auto& arr = *m_value->m_data.m_value.array;
|
||||
arr.erase(arr.begin() + static_cast<typename array_t::difference_type>(m_old_size), arr.end());
|
||||
}
|
||||
m_value->set_parents();
|
||||
}
|
||||
|
||||
private:
|
||||
basic_json* m_value;
|
||||
std::size_t m_old_size;
|
||||
bool m_committed = false;
|
||||
};
|
||||
|
||||
/// Inserts the elements that fill() appends to the array at pos. Copying
|
||||
/// elements into the middle of a vector (vector::insert) leaves the array
|
||||
/// changed when a copy throws. Appending is safe (elements are moved on
|
||||
/// reallocation, and moving a basic_json cannot throw): if fill() throws,
|
||||
/// the appended elements are erased again; if it succeeds, they are
|
||||
/// rotated into place, which cannot throw either.
|
||||
template<typename Fill>
|
||||
iterator insert_by_appending(const const_iterator& pos, Fill fill)
|
||||
{
|
||||
JSON_ASSERT(m_data.m_value.array != nullptr);
|
||||
auto& arr = *m_data.m_value.array;
|
||||
const auto old_size = arr.size();
|
||||
const auto insert_pos = std::distance(arr.begin(), pos.m_it.array_iterator);
|
||||
|
||||
array_append_rollback rollback(this, old_size);
|
||||
fill(arr);
|
||||
if (static_cast<std::size_t>(insert_pos) != old_size)
|
||||
{
|
||||
std::rotate(arr.begin() + insert_pos, arr.begin() + static_cast<typename array_t::difference_type>(old_size), arr.end());
|
||||
}
|
||||
rollback.commit();
|
||||
|
||||
iterator result(this);
|
||||
result.m_it.array_iterator = arr.begin() + insert_pos;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// inserts a copy of val after checking the arguments; copy before the array is touched
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_checked(const const_iterator& pos, const basic_json& val)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
basic_json tmp(val);
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
}
|
||||
|
||||
/// inserts cnt copies of val after checking the arguments
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_checked(const const_iterator& pos, size_type cnt, const basic_json& val)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
if (copy_cannot_throw(val))
|
||||
{
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
return insert_by_appending(pos, [cnt, &val](array_t& arr)
|
||||
{
|
||||
// no reserve(): val may refer to an element of this array
|
||||
for (size_type i = 0; i < cnt; ++i)
|
||||
{
|
||||
arr.push_back(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// inserts copies of the elements of the array range [first, last); appending
|
||||
/// them is no slower than vector::insert, also for numbers, so no type check
|
||||
template<typename ArrayIterator>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_range(const_iterator pos, ArrayIterator first, ArrayIterator last) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
return insert_by_appending(pos, [first, last](array_t& arr)
|
||||
{
|
||||
detail::reserve_array(arr, arr.size() + static_cast<std::size_t>(std::distance(first, last)), detail::priority_tag<1> {});
|
||||
for (auto it = first; it != last; ++it)
|
||||
{
|
||||
arr.push_back(*it);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// inserts member v at the end of the object, with a hint where the container takes one
|
||||
template<typename Object>
|
||||
static auto insert_member(Object& o, const typename Object::value_type& v, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(o.insert(o.end(), v), void())
|
||||
{
|
||||
o.insert(o.end(), v);
|
||||
}
|
||||
|
||||
template<typename Object>
|
||||
static void insert_member(Object& o, const typename Object::value_type& v, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
o.insert(v);
|
||||
}
|
||||
|
||||
/// Undoes an object range insertion into an empty object that did not
|
||||
/// complete: all members present were added by it, so clear the object.
|
||||
class object_clear_on_failure
|
||||
{
|
||||
public:
|
||||
explicit object_clear_on_failure(basic_json* v) noexcept : m_value(v) {}
|
||||
object_clear_on_failure(const object_clear_on_failure&) = delete;
|
||||
object_clear_on_failure& operator=(const object_clear_on_failure&) = delete;
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~object_clear_on_failure()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
m_value->m_data.m_value.object->clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
basic_json* m_value;
|
||||
bool m_committed = false;
|
||||
};
|
||||
|
||||
/// Undoes an object range insertion that did not complete: erases the
|
||||
/// members it added, then sets the parents. Which members were added is
|
||||
/// remembered as one bit per source member (no allocation for the first
|
||||
/// 256 members), so the happy path costs a size comparison per member.
|
||||
template<typename SourceIterator>
|
||||
class object_insert_rollback
|
||||
{
|
||||
public:
|
||||
object_insert_rollback(basic_json* v, SourceIterator first) : m_value(v), m_first(first) {}
|
||||
object_insert_rollback(const object_insert_rollback&) = delete;
|
||||
object_insert_rollback& operator=(const object_insert_rollback&) = delete;
|
||||
|
||||
/// call before each run of up to 64 members is inserted; may throw, which leaves the log complete
|
||||
void start_chunk()
|
||||
{
|
||||
if (m_count != 0)
|
||||
{
|
||||
if (m_flushed < inline_words)
|
||||
{
|
||||
m_inline[m_flushed] = m_word;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_more.push_back(m_word);
|
||||
}
|
||||
++m_flushed;
|
||||
m_word = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// call after a member was inserted (or found to exist)
|
||||
void record(bool added) noexcept
|
||||
{
|
||||
m_word |= static_cast<std::uint64_t>(added) << (m_count % 64);
|
||||
++m_count;
|
||||
}
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~object_insert_rollback()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
auto& object = *m_value->m_data.m_value.object;
|
||||
auto it = m_first;
|
||||
for (std::size_t i = 0; i < m_count; ++i, ++it)
|
||||
{
|
||||
const std::size_t chunk = i / 64;
|
||||
const std::uint64_t word = chunk >= m_flushed ? m_word : (chunk < inline_words ? m_inline[chunk] : m_more[chunk - inline_words]);
|
||||
if (((word >> (i % 64)) & 1U) != 0)
|
||||
{
|
||||
object.erase(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_value->set_parents();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t inline_words = 4;
|
||||
|
||||
basic_json* m_value;
|
||||
SourceIterator m_first;
|
||||
bool m_committed = false;
|
||||
std::size_t m_count = 0;
|
||||
std::size_t m_flushed = 0;
|
||||
std::uint64_t m_word = 0;
|
||||
std::uint64_t m_inline[inline_words] {};
|
||||
std::vector<std::uint64_t> m_more {};
|
||||
};
|
||||
|
||||
public:
|
||||
/// @brief inserts element into array
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/insert/
|
||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// The common cases are inlined: an array, a fitting iterator, and either
|
||||
// a value whose copy cannot throw or the end position (vector::insert
|
||||
// has no effects on failure there). Everything else, including the
|
||||
// exceptions, is out of line.
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array() || pos.m_object != this ||
|
||||
(!copy_cannot_throw(val) && pos.m_it.array_iterator != m_data.m_value.array->end())))
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
return insert_checked(pos, val);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
@@ -5235,12 +4981,19 @@ private:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/insert/
|
||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// see the single-element overload
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array() || pos.m_object != this || !copy_cannot_throw(val)))
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
return insert_checked(pos, cnt, val);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
@@ -5277,7 +5030,8 @@ private:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this);
|
||||
}
|
||||
|
||||
return insert_range(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||
}
|
||||
|
||||
/// @brief inserts elements from initializer list into array
|
||||
@@ -5330,34 +5084,8 @@ private:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
||||
}
|
||||
|
||||
// Strong exception safety: range insertion into the container offers
|
||||
// only the basic guarantee, so insert member by member (as the
|
||||
// container does) and, if one insertion throws, erase the members this
|
||||
// call added.
|
||||
auto& object = *m_data.m_value.object;
|
||||
if (object.empty())
|
||||
{
|
||||
// all members are new, so the container's own (faster) range
|
||||
// insertion can be used, and a failure leaves only new members
|
||||
object_clear_on_failure clear_on_failure(this);
|
||||
object.insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
||||
clear_on_failure.commit();
|
||||
set_parents();
|
||||
return;
|
||||
}
|
||||
|
||||
object_insert_rollback<decltype(first.m_it.object_iterator)> rollback(this, first.m_it.object_iterator);
|
||||
for (auto it = first.m_it.object_iterator; it != last.m_it.object_iterator;)
|
||||
{
|
||||
rollback.start_chunk();
|
||||
for (std::size_t i = 0; i < 64 && it != last.m_it.object_iterator; ++i, ++it)
|
||||
{
|
||||
const auto old_size = object.size();
|
||||
insert_member(object, *it, detail::priority_tag<1> {});
|
||||
rollback.record(object.size() != old_size);
|
||||
}
|
||||
}
|
||||
rollback.commit();
|
||||
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
|
||||
|
||||
Generated
+66
-300
@@ -18474,11 +18474,13 @@ class binary_reader
|
||||
|
||||
const std::size_t element_type_parse_position = chars_read;
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on: they are skipped unless
|
||||
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||
// and those names are not passed on: they are skipped, and only
|
||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
||||
const bool is_object = container_stack.back().is_object;
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
||||
? get_bson_cstr(key, is_object)
|
||||
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -18502,7 +18504,7 @@ class binary_reader
|
||||
@param[in] store whether the characters are needed; if not (the names of
|
||||
an array's elements), they are skipped and @a result may
|
||||
be left unchanged, and they are not checked against
|
||||
@ref error_handler
|
||||
@ref error_handler (@ref check_bson_cstr checks them)
|
||||
@return `true` if the \\x00-byte indicating the end of the string was
|
||||
encountered before the EOF; false` indicates an unexpected EOF.
|
||||
*/
|
||||
@@ -18561,6 +18563,42 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip a C-style string that error_handler_t::strict must check
|
||||
|
||||
The names of an array's elements are not passed on. On contiguous input,
|
||||
a well-formed name is checked in place and skipped without being copied.
|
||||
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
||||
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
||||
|
||||
@param[in,out] result an empty string, which may receive the name
|
||||
@return whether the name was read and is well-formed
|
||||
*/
|
||||
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining != 0)
|
||||
{
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
||||
{
|
||||
// consume the string and its \x00-byte, as get_bson_cstr does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
current = 0x00;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: the name is read and checked byte by byte
|
||||
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
||||
{
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
@@ -42727,277 +42765,23 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/// whether copying a value cannot throw: the types whose copy allocates nothing
|
||||
/// (a mask of the types, tested with one shift)
|
||||
static bool copy_cannot_throw(const basic_json& v) noexcept
|
||||
{
|
||||
constexpr unsigned scalar_types = (1U << static_cast<unsigned>(value_t::null)) |
|
||||
(1U << static_cast<unsigned>(value_t::boolean)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_integer)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_unsigned)) |
|
||||
(1U << static_cast<unsigned>(value_t::number_float)) |
|
||||
(1U << static_cast<unsigned>(value_t::discarded));
|
||||
return ((1U << static_cast<unsigned>(v.m_data.m_type)) & scalar_types) != 0;
|
||||
}
|
||||
|
||||
/// Undoes an array insertion that did not complete: erases the elements
|
||||
/// appended at the end and sets the parents, which the container may have
|
||||
/// left stale when it reallocated. Does nothing on the success path but
|
||||
/// setting the parents.
|
||||
class array_append_rollback
|
||||
{
|
||||
public:
|
||||
array_append_rollback(basic_json* v, std::size_t old_size) noexcept
|
||||
: m_value(v), m_old_size(old_size) {}
|
||||
array_append_rollback(const array_append_rollback&) = delete;
|
||||
array_append_rollback& operator=(const array_append_rollback&) = delete;
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~array_append_rollback()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
auto& arr = *m_value->m_data.m_value.array;
|
||||
arr.erase(arr.begin() + static_cast<typename array_t::difference_type>(m_old_size), arr.end());
|
||||
}
|
||||
m_value->set_parents();
|
||||
}
|
||||
|
||||
private:
|
||||
basic_json* m_value;
|
||||
std::size_t m_old_size;
|
||||
bool m_committed = false;
|
||||
};
|
||||
|
||||
/// Inserts the elements that fill() appends to the array at pos. Copying
|
||||
/// elements into the middle of a vector (vector::insert) leaves the array
|
||||
/// changed when a copy throws. Appending is safe (elements are moved on
|
||||
/// reallocation, and moving a basic_json cannot throw): if fill() throws,
|
||||
/// the appended elements are erased again; if it succeeds, they are
|
||||
/// rotated into place, which cannot throw either.
|
||||
template<typename Fill>
|
||||
iterator insert_by_appending(const const_iterator& pos, Fill fill)
|
||||
{
|
||||
JSON_ASSERT(m_data.m_value.array != nullptr);
|
||||
auto& arr = *m_data.m_value.array;
|
||||
const auto old_size = arr.size();
|
||||
const auto insert_pos = std::distance(arr.begin(), pos.m_it.array_iterator);
|
||||
|
||||
array_append_rollback rollback(this, old_size);
|
||||
fill(arr);
|
||||
if (static_cast<std::size_t>(insert_pos) != old_size)
|
||||
{
|
||||
std::rotate(arr.begin() + insert_pos, arr.begin() + static_cast<typename array_t::difference_type>(old_size), arr.end());
|
||||
}
|
||||
rollback.commit();
|
||||
|
||||
iterator result(this);
|
||||
result.m_it.array_iterator = arr.begin() + insert_pos;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// inserts a copy of val after checking the arguments; copy before the array is touched
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_checked(const const_iterator& pos, const basic_json& val)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
basic_json tmp(val);
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
}
|
||||
|
||||
/// inserts cnt copies of val after checking the arguments
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_checked(const const_iterator& pos, size_type cnt, const basic_json& val)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
if (copy_cannot_throw(val))
|
||||
{
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
return insert_by_appending(pos, [cnt, &val](array_t& arr)
|
||||
{
|
||||
// no reserve(): val may refer to an element of this array
|
||||
for (size_type i = 0; i < cnt; ++i)
|
||||
{
|
||||
arr.push_back(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// inserts copies of the elements of the array range [first, last); appending
|
||||
/// them is no slower than vector::insert, also for numbers, so no type check
|
||||
template<typename ArrayIterator>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
iterator insert_range(const_iterator pos, ArrayIterator first, ArrayIterator last) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
return insert_by_appending(pos, [first, last](array_t& arr)
|
||||
{
|
||||
detail::reserve_array(arr, arr.size() + static_cast<std::size_t>(std::distance(first, last)), detail::priority_tag<1> {});
|
||||
for (auto it = first; it != last; ++it)
|
||||
{
|
||||
arr.push_back(*it);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// inserts member v at the end of the object, with a hint where the container takes one
|
||||
template<typename Object>
|
||||
static auto insert_member(Object& o, const typename Object::value_type& v, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(o.insert(o.end(), v), void())
|
||||
{
|
||||
o.insert(o.end(), v);
|
||||
}
|
||||
|
||||
template<typename Object>
|
||||
static void insert_member(Object& o, const typename Object::value_type& v, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
o.insert(v);
|
||||
}
|
||||
|
||||
/// Undoes an object range insertion into an empty object that did not
|
||||
/// complete: all members present were added by it, so clear the object.
|
||||
class object_clear_on_failure
|
||||
{
|
||||
public:
|
||||
explicit object_clear_on_failure(basic_json* v) noexcept : m_value(v) {}
|
||||
object_clear_on_failure(const object_clear_on_failure&) = delete;
|
||||
object_clear_on_failure& operator=(const object_clear_on_failure&) = delete;
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~object_clear_on_failure()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
m_value->m_data.m_value.object->clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
basic_json* m_value;
|
||||
bool m_committed = false;
|
||||
};
|
||||
|
||||
/// Undoes an object range insertion that did not complete: erases the
|
||||
/// members it added, then sets the parents. Which members were added is
|
||||
/// remembered as one bit per source member (no allocation for the first
|
||||
/// 256 members), so the happy path costs a size comparison per member.
|
||||
template<typename SourceIterator>
|
||||
class object_insert_rollback
|
||||
{
|
||||
public:
|
||||
object_insert_rollback(basic_json* v, SourceIterator first) : m_value(v), m_first(first) {}
|
||||
object_insert_rollback(const object_insert_rollback&) = delete;
|
||||
object_insert_rollback& operator=(const object_insert_rollback&) = delete;
|
||||
|
||||
/// call before each run of up to 64 members is inserted; may throw, which leaves the log complete
|
||||
void start_chunk()
|
||||
{
|
||||
if (m_count != 0)
|
||||
{
|
||||
if (m_flushed < inline_words)
|
||||
{
|
||||
m_inline[m_flushed] = m_word;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_more.push_back(m_word);
|
||||
}
|
||||
++m_flushed;
|
||||
m_word = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// call after a member was inserted (or found to exist)
|
||||
void record(bool added) noexcept
|
||||
{
|
||||
m_word |= static_cast<std::uint64_t>(added) << (m_count % 64);
|
||||
++m_count;
|
||||
}
|
||||
|
||||
void commit() noexcept
|
||||
{
|
||||
m_committed = true;
|
||||
}
|
||||
|
||||
~object_insert_rollback()
|
||||
{
|
||||
if (!m_committed)
|
||||
{
|
||||
auto& object = *m_value->m_data.m_value.object;
|
||||
auto it = m_first;
|
||||
for (std::size_t i = 0; i < m_count; ++i, ++it)
|
||||
{
|
||||
const std::size_t chunk = i / 64;
|
||||
const std::uint64_t word = chunk >= m_flushed ? m_word : (chunk < inline_words ? m_inline[chunk] : m_more[chunk - inline_words]);
|
||||
if (((word >> (i % 64)) & 1U) != 0)
|
||||
{
|
||||
object.erase(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_value->set_parents();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t inline_words = 4;
|
||||
|
||||
basic_json* m_value;
|
||||
SourceIterator m_first;
|
||||
bool m_committed = false;
|
||||
std::size_t m_count = 0;
|
||||
std::size_t m_flushed = 0;
|
||||
std::uint64_t m_word = 0;
|
||||
std::uint64_t m_inline[inline_words] {};
|
||||
std::vector<std::uint64_t> m_more {};
|
||||
};
|
||||
|
||||
public:
|
||||
/// @brief inserts element into array
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/insert/
|
||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// The common cases are inlined: an array, a fitting iterator, and either
|
||||
// a value whose copy cannot throw or the end position (vector::insert
|
||||
// has no effects on failure there). Everything else, including the
|
||||
// exceptions, is out of line.
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array() || pos.m_object != this ||
|
||||
(!copy_cannot_throw(val) && pos.m_it.array_iterator != m_data.m_value.array->end())))
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
return insert_checked(pos, val);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
@@ -43027,12 +42811,19 @@ private:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/insert/
|
||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// see the single-element overload
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array() || pos.m_object != this || !copy_cannot_throw(val)))
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
return insert_checked(pos, cnt, val);
|
||||
throw_insert_wrong_type(*this);
|
||||
}
|
||||
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
@@ -43069,7 +42860,8 @@ private:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this);
|
||||
}
|
||||
|
||||
return insert_range(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||
}
|
||||
|
||||
/// @brief inserts elements from initializer list into array
|
||||
@@ -43122,34 +42914,8 @@ private:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
||||
}
|
||||
|
||||
// Strong exception safety: range insertion into the container offers
|
||||
// only the basic guarantee, so insert member by member (as the
|
||||
// container does) and, if one insertion throws, erase the members this
|
||||
// call added.
|
||||
auto& object = *m_data.m_value.object;
|
||||
if (object.empty())
|
||||
{
|
||||
// all members are new, so the container's own (faster) range
|
||||
// insertion can be used, and a failure leaves only new members
|
||||
object_clear_on_failure clear_on_failure(this);
|
||||
object.insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
||||
clear_on_failure.commit();
|
||||
set_parents();
|
||||
return;
|
||||
}
|
||||
|
||||
object_insert_rollback<decltype(first.m_it.object_iterator)> rollback(this, first.m_it.object_iterator);
|
||||
for (auto it = first.m_it.object_iterator; it != last.m_it.object_iterator;)
|
||||
{
|
||||
rollback.start_chunk();
|
||||
for (std::size_t i = 0; i < 64 && it != last.m_it.object_iterator; ++i, ++it)
|
||||
{
|
||||
const auto old_size = object.size();
|
||||
insert_member(object, *it, detail::priority_tag<1> {});
|
||||
rollback.record(object.size() != old_size);
|
||||
}
|
||||
}
|
||||
rollback.commit();
|
||||
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
|
||||
|
||||
Generated
+9
-4
@@ -8511,6 +8511,7 @@ class output_buffer
|
||||
: m_out(sized(out, estimate))
|
||||
, m_pos(&m_out[0])
|
||||
, m_end(m_pos + m_out.size())
|
||||
, m_reserved((std::max)(estimate, m_out.size()))
|
||||
{}
|
||||
|
||||
void finish()
|
||||
@@ -8580,11 +8581,12 @@ class output_buffer
|
||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||
// (a step does not go beyond the reserved estimate, so that a good
|
||||
// estimate is never copied to a larger allocation; not a clamp: the
|
||||
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||
if (size > m_out.capacity())
|
||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
||||
if (size > m_reserved)
|
||||
{
|
||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||
m_reserved = (std::max)(m_reserved * 2, size);
|
||||
m_out.reserve(m_reserved);
|
||||
}
|
||||
m_out.resize(size);
|
||||
m_pos = &m_out[0] + used;
|
||||
@@ -8594,6 +8596,9 @@ class output_buffer
|
||||
StringType& m_out;
|
||||
char* m_pos;
|
||||
char* m_end;
|
||||
/// what was last passed to reserve(), a lower bound of the capacity
|
||||
/// (StringType need not have a capacity() member)
|
||||
std::size_t m_reserved;
|
||||
};
|
||||
|
||||
/// The length of the run at s that dump() writes unchanged without
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <functional>
|
||||
#include <valarray>
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
@@ -805,209 +804,3 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
#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;
|
||||
}
|
||||
|
||||
// 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 exactly as it was, with
|
||||
// all parent pointers intact (copying a value asserts the invariant).
|
||||
template<class BasicJsonType>
|
||||
void check_strong_guarantee(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();
|
||||
|
||||
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.dump() == before);
|
||||
CHECK(children_name_their_parent(j));
|
||||
const BasicJsonType copy(j);
|
||||
CHECK(copy.dump() == before);
|
||||
}
|
||||
}
|
||||
|
||||
// the operation allocates, so at least one attempt must have failed
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
template<class BasicJsonType>
|
||||
void check_insert_strong_guarantee()
|
||||
{
|
||||
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 j;
|
||||
};
|
||||
|
||||
for (const bool at_end :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(at_end)
|
||||
|
||||
SECTION("insert(pos, const basic_json&)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, val);
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, basic_json&&)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(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_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, 3, val);
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, const_iterator, const_iterator)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, source.begin(), source.end());
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, initializer_list_t)")
|
||||
{
|
||||
check_strong_guarantee<json_t>(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_strong_guarantee<json_t>([]()
|
||||
{
|
||||
return json_t({{"a", 1}});
|
||||
}, [&](json_t& j)
|
||||
{
|
||||
j.insert(members.begin(), members.end());
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("some members exist already")
|
||||
{
|
||||
check_strong_guarantee<json_t>([]()
|
||||
{
|
||||
return json_t({{"a", 1}, {"n", std::string(64, 'x')}});
|
||||
}, [&](json_t& j)
|
||||
{
|
||||
j.insert(members.begin(), members.end());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("insert() provides the strong exception guarantee (#5986)")
|
||||
{
|
||||
SECTION("std::map-backed object_t")
|
||||
{
|
||||
check_insert_strong_guarantee<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
|
||||
SECTION("ordered_map-backed object_t")
|
||||
{
|
||||
check_insert_strong_guarantee<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -483,4 +484,56 @@ TEST_CASE("alternative string type")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("alternative string type with json_view")
|
||||
{
|
||||
// regression test for #5994: basic_json_view::dump() called
|
||||
// StringType::capacity(), which alt_string (and the documented StringType
|
||||
// requirements) do not provide
|
||||
using alt_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::read_only>;
|
||||
using alt_editable_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::editable>;
|
||||
|
||||
const char* text = "{\"a\":[1,\"x\\u00e4\"],\"b\":{\"c\":true,\"d\":null},\"e\":2.5}";
|
||||
const alt_json j = alt_json::parse(text);
|
||||
|
||||
SECTION("read-only document")
|
||||
{
|
||||
const alt_document d = alt_document::parse(text);
|
||||
CHECK(d.root().dump() == j.dump());
|
||||
CHECK(d.root().dump(2) == j.dump(2));
|
||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(d.root().materialize() == j);
|
||||
CHECK(d.root().contains(alt_string("b")));
|
||||
CHECK(d.root()[alt_string("b")][alt_string("c")].dump() == "true");
|
||||
}
|
||||
|
||||
SECTION("long output")
|
||||
{
|
||||
// more than one growth step of the output buffer
|
||||
alt_json big = alt_json::array();
|
||||
for (int i = 0; i < 20000; ++i)
|
||||
{
|
||||
big.push_back(alt_json::parse(text));
|
||||
}
|
||||
const alt_string big_text = big.dump();
|
||||
const alt_document d = alt_document::parse(big_text);
|
||||
CHECK(d.root().dump() == big_text);
|
||||
CHECK(d.root().dump(4) == big.dump(4));
|
||||
}
|
||||
|
||||
SECTION("editable document")
|
||||
{
|
||||
alt_editable_document d = alt_editable_document::parse(text);
|
||||
d.set(d.root(), "f", "new");
|
||||
d.push_back(d.root()[alt_string("a")], 3);
|
||||
alt_json expected = j;
|
||||
expected["f"] = "new";
|
||||
expected["a"].push_back(3);
|
||||
CHECK(d.root().materialize() == expected);
|
||||
CHECK(alt_json::parse(d.root().dump()) == expected);
|
||||
|
||||
const alt_editable_document loaded = alt_editable_document::load(d.save());
|
||||
CHECK(loaded.root().materialize() == expected);
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -23,6 +23,7 @@ using nlohmann::json;
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
@@ -1199,6 +1200,31 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
from_stream = e.what();
|
||||
}
|
||||
CHECK(from_vector == from_stream);
|
||||
|
||||
// error_handler_t::strict checks the array element names in place on
|
||||
// contiguous input and byte by byte otherwise; the names are
|
||||
// well-formed, so both give the same value or error as above
|
||||
std::string strict_from_vector;
|
||||
std::string strict_from_list;
|
||||
try
|
||||
{
|
||||
strict_from_vector = json::from_bson(input, true, true, json::error_handler_t::strict).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
strict_from_vector = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const std::list<std::uint8_t> list(input.begin(), input.end());
|
||||
strict_from_list = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
strict_from_list = e.what();
|
||||
}
|
||||
CHECK(strict_from_vector == from_vector);
|
||||
CHECK(strict_from_list == from_vector);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1227,6 +1253,18 @@ TEST_CASE("BSON array element names")
|
||||
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||
CHECK(json::from_bson(stream) == expected);
|
||||
|
||||
// input that is not contiguous reads the names byte by byte and must reject
|
||||
// the same name
|
||||
const std::list<std::uint8_t> list(bson.begin(), bson.end());
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
|
||||
// without exceptions, the rejected name ends the parse
|
||||
CHECK(json::from_bson(bson, true, false, json::error_handler_t::strict).is_discarded());
|
||||
|
||||
// well-formed names pass the check without being stored
|
||||
const json valid = {{"a", {true, false, {{"k", json::array({1, 2})}}}}};
|
||||
CHECK(json::from_bson(json::to_bson(valid), true, true, json::error_handler_t::strict) == valid);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Reference in new issue
Block a user