[libcxx-commits] [libcxx] [libc++][format] Don't leave the output buffer full after a bulk write (PR #226791)

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Sep 27 09:55:00 PDT 2026


https://github.com/16bit-ykiko updated https://github.com/llvm/llvm-project/pull/226791

>From 6cf6bb2cee88a2cd73230b9035938a557e8c28fa Mon Sep 17 00:00:00 2001
From: ykiko <ykiko at clice.io>
Date: Sun, 27 Sep 2026 22:32:40 +0800
Subject: [PATCH 1/2] [libc++][format] Don't leave the output buffer full after
 a bulk write

__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
---
 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 bf2bebf9b2a85..f53f4d60b7d4c 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 0000000000000..32dd112c9f312
--- /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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// 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;
+}

>From af9f04b4310166fc1cbc3662e7704580b7835fe1 Mon Sep 17 00:00:00 2001
From: ykiko <ykiko at clice.io>
Date: Mon, 28 Sep 2026 00:54:43 +0800
Subject: [PATCH 2/2] Use REQUIRES: std-at-least-c++20

---
 .../std/utilities/format/format.functions/bug_154670.pass.cpp   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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
index 32dd112c9f312..7f36d7d95b6f9 100644
--- a/libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.functions/bug_154670.pass.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
 
 // XFAIL: availability-fp_to_chars-missing
 



More information about the libcxx-commits mailing list