Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,25 @@ jobs:
uses: threeal/cmake-action@v2.0.0
with:
options: CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }}


cmake-no-int64:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: checkout project
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: build without 64-bit integer storage
run: |
cmake -S . -B build-no-int64 \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_STANDARD=11 \
-DCMAKE_CXX_FLAGS=-DJSON_NO_INT64 \
-DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF
cmake --build build-no-int64 --parallel 2

- name: test without 64-bit integer storage
run: ctest --test-dir build-no-int64 --output-on-failure
3 changes: 2 additions & 1 deletion src/lib_json/json_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,8 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) {

if (isNegative) {
// We use the same magnitude assumption here, just in case.
const auto last_digit = static_cast<Value::UInt>(value % 10);
// Keep the subtraction signed when JSON_NO_INT64 makes LargestInt an int.
const auto last_digit = static_cast<Value::LargestInt>(value % 10);
decoded = -Value::LargestInt(value / 10) * 10 - last_digit;
} else if (value <= Value::LargestUInt(Value::maxLargestInt)) {
decoded = Value::LargestInt(value);
Expand Down
4 changes: 3 additions & 1 deletion src/lib_json/json_value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ static inline bool InRange(double d, T min, U 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)
#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
#if defined(JSON_HAS_INT64)
static inline double integerToDouble(Json::UInt64 value) {
return static_cast<double>(Int64(value / 2)) * 2.0 +
static_cast<double>(Int64(value & 1));
}
#endif

template <typename T> static inline double integerToDouble(T value) {
return static_cast<double>(value);
Expand Down
2 changes: 2 additions & 0 deletions src/test_lib_json/jsontest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,15 @@ TestResult& TestResult::addToLastFailure(const Json::String& message) {
return *this;
}

#if defined(JSON_HAS_INT64)
TestResult& TestResult::operator<<(Json::Int64 value) {
return addToLastFailure(Json::valueToString(value));
}

TestResult& TestResult::operator<<(Json::UInt64 value) {
return addToLastFailure(Json::valueToString(value));
}
#endif

TestResult& TestResult::operator<<(bool value) {
return addToLastFailure(value ? "true" : "false");
Expand Down
2 changes: 2 additions & 0 deletions src/test_lib_json/jsontest.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ class TestResult {
// Specialized versions.
TestResult& operator<<(bool value);
// std:ostream does not support 64bits integers on all STL implementation
#if defined(JSON_HAS_INT64)
TestResult& operator<<(Json::Int64 value);
TestResult& operator<<(Json::UInt64 value);
#endif

private:
TestResult& addToLastFailure(const Json::String& message);
Expand Down
108 changes: 102 additions & 6 deletions src/test_lib_json/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "jsontest.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <functional>
#include <iomanip>
Expand All @@ -39,9 +40,15 @@ JSON_API size_t& newlineScanByteCountForTesting();
#define kint32max Json::Value::maxInt
#define kint32min Json::Value::minInt
#define kuint32max Json::Value::maxUInt
#if defined(JSON_HAS_INT64)
#define kint64max Json::Value::maxInt64
#define kint64min Json::Value::minInt64
#define kuint64max Json::Value::maxUInt64
#else
#define kint64max INT64_MAX
#define kint64min INT64_MIN
#define kuint64max UINT64_MAX
#endif

// static const double kdint64max = double(kint64max);
// static const float kfint64max = float(kint64max);
Expand All @@ -54,6 +61,7 @@ static const float kfuint32max = float(kuint32max);
// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////

#if defined(JSON_HAS_INT64)
#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
static inline double uint64ToDouble(Json::UInt64 value) {
return static_cast<double>(value);
Expand All @@ -64,6 +72,7 @@ static inline double uint64ToDouble(Json::UInt64 value) {
static_cast<double>(Json::Int64(value & 1));
}
#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
#endif // if defined(JSON_HAS_INT64)

// local_ is the collection for the testcases in this code file.
static std::deque<JsonTest::TestCaseFactory> local_;
Expand Down Expand Up @@ -97,9 +106,9 @@ struct ValueTest : JsonTest::TestCase {
object2_["null"] = Json::nullValue;
object2_["bool"] = true;
object2_["int"] = Json::Int{Json::Value::maxInt};
object2_["int64"] = Json::Int64{Json::Value::maxInt64};
object2_["int64"] = Json::LargestInt{Json::Value::maxLargestInt};
object2_["uint"] = Json::UInt{Json::Value::maxUInt};
object2_["uint64"] = Json::UInt64{Json::Value::maxUInt64};
object2_["uint64"] = Json::LargestUInt{Json::Value::maxLargestUInt};
object2_["integral"] = 1234;
object2_["double"] = 1234.56789;
object2_["numeric"] = 0.12345f;
Expand Down Expand Up @@ -326,8 +335,12 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, objects) {
JSONTEST_ASSERT(object3_.findInt("int") == nullptr);

const Json::Value* int64Found = object2_.findInt64("int64");
#if defined(JSON_HAS_INT64)
JSONTEST_ASSERT(int64Found != nullptr);
JSONTEST_ASSERT_EQUAL(Json::Int64{Json::Value::maxInt64}, *int64Found);
#else
JSONTEST_ASSERT(int64Found == nullptr);
#endif
JSONTEST_ASSERT(object3_.findInt64("int64") == nullptr);

const Json::Value* uintFound = object2_.findUInt("uint");
Expand All @@ -336,8 +349,12 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, objects) {
JSONTEST_ASSERT(object3_.findUInt("uint") == nullptr);

const Json::Value* uint64Found = object2_.findUInt64("uint64");
#if defined(JSON_HAS_INT64)
JSONTEST_ASSERT(uint64Found != nullptr);
JSONTEST_ASSERT_EQUAL(Json::UInt64{Json::Value::maxUInt64}, *uint64Found);
#else
JSONTEST_ASSERT(uint64Found == nullptr);
#endif
JSONTEST_ASSERT(object3_.findUInt64("uint64") == nullptr);

const Json::Value* integralFound = object2_.findIntegral("integral");
Expand Down Expand Up @@ -1151,7 +1168,7 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, integers) {
JSONTEST_ASSERT_EQUAL(double(kint64max), val.asDouble());
JSONTEST_ASSERT_EQUAL(float(kint64max), val.asFloat());
JSONTEST_ASSERT_EQUAL(true, val.asBool());
JSONTEST_ASSERT_STRING_EQUAL("9.22337e+18", val.asString());
JSONTEST_ASSERT_STRING_EQUAL("9.2233720368547758e+18", val.asString());

// int64 min
val = Json::Value(double(kint64min));
Expand All @@ -1170,7 +1187,7 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, integers) {
JSONTEST_ASSERT_EQUAL(double(kint64min), val.asDouble());
JSONTEST_ASSERT_EQUAL(float(kint64min), val.asFloat());
JSONTEST_ASSERT_EQUAL(true, val.asBool());
JSONTEST_ASSERT_STRING_EQUAL("-9.22337e+18", val.asString());
JSONTEST_ASSERT_STRING_EQUAL("-9.2233720368547758e+18", val.asString());

// uint64 max
val = Json::Value(double(kuint64max));
Expand All @@ -1189,7 +1206,7 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, integers) {
JSONTEST_ASSERT_EQUAL(double(kuint64max), val.asDouble());
JSONTEST_ASSERT_EQUAL(float(kuint64max), val.asFloat());
JSONTEST_ASSERT_EQUAL(true, val.asBool());
JSONTEST_ASSERT_STRING_EQUAL("1.84467e+19", val.asString());
JSONTEST_ASSERT_STRING_EQUAL("1.8446744073709552e+19", val.asString());
#else // ifdef JSON_NO_INT64
// 2^40 (signed constructor arg)
val = Json::Value(Json::Int64(1) << 40);
Expand Down Expand Up @@ -1961,13 +1978,15 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, typeChecksThrowExceptions) {
JSONTEST_ASSERT_THROWS(objVal.asUInt());
JSONTEST_ASSERT_THROWS(arrVal.asUInt());

#if defined(JSON_HAS_INT64)
JSONTEST_ASSERT_THROWS(strVal.asInt64());
JSONTEST_ASSERT_THROWS(objVal.asInt64());
JSONTEST_ASSERT_THROWS(arrVal.asInt64());

JSONTEST_ASSERT_THROWS(strVal.asUInt64());
JSONTEST_ASSERT_THROWS(objVal.asUInt64());
JSONTEST_ASSERT_THROWS(arrVal.asUInt64());
#endif

JSONTEST_ASSERT_THROWS(strVal.asDouble());
JSONTEST_ASSERT_THROWS(objVal.asDouble());
Expand Down Expand Up @@ -3252,6 +3271,81 @@ JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseNumber) {
}
}

JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseNegativeIntegers) {
Json::CharReaderBuilder builder;
CharReaderPtr reader(builder.newCharReader());
const Json::LargestInt values[] = {0,
-1,
-9,
-10,
-11,
-19,
-20,
-99,
-100,
Json::Value::minInt,
Json::Value::minLargestInt + 1,
Json::Value::minLargestInt};
for (const auto expected : values) {
const Json::String number =
expected == 0 ? "-0" : Json::valueToString(expected);
const Json::String documents[] = {number, "[" + number + "]",
"{\"test\":" + number + "}"};
for (const auto& document : documents) {
Json::Value root;
Json::String errors;
const bool ok = reader->parse(
document.data(), document.data() + document.size(), &root, &errors);
JSONTEST_ASSERT(ok);
JSONTEST_ASSERT(errors.empty());
if (!ok)
continue;
const Json::Value& actual = root.isArray() ? root[0]
: root.isObject() ? root["test"]
: root;
JSONTEST_ASSERT_EQUAL(Json::intValue, actual.type());
JSONTEST_ASSERT_EQUAL(Json::Value(expected), actual);
if (actual.type() == Json::intValue)
JSONTEST_ASSERT_EQUAL(expected, actual.asLargestInt());
}
}
}

JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseIntegerLimits) {
Json::CharReaderBuilder builder;
CharReaderPtr reader(builder.newCharReader());
const struct {
Json::String document;
Json::Value expected;
} cases[] = {
{"0", Json::Value(0)},
{"1", Json::Value(1)},
{Json::valueToString(Json::Value::maxLargestInt),
Json::Value(Json::Value::maxLargestInt)},
{Json::valueToString(Json::Value::maxLargestUInt),
Json::Value(Json::Value::maxLargestUInt)},
#if defined(JSON_HAS_INT64)
{"9223372036854775808", Json::Value(Json::UInt64(1) << 63)},
{"-9223372036854775809", Json::Value(-9223372036854775809.0)},
{"18446744073709551616", Json::Value(18446744073709551616.0)},
#else
{"2147483648", Json::Value(Json::UInt(1) << 31)},
{"-2147483649", Json::Value(-2147483649.0)},
{"4294967296", Json::Value(4294967296.0)},
#endif
};
for (const auto& c : cases) {
Json::Value root;
Json::String errors;
JSONTEST_ASSERT(reader->parse(c.document.data(),
c.document.data() + c.document.size(), &root,
&errors));
JSONTEST_ASSERT(errors.empty());
JSONTEST_ASSERT_EQUAL(c.expected.type(), root.type());
JSONTEST_ASSERT_EQUAL(c.expected, root);
}
}

JSONTEST_FIXTURE_LOCAL(CharReaderTest, parseSubnormal) {
// Regression test for #1427: subnormal doubles make operator>> set failbit
// even though it produced the correctly-rounded value, so they used to fail
Expand Down Expand Up @@ -4407,9 +4501,11 @@ JSONTEST_FIXTURE_LOCAL(MemberTemplateIs, BehavesSameAsNamedIs) {
for (const Json::Value& j : values) {
JSONTEST_ASSERT_EQUAL(j.is<bool>(), j.isBool());
JSONTEST_ASSERT_EQUAL(j.is<Json::Int>(), j.isInt());
JSONTEST_ASSERT_EQUAL(j.is<Json::Int64>(), j.isInt64());
JSONTEST_ASSERT_EQUAL(j.is<Json::UInt>(), j.isUInt());
#if defined(JSON_HAS_INT64)
JSONTEST_ASSERT_EQUAL(j.is<Json::Int64>(), j.isInt64());
JSONTEST_ASSERT_EQUAL(j.is<Json::UInt64>(), j.isUInt64());
#endif
JSONTEST_ASSERT_EQUAL(j.is<double>(), j.isDouble());
JSONTEST_ASSERT_EQUAL(j.is<Json::String>(), j.isString());
}
Expand Down
1 change: 1 addition & 0 deletions test/data/legacy_test_integer_06_64bits.expected-no-int64
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.=9.223372036854776e+18
1 change: 1 addition & 0 deletions test/data/legacy_test_integer_07_64bits.expected-no-int64
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.=-9.223372036854776e+18
1 change: 1 addition & 0 deletions test/data/legacy_test_integer_08_64bits.expected-no-int64
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.=1.844674407370955e+19
16 changes: 16 additions & 0 deletions test/runjsontests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import os.path
import optparse
import subprocess

VALGRIND_CMD = 'valgrind --tool=memcheck --leak-check=yes --undef-value-errors=yes '

Expand Down Expand Up @@ -66,9 +67,22 @@ class FailError(Exception):
def __init__(self, msg):
super(Exception, self).__init__(msg)

def isInt64Enabled(jsontest_executable_path):
process = subprocess.Popen([jsontest_executable_path, '--json-config'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, errors = process.communicate()
configuration = output.decode('utf-8').strip()
# The existing --json-config command exits with the usage status (3).
if process.returncode not in (0, 3) or configuration not in (
'JSON_NO_INT64=0', 'JSON_NO_INT64=1'):
raise FailError('Could not read JsonCpp integer configuration: %s' %
errors.decode('utf-8', 'replace'))
return configuration == 'JSON_NO_INT64=0'

def runAllTests(jsontest_executable_path, input_path = None,
use_valgrind=False, with_json_checker=False,
writerClass='StyledWriter'):
int64_enabled = isInt64Enabled(jsontest_executable_path)
if not input_path:
input_path = os.path.join(os.getcwd(), 'data')

Expand Down Expand Up @@ -144,6 +158,8 @@ def runAllTests(jsontest_executable_path, input_path = None,
failed_tests.append((input_path, 'Parsing failed:\n' + process_output))
else:
expected_output_path = os.path.splitext(input_path)[0] + '.expected'
if not int64_enabled and os.path.isfile(expected_output_path + '-no-int64'):
expected_output_path += '-no-int64'
expected_output = open(expected_output_path, 'rt', encoding = 'utf-8').read()
detail = (compareOutputs(expected_output, actual_output, 'input')
or compareOutputs(expected_output, actual_rewrite_output, 'rewrite'))
Expand Down