From 3b810c305ab64172b3e1020b310bcbaa68b3376c Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Mon, 10 Nov 2025 17:33:20 +0000 Subject: [PATCH] fix: JSON parsing of negative `STNumber` and `STAmount` (#5990) This change fixes JSON parsing of negative `int` input in `STNumber` and `STAmount`. The conversion of JSON to `STNumber` or `STAmount` may trigger a condition where we negate smallest possible `int` value, which is undefined behaviour. We use a temporary storage as `int64_t` to avoid this bug. Note that this only affects RPC, because we do not parse JSON in the protocol layer, and hence no amendment is needed. --- include/xrpl/json/json_value.h | 11 +- src/libxrpl/json/json_value.cpp | 66 ++++++++- src/libxrpl/protocol/STAmount.cpp | 2 +- src/libxrpl/protocol/STNumber.cpp | 2 +- src/test/app/ValidatorSite_test.cpp | 4 +- src/test/protocol/STAmount_test.cpp | 212 ++++++++++++++++++++++++++++ src/test/protocol/STNumber_test.cpp | 25 ++++ src/tests/libxrpl/json/Value.cpp | 193 ++++++++++++++++++------- 8 files changed, 454 insertions(+), 61 deletions(-) diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index 878122c461..6b460ecd3b 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -139,9 +140,9 @@ public: using ArrayIndex = UInt; static Value const null; - static Int const minInt; - static Int const maxInt; - static UInt const maxUInt; + static constexpr Int minInt = std::numeric_limits::min(); + static constexpr Int maxInt = std::numeric_limits::max(); + static constexpr UInt maxUInt = std::numeric_limits::max(); private: class CZString @@ -244,6 +245,10 @@ public: bool asBool() const; + /** Correct absolute value from int or unsigned int */ + UInt + asAbsUInt() const; + // TODO: What is the "empty()" method this docstring mentions? /** isNull() tests to see if this field is null. Don't use this method to test for emptiness: use empty(). */ diff --git a/src/libxrpl/json/json_value.cpp b/src/libxrpl/json/json_value.cpp index f283ba3f25..a9478f51ab 100644 --- a/src/libxrpl/json/json_value.cpp +++ b/src/libxrpl/json/json_value.cpp @@ -14,9 +14,6 @@ namespace Json { Value const Value::null; -Int const Value::minInt = Int(~(UInt(-1) / 2)); -Int const Value::maxInt = Int(UInt(-1) / 2); -UInt const Value::maxUInt = UInt(-1); class DefaultValueAllocator : public ValueAllocator { @@ -550,6 +547,69 @@ Value::asInt() const return 0; // unreachable; } +UInt +Value::asAbsUInt() const +{ + switch (type_) + { + case nullValue: + return 0; + + case intValue: { + // Doing this conversion through int64 avoids overflow error for + // value_.int_ = -1 * 2^31 i.e. numeric_limits::min(). + if (value_.int_ < 0) + return static_cast(value_.int_) * -1; + return value_.int_; + } + + case uintValue: + return value_.uint_; + + case realValue: { + if (value_.real_ < 0) + { + JSON_ASSERT_MESSAGE( + -1 * value_.real_ <= maxUInt, + "Real out of unsigned integer range"); + return UInt(-1 * value_.real_); + } + JSON_ASSERT_MESSAGE( + value_.real_ <= maxUInt, "Real out of unsigned integer range"); + return UInt(value_.real_); + } + + case booleanValue: + return value_.bool_ ? 1 : 0; + + case stringValue: { + char const* const str{value_.string_ ? value_.string_ : ""}; + auto const temp = beast::lexicalCastThrow(str); + if (temp < 0) + { + JSON_ASSERT_MESSAGE( + -1 * temp <= maxUInt, + "String out of unsigned integer range"); + return -1 * temp; + } + JSON_ASSERT_MESSAGE( + temp <= maxUInt, "String out of unsigned integer range"); + return temp; + } + + case arrayValue: + case objectValue: + JSON_ASSERT_MESSAGE(false, "Type is not convertible to int"); + + // LCOV_EXCL_START + default: + UNREACHABLE("Json::Value::asAbsInt : invalid type"); + // LCOV_EXCL_STOP + } + + return 0; // unreachable; +} + Value::UInt Value::asUInt() const { diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 4aac551003..d659ee0d05 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1087,7 +1087,7 @@ amountFromJson(SField const& name, Json::Value const& v) } else { - parts.mantissa = -value.asInt(); + parts.mantissa = value.asAbsUInt(); parts.negative = true; } } diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 889dd9ee68..c353f6b795 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -169,7 +169,7 @@ numberFromJson(SField const& field, Json::Value const& value) } else { - parts.mantissa = -value.asInt(); + parts.mantissa = value.asAbsUInt(); parts.negative = true; } } diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index e94ca6a009..be8ae8013f 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -572,7 +572,7 @@ public: false, true, 1, - std::chrono::seconds{Json::Value::maxInt + 1}}}); + std::chrono::seconds{Json::Value::minInt}}}); // force an out-of-range validUntil value on the future list // The first list is accepted. The second fails. The parser // returns the "best" result, so this looks like a success. @@ -608,7 +608,7 @@ public: false, true, 1, - std::chrono::seconds{Json::Value::maxInt + 1}, + std::chrono::seconds{Json::Value::minInt}, std::chrono::seconds{Json::Value::maxInt - 6000}}}); // verify refresh intervals are properly clamped testFetchList( diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index 28e4fe6aac..694e351303 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ripple { @@ -585,6 +586,216 @@ public: #endif } + void + testParseJson() + { + static_assert(!std::is_convertible_v); + + { + STAmount const stnum{sfNumber}; + BEAST_EXPECT(stnum.getSType() == STI_AMOUNT); + BEAST_EXPECT(stnum.getText() == "0"); + BEAST_EXPECT(stnum.isDefault() == true); + BEAST_EXPECT(stnum.value() == Number{0}); + } + + { + BEAST_EXPECT( + amountFromJson(sfNumber, Json::Value(42)) == XRPAmount(42)); + BEAST_EXPECT( + amountFromJson(sfNumber, Json::Value(-42)) == XRPAmount(-42)); + + BEAST_EXPECT( + amountFromJson(sfNumber, Json::UInt(42)) == XRPAmount(42)); + + BEAST_EXPECT(amountFromJson(sfNumber, "-123") == XRPAmount(-123)); + + BEAST_EXPECT(amountFromJson(sfNumber, "123") == XRPAmount(123)); + BEAST_EXPECT(amountFromJson(sfNumber, "-123") == XRPAmount(-123)); + + BEAST_EXPECT(amountFromJson(sfNumber, "3.14e2") == XRPAmount(314)); + BEAST_EXPECT( + amountFromJson(sfNumber, "-3.14e2") == XRPAmount(-314)); + + BEAST_EXPECT(amountFromJson(sfNumber, "0") == XRPAmount(0)); + BEAST_EXPECT(amountFromJson(sfNumber, "-0") == XRPAmount(0)); + + constexpr auto imin = std::numeric_limits::min(); + BEAST_EXPECT(amountFromJson(sfNumber, imin) == XRPAmount(imin)); + BEAST_EXPECT( + amountFromJson(sfNumber, std::to_string(imin)) == + XRPAmount(imin)); + + constexpr auto imax = std::numeric_limits::max(); + BEAST_EXPECT(amountFromJson(sfNumber, imax) == XRPAmount(imax)); + BEAST_EXPECT( + amountFromJson(sfNumber, std::to_string(imax)) == + XRPAmount(imax)); + + constexpr auto umax = std::numeric_limits::max(); + BEAST_EXPECT(amountFromJson(sfNumber, umax) == XRPAmount(umax)); + BEAST_EXPECT( + amountFromJson(sfNumber, std::to_string(umax)) == + XRPAmount(umax)); + + // XRP does not handle fractional part + try + { + auto _ = amountFromJson(sfNumber, "0.0"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = + "XRP and MPT must be specified as integral amount."; + BEAST_EXPECT(e.what() == expected); + } + + // XRP does not handle fractional part + try + { + auto _ = amountFromJson(sfNumber, "1000e-2"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = + "XRP and MPT must be specified as integral amount."; + BEAST_EXPECT(e.what() == expected); + } + + // Obvious non-numbers tested here + try + { + auto _ = amountFromJson(sfNumber, ""); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "e"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'e' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "1e"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'1e' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "e2"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'e2' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, Json::Value()); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = + "XRP may not be specified with a null Json value"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson( + sfNumber, + "123456789012345678901234567890123456789012345678901234" + "5678" + "901234567890123456789012345678901234567890123456789012" + "3456" + "78901234567890123456789012345678901234567890"); + BEAST_EXPECT(false); + } + catch (std::bad_cast const& e) + { + BEAST_EXPECT(true); + } + + // We do not handle leading zeros + try + { + auto _ = amountFromJson(sfNumber, "001"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'001' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "000.0"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'000.0' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + // We do not handle dangling dot + try + { + auto _ = amountFromJson(sfNumber, ".1"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'.1' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "1."); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'1.' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + + try + { + auto _ = amountFromJson(sfNumber, "1.e3"); + BEAST_EXPECT(false); + } + catch (std::runtime_error const& e) + { + std::string const expected = "'1.e3' is not a number"; + BEAST_EXPECT(e.what() == expected); + } + } + } + void testConvertXRP() { @@ -1022,6 +1233,7 @@ public: testArithmetic(); testUnderflow(); testRounding(); + testParseJson(); testConvertXRP(); testConvertIOU(); testCanAddXRP(); diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 927bc674e0..af455c6592 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -126,6 +127,30 @@ struct STNumber_test : public beast::unit_test::suite BEAST_EXPECT( numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0)); + constexpr auto imin = std::numeric_limits::min(); + BEAST_EXPECT( + numberFromJson(sfNumber, imin) == + STNumber(sfNumber, Number(imin, 0))); + BEAST_EXPECT( + numberFromJson(sfNumber, std::to_string(imin)) == + STNumber(sfNumber, Number(imin, 0))); + + constexpr auto imax = std::numeric_limits::max(); + BEAST_EXPECT( + numberFromJson(sfNumber, imax) == + STNumber(sfNumber, Number(imax, 0))); + BEAST_EXPECT( + numberFromJson(sfNumber, std::to_string(imax)) == + STNumber(sfNumber, Number(imax, 0))); + + constexpr auto umax = std::numeric_limits::max(); + BEAST_EXPECT( + numberFromJson(sfNumber, umax) == + STNumber(sfNumber, Number(umax, 0))); + BEAST_EXPECT( + numberFromJson(sfNumber, std::to_string(umax)) == + STNumber(sfNumber, Number(umax, 0))); + // Obvious non-numbers tested here try { diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp index b73e8aadc9..df777c98fc 100644 --- a/src/tests/libxrpl/json/Value.cpp +++ b/src/tests/libxrpl/json/Value.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,14 @@ namespace ripple { TEST_SUITE_BEGIN("json_value"); +TEST_CASE("limits") +{ + using namespace Json; + static_assert(Value::minInt == Int(~(UInt(-1) / 2))); + static_assert(Value::maxInt == Int(UInt(-1) / 2)); + static_assert(Value::maxUInt == UInt(-1)); +} + TEST_CASE("construct and compare Json::StaticString") { static constexpr char sample[]{"Contents of a Json::StaticString"}; @@ -582,8 +591,6 @@ TEST_CASE("bad json") TEST_CASE("edge cases") { - std::string json; - std::uint32_t max_uint = std::numeric_limits::max(); std::int32_t max_int = std::numeric_limits::max(); std::int32_t min_int = std::numeric_limits::min(); @@ -592,71 +599,145 @@ TEST_CASE("edge cases") std::int32_t a_large_int = max_int - 1978; std::int32_t a_small_int = min_int + 1978; - json = "{\"max_uint\":" + std::to_string(max_uint); - json += ",\"max_int\":" + std::to_string(max_int); - json += ",\"min_int\":" + std::to_string(min_int); - json += ",\"a_uint\":" + std::to_string(a_uint); - json += ",\"a_large_int\":" + std::to_string(a_large_int); - json += ",\"a_small_int\":" + std::to_string(a_small_int); - json += "}"; + { + std::string json = "{\"max_uint\":" + std::to_string(max_uint); + json += ",\"max_int\":" + std::to_string(max_int); + json += ",\"min_int\":" + std::to_string(min_int); + json += ",\"a_uint\":" + std::to_string(a_uint); + json += ",\"a_large_int\":" + std::to_string(a_large_int); + json += ",\"a_small_int\":" + std::to_string(a_small_int); + json += "}"; - Json::Value j1; - Json::Reader r1; + Json::Value j1; + Json::Reader r1; - CHECK(r1.parse(json, j1)); - CHECK(j1["max_uint"].asUInt() == max_uint); - CHECK(j1["max_int"].asInt() == max_int); - CHECK(j1["min_int"].asInt() == min_int); - CHECK(j1["a_uint"].asUInt() == a_uint); - CHECK(j1["a_uint"] > a_large_int); - CHECK(j1["a_uint"] > a_small_int); - CHECK(j1["a_large_int"].asInt() == a_large_int); - CHECK(j1["a_large_int"].asUInt() == a_large_int); - CHECK(j1["a_large_int"] < a_uint); - CHECK(j1["a_small_int"].asInt() == a_small_int); - CHECK(j1["a_small_int"] < a_uint); + CHECK(r1.parse(json, j1)); + CHECK(j1["max_uint"].asUInt() == max_uint); + CHECK(j1["max_uint"].asAbsUInt() == max_uint); + CHECK(j1["max_int"].asInt() == max_int); + CHECK(j1["max_int"].asAbsUInt() == max_int); + CHECK(j1["min_int"].asInt() == min_int); + CHECK( + j1["min_int"].asAbsUInt() == + static_cast(min_int) * -1); + CHECK(j1["a_uint"].asUInt() == a_uint); + CHECK(j1["a_uint"].asAbsUInt() == a_uint); + CHECK(j1["a_uint"] > a_large_int); + CHECK(j1["a_uint"] > a_small_int); + CHECK(j1["a_large_int"].asInt() == a_large_int); + CHECK(j1["a_large_int"].asAbsUInt() == a_large_int); + CHECK(j1["a_large_int"].asUInt() == a_large_int); + CHECK(j1["a_large_int"] < a_uint); + CHECK(j1["a_small_int"].asInt() == a_small_int); + CHECK( + j1["a_small_int"].asAbsUInt() == + static_cast(a_small_int) * -1); + CHECK(j1["a_small_int"] < a_uint); + } - json = "{\"overflow\":"; - json += std::to_string(std::uint64_t(max_uint) + 1); - json += "}"; + std::uint64_t overflow = std::uint64_t(max_uint) + 1; + { + std::string json = "{\"overflow\":"; + json += std::to_string(overflow); + json += "}"; - Json::Value j2; - Json::Reader r2; + Json::Value j2; + Json::Reader r2; - CHECK(!r2.parse(json, j2)); + CHECK(!r2.parse(json, j2)); + } - json = "{\"underflow\":"; - json += std::to_string(std::int64_t(min_int) - 1); - json += "}"; + std::int64_t underflow = std::int64_t(min_int) - 1; + { + std::string json = "{\"underflow\":"; + json += std::to_string(underflow); + json += "}"; - Json::Value j3; - Json::Reader r3; + Json::Value j3; + Json::Reader r3; - CHECK(!r3.parse(json, j3)); + CHECK(!r3.parse(json, j3)); + } - Json::Value intString{"4294967296"}; - CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + { + Json::Value intString{std::to_string(overflow)}; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + CHECK_THROWS_AS(intString.asAbsUInt(), Json::error); - intString = "4294967295"; - CHECK(intString.asUInt() == 4294967295u); + intString = "4294967295"; + CHECK(intString.asUInt() == 4294967295u); + CHECK(intString.asAbsUInt() == 4294967295u); - intString = "0"; - CHECK(intString.asUInt() == 0); + intString = "0"; + CHECK(intString.asUInt() == 0); + CHECK(intString.asAbsUInt() == 0); - intString = "-1"; - CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + intString = "-1"; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + CHECK(intString.asAbsUInt() == 1); - intString = "2147483648"; - CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + intString = "-4294967295"; + CHECK(intString.asAbsUInt() == 4294967295); - intString = "2147483647"; - CHECK(intString.asInt() == 2147483647); + intString = "-4294967296"; + CHECK_THROWS_AS(intString.asAbsUInt(), Json::error); - intString = "-2147483648"; - CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL + intString = "2147483648"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + CHECK(intString.asAbsUInt() == 2147483648); - intString = "-2147483649"; - CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + intString = "2147483647"; + CHECK(intString.asInt() == 2147483647); + CHECK(intString.asAbsUInt() == 2147483647); + + intString = "-2147483648"; + CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL + CHECK(intString.asAbsUInt() == 2147483648LL); + + intString = "-2147483649"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + CHECK(intString.asAbsUInt() == 2147483649); + } + + { + Json::Value intReal{4294967297.0}; + CHECK_THROWS_AS(intReal.asUInt(), Json::error); + CHECK_THROWS_AS(intReal.asAbsUInt(), Json::error); + + intReal = 4294967295.0; + CHECK(intReal.asUInt() == 4294967295u); + CHECK(intReal.asAbsUInt() == 4294967295u); + + intReal = 0.0; + CHECK(intReal.asUInt() == 0); + CHECK(intReal.asAbsUInt() == 0); + + intReal = -1.0; + CHECK_THROWS_AS(intReal.asUInt(), Json::error); + CHECK(intReal.asAbsUInt() == 1); + + intReal = -4294967295.0; + CHECK(intReal.asAbsUInt() == 4294967295); + + intReal = -4294967296.0; + CHECK_THROWS_AS(intReal.asAbsUInt(), Json::error); + + intReal = 2147483648.0; + CHECK_THROWS_AS(intReal.asInt(), Json::error); + CHECK(intReal.asAbsUInt() == 2147483648); + + intReal = 2147483647.0; + CHECK(intReal.asInt() == 2147483647); + CHECK(intReal.asAbsUInt() == 2147483647); + + intReal = -2147483648.0; + CHECK(intReal.asInt() == -2147483648LL); // MSVC wants the LL + CHECK(intReal.asAbsUInt() == 2147483648LL); + + intReal = -2147483649.0; + CHECK_THROWS_AS(intReal.asInt(), Json::error); + CHECK(intReal.asAbsUInt() == 2147483649); + } } TEST_CASE("copy") @@ -793,6 +874,7 @@ TEST_CASE("conversions") CHECK(val.asString() == ""); CHECK(val.asInt() == 0); CHECK(val.asUInt() == 0); + CHECK(val.asAbsUInt() == 0); CHECK(val.asDouble() == 0.0); CHECK(val.asBool() == false); @@ -813,6 +895,7 @@ TEST_CASE("conversions") CHECK(val.asString() == "-1234"); CHECK(val.asInt() == -1234); CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK(val.asAbsUInt() == 1234u); CHECK(val.asDouble() == -1234.0); CHECK(val.asBool() == true); @@ -833,6 +916,7 @@ TEST_CASE("conversions") CHECK(val.asString() == "1234"); CHECK(val.asInt() == 1234); CHECK(val.asUInt() == 1234u); + CHECK(val.asAbsUInt() == 1234u); CHECK(val.asDouble() == 1234.0); CHECK(val.asBool() == true); @@ -853,6 +937,7 @@ TEST_CASE("conversions") CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); CHECK(val.asInt() == 2); CHECK(val.asUInt() == 2u); + CHECK(val.asAbsUInt() == 2u); CHECK(val.asDouble() == 2.0); CHECK(val.asBool() == true); @@ -873,6 +958,7 @@ TEST_CASE("conversions") CHECK(val.asString() == "54321"); CHECK(val.asInt() == 54321); CHECK(val.asUInt() == 54321u); + CHECK(val.asAbsUInt() == 54321); CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK(val.asBool() == true); @@ -893,6 +979,7 @@ TEST_CASE("conversions") CHECK(val.asString() == ""); CHECK_THROWS_AS(val.asInt(), std::exception); CHECK_THROWS_AS(val.asUInt(), std::exception); + CHECK_THROWS_AS(val.asAbsUInt(), std::exception); CHECK_THROWS_AS(val.asDouble(), std::exception); CHECK(val.asBool() == false); @@ -913,6 +1000,7 @@ TEST_CASE("conversions") CHECK(val.asString() == "false"); CHECK(val.asInt() == 0); CHECK(val.asUInt() == 0); + CHECK(val.asAbsUInt() == 0); CHECK(val.asDouble() == 0.0); CHECK(val.asBool() == false); @@ -933,6 +1021,7 @@ TEST_CASE("conversions") CHECK(val.asString() == "true"); CHECK(val.asInt() == 1); CHECK(val.asUInt() == 1); + CHECK(val.asAbsUInt() == 1); CHECK(val.asDouble() == 1.0); CHECK(val.asBool() == true); @@ -953,6 +1042,7 @@ TEST_CASE("conversions") CHECK_THROWS_AS(val.asString(), Json::error); CHECK_THROWS_AS(val.asInt(), Json::error); CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asAbsUInt(), Json::error); CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK(val.asBool() == false); // empty or not @@ -973,6 +1063,7 @@ TEST_CASE("conversions") CHECK_THROWS_AS(val.asString(), Json::error); CHECK_THROWS_AS(val.asInt(), Json::error); CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asAbsUInt(), Json::error); CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK(val.asBool() == false); // empty or not