mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 06:00:29 +00:00
Give operator>> a strong exception-safety guarantee
operator>> parsed directly into its basic_json& target, so a parse error left the target holding whatever was parsed before the error instead of its previous value. With JSON_DIAGNOSTICS=1, that partial value also violated the class invariant, because the parent pointers of an array or object's elements are only set when the container is closed, which a failed parse never reaches; copying such a value then aborted in assert_invariant(). Fix it the way basic_json::parse() already handles this: parse into a temporary and move it into the target only once parsing succeeds, so the target is left unchanged if an exception is thrown. Fixes #5652. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -18,6 +18,10 @@ Deserializes an input stream to a JSON value.
|
|||||||
|
|
||||||
the stream `i`
|
the stream `i`
|
||||||
|
|
||||||
|
## Exception safety
|
||||||
|
|
||||||
|
Strong guarantee: if an exception is thrown, there are no changes in `j`.
|
||||||
|
|
||||||
## Exceptions
|
## 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.
|
||||||
@@ -118,3 +122,5 @@ being read.
|
|||||||
it as end of input; planned to become the default in version 4.0.0.
|
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
|
- `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.
|
the stream; planned to become the default in version 4.0.0.
|
||||||
|
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
|
||||||
|
value if parsing throws.
|
||||||
|
|||||||
@@ -5050,7 +5050,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||||
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
||||||
{
|
{
|
||||||
parser(detail::input_adapter(i)).parse(false, j);
|
// parse into a temporary so that j is left unchanged if parsing fails
|
||||||
|
basic_json result;
|
||||||
|
parser(detail::input_adapter(i)).parse(false, result);
|
||||||
|
j = std::move(result);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|||||||
@@ -31131,7 +31131,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/
|
||||||
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
friend std::istream& operator>>(std::istream& i, basic_json& j)
|
||||||
{
|
{
|
||||||
parser(detail::input_adapter(i)).parse(false, j);
|
// parse into a temporary so that j is left unchanged if parsing fails
|
||||||
|
basic_json result;
|
||||||
|
parser(detail::input_adapter(i)).parse(false, result);
|
||||||
|
j = std::move(result);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
TEST_CASE("Better diagnostics")
|
TEST_CASE("Better diagnostics")
|
||||||
{
|
{
|
||||||
SECTION("empty JSON Pointer")
|
SECTION("empty JSON Pointer")
|
||||||
@@ -461,6 +463,21 @@ TEST_CASE("Regression tests for extended diagnostics")
|
|||||||
CHECK(copy == j);
|
CHECK(copy == j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Regression test for issue #5652 - operator>> leaves a partial value in its target on a parse error")
|
||||||
|
{
|
||||||
|
json j = "old value";
|
||||||
|
std::istringstream is("[1, x");
|
||||||
|
CHECK_THROWS_WITH_AS(is >> j, "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '1, x'", json::parse_error);
|
||||||
|
|
||||||
|
// j must be left unchanged, as json::parse() guarantees for its result
|
||||||
|
CHECK(j == "old value");
|
||||||
|
|
||||||
|
// copying j must not trigger assert_invariant(): a failed parse must
|
||||||
|
// not leave array/object elements without a parent pointer
|
||||||
|
json const copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(copy == j);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||||
|
|||||||
Reference in New Issue
Block a user