From 8be6e9cc2b78be2564e5a026113ed22386f64f44 Mon Sep 17 00:00:00 2001 From: Gabriel Feyer Date: Sun, 8 Feb 2026 12:29:05 -0500 Subject: [PATCH 1/4] AVRO-4228: Fix BinaryDecoder::arrayNext() to handle negative block counts --- lang/c++/impl/BinaryDecoder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/c++/impl/BinaryDecoder.cc b/lang/c++/impl/BinaryDecoder.cc index a970d605207..36110ebd550 100644 --- a/lang/c++/impl/BinaryDecoder.cc +++ b/lang/c++/impl/BinaryDecoder.cc @@ -170,7 +170,7 @@ size_t BinaryDecoder::doDecodeItemCount() { } size_t BinaryDecoder::arrayNext() { - return static_cast(doDecodeLong()); + return doDecodeItemCount(); } size_t BinaryDecoder::skipArray() { From dfe68155774439cdb1972b4f0377e22598d576d2 Mon Sep 17 00:00:00 2001 From: Gabriel Feyer Date: Sun, 8 Feb 2026 12:38:36 -0500 Subject: [PATCH 2/4] AVRO-4228: Add test for arrayNext() with negative block counts --- lang/c++/test/CodecTests.cc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lang/c++/test/CodecTests.cc b/lang/c++/test/CodecTests.cc index fb888778368..56737311363 100644 --- a/lang/c++/test/CodecTests.cc +++ b/lang/c++/test/CodecTests.cc @@ -2100,6 +2100,42 @@ static void testJsonCodecReinit() { } } +static void testArrayNegativeBlockCount() { + // Array of ints [10, 20, 30, 40, 50] encoded with a negative block count. + // Per the Avro spec, a negative count means: abs(count) items follow, + // preceded by a long byte-size of the block. + // + // Block 1: count=-3, bytesize=3, items: 10, 20, 30 + // Block 2: count=2, items: 40, 50 + // Terminal: count=0 + const uint8_t data[] = { + 0x05, // zigzag(-3) = 5 + 0x06, // zigzag(3) = 6 (byte-size of block) + 0x14, 0x28, 0x3c, // zigzag ints: 10, 20, 30 + 0x04, // zigzag(2) = 4 + 0x50, 0x64, // zigzag ints: 40, 50 + 0x00 // terminal + }; + + InputStreamPtr is = memoryInputStream(data, sizeof(data)); + DecoderPtr d = binaryDecoder(); + d->init(*is); + + std::vector result; + for (size_t n = d->arrayStart(); n != 0; n = d->arrayNext()) { + for (size_t i = 0; i < n; ++i) { + result.push_back(d->decodeInt()); + } + } + + BOOST_CHECK_EQUAL(result.size(), 5u); + BOOST_CHECK_EQUAL(result[0], 10); + BOOST_CHECK_EQUAL(result[1], 20); + BOOST_CHECK_EQUAL(result[2], 30); + BOOST_CHECK_EQUAL(result[3], 40); + BOOST_CHECK_EQUAL(result[4], 50); +} + static void testByteCount() { OutputStreamPtr os1 = memoryOutputStream(); EncoderPtr e1 = binaryEncoder(); @@ -2125,6 +2161,7 @@ init_unit_test_suite(int, char *[]) { ts->add(BOOST_PARAM_TEST_CASE(&avro::testJson, avro::jsonData, ENDOF(avro::jsonData))); ts->add(BOOST_TEST_CASE(avro::testJsonCodecReinit)); + ts->add(BOOST_TEST_CASE(avro::testArrayNegativeBlockCount)); ts->add(BOOST_TEST_CASE(avro::testByteCount)); return ts; From cd52d258977ed069f227c3b2522af40bb8f65e41 Mon Sep 17 00:00:00 2001 From: Gabriel Feyer Date: Mon, 9 Feb 2026 08:21:11 -0500 Subject: [PATCH 3/4] AVRO-4228: Move negative block count to second block in test --- lang/c++/test/CodecTests.cc | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lang/c++/test/CodecTests.cc b/lang/c++/test/CodecTests.cc index 56737311363..59aa023dbd1 100644 --- a/lang/c++/test/CodecTests.cc +++ b/lang/c++/test/CodecTests.cc @@ -2101,19 +2101,20 @@ static void testJsonCodecReinit() { } static void testArrayNegativeBlockCount() { - // Array of ints [10, 20, 30, 40, 50] encoded with a negative block count. + // Array of ints [10, 20, 30, 40, 50] encoded with a negative block count + // in the second block, which exercises arrayNext(). // Per the Avro spec, a negative count means: abs(count) items follow, // preceded by a long byte-size of the block. // - // Block 1: count=-3, bytesize=3, items: 10, 20, 30 - // Block 2: count=2, items: 40, 50 + // Block 1: count=2, items: 10, 20 (read by arrayStart) + // Block 2: count=-3, bytesize=3, items: 30, 40, 50 (read by arrayNext) // Terminal: count=0 const uint8_t data[] = { - 0x05, // zigzag(-3) = 5 - 0x06, // zigzag(3) = 6 (byte-size of block) - 0x14, 0x28, 0x3c, // zigzag ints: 10, 20, 30 - 0x04, // zigzag(2) = 4 - 0x50, 0x64, // zigzag ints: 40, 50 + 0x04, // zigzag(2) = 4: block count = 2 + 0x14, 0x28, // zigzag ints: 10, 20 + 0x05, // zigzag(-3) = 5: block count = -3 + 0x06, // zigzag(3) = 6: byte-size of block + 0x3c, 0x50, 0x64, // zigzag ints: 30, 40, 50 0x00 // terminal }; @@ -2128,12 +2129,9 @@ static void testArrayNegativeBlockCount() { } } - BOOST_CHECK_EQUAL(result.size(), 5u); - BOOST_CHECK_EQUAL(result[0], 10); - BOOST_CHECK_EQUAL(result[1], 20); - BOOST_CHECK_EQUAL(result[2], 30); - BOOST_CHECK_EQUAL(result[3], 40); - BOOST_CHECK_EQUAL(result[4], 50); + const std::vector expected = {10, 20, 30, 40, 50}; + BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), + expected.begin(), expected.end()); } static void testByteCount() { From 8e7dd3ae2d4974b592e04293b28449dd50331d06 Mon Sep 17 00:00:00 2001 From: Gabriel Feyer Date: Mon, 9 Feb 2026 08:29:18 -0500 Subject: [PATCH 4/4] AVRO-4228: Avoid undefined behavior when negating INT64_MIN in doDecodeItemCount --- lang/c++/impl/BinaryDecoder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/c++/impl/BinaryDecoder.cc b/lang/c++/impl/BinaryDecoder.cc index 36110ebd550..b334de7cf5d 100644 --- a/lang/c++/impl/BinaryDecoder.cc +++ b/lang/c++/impl/BinaryDecoder.cc @@ -164,7 +164,7 @@ size_t BinaryDecoder::doDecodeItemCount() { auto result = doDecodeLong(); if (result < 0) { doDecodeLong(); - return static_cast(-result); + return static_cast(-(result + 1)) + 1; } return static_cast(result); }