mirror of
https://github.com/nlohmann/json.git
synced 2026-09-06 00:08:00 +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,
|
void dump(const BasicJsonType& val,
|
||||||
const bool pretty_print,
|
const bool pretty_print,
|
||||||
const bool ensure_ascii,
|
const bool ensure_ascii,
|
||||||
const unsigned int indent_step,
|
const std::size_t indent_step,
|
||||||
const unsigned int current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
||||||
flush();
|
flush();
|
||||||
@@ -127,8 +127,8 @@ class serializer
|
|||||||
void dump_internal(const BasicJsonType& val,
|
void dump_internal(const BasicJsonType& val,
|
||||||
const bool pretty_print,
|
const bool pretty_print,
|
||||||
const bool ensure_ascii,
|
const bool ensure_ascii,
|
||||||
const unsigned int indent_step,
|
const std::size_t indent_step,
|
||||||
const unsigned int current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
switch (val.m_data.m_type)
|
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
|
@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
|
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);
|
JSON_ASSERT(new_indent >= current_indent);
|
||||||
return new_indent;
|
return new_indent;
|
||||||
}
|
}
|
||||||
@@ -449,7 +451,7 @@ class serializer
|
|||||||
put_buffer(string_buffer, bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_chars(s.data() + i, run);
|
put_string(s, i, i + run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -706,13 +708,6 @@ class serializer
|
|||||||
write_buffer[write_buffer_pos++] = c;
|
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
|
@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
|
flushing does not disturb what the buffer holds, so re-filling it between
|
||||||
flushes would be redundant work.
|
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
|
// closing braces at the outermost level ask for no indentation at all
|
||||||
if (indent == 0)
|
if (indent == 0)
|
||||||
@@ -738,10 +733,10 @@ class serializer
|
|||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
// whenever the indentation is narrower than the buffer, which is the
|
// whenever the indentation is narrower than the buffer, which is the
|
||||||
// case for every sane indent_step
|
// 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);
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
||||||
write_buffer_pos += head;
|
write_buffer_pos += head;
|
||||||
indent -= static_cast<unsigned int>(head);
|
indent -= head;
|
||||||
|
|
||||||
if (JSON_HEDLEY_LIKELY(indent == 0))
|
if (JSON_HEDLEY_LIKELY(indent == 0))
|
||||||
{
|
{
|
||||||
@@ -758,7 +753,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
write_buffer_pos = capacity;
|
write_buffer_pos = capacity;
|
||||||
flush();
|
flush();
|
||||||
indent -= static_cast<unsigned int>(capacity);
|
indent -= capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the buffer still holds indentation characters throughout, so the tail
|
// the buffer still holds indentation characters throughout, so the tail
|
||||||
@@ -778,14 +773,30 @@ class serializer
|
|||||||
void put_literal(const char (&s)[N])
|
void put_literal(const char (&s)[N])
|
||||||
{
|
{
|
||||||
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
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();
|
flush();
|
||||||
}
|
}
|
||||||
std::memcpy(write_buffer.data() + write_buffer_pos, s, N - 1);
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
||||||
write_buffer_pos += N - 1;
|
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);
|
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)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
void put_chars(const char* s, std::size_t length)
|
void put_chars(const char* s, std::size_t length)
|
||||||
{
|
{
|
||||||
@@ -1050,7 +1071,7 @@ class serializer
|
|||||||
auto* begin = number_buffer.data();
|
auto* begin = number_buffer.data();
|
||||||
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
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)
|
JSON_HEDLEY_NON_NULL(1)
|
||||||
|
|||||||
@@ -1345,7 +1345,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
if (indent >= 0)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21233,8 +21233,8 @@ class serializer
|
|||||||
void dump(const BasicJsonType& val,
|
void dump(const BasicJsonType& val,
|
||||||
const bool pretty_print,
|
const bool pretty_print,
|
||||||
const bool ensure_ascii,
|
const bool ensure_ascii,
|
||||||
const unsigned int indent_step,
|
const std::size_t indent_step,
|
||||||
const unsigned int current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
||||||
flush();
|
flush();
|
||||||
@@ -21252,8 +21252,8 @@ class serializer
|
|||||||
void dump_internal(const BasicJsonType& val,
|
void dump_internal(const BasicJsonType& val,
|
||||||
const bool pretty_print,
|
const bool pretty_print,
|
||||||
const bool ensure_ascii,
|
const bool ensure_ascii,
|
||||||
const unsigned int indent_step,
|
const std::size_t indent_step,
|
||||||
const unsigned int current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
switch (val.m_data.m_type)
|
switch (val.m_data.m_type)
|
||||||
{
|
{
|
||||||
@@ -21510,11 +21510,13 @@ class serializer
|
|||||||
@brief the indentation level to use for the children of the current value
|
@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
|
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);
|
JSON_ASSERT(new_indent >= current_indent);
|
||||||
return new_indent;
|
return new_indent;
|
||||||
}
|
}
|
||||||
@@ -21574,7 +21576,7 @@ class serializer
|
|||||||
put_buffer(string_buffer, bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_chars(s.data() + i, run);
|
put_string(s, i, i + run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -21831,13 +21833,6 @@ class serializer
|
|||||||
write_buffer[write_buffer_pos++] = c;
|
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
|
@brief append @a indent indentation characters to the write buffer
|
||||||
|
|
||||||
@@ -21850,7 +21845,7 @@ class serializer
|
|||||||
flushing does not disturb what the buffer holds, so re-filling it between
|
flushing does not disturb what the buffer holds, so re-filling it between
|
||||||
flushes would be redundant work.
|
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
|
// closing braces at the outermost level ask for no indentation at all
|
||||||
if (indent == 0)
|
if (indent == 0)
|
||||||
@@ -21863,10 +21858,10 @@ class serializer
|
|||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
// whenever the indentation is narrower than the buffer, which is the
|
// whenever the indentation is narrower than the buffer, which is the
|
||||||
// case for every sane indent_step
|
// 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);
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
||||||
write_buffer_pos += head;
|
write_buffer_pos += head;
|
||||||
indent -= static_cast<unsigned int>(head);
|
indent -= head;
|
||||||
|
|
||||||
if (JSON_HEDLEY_LIKELY(indent == 0))
|
if (JSON_HEDLEY_LIKELY(indent == 0))
|
||||||
{
|
{
|
||||||
@@ -21883,7 +21878,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
write_buffer_pos = capacity;
|
write_buffer_pos = capacity;
|
||||||
flush();
|
flush();
|
||||||
indent -= static_cast<unsigned int>(capacity);
|
indent -= capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the buffer still holds indentation characters throughout, so the tail
|
// the buffer still holds indentation characters throughout, so the tail
|
||||||
@@ -21903,14 +21898,30 @@ class serializer
|
|||||||
void put_literal(const char (&s)[N])
|
void put_literal(const char (&s)[N])
|
||||||
{
|
{
|
||||||
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
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();
|
flush();
|
||||||
}
|
}
|
||||||
std::memcpy(write_buffer.data() + write_buffer_pos, s, N - 1);
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
||||||
write_buffer_pos += N - 1;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -21926,6 +21937,16 @@ class serializer
|
|||||||
put_chars(buffer.data(), length);
|
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)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
void put_chars(const char* s, std::size_t length)
|
void put_chars(const char* s, std::size_t length)
|
||||||
{
|
{
|
||||||
@@ -22175,7 +22196,7 @@ class serializer
|
|||||||
auto* begin = number_buffer.data();
|
auto* begin = number_buffer.data();
|
||||||
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
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)
|
JSON_HEDLEY_NON_NULL(1)
|
||||||
@@ -24032,7 +24053,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
if (indent >= 0)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user