mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 11:40:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80475865c0 |
@@ -88,7 +88,12 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
## Exceptions
|
||||
|
||||
- Throws [`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) in case of an unexpected token, or
|
||||
empty input like a null `FILE*` or `char*` pointer.
|
||||
empty input like a null `FILE*` or `char*` pointer, or an `std::istream` without a stream buffer
|
||||
(`#!cpp i.rdbuf() == nullptr`, for instance `#!cpp std::istream(nullptr)`).
|
||||
- If reading from an `std::istream` reaches the end of the input and `eofbit` is part of the stream's
|
||||
[`exceptions()`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions) mask, the `std::ios_base::failure`
|
||||
thrown by the stream itself propagates instead of a `parse_error`, the same as it would for the standard library's
|
||||
own extraction operators.
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -254,6 +259,8 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash
|
||||
(`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -100,6 +100,3 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to
|
||||
- Added in version 1.0.0.
|
||||
- Fixed in version 3.13.0 to also remove discarded values from a parent object; before, discarding an array or a value
|
||||
stored under an object key left a discarded member behind, which made the parse result serialize to invalid JSON.
|
||||
- Fixed in version 3.13.0 so that discarding an array or object at its start event also hides its content from the
|
||||
callback, as documented above; before, the callback was still called for the content, and the key of every member of
|
||||
a discarded object was kept in memory until the parse ended.
|
||||
|
||||
@@ -20,7 +20,12 @@ the stream `i`
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) in case of an unexpected token.
|
||||
- Throws [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) in case of an unexpected token, or if
|
||||
`i` has no stream buffer (`#!cpp i.rdbuf() == nullptr`, for instance `#!cpp std::istream(nullptr)`).
|
||||
- If reading from `i` reaches the end of the input and `eofbit` is part of `i`'s
|
||||
[`exceptions()`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions) mask, the `std::ios_base::failure`
|
||||
thrown by `i` itself propagates instead of a `parse_error`, the same as it would for the standard library's own
|
||||
extraction operators.
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -118,3 +123,5 @@ being read.
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave the character that terminates a number in
|
||||
the stream; planned to become the default in version 4.0.0.
|
||||
- Fixed a null pointer dereference for an `std::istream` without a stream buffer (now throws `parse_error.101`), and a
|
||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
||||
|
||||
@@ -106,7 +106,13 @@ class input_stream_adapter
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
#endif
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
// only call clear() if there is something to clear: it throws
|
||||
// std::ios_base::failure if the stream has exceptions() enabled
|
||||
// for a state bit that remains set, and a destructor must not throw
|
||||
if ((is->rdstate() & ~std::ios::eofbit) != 0)
|
||||
{
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,12 +817,16 @@ inline file_input_adapter input_adapter(std::FILE* file)
|
||||
|
||||
inline input_stream_adapter input_adapter(std::istream& stream)
|
||||
{
|
||||
if (stream.rdbuf() == nullptr)
|
||||
{
|
||||
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||
}
|
||||
return input_stream_adapter(stream);
|
||||
}
|
||||
|
||||
inline input_stream_adapter input_adapter(std::istream&& stream)
|
||||
{
|
||||
return input_stream_adapter(stream);
|
||||
return input_adapter(stream);
|
||||
}
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
|
||||
@@ -582,8 +582,8 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool start_object(std::size_t len)
|
||||
{
|
||||
// check callback for object start; not called inside a discarded container
|
||||
const bool keep = keep_stack.back() && callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||
// check callback for object start
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// the key this object will be stored under, read before handle_value()
|
||||
@@ -619,18 +619,6 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool key(string_t& val)
|
||||
{
|
||||
if (!keep_stack.back() || !ref_stack.back())
|
||||
{
|
||||
// the object is not stored: the value of this key is dropped in
|
||||
// handle_value() without touching the key stacks
|
||||
if (keep_stack.back())
|
||||
{
|
||||
BasicJsonType k = BasicJsonType(val);
|
||||
static_cast<void>(callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BasicJsonType k = BasicJsonType(val);
|
||||
|
||||
// check callback for the key
|
||||
@@ -716,7 +704,7 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool start_array(std::size_t len)
|
||||
{
|
||||
const bool keep = keep_stack.back() && callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// see start_object()
|
||||
|
||||
@@ -7650,7 +7650,13 @@ class input_stream_adapter
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
#endif
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
// only call clear() if there is something to clear: it throws
|
||||
// std::ios_base::failure if the stream has exceptions() enabled
|
||||
// for a state bit that remains set, and a destructor must not throw
|
||||
if ((is->rdstate() & ~std::ios::eofbit) != 0)
|
||||
{
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8355,12 +8361,16 @@ inline file_input_adapter input_adapter(std::FILE* file)
|
||||
|
||||
inline input_stream_adapter input_adapter(std::istream& stream)
|
||||
{
|
||||
if (stream.rdbuf() == nullptr)
|
||||
{
|
||||
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
||||
}
|
||||
return input_stream_adapter(stream);
|
||||
}
|
||||
|
||||
inline input_stream_adapter input_adapter(std::istream&& stream)
|
||||
{
|
||||
return input_stream_adapter(stream);
|
||||
return input_adapter(stream);
|
||||
}
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -11996,8 +12006,8 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool start_object(std::size_t len)
|
||||
{
|
||||
// check callback for object start; not called inside a discarded container
|
||||
const bool keep = keep_stack.back() && callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||
// check callback for object start
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// the key this object will be stored under, read before handle_value()
|
||||
@@ -12033,18 +12043,6 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool key(string_t& val)
|
||||
{
|
||||
if (!keep_stack.back() || !ref_stack.back())
|
||||
{
|
||||
// the object is not stored: the value of this key is dropped in
|
||||
// handle_value() without touching the key stacks
|
||||
if (keep_stack.back())
|
||||
{
|
||||
BasicJsonType k = BasicJsonType(val);
|
||||
static_cast<void>(callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BasicJsonType k = BasicJsonType(val);
|
||||
|
||||
// check callback for the key
|
||||
@@ -12130,7 +12128,7 @@ class json_sax_dom_callback_parser
|
||||
|
||||
bool start_array(std::size_t len)
|
||||
{
|
||||
const bool keep = keep_stack.back() && callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||
keep_stack.push_back(keep);
|
||||
|
||||
// see start_object()
|
||||
|
||||
@@ -1966,102 +1966,6 @@ TEST_CASE("parser class")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("no callback for the content of a discarded container (#5643)")
|
||||
{
|
||||
// discarding a container at its start event must also hide
|
||||
// everything inside it from the callback: none of the nested
|
||||
// keys, values, or nested containers' own start/end events may
|
||||
// be reported
|
||||
std::vector<std::string> log;
|
||||
bool first = true;
|
||||
const json j = json::parse(R"({"skip": {"k1": 1, "k2": [2, {"k3": 3}]}, "keep": 1})",
|
||||
[&](int depth, json::parse_event_t event, json & parsed)
|
||||
{
|
||||
static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"};
|
||||
log.push_back(std::to_string(depth) + " " + names[static_cast<int>(event)] + " " + parsed.dump());
|
||||
|
||||
if (depth == 1 && event == json::parse_event_t::object_start && first)
|
||||
{
|
||||
// discard "skip" right at its object_start event
|
||||
first = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
CHECK(log == std::vector<std::string>
|
||||
{
|
||||
"0 object_start <discarded>",
|
||||
"1 key \"skip\"",
|
||||
"1 object_start <discarded>",
|
||||
"1 key \"keep\"",
|
||||
"1 value 1",
|
||||
"0 object_end {\"keep\":1}"
|
||||
});
|
||||
CHECK(j == json({{"keep", 1}}));
|
||||
}
|
||||
|
||||
SECTION("callback still called inside a container whose key was rejected (#5643)")
|
||||
{
|
||||
// rejecting a key does not discard its value's container at the
|
||||
// container's own start event, so the callback is still called
|
||||
// for that container's content; only storing the container
|
||||
// under the rejected key is skipped
|
||||
// (documented for parser_callback_t: "the callback is still
|
||||
// called for the associated value, but its return value has no
|
||||
// further effect")
|
||||
const auto record = [](std::vector<std::string>& log, int depth, json::parse_event_t event, const json & parsed)
|
||||
{
|
||||
static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"};
|
||||
log.push_back(std::to_string(depth) + " " + names[static_cast<int>(event)] + " " + parsed.dump());
|
||||
};
|
||||
|
||||
std::vector<std::string> log_object;
|
||||
const json j_object = json::parse(R"({"skip": {"k1": 1}, "keep": 2})",
|
||||
[&](int depth, json::parse_event_t event, json & parsed)
|
||||
{
|
||||
record(log_object, depth, event, parsed);
|
||||
return !(event == json::parse_event_t::key && parsed == json("skip"));
|
||||
});
|
||||
|
||||
CHECK(log_object == std::vector<std::string>
|
||||
{
|
||||
"0 object_start <discarded>",
|
||||
"1 key \"skip\"",
|
||||
"1 object_start <discarded>",
|
||||
"2 key \"k1\"",
|
||||
"2 value 1",
|
||||
"1 key \"keep\"",
|
||||
"1 value 2",
|
||||
"0 object_end {\"keep\":2}"
|
||||
});
|
||||
CHECK(j_object == json({{"keep", 2}}));
|
||||
|
||||
// same for a rejected key whose value is an array rather than an object
|
||||
std::vector<std::string> log_array;
|
||||
const json j_array = json::parse(R"({"skip": [1, {"k1": 2}], "keep": 2})",
|
||||
[&](int depth, json::parse_event_t event, json & parsed)
|
||||
{
|
||||
record(log_array, depth, event, parsed);
|
||||
return !(event == json::parse_event_t::key && parsed == json("skip"));
|
||||
});
|
||||
|
||||
CHECK(log_array == std::vector<std::string>
|
||||
{
|
||||
"0 object_start <discarded>",
|
||||
"1 key \"skip\"",
|
||||
"1 array_start <discarded>",
|
||||
"2 value 1",
|
||||
"2 object_start <discarded>",
|
||||
"3 key \"k1\"",
|
||||
"3 value 2",
|
||||
"1 key \"keep\"",
|
||||
"1 value 2",
|
||||
"0 object_end {\"keep\":2}"
|
||||
});
|
||||
CHECK(j_array == json({{"keep", 2}}));
|
||||
}
|
||||
|
||||
SECTION("special cases")
|
||||
{
|
||||
// the following test cases cover the situation in which an empty
|
||||
|
||||
@@ -388,6 +388,37 @@ TEST_CASE("deserialization")
|
||||
}));
|
||||
}
|
||||
|
||||
SECTION("stream with eofbit in its exception mask (issue #5646)")
|
||||
{
|
||||
// reaching EOF while parsing a value that fills the whole input
|
||||
// (e.g., a number, or any value under strict parsing) makes
|
||||
// get_character() call std::istream::clear() to record eofbit;
|
||||
// with eofbit in the exception mask, that clear() itself throws
|
||||
// std::ios_base::failure - it must propagate to the caller instead
|
||||
// of ~input_stream_adapter() throwing a second exception while the
|
||||
// first is still unwinding, which would call std::terminate
|
||||
json _;
|
||||
|
||||
std::istringstream is1("1");
|
||||
is1.exceptions(std::ios::eofbit);
|
||||
CHECK_THROWS_AS(_ = json::parse(is1), std::ios_base::failure&);
|
||||
|
||||
// the same holds for the common std::ifstream::exceptions(failbit |
|
||||
// badbit | eofbit) pattern, because only eofbit ends up set
|
||||
std::istringstream is2("1");
|
||||
is2.exceptions(std::ios::failbit | std::ios::badbit | std::ios::eofbit);
|
||||
CHECK_THROWS_AS(_ = json::parse(is2), std::ios_base::failure&);
|
||||
}
|
||||
|
||||
SECTION("stream without a streambuf (issue #5646)")
|
||||
{
|
||||
// std::istream(nullptr) has badbit set and rdbuf() == nullptr;
|
||||
// get_character() must not dereference that null streambuf
|
||||
std::istream is(nullptr);
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json::string_t const s = R"(["foo",1,2,3,false,{"one":1})";
|
||||
|
||||
@@ -234,4 +234,33 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
||||
CHECK(j == json(1));
|
||||
CHECK(remaining(is) == "true");
|
||||
}
|
||||
|
||||
SECTION("stream with eofbit in its exception mask (issue #5646)")
|
||||
{
|
||||
// with JSON_PRECISE_STREAM_POSITION, get_character() peeks via
|
||||
// sb->sgetc() rather than consuming via sb->sbumpc(), but it still
|
||||
// calls std::istream::clear() to record eofbit once the streambuf is
|
||||
// exhausted; with eofbit in the exception mask, that clear() itself
|
||||
// throws std::ios_base::failure, which must propagate to the caller
|
||||
// instead of ~input_stream_adapter() throwing a second exception
|
||||
// while the first is still unwinding (which would call std::terminate)
|
||||
json _;
|
||||
|
||||
std::istringstream is1("1");
|
||||
is1.exceptions(std::ios::eofbit);
|
||||
CHECK_THROWS_AS(_ = json::parse(is1), std::ios_base::failure&);
|
||||
|
||||
std::istringstream is2("1");
|
||||
is2.exceptions(std::ios::failbit | std::ios::badbit | std::ios::eofbit);
|
||||
CHECK_THROWS_AS(_ = json::parse(is2), std::ios_base::failure&);
|
||||
}
|
||||
|
||||
SECTION("stream without a streambuf (issue #5646)")
|
||||
{
|
||||
// std::istream(nullptr) has badbit set and rdbuf() == nullptr;
|
||||
// get_character() must not dereference that null streambuf
|
||||
std::istream is(nullptr);
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user