https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/226868
Backport 372dc97670f7b3ce422dc26e5f626b2711426e8d Requested by: @frederick-vs-ja >From db61e1a92d35c8e4fa189140dae8ddd8c187b0fd Mon Sep 17 00:00:00 2001 From: ykiko <[email protected]> Date: Mon, 28 Sep 2026 09:48:22 +0800 Subject: [PATCH] [libc++][format] Don't leave the output buffer full after a bulk write (#226791) __output_buffer::push_back writes a code unit before it makes room, so the buffer must always have a free one. __copy, __transform and __fill write in chunks of the available space and could end with the buffer full. Formatting into a back_insert_iterator of a string, vector or deque goes through a 256 code unit buffer, so an argument or fill whose length is a multiple of 256, followed by a literal, wrote that literal past the end of the buffer. Make room in a full buffer at the end of these functions, as push_back does. Fixes #154670 (cherry picked from commit 372dc97670f7b3ce422dc26e5f626b2711426e8d) --- libcxx/include/__format/buffer.h | 12 +++ .../format.functions/bug_154670.pass.cpp | 74 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h index e7454f08f45d56..fbb67b02a6d177 100644 --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -250,6 +250,10 @@ class __output_buffer { __first += __chunk; __n -= __chunk; } while (__n); + + // push_back needs a free code unit, which the last chunk may have used. + if (__size_ == __capacity_) + __prepare_write(0); } /// A std::transform wrapper. @@ -276,6 +280,10 @@ class __output_buffer { __first += __chunk; __n -= __chunk; } while (__n); + + // push_back needs a free code unit, which the last chunk may have used. + if (__size_ == __capacity_) + __prepare_write(0); } /// A \c fill_n wrapper. @@ -293,6 +301,10 @@ class __output_buffer { __size_ += __chunk; __n -= __chunk; } while (__n); + + // push_back needs a free code unit, which the last chunk may have used. + if (__size_ == __capacity_) + __prepare_write(0); } [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __capacity() const { return __capacity_; } diff --git a/libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp b/libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp new file mode 100644 index 00000000000000..7f36d7d95b6f9f --- /dev/null +++ b/libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 + +// XFAIL: availability-fp_to_chars-missing + +// https://llvm.org/PR154670 +// +// Formatting into a back_insert_iterator of a string, vector or deque goes +// through a fixed-size buffer. Tests that the code unit written after an +// argument or a fill that ends at a multiple of the buffer's size is not +// written past the end of that buffer. + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <deque> +#include <format> +#include <iterator> +#include <string> +#include <vector> + +#include "make_string.h" +#include "test_macros.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template <class CharT, class Container> +void test(std::size_t size) { + const std::basic_string<CharT> arg(size, CharT('a')); + const std::basic_string<CharT> expected = arg + CharT('!'); + + { // The argument is copied. + Container out; + std::format_to(std::back_inserter(out), SV("{}!"), arg); + assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); + } + if (size != 0) { // The argument's padding is filled. + Container out; + std::format_to(std::back_inserter(out), SV("{:a<{}}!"), SV(""), size); + assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end())); + } + for (std::size_t n : {size, size + 1, size + 2}) { + Container out; + auto result = std::format_to_n(std::back_inserter(out), n, SV("{}!"), arg); + assert(result.size == static_cast<std::ptrdiff_t>(expected.size())); + std::size_t written = std::min(n, expected.size()); + assert(std::equal(out.begin(), out.end(), expected.begin(), expected.begin() + written)); + } +} + +template <class CharT> +void test() { + for (std::size_t size : {0, 1, 255, 256, 257, 511, 512, 513, 1024}) { + test<CharT, std::basic_string<CharT>>(size); + test<CharT, std::vector<CharT>>(size); + test<CharT, std::deque<CharT>>(size); + } +} + +int main(int, char**) { + test<char>(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test<wchar_t>(); +#endif + + return 0; +} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
