mirror of
https://github.com/nlohmann/json.git
synced 2026-10-02 15:05: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:
@@ -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