From 764756865421d63e08c0f00951f343d6c9f6d0ce Mon Sep 17 00:00:00 2001 From: Angadi Yashaswini Date: Mon, 3 Aug 2026 13:37:41 +0530 Subject: [PATCH] reject out-of-range code points in UTF-32 wide-string input Signed-off-by: Angadi Yashaswini --- include/nlohmann/detail/input/input_adapters.hpp | 8 ++++++-- single_include/nlohmann/json.hpp | 8 ++++++-- tests/src/unit-wstring.cpp | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 2c3561cbc0..beb4c1a55b 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -345,8 +345,12 @@ struct wide_string_input_helper } else { - // unknown character - utf8_bytes[0] = static_cast::int_type>(wc); + // A code point above U+10FFFF has no UTF-8 encoding. Passing the + // unit through would narrow it to int, where 0xFFFFFFFF becomes + // char_traits::eof() and would end the input silently, so + // emit a byte that is never valid UTF-8 and let the decoder + // reject it. + utf8_bytes[0] = static_cast::int_type>(0xFF); utf8_bytes_filled = 1; } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 124b5a9daa..c92b816ede 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -7332,8 +7332,12 @@ struct wide_string_input_helper } else { - // unknown character - utf8_bytes[0] = static_cast::int_type>(wc); + // A code point above U+10FFFF has no UTF-8 encoding. Passing the + // unit through would narrow it to int, where 0xFFFFFFFF becomes + // char_traits::eof() and would end the input silently, so + // emit a byte that is never valid UTF-8 and let the decoder + // reject it. + utf8_bytes[0] = static_cast::int_type>(0xFF); utf8_bytes_filled = 1; } } diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index ffbe70e7ea..a38df3aaa8 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -125,6 +125,16 @@ TEST_CASE("wide strings") std::u32string const w = U"\"\x110000"; json _; CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); + + // a code unit above U+10FFFF must not be narrowed onto the EOF + // sentinel: 0xFFFFFFFF would otherwise end the document silently and + // let everything following it pass the strict end-of-input check + std::u32string const trailing{U'[', U'1', U']', static_cast(0xFFFFFFFF), U'x'}; + CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&); + CHECK(!json::accept(trailing)); + + // the same unit inside a string is reported as an ill-formed byte + CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); } } }