mirror of
https://github.com/nlohmann/json.git
synced 2026-08-20 16:13:19 +00:00
Tighten the write-buffer helpers after review
More of @gregmarr's review on the put_* split: - Reattach the put_chars() doc comment, which the new helpers had been inserted in front of, leaving it describing put_indent(). - Compute the literal length once in put_literal() instead of spelling N - 1 at each use. - Add put_string(str, start, end), which keeps the pointer arithmetic and the bounds assertions inside the function instead of at the call site. With dump_float()'s to_chars() output moved onto put_buffer() as well, put_chars() now has no callers outside put_string()/put_buffer(): nothing passes a bare pointer and a count any more. - Carry the indentation as std::size_t rather than unsigned int. It is a size, it is compared and combined with buffer sizes throughout, and the casts in put_indent() disappear. next_indent() keeps its assertion, which is far harder to trip on a 64-bit size_t but still reachable where that is 32 bits. No output change: pretty and compact dumps, binary values included, are byte-identical to develop. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -108,8 +108,8 @@ class serializer
|
||||
void dump(const BasicJsonType& val,
|
||||
const bool pretty_print,
|
||||
const bool ensure_ascii,
|
||||
const unsigned int indent_step,
|
||||
const unsigned int current_indent = 0)
|
||||
const std::size_t indent_step,
|
||||
const std::size_t current_indent = 0)
|
||||
{
|
||||
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
||||
flush();
|
||||
@@ -127,8 +127,8 @@ class serializer
|
||||
void dump_internal(const BasicJsonType& val,
|
||||
const bool pretty_print,
|
||||
const bool ensure_ascii,
|
||||
const unsigned int indent_step,
|
||||
const unsigned int current_indent = 0)
|
||||
const std::size_t indent_step,
|
||||
const std::size_t current_indent = 0)
|
||||
{
|
||||
switch (val.m_data.m_type)
|
||||
{
|
||||
@@ -385,11 +385,13 @@ class serializer
|
||||
@brief the indentation level to use for the children of the current value
|
||||
|
||||
A very large @a indent_step can wrap the unsigned accumulation on deep
|
||||
nesting, which would silently truncate the indentation.
|
||||
nesting, which would silently truncate the indentation. Far harder to reach
|
||||
now that the accumulator is a std::size_t, but still reachable where that is
|
||||
32 bits wide.
|
||||
*/
|
||||
static unsigned int next_indent(const unsigned int current_indent, const unsigned int indent_step)
|
||||
static std::size_t next_indent(const std::size_t current_indent, const std::size_t indent_step)
|
||||
{
|
||||
const unsigned int new_indent = current_indent + indent_step;
|
||||
const std::size_t new_indent = current_indent + indent_step;
|
||||
JSON_ASSERT(new_indent >= current_indent);
|
||||
return new_indent;
|
||||
}
|
||||
@@ -449,7 +451,7 @@ class serializer
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
put_chars(s.data() + i, run);
|
||||
put_string(s, i, i + run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += run;
|
||||
@@ -706,13 +708,6 @@ class serializer
|
||||
write_buffer[write_buffer_pos++] = c;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append @a length characters to the write buffer
|
||||
|
||||
Runs that do not fit the buffer are written straight through the output
|
||||
adapter (after flushing what is pending), so large string/number payloads
|
||||
are not copied an extra time.
|
||||
*/
|
||||
/*!
|
||||
@brief append @a indent indentation characters to the write buffer
|
||||
|
||||
@@ -725,7 +720,7 @@ class serializer
|
||||
flushing does not disturb what the buffer holds, so re-filling it between
|
||||
flushes would be redundant work.
|
||||
*/
|
||||
void put_indent(unsigned int indent)
|
||||
void put_indent(std::size_t indent)
|
||||
{
|
||||
// closing braces at the outermost level ask for no indentation at all
|
||||
if (indent == 0)
|
||||
@@ -738,10 +733,10 @@ class serializer
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
// whenever the indentation is narrower than the buffer, which is the
|
||||
// case for every sane indent_step
|
||||
const std::size_t head = (std::min)(static_cast<std::size_t>(indent), capacity - write_buffer_pos);
|
||||
const std::size_t head = (std::min)(indent, capacity - write_buffer_pos);
|
||||
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
||||
write_buffer_pos += head;
|
||||
indent -= static_cast<unsigned int>(head);
|
||||
indent -= head;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(indent == 0))
|
||||
{
|
||||
@@ -758,7 +753,7 @@ class serializer
|
||||
{
|
||||
write_buffer_pos = capacity;
|
||||
flush();
|
||||
indent -= static_cast<unsigned int>(capacity);
|
||||
indent -= capacity;
|
||||
}
|
||||
|
||||
// the buffer still holds indentation characters throughout, so the tail
|
||||
@@ -778,14 +773,30 @@ class serializer
|
||||
void put_literal(const char (&s)[N])
|
||||
{
|
||||
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
||||
static_assert(N - 1 < write_buffer_size, "string literal must fit into the write buffer");
|
||||
// the array bound counts the terminating NUL, which is not written
|
||||
constexpr std::size_t length = N - 1;
|
||||
static_assert(length < write_buffer_size, "string literal must fit into the write buffer");
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + (N - 1) > write_buffer.size()))
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, s, N - 1);
|
||||
write_buffer_pos += N - 1;
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
||||
write_buffer_pos += length;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the characters of @a str in [@a start, @a end)
|
||||
|
||||
Keeps the pointer arithmetic and the bounds checking inside the function
|
||||
rather than at the call site, which is all @ref put_chars could offer.
|
||||
*/
|
||||
template<typename StringType>
|
||||
void put_string(const StringType& str, std::size_t start, std::size_t end)
|
||||
{
|
||||
JSON_ASSERT(start <= end);
|
||||
JSON_ASSERT(end <= str.size());
|
||||
put_chars(str.data() + start, end - start);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -801,6 +812,16 @@ class serializer
|
||||
put_chars(buffer.data(), length);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append @a length characters to the write buffer
|
||||
|
||||
Runs that do not fit the buffer are written straight through the output
|
||||
adapter (after flushing what is pending), so large string/number payloads
|
||||
are not copied an extra time.
|
||||
|
||||
The callers all reach this through @ref put_literal, @ref put_buffer or
|
||||
@ref put_string, which each derive the length from something that knows it.
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void put_chars(const char* s, std::size_t length)
|
||||
{
|
||||
@@ -1050,7 +1071,7 @@ class serializer
|
||||
auto* begin = number_buffer.data();
|
||||
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
||||
|
||||
put_chars(begin, static_cast<size_t>(end - begin));
|
||||
put_buffer(number_buffer, static_cast<std::size_t>(end - begin));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
|
||||
@@ -1345,7 +1345,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
if (indent >= 0)
|
||||
{
|
||||
s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
|
||||
s.dump(*this, true, ensure_ascii, static_cast<std::size_t>(indent));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user