From ea9066875c7f1bac807110e47c64df8e9ba876a7 Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Wed, 15 Jul 2026 19:27:22 +0530 Subject: [PATCH 1/8] use array storage in insert(pos, first, last) --- include/boost/json/impl/array.hpp | 2 +- test/array.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/json/impl/array.hpp b/include/boost/json/impl/array.hpp index 1edd75334..fb42ad60d 100644 --- a/include/boost/json/impl/array.hpp +++ b/include/boost/json/impl/array.hpp @@ -565,7 +565,7 @@ insert( revert_insert r(pos, n, *this); while(n--) { - ::new(r.p) value(*first++); + ::new(r.p) value(*first++, sp_); ++r.p; } return r.commit(); diff --git a/test/array.cpp b/test/array.cpp index f7cabb237..cc4bea6fe 100644 --- a/test/array.cpp +++ b/test/array.cpp @@ -971,7 +971,7 @@ class array_test array a({str_}, sp); a.insert(a.begin(), init.begin(), init.end()); - check(a); + check(a, sp); }); // random iterator (multiple growth) @@ -1008,7 +1008,7 @@ class array_test a.insert(a.begin(), make_input_iterator(init.begin()), make_input_iterator(init.end())); - check(a); + check(a, sp); }); // input iterator (multiple growth) From d5d59faaee51198d839cddc7482f9e96c646df4b Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Tue, 21 Jul 2026 21:59:10 +0530 Subject: [PATCH 2/8] fix string corruption in string_impl::shrink_to_fit --- include/boost/json/detail/impl/string_impl.ipp | 4 ++-- test/string.cpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/boost/json/detail/impl/string_impl.ipp b/include/boost/json/detail/impl/string_impl.ipp index ad75f8693..986e2c330 100644 --- a/include/boost/json/detail/impl/string_impl.ipp +++ b/include/boost/json/detail/impl/string_impl.ipp @@ -435,9 +435,9 @@ shrink_to_fit( auto const t = p_.t; if(t->size <= sbo_chars_) { - s_.k = short_string_; std::memcpy( s_.buf, data(), t->size); + s_.k = short_string_; s_.buf[sbo_chars_] = static_cast( sbo_chars_ - t->size); @@ -458,7 +458,7 @@ shrink_to_fit( std::memcpy( tmp.data(), data(), - size()); + size() + 1); destroy(sp); *this = tmp; #ifndef BOOST_NO_EXCEPTIONS diff --git a/test/string.cpp b/test/string.cpp index 282cad7f4..51e234be5 100644 --- a/test/string.cpp +++ b/test/string.cpp @@ -1367,6 +1367,24 @@ class string_test s.shrink_to_fit(); BOOST_TEST(s.capacity() < cap); + + std::string const copy( s.begin(), s.end() ); + + std::size_t const sbo_capacity = string{}.capacity(); + std::size_t n = sbo_capacity + 1; + BOOST_ASSERT(s.capacity() > n); + s.resize(n); + s.shrink_to_fit(); + BOOST_TEST(s.size() == n); + BOOST_TEST(s == string_view(copy.data(), n)); + BOOST_TEST(s.data()[s.size()] == '\0'); + + n = sbo_capacity - 1; + s.resize(n); + s.shrink_to_fit(); + BOOST_TEST(s.size() == n); + BOOST_TEST(s.capacity() == sbo_capacity); + BOOST_TEST(s == string_view(copy.data(), n)); }); } From 6a5777635f5e66bca39f384301d7d9dc014ee2a0 Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Fri, 10 Jul 2026 19:01:12 +0530 Subject: [PATCH 3/8] fixed UB with self-append in string --- include/boost/json/impl/string.hpp | 26 ++++++++++++++++++++++++-- test/string.cpp | 15 +++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/boost/json/impl/string.hpp b/include/boost/json/impl/string.hpp index a1666ef60..1c627788b 100644 --- a/include/boost/json/impl/string.hpp +++ b/include/boost/json/impl/string.hpp @@ -195,16 +195,38 @@ append( InputIt last, std::random_access_iterator_tag) { - auto const n = static_cast< size_type>(last - first); - char* out = impl_.append(n, sp_); + auto const size = impl_.size(); + detail::string_impl tmp; + char* p = impl_.data(); + if(n > impl_.capacity() - size) + { + tmp = detail::string_impl( + detail::string_impl::growth( + size + n, impl_.capacity()), + sp_); + p = tmp.data(); + } + + char* out = p + size; + // [first, last) may alias the current + // storage, so copy the new characters + // into the new storage before freeing it #if defined(_MSC_VER) && _MSC_VER <= 1900 while( first != last ) *out++ = *first++; #else std::copy(first, last, out); #endif + + if(p != impl_.data()) + { + std::memcpy(p, impl_.data(), size); + impl_.destroy(sp_); + impl_ = tmp; + } + impl_.term(size + n); } template diff --git a/test/string.cpp b/test/string.cpp index 51e234be5..b689176fc 100644 --- a/test/string.cpp +++ b/test/string.cpp @@ -2079,6 +2079,21 @@ class string_test BOOST_TEST(s == cs); }); } + + // self-append via the iterator overload forcing reallocation + { + fail_loop([&](storage_ptr const& sp) + { + string s(sp); + s.append(t.v2); + while(s.size() < s.capacity()) + s.push_back('*'); + std::string const cs(s.data(), s.size()); + auto const sv = s.subview(3); + s.append(sv.begin(), sv.end()); + BOOST_TEST(s == cs + cs.substr(3)); + }); + } } void From 5779b03b185f9623a59c3dd72a974db02943e92a Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Thu, 30 Jul 2026 12:43:51 +0530 Subject: [PATCH 4/8] reuse existing capacity in sbo_buffer::grow --- include/boost/json/detail/sbo_buffer.hpp | 5 +- test/Jamfile | 1 + test/sbo_buffer.cpp | 91 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 test/sbo_buffer.cpp diff --git a/include/boost/json/detail/sbo_buffer.hpp b/include/boost/json/detail/sbo_buffer.hpp index 78c95be7d..42132b3da 100644 --- a/include/boost/json/detail/sbo_buffer.hpp +++ b/include/boost/json/detail/sbo_buffer.hpp @@ -141,9 +141,6 @@ class sbo_buffer void grow( std::size_t size ) { - if( !size ) - return; - if( max_size() - size_ < size ) { BOOST_STATIC_CONSTEXPR source_location loc = BOOST_CURRENT_LOCATION; @@ -152,6 +149,8 @@ class sbo_buffer std::size_t const old_capacity = this->capacity(); std::size_t new_capacity = size_ + size; + if( new_capacity <= old_capacity ) + return; // growth factor 2 if( old_capacity <= max_size() - old_capacity ) // check for overflow diff --git a/test/Jamfile b/test/Jamfile index b15678386..a90246439 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -37,6 +37,7 @@ local SOURCES = pilfer.cpp pointer.cpp result_for.cpp + sbo_buffer.cpp serialize.cpp serializer.cpp snippets.cpp diff --git a/test/sbo_buffer.cpp b/test/sbo_buffer.cpp new file mode 100644 index 000000000..7e164a3a7 --- /dev/null +++ b/test/sbo_buffer.cpp @@ -0,0 +1,91 @@ +// +// Copyright (c) 2026 Ramya Eliger (ramya@digiscrypt.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/json +// + +#include +#include + +#include + +#include "test_suite.hpp" + +namespace boost { +namespace json { + +class sbo_buffer_test +{ +public: + void + testInlineStorage() + { + detail::sbo_buffer<32> buf; + std::size_t const cap = buf.capacity(); + BOOST_TEST(cap >= 32); + + char const* p = buf.append("1.2", 3); + BOOST_TEST(buf.size() == 3); + BOOST_TEST(string_view(p, buf.size()) == "1.2"); + BOOST_TEST(buf.capacity() == cap); + + p = buf.append("5e10", 4); + BOOST_TEST(buf.size() == 7); + BOOST_TEST(string_view(p, buf.size()) == "1.25e10"); + BOOST_TEST(buf.capacity() == cap); + } + + void + testReuse() + { + detail::sbo_buffer<32> buf; + std::size_t const cap = buf.capacity(); + for(int i = 0; i < 40; ++i) + { + buf.clear(); + char const* p = buf.append("1.25", 4); + BOOST_TEST(buf.size() == 4); + BOOST_TEST(string_view(p, buf.size()) == "1.25"); + } + BOOST_TEST(buf.capacity() == cap); + } + + void + testGrowth() + { + detail::sbo_buffer<32> buf; + std::string const s(1000, 'x'); + + char const* p = buf.append(s.data(), s.size()); + BOOST_TEST(buf.size() == s.size()); + BOOST_TEST(buf.capacity() >= s.size()); + BOOST_TEST(string_view(p, buf.size()) == s); + + std::size_t const cap = buf.capacity(); + buf.clear(); + p = buf.append("1.25", 4); + BOOST_TEST(buf.size() == 4); + BOOST_TEST(string_view(p, buf.size()) == "1.25"); + BOOST_TEST(buf.capacity() == cap); + + buf.reset(); + BOOST_TEST(buf.size() == 0); + BOOST_TEST(buf.capacity() == 32); + } + + void + run() + { + testInlineStorage(); + testReuse(); + testGrowth(); + } +}; + +TEST_SUITE(sbo_buffer_test, "boost.json.sbo_buffer"); + +} // namespace json +} // namespace boost From c3a56f4c6edfe5ef86c0e905c5e6609ecefea218 Mon Sep 17 00:00:00 2001 From: Ramya-9353 Date: Sun, 19 Jul 2026 14:01:40 +0000 Subject: [PATCH 5/8] fix overlapping memcpy in string::assign(char const*, size_type) When the source pointer aliases the string's own buffer, char_traits::copy (memcpy) is called on overlapping regions, which is undefined behaviour. Detect aliasing with ptr_in_range and use char_traits::move (memmove) followed by term() instead of delegating to impl_.assign, which would write a null terminator that corrupts the source before the copy. --- include/boost/json/impl/string.ipp | 15 ++++++++++++--- test/string.cpp | 12 +++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/boost/json/impl/string.ipp b/include/boost/json/impl/string.ipp index afc10ba6f..88217a210 100644 --- a/include/boost/json/impl/string.ipp +++ b/include/boost/json/impl/string.ipp @@ -176,9 +176,18 @@ assign( char const* s, size_type count) { - std::char_traits::copy( - impl_.assign(count, sp_), - s, count); + auto const p = impl_.data(); + if(detail::ptr_in_range(p, p + impl_.size(), s)) + { + std::char_traits::move(p, s, count); + impl_.term(count); + } + else + { + std::char_traits::copy( + impl_.assign(count, sp_), + s, count); + } return *this; } diff --git a/test/string.cpp b/test/string.cpp index b689176fc..35a5e23b2 100644 --- a/test/string.cpp +++ b/test/string.cpp @@ -838,7 +838,17 @@ class string_test s.assign(t.s2.c_str(), 3); BOOST_TEST(s == "ABC"); }); - }; + + // non-SBO: assign from a substring of itself + fail_loop([&](storage_ptr const& sp) + { + string s("abcdefghijklmnopqrstuvwxyz0123456789", sp); + string_view sub(s.data() + 3, s.size() - 3); + std::string expected(sub.data(), sub.size()); + s.assign(sub); + BOOST_TEST(s == expected); + }); + } // assign(char const* s) { From 5b7a754069a79c5fa325eb2e7755ab303367241d Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Mon, 13 Jul 2026 18:22:16 +0530 Subject: [PATCH 6/8] fix overflow check in parse_number_token --- include/boost/json/impl/pointer.ipp | 6 +++--- test/pointer.cpp | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/boost/json/impl/pointer.ipp b/include/boost/json/impl/pointer.ipp index d07c48082..0256c0c47 100644 --- a/include/boost/json/impl/pointer.ipp +++ b/include/boost/json/impl/pointer.ipp @@ -200,14 +200,14 @@ parse_number_token( return {}; } - std::size_t new_result = result * 10 + d; - if( new_result < result ) + // guard against std::size_t overflow in result * 10 + d + if( result > (std::size_t(-1) - d) / 10 ) { BOOST_JSON_FAIL(ec, error::token_overflow); return {}; } - result = new_result; + result = result * 10 + d; } return result; diff --git a/test/pointer.cpp b/test/pointer.cpp index ef4c12bb2..6b658befc 100644 --- a/test/pointer.cpp +++ b/test/pointer.cpp @@ -231,6 +231,14 @@ class pointer_test jv.find_pointer(s, ec); BOOST_TEST(ec == error::token_overflow); BOOST_TEST(hasLocation(ec)); + + // a token an order of magnitude past max() still overflows size_t + string s2 = "/foo/"; + s2 += std::to_string((std::numeric_limits::max)()); + s2 += '9'; + jv.find_pointer(s2, ec); + BOOST_TEST(ec == error::token_overflow); + BOOST_TEST(hasLocation(ec)); } void From e2dde54f6ad4bb8cd82730772106aba30edd76b0 Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Fri, 17 Jul 2026 15:40:32 +0530 Subject: [PATCH 7/8] split overflow guard into multiply and add checks --- include/boost/json/impl/pointer.ipp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/boost/json/impl/pointer.ipp b/include/boost/json/impl/pointer.ipp index 0256c0c47..0004c85d7 100644 --- a/include/boost/json/impl/pointer.ipp +++ b/include/boost/json/impl/pointer.ipp @@ -200,15 +200,19 @@ parse_number_token( return {}; } - // guard against std::size_t overflow in result * 10 + d - if( result > (std::size_t(-1) - d) / 10 ) + if( result > std::size_t(-1) / 10 ) { BOOST_JSON_FAIL(ec, error::token_overflow); return {}; } + result *= 10; - result = result * 10 + d; - + if( result > std::size_t(-1) - d ) + { + BOOST_JSON_FAIL(ec, error::token_overflow); + return {}; + } + result += d; } return result; } From 2b1dcb630ab0cc5de19518c9dbcf6b94f07f817c Mon Sep 17 00:00:00 2001 From: Anton Karpov Date: Mon, 3 Aug 2026 17:55:40 +0300 Subject: [PATCH 8/8] [doc] Drop the stray parameter from the on_bool handler docs The handler concept documents on_bool as taking a string_view of the remaining characters, but its declaration is `bool on_bool( bool b, error_code& ec )`. The line is a copy of the one in on_double above it, which really does take one. on_null below has no such line, so this is the only handler affected. --- include/boost/json/basic_parser.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/boost/json/basic_parser.hpp b/include/boost/json/basic_parser.hpp index e28924776..469cf6c55 100644 --- a/include/boost/json/basic_parser.hpp +++ b/include/boost/json/basic_parser.hpp @@ -205,7 +205,6 @@ namespace json { /// /// @return `true` on success. /// @param b The value - /// @param s The remaining characters /// @param ec Set to the error, if any occurred. /// bool on_bool( bool b, error_code& ec );