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.
This commit is contained in:
Bronek Kozicki
2025-11-10 17:33:20 +00:00
committed by GitHub
parent 3968efb5f1
commit 3b810c305a
8 changed files with 454 additions and 61 deletions

View File

@@ -5,6 +5,7 @@
#include <xrpl/json/json_forwards.h> #include <xrpl/json/json_forwards.h>
#include <cstring> #include <cstring>
#include <limits>
#include <map> #include <map>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -139,9 +140,9 @@ public:
using ArrayIndex = UInt; using ArrayIndex = UInt;
static Value const null; static Value const null;
static Int const minInt; static constexpr Int minInt = std::numeric_limits<Int>::min();
static Int const maxInt; static constexpr Int maxInt = std::numeric_limits<Int>::max();
static UInt const maxUInt; static constexpr UInt maxUInt = std::numeric_limits<UInt>::max();
private: private:
class CZString class CZString
@@ -244,6 +245,10 @@ public:
bool bool
asBool() const; asBool() const;
/** Correct absolute value from int or unsigned int */
UInt
asAbsUInt() const;
// TODO: What is the "empty()" method this docstring mentions? // TODO: What is the "empty()" method this docstring mentions?
/** isNull() tests to see if this field is null. Don't use this method to /** isNull() tests to see if this field is null. Don't use this method to
test for emptiness: use empty(). */ test for emptiness: use empty(). */

View File

@@ -14,9 +14,6 @@
namespace Json { namespace Json {
Value const Value::null; 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 class DefaultValueAllocator : public ValueAllocator
{ {
@@ -550,6 +547,69 @@ Value::asInt() const
return 0; // unreachable; 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<int>::min().
if (value_.int_ < 0)
return static_cast<std::int64_t>(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<std::int64_t>(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::UInt
Value::asUInt() const Value::asUInt() const
{ {

View File

@@ -1087,7 +1087,7 @@ amountFromJson(SField const& name, Json::Value const& v)
} }
else else
{ {
parts.mantissa = -value.asInt(); parts.mantissa = value.asAbsUInt();
parts.negative = true; parts.negative = true;
} }
} }

View File

@@ -169,7 +169,7 @@ numberFromJson(SField const& field, Json::Value const& value)
} }
else else
{ {
parts.mantissa = -value.asInt(); parts.mantissa = value.asAbsUInt();
parts.negative = true; parts.negative = true;
} }
} }

View File

