mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 11:17:13 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92dc52162e | ||
|
|
9d15d23131 | ||
|
|
1b84568e5e | ||
|
|
57424ab0b5 |
No files matched your search
@@ -12,6 +12,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
@@ -1854,18 +1855,23 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] rest the literal text after its first character, which
|
||||||
@param[in] length the length of the passed literal text
|
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
|
@param[in] return_type the token type to return on success
|
||||||
*/
|
*/
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
template<std::size_t N>
|
||||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type)
|
||||||
token_type return_type)
|
|
||||||
{
|
{
|
||||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
constexpr std::size_t length = N - 1;
|
||||||
for (std::size_t i = 1; i < length; ++i)
|
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";
|
error_message = "invalid literal";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -1874,6 +1880,27 @@ scan_number_done:
|
|||||||
return return_type;
|
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
|
// input management
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -2499,21 +2526,14 @@ scan_number_done:
|
|||||||
return token_type::value_separator;
|
return token_type::value_separator;
|
||||||
|
|
||||||
// literals
|
// literals
|
||||||
|
// (the text after the first character; no local arrays, which
|
||||||
|
// would give this function a stack canary on every token)
|
||||||
case 't':
|
case 't':
|
||||||
{
|
return scan_literal("rue", token_type::literal_true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
case 'f':
|
case 'f':
|
||||||
{
|
return scan_literal("alse", token_type::literal_false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
case 'n':
|
case 'n':
|
||||||
{
|
return scan_literal("ull", token_type::literal_null);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
|
|||||||
@@ -244,8 +244,9 @@ class parser
|
|||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
std::vector<bool> states;
|
// push_back() is a call with GCC)
|
||||||
|
std::vector<std::uint8_t> states;
|
||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
@@ -290,7 +291,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// remember we are now inside an object
|
||||||
states.push_back(false);
|
states.push_back(0);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -315,7 +316,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an array
|
// remember we are now inside an array
|
||||||
states.push_back(true);
|
states.push_back(1);
|
||||||
|
|
||||||
// parse values (no need to call get_token)
|
// parse values (no need to call get_token)
|
||||||
continue;
|
continue;
|
||||||
@@ -432,7 +433,7 @@ class parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.back()) // array
|
if (states.back() != 0) // array
|
||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
|
|||||||
Generated
+46
-25
@@ -9519,6 +9519,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
@@ -13802,18 +13803,23 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] rest the literal text after its first character, which
|
||||||
@param[in] length the length of the passed literal text
|
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
|
@param[in] return_type the token type to return on success
|
||||||
*/
|
*/
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
template<std::size_t N>
|
||||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type)
|
||||||
token_type return_type)
|
|
||||||
{
|
{
|
||||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
constexpr std::size_t length = N - 1;
|
||||||
for (std::size_t i = 1; i < length; ++i)
|
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";
|
error_message = "invalid literal";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -13822,6 +13828,27 @@ scan_number_done:
|
|||||||
return return_type;
|
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
|
// input management
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -14447,21 +14474,14 @@ scan_number_done:
|
|||||||
return token_type::value_separator;
|
return token_type::value_separator;
|
||||||
|
|
||||||
// literals
|
// literals
|
||||||
|
// (the text after the first character; no local arrays, which
|
||||||
|
// would give this function a stack canary on every token)
|
||||||
case 't':
|
case 't':
|
||||||
{
|
return scan_literal("rue", token_type::literal_true);
|
||||||
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);
|
|
||||||
}
|
|
||||||
case 'f':
|
case 'f':
|
||||||
{
|
return scan_literal("alse", token_type::literal_false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
case 'n':
|
case 'n':
|
||||||
{
|
return scan_literal("ull", token_type::literal_null);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
@@ -20575,8 +20595,9 @@ class parser
|
|||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
std::vector<bool> states;
|
// push_back() is a call with GCC)
|
||||||
|
std::vector<std::uint8_t> states;
|
||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
@@ -20621,7 +20642,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// remember we are now inside an object
|
||||||
states.push_back(false);
|
states.push_back(0);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -20646,7 +20667,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an array
|
// remember we are now inside an array
|
||||||
states.push_back(true);
|
states.push_back(1);
|
||||||
|
|
||||||
// parse values (no need to call get_token)
|
// parse values (no need to call get_token)
|
||||||
continue;
|
continue;
|
||||||
@@ -20763,7 +20784,7 @@ class parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.back()) // array
|
if (states.back() != 0) // array
|
||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
|
#include <cctype> // toupper
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cmath> // isfinite
|
#include <cmath> // isfinite
|
||||||
@@ -596,6 +597,63 @@ TEST_CASE("lexer number fast path")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer literal fast path")
|
||||||
|
{
|
||||||
|
// For contiguous input, the rest of a literal is compared at once, and a
|
||||||
|
// mismatch is left to the byte path, which reports the error at the
|
||||||
|
// character that differs. Every truncation and every wrong character of
|
||||||
|
// each literal, at the end of the input or followed by more text, must
|
||||||
|
// give the byte path's result (a std::list is read one character at a
|
||||||
|
// time).
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
return (byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc)).dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (const std::string literal :
|
||||||
|
{"true", "false", "null"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::vector<std::string> variants;
|
||||||
|
for (std::size_t length = 1; length <= literal.size(); ++length)
|
||||||
|
{
|
||||||
|
variants.push_back(literal.substr(0, length));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 1; i < literal.size(); ++i)
|
||||||
|
{
|
||||||
|
std::string changed = literal;
|
||||||
|
changed[i] = 'X';
|
||||||
|
variants.push_back(changed);
|
||||||
|
changed[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(literal[i])));
|
||||||
|
variants.push_back(changed);
|
||||||
|
}
|
||||||
|
for (const auto& variant : variants)
|
||||||
|
{
|
||||||
|
for (const std::string& doc :
|
||||||
|
{
|
||||||
|
variant, "[" + variant + "]", "[" + variant, variant + " ", variant + "1", "{\"k\":" + variant + "}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches)
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("lexer string fast path")
|
TEST_CASE("lexer string fast path")
|
||||||
{
|
{
|
||||||
// Build a byte string from explicit values: a hex escape in a string
|
// Build a byte string from explicit values: a hex escape in a string
|
||||||
|
|||||||
@@ -171,7 +171,8 @@ std::vector<std::string> corpus()
|
|||||||
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
|
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
|
||||||
R"("\ud800 lone surrogate")",
|
R"("\ud800 lone surrogate")",
|
||||||
// literals
|
// literals
|
||||||
"true", "false", "null", "tru", "nul", "falsey",
|
"true", "false", "null", "tru", "nul", "falsey", "[true,false,null]", "nulL", "truex", "[fals e]", "t",
|
||||||
|
"true1", "null null", "[false]",
|
||||||
// structures
|
// structures
|
||||||
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
|
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
|
||||||
// pretty-printed
|
// pretty-printed
|
||||||
|
|||||||
Reference in new issue
Block a user