Skip to content

Commit 43f3834

Browse files
authored
fix: make array operator[] dense when assigning past the end (#1611) (#1693)
* fix: make array operator[] dense when assigning past the end (#1611) Value::operator[](ArrayIndex) only inserted the requested index, so `arr[5] = x` on an empty array stored a single element while size() reported 6 (highest index + 1) and serialization emitted six elements (missing indices written as null). Range-for iteration walked the underlying sparse map and therefore visited only the one populated element -- inconsistent with both size() and the serialized output. JSON arrays are dense, so materialize the intervening indices as null when assigning beyond the current end, exactly as resize() already does when growing. Iteration, size(), equality, and serialization now agree. Note: this is a behavior change (arrays are now dense in memory after a sparse-looking assignment), so it targets the 1.10.0 minor release rather than a 1.9.x patch. It is ABI-compatible (no signature or layout change), so SOVERSION is unchanged. * chore: bump version to 1.10.0 master becomes the 1.10 line. The #1611 array fix changes runtime behavior (dense arrays) for existing valid code, so the next release is a minor bump, not a 1.9.x patch. SOVERSION stays at 27 (ABI unchanged).
1 parent 5f1f240 commit 43f3834

6 files changed

Lines changed: 44 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ project(jsoncpp
5757
# 3. ./CMakeLists.txt
5858
# 4. ./MODULE.bazel
5959
# IMPORTANT: also update the PROJECT_SOVERSION!!
60-
VERSION 1.9.9 # <major>[.<minor>[.<patch>[.<tweak>]]]
60+
VERSION 1.10.0 # <major>[.<minor>[.<patch>[.<tweak>]]]
6161
LANGUAGES CXX)
6262

6363
message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module(
99
# 3. /CMakeLists.txt
1010
# 4. /MODULE.bazel
1111
# IMPORTANT: also update the SOVERSION!!
12-
version = "1.9.9",
12+
version = "1.10.0",
1313
compatibility_level = 1,
1414
)
1515

include/json/version.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
// 4. /MODULE.bazel
1111
// IMPORTANT: also update the SOVERSION!!
1212

13-
#define JSONCPP_VERSION_STRING "1.9.9"
13+
#define JSONCPP_VERSION_STRING "1.10.0"
1414
#define JSONCPP_VERSION_MAJOR 1
15-
#define JSONCPP_VERSION_MINOR 9
16-
#define JSONCPP_VERSION_PATCH 9
15+
#define JSONCPP_VERSION_MINOR 10
16+
#define JSONCPP_VERSION_PATCH 0
1717
#define JSONCPP_VERSION_HEXA \
1818
((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \
1919
(JSONCPP_VERSION_PATCH << 8))

meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ project(
1010
# 3. /CMakeLists.txt
1111
# 4. /MODULE.bazel
1212
# IMPORTANT: also update the SOVERSION!!
13-
version : '1.9.9',
13+
version : '1.10.0',
1414
default_options : [
1515
'buildtype=release',
1616
'cpp_std=c++11',

src/lib_json/json_value.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,15 @@ Value& Value::operator[](ArrayIndex index) {
985985
if (it != value_.map_->end() && (*it).first == key)
986986
return (*it).second;
987987

988+
// JSON arrays are dense: materialize any gap between the current size and
989+
// `index` with null so that size(), iteration, and serialization stay
990+
// consistent. Without this, `arr[5] = x` on an empty array would store a
991+
// single element while size() reported 6 and serialization emitted six
992+
// (see issue #1611). resize() already grows arrays this same way.
993+
for (ArrayIndex i = size(); i < index; ++i)
994+
value_.map_->insert(value_.map_->end(),
995+
ObjectValues::value_type(CZString(i), nullSingleton()));
996+
988997
ObjectValues::value_type defaultValue(key, nullSingleton());
989998
it = value_.map_->insert(it, defaultValue);
990999
return (*it).second;

src/test_lib_json/main.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,35 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) {
524524
JSONTEST_ASSERT_EQUAL(e, Json::Value{});
525525
}
526526

527+
JSONTEST_FIXTURE_LOCAL(ValueTest, assignBeyondEndPopulatesGapsWithNull) {
528+
// Regression test for #1611: assigning past the end of an array via
529+
// operator[] must fill the intervening indices with null, so that size(),
530+
// iteration, and serialization all agree (JSON arrays are dense). Before the
531+
// fix, `arr[5] = x` stored a single element while size() reported 6 and
532+
// serialization emitted six, and range-for visited only the one element.
533+
Json::Value arr(Json::arrayValue);
534+
arr[5] = "Hello, World!";
535+
536+
JSONTEST_ASSERT_EQUAL(6u, arr.size());
537+
JSONTEST_ASSERT_EQUAL(6, std::distance(arr.begin(), arr.end()));
538+
for (Json::ArrayIndex i = 0; i < 5; ++i)
539+
JSONTEST_ASSERT_EQUAL(Json::Value{}, arr[i]);
540+
JSONTEST_ASSERT_EQUAL("Hello, World!", arr[5].asString());
541+
542+
// Iteration count matches size() and the dense serialization.
543+
Json::ArrayIndex iterated = 0;
544+
for (const Json::Value& e : arr) {
545+
(void)e;
546+
++iterated;
547+
}
548+
JSONTEST_ASSERT_EQUAL(6u, iterated);
549+
550+
Json::StreamWriterBuilder b;
551+
b.settings_["indentation"] = "";
552+
JSONTEST_ASSERT_EQUAL("[null,null,null,null,null,\"Hello, World!\"]",
553+
Json::writeString(b, arr));
554+
}
555+
527556
JSONTEST_FIXTURE_LOCAL(ValueTest, getArrayValue) {
528557
Json::Value array;
529558
for (Json::ArrayIndex i = 0; i < 5; i++)

0 commit comments

Comments
 (0)