@@ -572,7 +572,7 @@ public:
false, false,
true, true,
1, 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 // force an out-of-range validUntil value on the future list
// The first list is accepted. The second fails. The parser // The first list is accepted. The second fails. The parser
// returns the "best" result, so this looks like a success. // returns the "best" result, so this looks like a success.
@@ -608,7 +608,7 @@ public:
false, false,
true, true,
1, 1,
std::chrono::seconds{Json::Value::maxInt + 1}, std::chrono::seconds{Json::Value::minInt},
std::chrono::seconds{Json::Value::maxInt - 6000}}}); std::chrono::seconds{Json::Value::maxInt - 6000}}});
// verify refresh intervals are properly clamped // verify refresh intervals are properly clamped
testFetchList( testFetchList(

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/random.h> #include <xrpl/basics/random.h>
#include <xrpl/beast/unit_test.h> #include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
namespace ripple { namespace ripple {
@@ -585,6 +586,216 @@ public:
#endif #endif
} }
void
testParseJson()
{
static_assert(!std::is_convertible_v<STAmount*, Number*>);
{
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<int>::min();
BEAST_EXPECT(amountFromJson(sfNumber, imin) == XRPAmount(imin));
BEAST_EXPECT(
amountFromJson(sfNumber, std::to_string(imin)) ==
XRPAmount(imin));
constexpr auto imax = std::numeric_limits<int>::max();
BEAST_EXPECT(amountFromJson(sfNumber, imax) == XRPAmount(imax));
BEAST_EXPECT(
amountFromJson(sfNumber, std::to_string(imax)) ==
XRPAmount(imax));
constexpr auto umax = std::numeric_limits<unsigned int>::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 void
testConvertXRP() testConvertXRP()
{ {
@@ -1022,6 +1233,7 @@ public:
testArithmetic(); testArithmetic();
testUnderflow(); testUnderflow();
testRounding(); testRounding();
testParseJson();
testConvertXRP(); testConvertXRP();
testConvertIOU(); testConvertIOU();
testCanAddXRP(); testCanAddXRP();

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/unit_test/suite.h> #include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_forwards.h> #include <xrpl/json/json_forwards.h>
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STNumber.h> #include <xrpl/protocol/STNumber.h>
@@ -126,6 +127,30 @@ struct STNumber_test : public beast::unit_test::suite
BEAST_EXPECT( BEAST_EXPECT(
numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0)); numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
constexpr auto imin = std::numeric_limits<int>::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<int>::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<unsigned int>::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 // Obvious non-numbers tested here
try try
{ {

View File

@@ -7,6 +7,7 @@
#include <doctest/doctest.h> #include <doctest/doctest.h>
#include <algorithm> #include <algorithm>
#include <cmath>
#include <regex> #include <regex>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -15,6 +16,14 @@ namespace ripple {
TEST_SUITE_BEGIN("json_value"); 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") TEST_CASE("construct and compare Json::StaticString")
{ {
static constexpr char sample[]{"Contents of a Json::StaticString"}; static constexpr char sample[]{"Contents of a Json::StaticString"};
@@ -582,8 +591,6 @@ TEST_CASE("bad json")
TEST_CASE("edge cases") TEST_CASE("edge cases")
{ {
std::string json;
std::uint32_t max_uint = std::numeric_limits<std::uint32_t>::max(); std::uint32_t max_uint = std::numeric_limits<std::uint32_t>::max();
std::int32_t max_int = std::numeric_limits<std::int32_t>::max(); std::int32_t max_int = std::numeric_limits<std::int32_t>::max();
std::int32_t min_int = std::numeric_limits<std::int32_t>::min(); std::int32_t min_int = std::numeric_limits<std::int32_t>::min();
@@ -592,71 +599,145 @@ TEST_CASE("edge cases")
std::int32_t a_large_int = max_int - 1978; std::int32_t a_large_int = max_int - 1978;
std::int32_t a_small_int = min_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); std::string json = "{\"max_uint\":" + std::to_string(max_uint);
json += ",\"min_int\":" + std::to_string(min_int); json += ",\"max_int\":" + std::to_string(max_int);
json += ",\"a_uint\":" + std::to_string(a_uint); json += ",\"min_int\":" + std::to_string(min_int);
json += ",\"a_large_int\":" + std::to_string(a_large_int); json += ",\"a_uint\":" + std::to_string(a_uint);
json += ",\"a_small_int\":" + std::to_string(a_small_int); json += ",\"a_large_int\":" + std::to_string(a_large_int);
json += "}"; json += ",\"a_small_int\":" + std::to_string(a_small_int);
json += "}";
Json::Value j1; Json::Value j1;
Json::Reader r1; Json::Reader r1;
CHECK(r1.parse(json, j1)); CHECK(r1.parse(json, j1));
CHECK(j1["max_uint"].asUInt() == max_uint); CHECK(j1["max_uint"].asUInt() == max_uint);
CHECK(j1["max_int"].asInt() == max_int); CHECK(j1["max_uint"].asAbsUInt() == max_uint);
CHECK(j1["min_int"].asInt() == min_int); CHECK(j1["max_int"].asInt() == max_int);
CHECK(j1["a_uint"].asUInt() == a_uint); CHECK(j1["max_int"].asAbsUInt() == max_int);
CHECK(j1["a_uint"] > a_large_int); CHECK(j1["min_int"].asInt() == min_int);
CHECK(j1["a_uint"] > a_small_int); CHECK(
CHECK(j1["a_large_int"].asInt() == a_large_int); j1["min_int"].asAbsUInt() ==
CHECK(j1["a_large_int"].asUInt() == a_large_int); static_cast<std::int64_t>(min_int) * -1);
CHECK(j1["a_large_int"] < a_uint); CHECK(j1["a_uint"].asUInt() == a_uint);
CHECK(j1["a_small_int"].asInt() == a_small_int); CHECK(j1["a_uint"].asAbsUInt() == a_uint);
CHECK(j1["a_small_int"] < 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<std::int64_t>(a_small_int) * -1);
CHECK(j1["a_small_int"] < a_uint);
}
json = "{\"overflow\":"; std::uint64_t overflow = std::uint64_t(max_uint) + 1;
json += std::to_string(std::uint64_t(max_uint) + 1); {
json += "}"; std::string json = "{\"overflow\":";
json += std::to_string(overflow);
json += "}";
Json::Value j2; Json::Value j2;
Json::Reader r2; Json::Reader r2;
CHECK(!r2.parse(json, j2)); CHECK(!r2.parse(json, j2));
}
json = "{\"underflow\":"; std::int64_t underflow = std::int64_t(min_int) - 1;
json += std::to_string(std::int64_t(min_int) - 1); {
json += "}"; std::string json = "{\"underflow\":";
json += std::to_string(underflow);
json += "}";
Json::Value j3; Json::Value j3;
Json::Reader r3; 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"; intString = "4294967295";
CHECK(intString.asUInt() == 4294967295u); CHECK(intString.asUInt() == 4294967295u);
CHECK(intString.asAbsUInt() == 4294967295u);
intString = "0"; intString = "0";
CHECK(intString.asUInt() == 0); CHECK(intString.asUInt() == 0);
CHECK(intString.asAbsUInt() == 0);
intString = "-1"; intString = "-1";
CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast);
CHECK(intString.asAbsUInt() == 1);
intString = "2147483648"; intString = "-4294967295";
CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); CHECK(intString.asAbsUInt() == 4294967295);
intString = "2147483647"; intString = "-4294967296";
CHECK(intString.asInt() == 2147483647); CHECK_THROWS_AS(intString.asAbsUInt(), Json::error);
intString = "-2147483648"; intString = "2147483648";
CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast);
CHECK(intString.asAbsUInt() == 2147483648);
intString = "-2147483649"; intString = "2147483647";
CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); 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") TEST_CASE("copy")
@@ -793,6 +874,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == ""); CHECK(val.asString() == "");
CHECK(val.asInt() == 0); CHECK(val.asInt() == 0);
CHECK(val.asUInt() == 0); CHECK(val.asUInt() == 0);
CHECK(val.asAbsUInt() == 0);
CHECK(val.asDouble() == 0.0); CHECK(val.asDouble() == 0.0);
CHECK(val.asBool() == false); CHECK(val.asBool() == false);
@@ -813,6 +895,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == "-1234"); CHECK(val.asString() == "-1234");
CHECK(val.asInt() == -1234); CHECK(val.asInt() == -1234);
CHECK_THROWS_AS(val.asUInt(), Json::error); CHECK_THROWS_AS(val.asUInt(), Json::error);
CHECK(val.asAbsUInt() == 1234u);
CHECK(val.asDouble() == -1234.0); CHECK(val.asDouble() == -1234.0);
CHECK(val.asBool() == true); CHECK(val.asBool() == true);
@@ -833,6 +916,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == "1234"); CHECK(val.asString() == "1234");
CHECK(val.asInt() == 1234); CHECK(val.asInt() == 1234);
CHECK(val.asUInt() == 1234u); CHECK(val.asUInt() == 1234u);
CHECK(val.asAbsUInt() == 1234u);
CHECK(val.asDouble() == 1234.0); CHECK(val.asDouble() == 1234.0);
CHECK(val.asBool() == true); CHECK(val.asBool() == true);
@@ -853,6 +937,7 @@ TEST_CASE("conversions")
CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$")));
CHECK(val.asInt() == 2); CHECK(val.asInt() == 2);
CHECK(val.asUInt() == 2u); CHECK(val.asUInt() == 2u);
CHECK(val.asAbsUInt() == 2u);
CHECK(val.asDouble() == 2.0); CHECK(val.asDouble() == 2.0);
CHECK(val.asBool() == true); CHECK(val.asBool() == true);
@@ -873,6 +958,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == "54321"); CHECK(val.asString() == "54321");
CHECK(val.asInt() == 54321); CHECK(val.asInt() == 54321);
CHECK(val.asUInt() == 54321u); CHECK(val.asUInt() == 54321u);
CHECK(val.asAbsUInt() == 54321);
CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK_THROWS_AS(val.asDouble(), Json::error);
CHECK(val.asBool() == true); CHECK(val.asBool() == true);
@@ -893,6 +979,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == ""); CHECK(val.asString() == "");
CHECK_THROWS_AS(val.asInt(), std::exception); CHECK_THROWS_AS(val.asInt(), std::exception);
CHECK_THROWS_AS(val.asUInt(), std::exception); CHECK_THROWS_AS(val.asUInt(), std::exception);
CHECK_THROWS_AS(val.asAbsUInt(), std::exception);
CHECK_THROWS_AS(val.asDouble(), std::exception); CHECK_THROWS_AS(val.asDouble(), std::exception);
CHECK(val.asBool() == false); CHECK(val.asBool() == false);
@@ -913,6 +1000,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == "false"); CHECK(val.asString() == "false");
CHECK(val.asInt() == 0); CHECK(val.asInt() == 0);
CHECK(val.asUInt() == 0); CHECK(val.asUInt() == 0);
CHECK(val.asAbsUInt() == 0);
CHECK(val.asDouble() == 0.0); CHECK(val.asDouble() == 0.0);
CHECK(val.asBool() == false); CHECK(val.asBool() == false);
@@ -933,6 +1021,7 @@ TEST_CASE("conversions")
CHECK(val.asString() == "true"); CHECK(val.asString() == "true");
CHECK(val.asInt() == 1); CHECK(val.asInt() == 1);
CHECK(val.asUInt() == 1); CHECK(val.asUInt() == 1);
CHECK(val.asAbsUInt() == 1);
CHECK(val.asDouble() == 1.0); CHECK(val.asDouble() == 1.0);
CHECK(val.asBool() == true); CHECK(val.asBool() == true);
@@ -953,6 +1042,7 @@ TEST_CASE("conversions")
CHECK_THROWS_AS(val.asString(), Json::error); CHECK_THROWS_AS(val.asString(), Json::error);
CHECK_THROWS_AS(val.asInt(), Json::error); CHECK_THROWS_AS(val.asInt(), Json::error);
CHECK_THROWS_AS(val.asUInt(), Json::error); CHECK_THROWS_AS(val.asUInt(), Json::error);
CHECK_THROWS_AS(val.asAbsUInt(), Json::error);
CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK_THROWS_AS(val.asDouble(), Json::error);
CHECK(val.asBool() == false); // empty or not 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.asString(), Json::error);
CHECK_THROWS_AS(val.asInt(), Json::error); CHECK_THROWS_AS(val.asInt(), Json::error);
CHECK_THROWS_AS(val.asUInt(), Json::error); CHECK_THROWS_AS(val.asUInt(), Json::error);
CHECK_THROWS_AS(val.asAbsUInt(), Json::error);
CHECK_THROWS_AS(val.asDouble(), Json::error); CHECK_THROWS_AS(val.asDouble(), Json::error);
CHECK(val.asBool() == false); // empty or not CHECK(val.asBool() == false); // empty or not