json_value.cpp bug in the edges of uint/int (#1519)
* json_value.cpp bug in the edges of uint/int
Fixing bug of sending a number that is a bit bigger than max<uint64_t> it returns 0:
https://stackoverflow.com/questions/77261400/jsoncpp-do-not-protect-from-uint64-overflow-and-have-weird-behavior/77261716#77261716
* Update json_value.cpp
Fixing bug of sending a number that is a bit bigger than max<uint64_t> it returns 0: https://stackoverflow.com/questions/77261400/jsoncpp-do-not-protect-from-uint64-overflow-and-have-weird-behavior/77261716#77261716
* Update test cases
* json_value.cpp bug in the edges of uint/int
Fixing bug of sending a number that is a bit bigger than max<uint64_t> it returns 0:
https://stackoverflow.com/questions/77261400/jsoncpp-do-not-protect-from-uint64-overflow-and-have-weird-behavior/77261716#77261716
* Run clang tidy
---------
Co-authored-by: Jordan Bayles <[email protected]>
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index 5bd8d9a..72ba8e5 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -87,7 +87,8 @@
static inline bool InRange(double d, T min, U max) {
// The casts can lose precision, but we are looking only for
// an approximate range. Might fail on edge cases though. ~cdunn
- return d >= static_cast<double>(min) && d <= static_cast<double>(max);
+ return d >= static_cast<double>(min) && d <= static_cast<double>(max) &&
+ !(static_cast<U>(d) == min && d != static_cast<double>(min));
}
#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
static inline double integerToDouble(Json::UInt64 value) {
@@ -101,7 +102,8 @@
template <typename T, typename U>
static inline bool InRange(double d, T min, U max) {
- return d >= integerToDouble(min) && d <= integerToDouble(max);
+ return d >= integerToDouble(min) && d <= integerToDouble(max) &&
+ !(static_cast<U>(d) == min && d != integerToDouble(min));
}
#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
@@ -705,6 +707,11 @@
JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range");
return Int64(value_.uint_);
case realValue:
+ // If the double value is in proximity to minInt64, it will be rounded to
+ // minInt64. The correct value in this scenario is indeterminable
+ JSON_ASSERT_MESSAGE(
+ value_.real_ != minInt64,
+ "Double value is minInt64, precise value cannot be determined");
JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64),
"double out of Int64 range");
return Int64(value_.real_);
@@ -1311,8 +1318,12 @@
// Note that maxInt64 (= 2^63 - 1) is not exactly representable as a
// double, so double(maxInt64) will be rounded up to 2^63. Therefore we
// require the value to be strictly less than the limit.
- return value_.real_ >= double(minInt64) &&
- value_.real_ < double(maxInt64) && IsIntegral(value_.real_);
+ // minInt64 is -2^63 which can be represented as a double, but since double
+ // values in its proximity are also rounded to -2^63, we require the value
+ // to be strictly greater than the limit to avoid returning 'true' for
+ // values that are not in the range
+ return value_.real_ > double(minInt64) && value_.real_ < double(maxInt64) &&
+ IsIntegral(value_.real_);
default:
break;
}
@@ -1350,7 +1361,11 @@
// Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a
// double, so double(maxUInt64) will be rounded up to 2^64. Therefore we
// require the value to be strictly less than the limit.
- return value_.real_ >= double(minInt64) &&
+ // minInt64 is -2^63 which can be represented as a double, but since double
+ // values in its proximity are also rounded to -2^63, we require the value
+ // to be strictly greater than the limit to avoid returning 'true' for
+ // values that are not in the range
+ return value_.real_ > double(minInt64) &&
value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_);
#else
return value_.real_ >= minInt && value_.real_ <= maxUInt &&
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 55ab224..5a0ce01 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -1191,15 +1191,13 @@
JSONTEST_ASSERT_EQUAL(true, val.asBool());
JSONTEST_ASSERT_STRING_EQUAL("-9223372036854775808", val.asString());
- // int64 min (floating point constructor). Note that kint64min *is* exactly
- // representable as a double.
+ // int64 min (floating point constructor). Since double values in proximity of
+ // kint64min are rounded to kint64min, we don't check for conversion to int64.
val = Json::Value(double(kint64min));
JSONTEST_ASSERT_EQUAL(Json::realValue, val.type());
checks = IsCheck();
- checks.isInt64_ = true;
- checks.isIntegral_ = true;
checks.isDouble_ = true;
checks.isNumeric_ = true;
JSONTEST_ASSERT_PRED(checkIs(val, checks));
@@ -1208,8 +1206,6 @@
JSONTEST_ASSERT(!val.isConvertibleTo(Json::intValue));
JSONTEST_ASSERT(!val.isConvertibleTo(Json::uintValue));
- JSONTEST_ASSERT_EQUAL(kint64min, val.asInt64());
- JSONTEST_ASSERT_EQUAL(kint64min, val.asLargestInt());
JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asDouble());
JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asFloat());
JSONTEST_ASSERT_EQUAL(true, val.asBool());