Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704a324639 | ||
|
|
8e649abf57 | ||
|
|
2b2b3fcee7 |
No files matched your search
@@ -5,7 +5,9 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
||||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
|
earlier, instead of locking the file once and reading it with the unlocked stdio functions. This also turns off the
|
||||||
|
scan of the `FILE`'s buffer in bulk (see below), which [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)
|
||||||
|
does not affect.
|
||||||
|
|
||||||
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
||||||
Only the speed and the locking of the file differ (see the notes on locking below).
|
Only the speed and the locking of the file differ (see the notes on locking below).
|
||||||
@@ -37,9 +39,17 @@ The default value is `0`: the file is locked once and read with the unlocked std
|
|||||||
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
||||||
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
||||||
|
|
||||||
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
|
With glibc and the Apple C library, the parser also scans the characters that the `FILE` already holds in its
|
||||||
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
|
buffer in bulk, as it scans the get area of a stream (see
|
||||||
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
|
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)): it reads the same fields of the `FILE` that
|
||||||
|
the C library's own `getc_unlocked` reads, and it consumes exactly the characters that `getc_unlocked` would have
|
||||||
|
returned, so the file is left where it was left before.
|
||||||
|
|
||||||
|
Locking the file once makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3
|
||||||
|
times as fast with glibc, whose locking is cheaper. Scanning the buffer in bulk makes `parse()` another 1.7 to 2.6
|
||||||
|
times as fast (and `accept()` 2.9 to 4.4 times), about as fast as parsing from a `std::ifstream`. The binary
|
||||||
|
formats ([`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), ...) read
|
||||||
|
from a `FILE*` the same way.
|
||||||
|
|
||||||
!!! note "Locking"
|
!!! note "Locking"
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ them faster than others:
|
|||||||
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||||
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
||||||
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
||||||
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
|
| `#!cpp FILE*` | with glibc and the Apple C library, the characters the `FILE` holds in its buffer are scanned in bulk like a stream's; elsewhere one character at a time; the file is locked once for the whole parse |
|
||||||
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
||||||
|
|
||||||
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scan
|
|||||||
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||||
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||||
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||||
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
area is read one character at a time. A `#!cpp FILE*` is locked once per parse instead of once per character, and with
|
||||||
locks the file once per parse instead of once per character. See
|
glibc and the Apple C library, the characters in its buffer are scanned in bulk as a stream's get area is. See
|
||||||
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
||||||
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
||||||
parsing an equivalent `#!cpp std::string`.
|
parsing an equivalent `#!cpp std::string`.
|
||||||
|
|||||||
@@ -4887,14 +4887,6 @@ class binary_reader
|
|||||||
const char* context,
|
const char* context,
|
||||||
ContainerType& result)
|
ContainerType& result)
|
||||||
{
|
{
|
||||||
// a string that is in the block of input as a whole is appended from
|
|
||||||
// it, without the zero-filling resize() below
|
|
||||||
constexpr bool from_block = bulk_scan && std::is_same<ContainerType, string_t>::value;
|
|
||||||
if (append_from_block(len, result, std::integral_constant<bool, from_block> {}))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// upper bound on the number of bytes read (and allocated) per chunk
|
// upper bound on the number of bytes read (and allocated) per chunk
|
||||||
constexpr std::size_t chunk_size = 4096;
|
constexpr std::size_t chunk_size = 4096;
|
||||||
|
|
||||||
@@ -4931,36 +4923,6 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// append the @a len bytes at the current position to @a result, if the
|
|
||||||
/// input holds them in one block; false (with nothing read) otherwise
|
|
||||||
template<typename NumberType>
|
|
||||||
bool append_from_block(const NumberType len, string_t& result, std::true_type /*block*/)
|
|
||||||
{
|
|
||||||
if (len <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// (len > 0, so the unsigned type holds it; casting a signed char
|
|
||||||
// straight to std::size_t would trip bugprone-signed-char-misuse)
|
|
||||||
const auto ulen = static_cast<typename std::make_unsigned<NumberType>::type>(len);
|
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
|
||||||
if (static_cast<std::uintmax_t>(ulen) > static_cast<std::uintmax_t>(remaining))
|
|
||||||
{
|
|
||||||
return false; // (the loop reports the premature end)
|
|
||||||
}
|
|
||||||
const auto n = static_cast<std::size_t>(ulen);
|
|
||||||
result.append(reinterpret_cast<const typename string_t::value_type*>(ia.bulk_data()), n);
|
|
||||||
ia.bulk_skip(n);
|
|
||||||
chars_read += n;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename NumberType, typename ContainerType>
|
|
||||||
static bool append_from_block(const NumberType /*len*/, ContainerType& /*result*/, std::false_type /*block*/) noexcept
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] context further context information (for diagnostics)
|
@param[in] context further context information (for diagnostics)
|
||||||
@return whether the last read character is not EOF
|
@return whether the last read character is not EOF
|
||||||
|
|||||||
@@ -58,6 +58,19 @@
|
|||||||
#define JSON_UNLOCKED_FILE_IO 0
|
#define JSON_UNLOCKED_FILE_IO 0
|
||||||
#endif
|
#endif
|
||||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||||
|
|
||||||
|
// Whether file_input_adapter scans the buffer of the FILE directly, as
|
||||||
|
// getc_unlocked() reads it: the fields it uses are those that the C
|
||||||
|
// library's own getc_unlocked() macro uses (glibc: _IO_read_ptr and
|
||||||
|
// _IO_read_end; Apple: _p and _r). It needs the file locked for the whole
|
||||||
|
// parse, as JSON_UNLOCKED_FILE_IO == 1 has it (so JSON_DISABLE_UNLOCKED_FILE_IO
|
||||||
|
// and MemorySanitizer builds turn it off).
|
||||||
|
// (uClibc defines __GLIBC__ as well, but its FILE has other fields)
|
||||||
|
#if JSON_UNLOCKED_FILE_IO == 1 && ((defined(__GLIBC__) && !defined(__UCLIBC__)) || defined(__APPLE__))
|
||||||
|
#define JSON_FILE_WINDOW 1
|
||||||
|
#else
|
||||||
|
#define JSON_FILE_WINDOW 0
|
||||||
|
#endif
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -179,8 +192,12 @@ class token_carry_buffer
|
|||||||
char* m_capacity_end = nullptr;
|
char* m_capacity_end = nullptr;
|
||||||
};
|
};
|
||||||
/*!
|
/*!
|
||||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
Input adapter for stdio file access. The file is locked for the lifetime of the
|
||||||
buffer. This adapter is a very low level adapter.
|
adapter and read with the unlocked stdio functions (see
|
||||||
|
JSON_DISABLE_UNLOCKED_FILE_IO). With glibc and the Apple C library, the
|
||||||
|
characters in the buffer of the FILE are also scanned in bulk, as the get area
|
||||||
|
of a stream is (see input_stream_adapter): exactly the characters getc_unlocked()
|
||||||
|
would return are consumed, so the file is left where it was left before.
|
||||||
*/
|
*/
|
||||||
class file_input_adapter
|
class file_input_adapter
|
||||||
{
|
{
|
||||||
@@ -199,6 +216,12 @@ class file_input_adapter
|
|||||||
file_input_adapter(const file_input_adapter&) = delete;
|
file_input_adapter(const file_input_adapter&) = delete;
|
||||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||||
: m_file(rhs.m_file)
|
: m_file(rhs.m_file)
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
, token_carry(std::move(rhs.token_carry))
|
||||||
|
, token_segment(rhs.token_segment)
|
||||||
|
, current_in_buffer(rhs.current_in_buffer)
|
||||||
|
, tracking_token(rhs.tracking_token)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// the lock is owned by the thread, not the adapter: hand it over
|
// the lock is owned by the thread, not the adapter: hand it over
|
||||||
rhs.m_file = nullptr;
|
rhs.m_file = nullptr;
|
||||||
@@ -210,6 +233,20 @@ class file_input_adapter
|
|||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
std::char_traits<char>::int_type get_character()
|
||||||
|
{
|
||||||
|
// what getc_unlocked() does, while the buffer has characters
|
||||||
|
const char* const next = buffer_ptr();
|
||||||
|
if (JSON_HEDLEY_LIKELY(next != buffer_end()))
|
||||||
|
{
|
||||||
|
current_in_buffer = true;
|
||||||
|
buffer_bump(1);
|
||||||
|
return std::char_traits<char>::to_int_type(*next);
|
||||||
|
}
|
||||||
|
return get_character_slow();
|
||||||
|
}
|
||||||
|
#else
|
||||||
std::char_traits<char>::int_type get_character() noexcept
|
std::char_traits<char>::int_type get_character() noexcept
|
||||||
{
|
{
|
||||||
// the file is locked for the lifetime of the adapter, so the
|
// the file is locked for the lifetime of the adapter, so the
|
||||||
@@ -222,6 +259,7 @@ class file_input_adapter
|
|||||||
return std::fgetc(m_file);
|
return std::fgetc(m_file);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// returns the number of characters successfully read
|
// returns the number of characters successfully read
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -230,7 +268,134 @@ class file_input_adapter
|
|||||||
return fread(dest, 1, sizeof(T) * count, m_file);
|
return fread(dest, 1, sizeof(T) * count, m_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
// The lexer may scan the buffer of the FILE directly, as the window of
|
||||||
|
// input_stream_adapter: the buffer ends wherever the C library's buffer
|
||||||
|
// ends, and is refilled by the next get_character().
|
||||||
|
static constexpr bool supports_bulk_scan = true;
|
||||||
|
static constexpr bool bulk_window = true;
|
||||||
|
|
||||||
|
// Pointer to the next unread character; only valid when bulk_remaining() > 0.
|
||||||
|
const char* bulk_data() const
|
||||||
|
{
|
||||||
|
return buffer_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of unread characters in the buffer.
|
||||||
|
std::size_t bulk_remaining() const
|
||||||
|
{
|
||||||
|
if (!current_in_buffer)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<std::size_t>(buffer_end() - buffer_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume @a n characters previously inspected via bulk_data().
|
||||||
|
void bulk_skip(std::size_t n)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(n <= bulk_remaining());
|
||||||
|
buffer_bump(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The adapter keeps the current token for the lexer's error messages, as
|
||||||
|
// input_stream_adapter does.
|
||||||
|
static constexpr bool supports_token_tracking = true;
|
||||||
|
|
||||||
|
void begin_token() noexcept
|
||||||
|
{
|
||||||
|
tracking_token = true;
|
||||||
|
token_carry.clear();
|
||||||
|
token_segment = buffer_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_token_start(std::char_traits<char>::int_type c)
|
||||||
|
{
|
||||||
|
tracking_token = true;
|
||||||
|
token_carry.clear();
|
||||||
|
if (current_in_buffer)
|
||||||
|
{
|
||||||
|
token_segment = buffer_ptr() - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token_segment = nullptr;
|
||||||
|
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||||
|
{
|
||||||
|
token_carry.copy_to(out);
|
||||||
|
if (token_segment != nullptr)
|
||||||
|
{
|
||||||
|
out.insert(out.end(), token_segment, buffer_ptr());
|
||||||
|
}
|
||||||
|
if (drop_last && !out.empty())
|
||||||
|
{
|
||||||
|
out.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
// the buffer of the FILE: [buffer_ptr(), buffer_end()) are the characters
|
||||||
|
// getc_unlocked() would return next
|
||||||
|
#if defined(__GLIBC__)
|
||||||
|
const char* buffer_ptr() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_IO_read_ptr;
|
||||||
|
}
|
||||||
|
const char* buffer_end() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_IO_read_end;
|
||||||
|
}
|
||||||
|
void buffer_bump(std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
m_file->_IO_read_ptr += n;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const char* buffer_ptr() const noexcept
|
||||||
|
{
|
||||||
|
return reinterpret_cast<const char*>(m_file->_p);
|
||||||
|
}
|
||||||
|
const char* buffer_end() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_r > 0 ? reinterpret_cast<const char*>(m_file->_p) + m_file->_r : reinterpret_cast<const char*>(m_file->_p);
|
||||||
|
}
|
||||||
|
void buffer_bump(std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
m_file->_p += n;
|
||||||
|
m_file->_r -= static_cast<int>(n);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the buffer is exhausted: getc_unlocked() refills it
|
||||||
|
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_slow()
|
||||||
|
{
|
||||||
|
if (token_segment != nullptr)
|
||||||
|
{
|
||||||
|
token_carry.append(token_segment, buffer_end());
|
||||||
|
token_segment = nullptr;
|
||||||
|
}
|
||||||
|
const auto res = getc_unlocked(m_file);
|
||||||
|
if (res == EOF)
|
||||||
|
{
|
||||||
|
current_in_buffer = false;
|
||||||
|
return std::char_traits<char>::eof();
|
||||||
|
}
|
||||||
|
// the character was taken from the refilled buffer, at buffer_ptr() - 1
|
||||||
|
current_in_buffer = true;
|
||||||
|
if (tracking_token)
|
||||||
|
{
|
||||||
|
token_segment = buffer_ptr() - 1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Lock the file once for the whole parse instead of once per character.
|
// Lock the file once for the whole parse instead of once per character.
|
||||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||||
// same thread keep working.
|
// same thread keep working.
|
||||||
@@ -259,6 +424,16 @@ class file_input_adapter
|
|||||||
|
|
||||||
/// the file pointer to read from
|
/// the file pointer to read from
|
||||||
std::FILE* m_file;
|
std::FILE* m_file;
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
/// the part of the current token that was in earlier buffers
|
||||||
|
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// start of the current token's part in the buffer, or nullptr
|
||||||
|
const char* token_segment = nullptr;
|
||||||
|
/// whether the character last returned by get_character() is in the buffer
|
||||||
|
bool current_in_buffer = false;
|
||||||
|
/// whether a token is tracked at all; the binary readers never start one
|
||||||
|
bool tracking_token = false;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
|
#undef JSON_FILE_WINDOW
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
#undef JSON_LITTLE_ENDIAN_TARGET
|
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
|||||||
Generated
+178
-40
@@ -9167,6 +9167,19 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#define JSON_UNLOCKED_FILE_IO 0
|
#define JSON_UNLOCKED_FILE_IO 0
|
||||||
#endif
|
#endif
|
||||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||||
|
|
||||||
|
// Whether file_input_adapter scans the buffer of the FILE directly, as
|
||||||
|
// getc_unlocked() reads it: the fields it uses are those that the C
|
||||||
|
// library's own getc_unlocked() macro uses (glibc: _IO_read_ptr and
|
||||||
|
// _IO_read_end; Apple: _p and _r). It needs the file locked for the whole
|
||||||
|
// parse, as JSON_UNLOCKED_FILE_IO == 1 has it (so JSON_DISABLE_UNLOCKED_FILE_IO
|
||||||
|
// and MemorySanitizer builds turn it off).
|
||||||
|
// (uClibc defines __GLIBC__ as well, but its FILE has other fields)
|
||||||
|
#if JSON_UNLOCKED_FILE_IO == 1 && ((defined(__GLIBC__) && !defined(__UCLIBC__)) || defined(__APPLE__))
|
||||||
|
#define JSON_FILE_WINDOW 1
|
||||||
|
#else
|
||||||
|
#define JSON_FILE_WINDOW 0
|
||||||
|
#endif
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -9288,8 +9301,12 @@ class token_carry_buffer
|
|||||||
char* m_capacity_end = nullptr;
|
char* m_capacity_end = nullptr;
|
||||||
};
|
};
|
||||||
/*!
|
/*!
|
||||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
Input adapter for stdio file access. The file is locked for the lifetime of the
|
||||||
buffer. This adapter is a very low level adapter.
|
adapter and read with the unlocked stdio functions (see
|
||||||
|
JSON_DISABLE_UNLOCKED_FILE_IO). With glibc and the Apple C library, the
|
||||||
|
characters in the buffer of the FILE are also scanned in bulk, as the get area
|
||||||
|
of a stream is (see input_stream_adapter): exactly the characters getc_unlocked()
|
||||||
|
would return are consumed, so the file is left where it was left before.
|
||||||
*/
|
*/
|
||||||
class file_input_adapter
|
class file_input_adapter
|
||||||
{
|
{
|
||||||
@@ -9308,6 +9325,12 @@ class file_input_adapter
|
|||||||
file_input_adapter(const file_input_adapter&) = delete;
|
file_input_adapter(const file_input_adapter&) = delete;
|
||||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||||
: m_file(rhs.m_file)
|
: m_file(rhs.m_file)
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
, token_carry(std::move(rhs.token_carry))
|
||||||
|
, token_segment(rhs.token_segment)
|
||||||
|
, current_in_buffer(rhs.current_in_buffer)
|
||||||
|
, tracking_token(rhs.tracking_token)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// the lock is owned by the thread, not the adapter: hand it over
|
// the lock is owned by the thread, not the adapter: hand it over
|
||||||
rhs.m_file = nullptr;
|
rhs.m_file = nullptr;
|
||||||
@@ -9319,6 +9342,20 @@ class file_input_adapter
|
|||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
std::char_traits<char>::int_type get_character()
|
||||||
|
{
|
||||||
|
// what getc_unlocked() does, while the buffer has characters
|
||||||
|
const char* const next = buffer_ptr();
|
||||||
|
if (JSON_HEDLEY_LIKELY(next != buffer_end()))
|
||||||
|
{
|
||||||
|
current_in_buffer = true;
|
||||||
|
buffer_bump(1);
|
||||||
|
return std::char_traits<char>::to_int_type(*next);
|
||||||
|
}
|
||||||
|
return get_character_slow();
|
||||||
|
}
|
||||||
|
#else
|
||||||
std::char_traits<char>::int_type get_character() noexcept
|
std::char_traits<char>::int_type get_character() noexcept
|
||||||
{
|
{
|
||||||
// the file is locked for the lifetime of the adapter, so the
|
// the file is locked for the lifetime of the adapter, so the
|
||||||
@@ -9331,6 +9368,7 @@ class file_input_adapter
|
|||||||
return std::fgetc(m_file);
|
return std::fgetc(m_file);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// returns the number of characters successfully read
|
// returns the number of characters successfully read
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -9339,7 +9377,134 @@ class file_input_adapter
|
|||||||
return fread(dest, 1, sizeof(T) * count, m_file);
|
return fread(dest, 1, sizeof(T) * count, m_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
// The lexer may scan the buffer of the FILE directly, as the window of
|
||||||
|
// input_stream_adapter: the buffer ends wherever the C library's buffer
|
||||||
|
// ends, and is refilled by the next get_character().
|
||||||
|
static constexpr bool supports_bulk_scan = true;
|
||||||
|
static constexpr bool bulk_window = true;
|
||||||
|
|
||||||
|
// Pointer to the next unread character; only valid when bulk_remaining() > 0.
|
||||||
|
const char* bulk_data() const
|
||||||
|
{
|
||||||
|
return buffer_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of unread characters in the buffer.
|
||||||
|
std::size_t bulk_remaining() const
|
||||||
|
{
|
||||||
|
if (!current_in_buffer)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<std::size_t>(buffer_end() - buffer_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume @a n characters previously inspected via bulk_data().
|
||||||
|
void bulk_skip(std::size_t n)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(n <= bulk_remaining());
|
||||||
|
buffer_bump(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The adapter keeps the current token for the lexer's error messages, as
|
||||||
|
// input_stream_adapter does.
|
||||||
|
static constexpr bool supports_token_tracking = true;
|
||||||
|
|
||||||
|
void begin_token() noexcept
|
||||||
|
{
|
||||||
|
tracking_token = true;
|
||||||
|
token_carry.clear();
|
||||||
|
token_segment = buffer_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_token_start(std::char_traits<char>::int_type c)
|
||||||
|
{
|
||||||
|
tracking_token = true;
|
||||||
|
token_carry.clear();
|
||||||
|
if (current_in_buffer)
|
||||||
|
{
|
||||||
|
token_segment = buffer_ptr() - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token_segment = nullptr;
|
||||||
|
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||||
|
{
|
||||||
|
token_carry.copy_to(out);
|
||||||
|
if (token_segment != nullptr)
|
||||||
|
{
|
||||||
|
out.insert(out.end(), token_segment, buffer_ptr());
|
||||||
|
}
|
||||||
|
if (drop_last && !out.empty())
|
||||||
|
{
|
||||||
|
out.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
// the buffer of the FILE: [buffer_ptr(), buffer_end()) are the characters
|
||||||
|
// getc_unlocked() would return next
|
||||||
|
#if defined(__GLIBC__)
|
||||||
|
const char* buffer_ptr() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_IO_read_ptr;
|
||||||
|
}
|
||||||
|
const char* buffer_end() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_IO_read_end;
|
||||||
|
}
|
||||||
|
void buffer_bump(std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
m_file->_IO_read_ptr += n;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const char* buffer_ptr() const noexcept
|
||||||
|
{
|
||||||
|
return reinterpret_cast<const char*>(m_file->_p);
|
||||||
|
}
|
||||||
|
const char* buffer_end() const noexcept
|
||||||
|
{
|
||||||
|
return m_file->_r > 0 ? reinterpret_cast<const char*>(m_file->_p) + m_file->_r : reinterpret_cast<const char*>(m_file->_p);
|
||||||
|
}
|
||||||
|
void buffer_bump(std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
m_file->_p += n;
|
||||||
|
m_file->_r -= static_cast<int>(n);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the buffer is exhausted: getc_unlocked() refills it
|
||||||
|
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_slow()
|
||||||
|
{
|
||||||
|
if (token_segment != nullptr)
|
||||||
|
{
|
||||||
|
token_carry.append(token_segment, buffer_end());
|
||||||
|
token_segment = nullptr;
|
||||||
|
}
|
||||||
|
const auto res = getc_unlocked(m_file);
|
||||||
|
if (res == EOF)
|
||||||
|
{
|
||||||
|
current_in_buffer = false;
|
||||||
|
return std::char_traits<char>::eof();
|
||||||
|
}
|
||||||
|
// the character was taken from the refilled buffer, at buffer_ptr() - 1
|
||||||
|
current_in_buffer = true;
|
||||||
|
if (tracking_token)
|
||||||
|
{
|
||||||
|
token_segment = buffer_ptr() - 1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Lock the file once for the whole parse instead of once per character.
|
// Lock the file once for the whole parse instead of once per character.
|
||||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||||
// same thread keep working.
|
// same thread keep working.
|
||||||
@@ -9368,6 +9533,16 @@ class file_input_adapter
|
|||||||
|
|
||||||
/// the file pointer to read from
|
/// the file pointer to read from
|
||||||
std::FILE* m_file;
|
std::FILE* m_file;
|
||||||
|
#if JSON_FILE_WINDOW
|
||||||
|
/// the part of the current token that was in earlier buffers
|
||||||
|
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// start of the current token's part in the buffer, or nullptr
|
||||||
|
const char* token_segment = nullptr;
|
||||||
|
/// whether the character last returned by get_character() is in the buffer
|
||||||
|
bool current_in_buffer = false;
|
||||||
|
/// whether a token is tracked at all; the binary readers never start one
|
||||||
|
bool tracking_token = false;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -22903,14 +23078,6 @@ class binary_reader
|
|||||||
const char* context,
|
const char* context,
|
||||||
ContainerType& result)
|
ContainerType& result)
|
||||||
{
|
{
|
||||||
// a string that is in the block of input as a whole is appended from
|
|
||||||
// it, without the zero-filling resize() below
|
|
||||||
constexpr bool from_block = bulk_scan && std::is_same<ContainerType, string_t>::value;
|
|
||||||
if (append_from_block(len, result, std::integral_constant<bool, from_block> {}))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// upper bound on the number of bytes read (and allocated) per chunk
|
// upper bound on the number of bytes read (and allocated) per chunk
|
||||||
constexpr std::size_t chunk_size = 4096;
|
constexpr std::size_t chunk_size = 4096;
|
||||||
|
|
||||||
@@ -22947,36 +23114,6 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// append the @a len bytes at the current position to @a result, if the
|
|
||||||
/// input holds them in one block; false (with nothing read) otherwise
|
|
||||||
template<typename NumberType>
|
|
||||||
bool append_from_block(const NumberType len, string_t& result, std::true_type /*block*/)
|
|
||||||
{
|
|
||||||
if (len <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// (len > 0, so the unsigned type holds it; casting a signed char
|
|
||||||
// straight to std::size_t would trip bugprone-signed-char-misuse)
|
|
||||||
const auto ulen = static_cast<typename std::make_unsigned<NumberType>::type>(len);
|
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
|
||||||
if (static_cast<std::uintmax_t>(ulen) > static_cast<std::uintmax_t>(remaining))
|
|
||||||
{
|
|
||||||
return false; // (the loop reports the premature end)
|
|
||||||
}
|
|
||||||
const auto n = static_cast<std::size_t>(ulen);
|
|
||||||
result.append(reinterpret_cast<const typename string_t::value_type*>(ia.bulk_data()), n);
|
|
||||||
ia.bulk_skip(n);
|
|
||||||
chars_read += n;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename NumberType, typename ContainerType>
|
|
||||||
static bool append_from_block(const NumberType /*len*/, ContainerType& /*result*/, std::false_type /*block*/) noexcept
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] context further context information (for diagnostics)
|
@param[in] context further context information (for diagnostics)
|
||||||
@return whether the last read character is not EOF
|
@return whether the last read character is not EOF
|
||||||
@@ -46386,6 +46523,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
|||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
|
#undef JSON_FILE_WINDOW
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
#undef JSON_LITTLE_ENDIAN_TARGET
|
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ file_ptr open_with(const char* name, const std::string& content)
|
|||||||
return {std::fopen(name, "rb"), &std::fclose};
|
return {std::fopen(name, "rb"), &std::fclose};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the smallest size setvbuf() accepts for _IOFBF: the Microsoft C runtime
|
||||||
|
// rejects sizes below 2 as an invalid parameter, which ends the process
|
||||||
|
#if defined(_WIN32)
|
||||||
|
constexpr int min_full_buffer = 2;
|
||||||
|
#else
|
||||||
|
constexpr int min_full_buffer = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
// The stdio lock is recursive, so whether parsing released it can only be
|
// The stdio lock is recursive, so whether parsing released it can only be
|
||||||
// seen from another thread: ftrylockfile() fails there while this thread
|
// seen from another thread: ftrylockfile() fails there while this thread
|
||||||
// holds the lock. ftrylockfile() is POSIX; elsewhere, the check is skipped.
|
// holds the lock. ftrylockfile() is POSIX; elsewhere, the check is skipped.
|
||||||
@@ -225,6 +233,129 @@ TEST_CASE("parsing from a FILE*")
|
|||||||
check_unlocked(f.get());
|
check_unlocked(f.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("tokens across the edges of the file's buffer")
|
||||||
|
{
|
||||||
|
// the adapter reads the buffer of the FILE directly where it can (see
|
||||||
|
// JSON_FILE_WINDOW): with buffers of 1 to 70 bytes and none, every
|
||||||
|
// token crosses an edge somewhere; the results, the error messages
|
||||||
|
// and their positions must be those of the string
|
||||||
|
const std::vector<std::string> documents =
|
||||||
|
{
|
||||||
|
input,
|
||||||
|
R"({"long": "a string that is longer than the buffer of the file, with \"escapes\" and \u00e4", "n": [123456789012345678901, -0.000123e-45, 1.5E+300, 0, -0]})",
|
||||||
|
"[\n \"indented\",\n \"caf\xc3\xa9 \xe2\x82\xac \xf0\x9f\x98\x80\",\n {\n \"key\": 1\n }\n]",
|
||||||
|
R"({"a": [1, 2, 3], "b": "unterminated)",
|
||||||
|
R"([1, 2, 3, 1e999])",
|
||||||
|
R"({"a": "invalid escape \x"})",
|
||||||
|
"[\"invalid UTF-8 \xc3\x28\"]",
|
||||||
|
"[1, 2, 3] trailing",
|
||||||
|
R"([truex])",
|
||||||
|
};
|
||||||
|
for (const auto& document : documents)
|
||||||
|
{
|
||||||
|
std::string expected;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
expected = json::parse(document).dump();
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
expected = e.what();
|
||||||
|
}
|
||||||
|
const bool expected_accept = json::accept(document);
|
||||||
|
for (int size = 0; size <= 70; ++size)
|
||||||
|
{
|
||||||
|
if (size != 0 && size < min_full_buffer)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CAPTURE(document)
|
||||||
|
CAPTURE(size)
|
||||||
|
std::vector<char> buffer(static_cast<std::size_t>(size) + 1);
|
||||||
|
for (int pass = 0; pass < 2; ++pass)
|
||||||
|
{
|
||||||
|
const file_ptr f = open_with(name, document);
|
||||||
|
REQUIRE(f != nullptr);
|
||||||
|
// size 0: unbuffered
|
||||||
|
REQUIRE(std::setvbuf(f.get(), size == 0 ? nullptr : buffer.data(), size == 0 ? _IONBF : _IOFBF, static_cast<std::size_t>(size)) == 0);
|
||||||
|
if (pass == 0)
|
||||||
|
{
|
||||||
|
std::string actual;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actual = json::parse(f.get()).dump();
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
actual = e.what();
|
||||||
|
}
|
||||||
|
CHECK(actual == expected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK(json::accept(f.get()) == expected_accept);
|
||||||
|
}
|
||||||
|
check_unlocked(f.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("characters pushed back with ungetc() before parsing")
|
||||||
|
{
|
||||||
|
// (glibc and Apple keep them in a separate buffer)
|
||||||
|
const file_ptr f = open_with(name, "2, 3]");
|
||||||
|
REQUIRE(f != nullptr);
|
||||||
|
REQUIRE(std::ungetc(',', f.get()) == ',');
|
||||||
|
REQUIRE(std::ungetc('1', f.get()) == '1');
|
||||||
|
REQUIRE(std::ungetc('[', f.get()) == '[');
|
||||||
|
CHECK(json::parse(f.get()) == json({1, 2, 3}));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("SAX events across the edges of the file's buffer")
|
||||||
|
{
|
||||||
|
const std::string document = R"({"key": "a value that is longer than the buffer", "n": [1, -2.5e3, true, null]})";
|
||||||
|
std::vector<long> expected;
|
||||||
|
{
|
||||||
|
const file_ptr f = open_with(name, document);
|
||||||
|
REQUIRE(f != nullptr);
|
||||||
|
position_reading_sax sax(f.get());
|
||||||
|
CHECK(json::sax_parse(f.get(), &sax));
|
||||||
|
expected = sax.positions();
|
||||||
|
}
|
||||||
|
for (int size = min_full_buffer; size <= 20; ++size)
|
||||||
|
{
|
||||||
|
CAPTURE(size)
|
||||||
|
std::vector<char> buffer(static_cast<std::size_t>(size));
|
||||||
|
const file_ptr f = open_with(name, document);
|
||||||
|
REQUIRE(f != nullptr);
|
||||||
|
REQUIRE(std::setvbuf(f.get(), buffer.data(), _IOFBF, buffer.size()) == 0);
|
||||||
|
position_reading_sax sax(f.get());
|
||||||
|
CHECK(json::sax_parse(f.get(), &sax));
|
||||||
|
// the events happen at the same positions of the file
|
||||||
|
CHECK(sax.positions() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary strings across the edges of the file's buffer")
|
||||||
|
{
|
||||||
|
const json value = {{"a string that is longer than the buffer of the file", "and a value that is longer, too"}, {"n", {1, 2}}};
|
||||||
|
const std::vector<std::uint8_t> cbor = json::to_cbor(value);
|
||||||
|
for (int size = min_full_buffer; size <= 20; ++size)
|
||||||
|
{
|
||||||
|
CAPTURE(size)
|
||||||
|
std::vector<char> buffer(static_cast<std::size_t>(size));
|
||||||
|
const file_ptr f = open_with(name, std::string(cbor.begin(), cbor.end()));
|
||||||
|
REQUIRE(f != nullptr);
|
||||||
|
REQUIRE(std::setvbuf(f.get(), buffer.data(), _IOFBF, buffer.size()) == 0);
|
||||||
|
CHECK(json::from_cbor(f.get()) == value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("binary formats read from a FILE*")
|
SECTION("binary formats read from a FILE*")
|
||||||
{
|
{
|
||||||
const json j = {{"string", std::string(300, 'x')}, {"array", {1, 2, 3}}, {"number", 1.5}};
|
const json j = {{"string", std::string(300, 'x')}, {"array", {1, 2, 3}}, {"number", 1.5}};
|
||||||
|
|||||||
Reference in new issue
Block a user