mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 03:07:12 +00:00
Match literals without a stack array and keep parser states in bytes
scan_after_whitespace() built the expected text of true, false, and null in local std::array objects. With -fstack-protector-strong (the default of Ubuntu's GCC, among others), a local array gives the function a stack canary check, which then ran on every token. scan_literal() now takes the text after the first character as a string literal, with its length as a template parameter; for contiguous input it compares the rest at once and falls back to the byte loop on a mismatch, so errors are reported at the same character. It is out of line, which keeps the token dispatch small. The parser kept the nesting of arrays and objects in a std::vector<bool>, whose push_back() GCC calls out of line; it is a std::vector<std::uint8_t> now. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
5 files changed
+154
-53
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstring> // memcmp
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
#include <utility> // move
|
||||
@@ -1841,18 +1842,23 @@ scan_number_done:
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] literal_text the literal text to expect
|
||||
@param[in] length the length of the passed literal text
|
||||
@param[in] return_type the token type to return on success
|
||||
@param[in] rest the literal text after its first character, which
|
||||
is current (its length is a template parameter, so
|
||||
that the comparison of the contiguous fast path is
|
||||
a fixed-size load, not a call of memcmp)
|
||||
@param[in] return_type the token type to return on success
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
||||
token_type return_type)
|
||||
template<std::size_t N>
|
||||
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type)
|
||||
{
|
||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
||||
for (std::size_t i = 1; i < length; ++i)
|
||||
constexpr std::size_t length = N - 1;
|
||||
if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))
|
||||
return return_type;
|
||||
}
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
|
||||
{
|
||||
error_message = "invalid literal";
|
||||
return token_type::parse_error;
|
||||
@@ -1861,6 +1867,27 @@ scan_number_done:
|
||||
return return_type;
|
||||
}
|
||||
|
||||
/// contiguous input: compare the rest of a literal at once; false (and
|
||||
/// nothing consumed) if it does not match, so that the byte path reports
|
||||
/// the error at the right character
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||
{
|
||||
if (next_unget || ia.bulk_remaining() < length
|
||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
|
||||
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// input management
|
||||
/////////////////////
|
||||
@@ -2486,21 +2513,14 @@ scan_number_done:
|
||||
return token_type::value_separator;
|
||||
|
||||
// literals
|
||||
// (the text after the first character; no local arrays, which
|
||||
// would give this function a stack canary on every token)
|
||||
case 't':
|
||||
{
|
||||
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
|
||||
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
|
||||
}
|
||||
return scan_literal("rue", token_type::literal_true);
|
||||
case 'f':
|
||||
{
|
||||
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
|
||||
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
|
||||
}
|
||||
return scan_literal("alse", token_type::literal_false);
|
||||
case 'n':
|
||||
{
|
||||
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
|
||||
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
|
||||
}
|
||||
return scan_literal("ull", token_type::literal_null);
|
||||
|
||||
// string
|
||||
case '\"':
|
||||
|
||||
@@ -244,8 +244,9 @@ class parser
|
||||
bool sax_parse_internal(SAX* sax)
|
||||
{
|
||||
// stack to remember the hierarchy of structured values we are parsing
|
||||
// true = array; false = object
|
||||
std::vector<bool> states;
|
||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||
// push_back() is a call with GCC)
|
||||
std::vector<std::uint8_t> states;
|
||||
// value to avoid a goto (see comment where set to true)
|
||||
bool skip_to_state_evaluation = false;
|
||||
|
||||
@@ -290,7 +291,7 @@ class parser
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
states.push_back(0);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
@@ -315,7 +316,7 @@ class parser
|
||||
}
|
||||
|
||||
// remember we are now inside an array
|
||||
states.push_back(true);
|
||||
states.push_back(1);
|
||||
|
||||
// parse values (no need to call get_token)
|
||||
continue;
|
||||
@@ -432,7 +433,7 @@ class parser
|
||||
return true;
|
||||
}
|
||||
|
||||
if (states.back()) // array
|
||||
if (states.back() != 0) // array
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
|
||||
Reference in new issue
Block a user