From 466e56d92d3f63e98a5a118ab885b7f17884a537 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 9 Oct 2026 23:35:38 +0200 Subject: [PATCH] Move the MessagePack SAX tests into unit-sax_parse.cpp With develop's latest changes, the MinGW Debug object of unit-msgpack.cpp has 33134 COFF sections on its own and 33411 with this PR, more than the 32767 that the MinGW linker of the "clang (20.1.8)" job can relocate. Its sax_parse calls, all with utils::SaxCountdown, instantiate a parser and binary reader of their own; moving those tests into unit-sax_parse.cpp brings the object down to 32271 sections (unit-sax_parse: 20678). The moved tests are unchanged: the "SAX aborts" sections, the user-defined SAX consumer of #5405, the deep value read through the SAX interface (#5104), and "MessagePack SAX parsing stops at every event". Signed-off-by: Niels Lohmann --- .github/CONTRIBUTING.md | 4 +- tests/src/unit-msgpack.cpp | 75 -------------------------------- tests/src/unit-sax_parse.cpp | 84 ++++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 80 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 95d804ada..1ec71b953 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -111,8 +111,8 @@ context which existing file needs to be extended, and only very few cases requir When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue. `unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one. -Regression tests that call `sax_parse` go into `unit-sax_parse.cpp` instead: every call instantiates the parser and -the binary reader that recover from errors, which grows a test file considerably. +Tests that call `sax_parse` go into `unit-sax_parse.cpp` rather than into a large test file: every call instantiates +the parser and the binary reader that recover from errors, which grows a test file considerably. #### Exceptions diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 1e1005ad7..6d5239395 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -32,8 +32,6 @@ using nlohmann::json; #include "round_trip_corpus.hpp" #include "test_utils.hpp" #include "custom_object_key_type.hpp" -#include "sax_countdown.hpp" -using utils::SaxCountdown; TEST_CASE("MessagePack") @@ -1646,30 +1644,6 @@ TEST_CASE("MessagePack") } } } - - SECTION("SAX aborts") - { - SECTION("start_array(len)") - { - std::vector const v = {0x93, 0x01, 0x02, 0x03}; - SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); - } - - SECTION("start_object(len)") - { - std::vector const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2}; - SaxCountdown scp(0); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); - } - - SECTION("key()") - { - std::vector const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2}; - SaxCountdown scp(1); - CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); - } - } } TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays") @@ -1740,21 +1714,6 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays") CHECK(json::from_msgpack(packed) == j); } } - - SECTION("a user-defined SAX consumer is unaffected by the internal DOM reserve optimization") - { - // the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser; - // a custom SAX consumer that does not touch a DOM array sees identical events - json j = json::array(); - for (int i = 0; i < 100; ++i) - { - j.push_back(i); - } - const auto packed = json::to_msgpack(j); - - SaxCountdown scp(1000000); // large enough to never trigger an abort - CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack)); - } } TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte") @@ -1793,15 +1752,6 @@ TEST_CASE("MessagePack nesting does not consume the call stack") CHECK(json::from_msgpack(input, true, false).is_discarded()); } - SECTION("a well-formed deep value is read through the SAX interface") - { - std::vector input(300000, 0x91); - input.push_back(0x01); // innermost value - - SaxCountdown accept_all(600001); - CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack)); - } - SECTION("a well-formed deep value is read into a value") { const std::size_t depth = 10000; @@ -1849,31 +1799,6 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload" #endif } -TEST_CASE("MessagePack SAX parsing stops at every event") -{ - // Containers are opened and closed by the loop that reads them; a SAX - // handler that rejects any event - including the end of a nested - // container - must stop the parse right there. - const auto count_events = [](const std::vector& input) - { - int events = 0; - while (true) - { - SaxCountdown scp(events); - if (json::sax_parse(input, &scp, json::input_format_t::msgpack)) - { - return events; - } - ++events; - REQUIRE(events < 1000); - } - }; - - // 20 events: every container kind closes inside another one - const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})"); - CHECK(count_events(json::to_msgpack(j)) == 20); -} - TEST_CASE("single MessagePack roundtrip") { SECTION("sample.json") diff --git a/tests/src/unit-sax_parse.cpp b/tests/src/unit-sax_parse.cpp index 207c6e9d1..6ca485ff5 100644 --- a/tests/src/unit-sax_parse.cpp +++ b/tests/src/unit-sax_parse.cpp @@ -9,9 +9,9 @@ ///////////////////////////////////////////////////////////////////// // Tests that call basic_json::sax_parse have a file of their own: every // sax_parse call instantiates the parser and binary reader that recover from -// errors (see #3989), and in unit-regression2.cpp and unit-regression3.cpp -// this made the objects too large for the MinGW linker to relocate (see -// #5511). +// errors (see #3989), and in unit-regression2.cpp, unit-regression3.cpp, and +// unit-msgpack.cpp this made the objects too large for the MinGW linker to +// relocate (see #5511). ///////////////////////////////////////////////////////////////////// #include "doctest_compatibility.h" @@ -36,6 +36,9 @@ using json = nlohmann::json; #include #include +#include "sax_countdown.hpp" +using utils::SaxCountdown; + // a narrow number_float_t, so that a double read from binary input can // overflow it using float_json = nlohmann::basic_json; @@ -687,4 +690,79 @@ TEST_CASE("issue #5676 - SAX parsing of CBOR tags") #endif } + +TEST_CASE("MessagePack SAX aborts") +{ + SECTION("start_array(len)") + { + std::vector const v = {0x93, 0x01, 0x02, 0x03}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); + } + + SECTION("start_object(len)") + { + std::vector const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2}; + SaxCountdown scp(0); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); + } + + SECTION("key()") + { + std::vector const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2}; + SaxCountdown scp(1); + CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack)); + } +} + +TEST_CASE("issue #5405 - a user-defined SAX consumer is unaffected by the internal DOM reserve optimization") +{ + // the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser; + // a custom SAX consumer that does not touch a DOM array sees identical events + json j = json::array(); + for (int i = 0; i < 100; ++i) + { + j.push_back(i); + } + const auto packed = json::to_msgpack(j); + + SaxCountdown scp(1000000); // large enough to never trigger an abort + CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack)); +} + +TEST_CASE("MessagePack nesting does not consume the call stack - SAX interface") +{ + // see the test case of the same name in unit-msgpack.cpp (#5104) + std::vector input(300000, 0x91); + input.push_back(0x01); // innermost value + + SaxCountdown accept_all(600001); + CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack)); +} + +TEST_CASE("MessagePack SAX parsing stops at every event") +{ + // Containers are opened and closed by the loop that reads them; a SAX + // handler that rejects any event - including the end of a nested + // container - must stop the parse right there. + const auto count_events = [](const std::vector& input) + { + int events = 0; + while (true) + { + SaxCountdown scp(events); + if (json::sax_parse(input, &scp, json::input_format_t::msgpack)) + { + return events; + } + ++events; + REQUIRE(events < 1000); + } + }; + + // 20 events: every container kind closes inside another one + const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})"); + CHECK(count_events(json::to_msgpack(j)) == 20); +} + DOCTEST_CLANG_SUPPRESS_WARNING_POP