@@ -6811,7 +6811,7 @@ NLOHMANN_JSON_NAMESPACE_END
68116811#include <array> // array
68126812#include <cmath> // ldexp
68136813#include <cstddef> // size_t
6814- #include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
6814+ #include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
68156815#include <cstdio> // snprintf
68166816#include <cstring> // memcpy
68176817#include <iterator> // back_inserter
@@ -7035,9 +7035,51 @@ class iterator_input_adapter
70357035 out.insert(out.end(), from, to);
70367036 }
70377037
7038- // for general iterators, we cannot really do something better than falling back to processing the range one-by-one
7038+ // Copy up to count * sizeof(T) bytes into dest, returning the number of
7039+ // bytes actually read. For contiguous iterators (e.g. pointers) this is a
7040+ // single std::memcpy; for general iterators we fall back to processing the
7041+ // range one-by-one.
70397042 template<class T>
70407043 std::size_t get_elements(T* dest, std::size_t count = 1)
7044+ {
7045+ return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
7046+ }
7047+
7048+ private:
7049+ // whether IteratorType refers to a contiguous range and therefore supports
7050+ // a std::memcpy fast path (pointers always do; in C++20 we can also detect
7051+ // library iterators such as those of std::vector and std::string)
7052+ static constexpr bool iterator_is_contiguous =
7053+ #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
7054+ std::contiguous_iterator<IteratorType> ||
7055+ #endif
7056+ std::is_pointer<IteratorType>::value;
7057+
7058+ // contiguous fast path: bulk copy the remaining range with std::memcpy
7059+ template<class T>
7060+ std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
7061+ {
7062+ const std::size_t wanted = count * sizeof(T);
7063+ const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
7064+ const std::size_t copied = (std::min)(wanted, available);
7065+ if (JSON_HEDLEY_LIKELY(copied != 0))
7066+ {
7067+ // the copy must stay within both buffers: the caller-provided
7068+ // destination holds `wanted` bytes and the remaining input range
7069+ // holds `available` bytes, and `copied` is the minimum of the two
7070+ JSON_ASSERT(copied <= wanted); // does not overrun the destination
7071+ JSON_ASSERT(copied <= available); // does not read past the input end
7072+ // &*current yields the raw address for both raw pointers and
7073+ // non-pointer contiguous iterators (e.g. std::vector's iterator)
7074+ std::memcpy(dest, &*current, copied);
7075+ std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
7076+ }
7077+ return copied;
7078+ }
7079+
7080+ // general fallback: copy the range one element at a time
7081+ template<class T>
7082+ std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
70417083 {
70427084 auto* ptr = reinterpret_cast<char*>(dest);
70437085 for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
@@ -7055,7 +7097,6 @@ class iterator_input_adapter
70557097 return count * sizeof(T);
70567098 }
70577099
7058- private:
70597100 IteratorType begin;
70607101 IteratorType current;
70617102 IteratorType end;
@@ -13193,18 +13234,7 @@ class binary_reader
1319313234 const NumberType len,
1319413235 string_t& result)
1319513236 {
13196- bool success = true;
13197- for (NumberType i = 0; i < len; i++)
13198- {
13199- get();
13200- if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
13201- {
13202- success = false;
13203- break;
13204- }
13205- result.push_back(static_cast<typename string_t::value_type>(current));
13206- }
13207- return success;
13237+ return get_bytes(format, len, "string", result);
1320813238 }
1320913239
1321013240 /*!
@@ -13226,18 +13256,66 @@ class binary_reader
1322613256 const NumberType len,
1322713257 binary_t& result)
1322813258 {
13229- bool success = true;
13230- for (NumberType i = 0; i < len; i++)
13231- {
13232- get();
13233- if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
13234- {
13235- success = false;
13236- break;
13237- }
13238- result.push_back(static_cast<typename binary_t::value_type>(current));
13259+ return get_bytes(format, len, "binary", result);
13260+ }
13261+
13262+ /*!
13263+ @brief read @a len bytes from the input into a string or byte container
13264+
13265+ @tparam NumberType the type of the length
13266+ @tparam ContainerType the destination container (string_t or binary_t)
13267+ @param[in] format the current format (for diagnostics)
13268+ @param[in] len number of bytes to read
13269+ @param[in] context further context information (for diagnostics)
13270+ @param[out] result container the bytes are appended to
13271+
13272+ @return whether reading completed
13273+
13274+ @note We cannot reserve @a len bytes for the result up front, because
13275+ @a len may be far larger than the actual input. Instead we read in
13276+ bounded chunks, so the peak allocation is capped regardless of the
13277+ claimed length while the per-byte loop is replaced by block copies
13278+ (a std::memcpy for contiguous inputs). @ref unexpect_eof() still
13279+ detects a premature end of input.
13280+ */
13281+ template<typename NumberType, typename ContainerType>
13282+ bool get_bytes(const input_format_t format,
13283+ NumberType len,
13284+ const char* context,
13285+ ContainerType& result)
13286+ {
13287+ // upper bound on the number of bytes read (and allocated) per chunk
13288+ constexpr std::size_t chunk_size = 4096;
13289+
13290+ while (len > 0)
13291+ {
13292+ // number of bytes to read this iteration: min(chunk_size, len),
13293+ // computed without truncating chunk_size to a narrow NumberType
13294+ const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
13295+ ? static_cast<std::size_t>(len)
13296+ : chunk_size;
13297+ const std::size_t old_size = result.size();
13298+ result.resize(old_size + wanted);
13299+ // resize() is required to make size() exactly old_size + wanted;
13300+ // that is the room get_elements() is allowed to write into
13301+ JSON_ASSERT(result.size() == old_size + wanted);
13302+ const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
13303+ chars_read += bytes_read;
13304+ if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
13305+ {
13306+ // premature end of input: shrink to what was actually read and
13307+ // report the failure at the first missing byte (same position
13308+ // accounting as get_to() for partial number reads)
13309+ result.resize(old_size + bytes_read);
13310+ ++chars_read;
13311+ current = char_traits<char_type>::eof();
13312+ return unexpect_eof(format, context);
13313+ }
13314+ // a full chunk was read; get_elements() never returns more than requested
13315+ JSON_ASSERT(bytes_read == wanted);
13316+ len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
1323913317 }
13240- return success ;
13318+ return true ;
1324113319 }
1324213320
1324313321 /*!
0 commit comments