JsonCpp 不防止 uint64 溢出,并且有奇怪的行为

JsonCpp do not protect from uint64 overflow and have weird behavior

提问人:Danny Cohen 提问时间:10/10/2023 最后编辑:Danny Cohen 更新时间:10/10/2023 访问量:113

问:

我正在玩 JsonCpp,并在到达 uint64 范围结束时注意到奇怪的行为。

范围内的数字工作正常。 高于范围但介于 0 之间的数字返回 0。 上面的数字会引发异常。[2**64,2**64+2**11]2**64+2**11

这是意料之中的吗?我可以配置 JsonCpp 为我执行这些验证吗?

提前致谢!

下面是演示该问题的代码片段。

#include <iostream>
#include <jsoncpp/json/json.h>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>


int main() {
    //std::string uint64Str = "1234"; // any number in range [0, 2**64-1] works correctly (prints uint64Str)
    //std::string uint64Str = "18446744073709551615"; // any number in range [0, 2**64-1] works correctly, including 2**64-1 (prints uint64Str)
    //std::string uint64Str = "18446744073709551616"; //max uint64 + 1 print 0
    //std::string uint64Str = "18446744073709551617"; //max uint64 + 2 print 0
    // ...
    //std::string uint64Str = "18446744073709553663"; //max uint64 + 2048 print 0
    //std::string uint64Str = "18446744073709553664"; //max uint64 + 2049 print 0
    std::string uint64Str = "18446744073709553665"; //max uint64 + 2050 (or more) throws instance of 'Json::LogicError' what():  double out of UInt64 range
    Json::Value root;
    Json::Reader reader;
    bool parsingResult = reader.parse("{\"key\":"+uint64Str+"}", root);
        std::cout<<root["key"].asUInt64()<<std::endl;
    
        return 0;
}

C++ 整数溢出 jsoncpp

评论

1赞 463035818_is_not_an_ai 10/10/2023
无符号整数不会溢出。他们环绕着。你看到的行为有什么“奇怪”之处?
1赞 463035818_is_not_an_ai 10/10/2023
的价值是什么?返回的值是否可能指示解析成功/失败?parsingResult
0赞 Danny Cohen 10/10/2023
parsingResult 在所有情况下都是 true,它不会引发异常
0赞 Ted Lyngmo 10/10/2023
@DannyCohen 否,当您尝试通过调用uint64_tasUInt64()
1赞 n. m. could be an AI 10/10/2023
JSON 中没有 int 或 uint。只有数字,它们在范围和精度上是无限的。所以解析不应该失败。根据 jsoncpp 的来源,它应该解析并内部存储大于 max uint64 值的数字为 double(或者它的意图也是如此)。然后 asUInt64 应该为此类数字引发异常。它看起来像 asUInt64 中的一个错误。

答:

1赞 Ted Lyngmo 10/10/2023 #1

问题是非常大的数字被存储为实数(它不适合 )。你也需要把它当作一个真实的来阅读:uint64_t

std::cout << root["key"].asDouble() << std::endl;
//                       ^^^^^^^^^^

实际的异常来自内部,其中检查 a 是否可以适合请求的范围:switchValue::UInt64 Value::asUInt64() constrealValue

case realValue:
      JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64),
                          "double out of UInt64 range");
      return UInt64( value_.real_ );

评论

0赞 Severin Pappadeux 10/10/2023
这是Javascript约定,但我认为它不适用于C++
0赞 Ted Lyngmo 10/10/2023
@SeverinPappadeux 什么是 javascript 约定?我对javascript几乎一无所知,所以我说不出来。
0赞 Severin Pappadeux 10/10/2023
我相信 Javascript 没有整数。整数是使用 53 位尾数的双精度
1赞 Danny Cohen 10/10/2023
@TedLyngmo我想在通过这个范围时出现例外。我想我会使用你以前的建议。谢谢!
2赞 n. m. could be an AI 10/10/2023
@DannyCohen我认为您应该向 JsonCPP 提交错误报告。为这些数字返回 0 是完全不合理的。
1赞 Danny Cohen 10/10/2023 #2

感谢 TedLyngmo 的指导。

在他的回答和额外的研究之后。 大于 max 的数字存储在实数(双精度)中。

然后运行以下代码:

case realValue:
      JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64),
                          "double out of UInt64 range");
      return UInt64( value_.real_ );

我希望该函数在超出范围的数字上失败。 但是,它的实现是:InRange

template <typename T, typename U>
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);
}

它将 max 转换为 double,并将 max 向上舍入 (to ),如果 d 介于 is 之间,则也舍入 (to )。 所以它认为它在范围内。 将 d 转换为 uint64_t 时,您将得到 max+1,即 0。2**64+2max<uint64> to 2**62+2**112**64+2

该功能确实提到,这并不确切......然而,我认为这是库中的一个错误。InRange

我将使用的解决方案是:

uint64_t getUint64(const Json::Value& shouldBeUInt64) {
    uint64_t res = shouldBeUInt64.asUInt64();
    if (res == 0 and shouldBeUInt64.asDouble()>0){
        throw Json::LogicError("double out of UInt64 range");
    }
    return res;
}

更新(2023年11月25日): 请参阅此处的建议修复:https://github.com/open-source-parsers/jsoncpp/pull/1519