[llvm-branch-commits] [libcxx] release/23.x: [libc++][format] Don't leave the output buffer full after a bulk write (#226791) (PR #226868)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Sep 27 18:55:13 PDT 2026


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 <ykiko at clice.io>
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;
+}



More information about the llvm-branch-commits mailing list