mirror of
https://github.com/nlohmann/json.git
synced 2026-10-02 06:55:16 +00:00
Fix std::terminate and null pointer access in input_stream_adapter (#5699)
Parsing from a std::istream crashed in two unusual but valid stream states, both in input_stream_adapter: - With eofbit in the stream's exceptions() mask, get_character() sets eofbit via is->clear(), which throws std::ios_base::failure. While that exception unwinds, ~input_stream_adapter() called clear() again to reset eofbit, which is still set and still in the exception mask, so it throws a second time out of the (implicitly noexcept) destructor and std::terminate() is called. The destructor now only calls clear() if a bit other than eofbit remains set, so the first exception can propagate normally. - For an std::istream without a stream buffer (rdbuf() == nullptr, e.g. std::istream(nullptr)), the constructor stored the null pointer without checking it, and get_character() dereferenced it. input_adapter(std::istream&) now throws parse_error.101 for such a stream, the same as it already does for a null FILE* or char*. Added regression tests to unit-deserialization.cpp and, for the JSON_PRECISE_STREAM_POSITION variant of get_character(), to unit-precise-stream-position.cpp; both crashed before this fix. Documented the two exceptions in parse.md and operator_gtgt.md. Fixes #5646. Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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})";
|
||||
|
||||
Reference in New Issue
Block a user