[libcxx-commits] [libcxx] [libc++][format] Discard contents since null-terminator in character arrays in formatting (PR #116571)

A. Jiang via libcxx-commits libcxx-commits at lists.llvm.org
Sun Feb 23 07:12:58 PST 2025


https://github.com/frederick-vs-ja updated https://github.com/llvm/llvm-project/pull/116571

>From 96f2f4c322ea679520c24e76b0e1ff0f0368ac63 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Mon, 18 Nov 2024 10:04:33 +0800
Subject: [PATCH 1/9] [libc++][format] Decay character arrays in formatting

Currently, built-in `char`/`wchar_t` arrays are assumed to be
null-terminated sequence with the terminator being the last element in
formatting. This doesn't conform to [format.arg]/6.9.

> otherwise, if `decay_t<TD>` is `char_type*` or `const char_type*`,
> initializes value with `static_cast<const char_type*>(v)`;

The standard wording specifies that character arrays are decayed to
pointers. When the null terminator is not the last element or there's no
null terminator (the latter case is UB), libc++ currently produces
different results.
---
 libcxx/include/__format/format_arg_store.h    | 23 ++++---------------
 .../format/format.functions/format_tests.h    |  4 ++++
 2 files changed, 9 insertions(+), 18 deletions(-)

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index 8b2c95c657c9b..78b142ebcdb74 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -101,20 +101,14 @@ consteval __arg_t __determine_arg_t() {
   return __arg_t::__long_double;
 }
 
-// Char pointer
+// Char pointer or array
 template <class _Context, class _Tp>
-  requires(same_as<typename _Context::char_type*, _Tp> || same_as<const typename _Context::char_type*, _Tp>)
+  requires(same_as<typename _Context::char_type*, _Tp> || same_as<const typename _Context::char_type*, _Tp>) ||
+          (is_array_v<_Tp> && same_as<_Tp, typename _Context::char_type[extent_v<_Tp>]>)
 consteval __arg_t __determine_arg_t() {
   return __arg_t::__const_char_type_ptr;
 }
 
-// Char array
-template <class _Context, class _Tp>
-  requires(is_array_v<_Tp> && same_as<_Tp, typename _Context::char_type[extent_v<_Tp>]>)
-consteval __arg_t __determine_arg_t() {
-  return __arg_t::__string_view;
-}
-
 // String view
 template <class _Context, class _Tp>
   requires(same_as<typename _Context::char_type, typename _Tp::value_type> &&
@@ -188,15 +182,8 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   else if constexpr (__arg == __arg_t::__unsigned_long_long)
     return basic_format_arg<_Context>{__arg, static_cast<unsigned long long>(__value)};
   else if constexpr (__arg == __arg_t::__string_view)
-    // Using std::size on a character array will add the NUL-terminator to the size.
-    if constexpr (is_array_v<_Dp>)
-      return basic_format_arg<_Context>{
-          __arg, basic_string_view<typename _Context::char_type>{__value, extent_v<_Dp> - 1}};
-    else
-      // When the _Traits or _Allocator are different an implicit conversion will
-      // fail.
-      return basic_format_arg<_Context>{
-          __arg, basic_string_view<typename _Context::char_type>{__value.data(), __value.size()}};
+    return basic_format_arg<_Context>{
+        __arg, basic_string_view<typename _Context::char_type>{__value.data(), __value.size()}};
   else if constexpr (__arg == __arg_t::__ptr)
     return basic_format_arg<_Context>{__arg, static_cast<const void*>(__value)};
   else if constexpr (__arg == __arg_t::__handle)
diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index b2ed6775fe8a1..33212fa663e4a 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h
@@ -3189,6 +3189,10 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
     const CharT* data = buffer;
     check(SV("hello 09azAZ!"), SV("hello {}"), data);
   }
+  {
+    CharT buffer[] = {CharT('a'), CharT('b'), CharT('c'), 0, CharT('d'), CharT('e'), CharT('f'), 0};
+    check(SV("hello abc"), SV("hello {}"), buffer);
+  }
   {
     std::basic_string<CharT> data = STR("world");
     check(SV("hello world"), SV("hello {}"), data);

>From e051073552c2f02552597d6ed1c3b0c717e9d234 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Wed, 27 Nov 2024 14:36:40 +0800
Subject: [PATCH 2/9] Address review comments

---
 libcxx/include/__format/format_arg_store.h    | 35 +++++++++++++++----
 .../format/format.functions/format_tests.h    |  2 ++
 2 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index 78b142ebcdb74..f514ea4def8dd 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -17,6 +17,7 @@
 #include <__concepts/arithmetic.h>
 #include <__concepts/same_as.h>
 #include <__config>
+#include <__cstddef/size_t.h>
 #include <__format/concepts.h>
 #include <__format/format_arg.h>
 #include <__type_traits/conditional.h>
@@ -32,6 +33,12 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace __format {
 
+template <class _Arr, class _Elem>
+inline constexpr bool __is_bounded_array_of = false;
+
+template <class _Elem, size_t _Len>
+inline constexpr bool __is_bounded_array_of<_Elem[_Len], _Elem> = true;
+
 /// \returns The @c __arg_t based on the type of the formatting argument.
 ///
 /// \pre \c __formattable<_Tp, typename _Context::char_type>
@@ -101,14 +108,20 @@ consteval __arg_t __determine_arg_t() {
   return __arg_t::__long_double;
 }
 
-// Char pointer or array
+// Char pointer
 template <class _Context, class _Tp>
-  requires(same_as<typename _Context::char_type*, _Tp> || same_as<const typename _Context::char_type*, _Tp>) ||
-          (is_array_v<_Tp> && same_as<_Tp, typename _Context::char_type[extent_v<_Tp>]>)
+  requires(same_as<typename _Context::char_type*, _Tp> || same_as<const typename _Context::char_type*, _Tp>)
 consteval __arg_t __determine_arg_t() {
   return __arg_t::__const_char_type_ptr;
 }
 
+// Char array
+template <class _Context, class _Tp>
+  requires __is_bounded_array_of<_Tp, typename _Context::char_type>
+consteval __arg_t __determine_arg_t() {
+  return __arg_t::__string_view;
+}
+
 // String view
 template <class _Context, class _Tp>
   requires(same_as<typename _Context::char_type, typename _Tp::value_type> &&
@@ -162,13 +175,14 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   static_assert(__arg != __arg_t::__none, "the supplied type is not formattable");
   static_assert(__formattable_with<_Tp, _Context>);
 
+  using __context_char_type = _Context::char_type;
   // Not all types can be used to directly initialize the
   // __basic_format_arg_value.  First handle all types needing adjustment, the
   // final else requires no adjustment.
   if constexpr (__arg == __arg_t::__char_type)
 
 #  if _LIBCPP_HAS_WIDE_CHARACTERS
-    if constexpr (same_as<typename _Context::char_type, wchar_t> && same_as<_Dp, char>)
+    if constexpr (same_as<__context_char_type, wchar_t> && same_as<_Dp, char>)
       return basic_format_arg<_Context>{__arg, static_cast<wchar_t>(static_cast<unsigned char>(__value))};
     else
 #  endif
@@ -182,8 +196,17 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   else if constexpr (__arg == __arg_t::__unsigned_long_long)
     return basic_format_arg<_Context>{__arg, static_cast<unsigned long long>(__value)};
   else if constexpr (__arg == __arg_t::__string_view)
-    return basic_format_arg<_Context>{
-        __arg, basic_string_view<typename _Context::char_type>{__value.data(), __value.size()}};
+    // Using std::size on a character array will add the NUL-terminator to the size.
+    if constexpr (__is_bounded_array_of<_Dp, __context_char_type>) {
+      if (const auto __pzero = char_traits<__context_char_type>::find(__value, extent_v<_Dp>, __context_char_type{}))
+        return basic_format_arg<_Context>{
+            __arg, basic_string_view<__context_char_type>{__value, static_cast<size_t>(__pzero - __value)}};
+      else
+        // The behavior is undefined in this case.
+        return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__value, extent_v<_Dp>}};
+    } else
+      // When the _Traits or _Allocator are different an implicit conversion will fail.
+      return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__value.data(), __value.size()}};
   else if constexpr (__arg == __arg_t::__ptr)
     return basic_format_arg<_Context>{__arg, static_cast<const void*>(__value)};
   else if constexpr (__arg == __arg_t::__handle)
diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index 33212fa663e4a..424205d073304 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h
@@ -3190,6 +3190,8 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
     check(SV("hello 09azAZ!"), SV("hello {}"), data);
   }
   {
+    // https://github.com/llvm/llvm-project/issues/115935
+    // Contents after the embedded null character are discarded.
     CharT buffer[] = {CharT('a'), CharT('b'), CharT('c'), 0, CharT('d'), CharT('e'), CharT('f'), 0};
     check(SV("hello abc"), SV("hello {}"), buffer);
   }

>From 2c10c7784c26362f35550fdac23824d197a0ba9b Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Mon, 23 Dec 2024 00:10:41 +0800
Subject: [PATCH 3/9] Address review comments

---
 libcxx/include/__format/format_arg_store.h | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index f514ea4def8dd..a4144faa63b6e 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -198,12 +198,14 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   else if constexpr (__arg == __arg_t::__string_view)
     // Using std::size on a character array will add the NUL-terminator to the size.
     if constexpr (__is_bounded_array_of<_Dp, __context_char_type>) {
-      if (const auto __pzero = char_traits<__context_char_type>::find(__value, extent_v<_Dp>, __context_char_type{}))
+      const auto __pbegin = std::begin(__value);
+      if (const _Dp* __pzero = char_traits<__context_char_type>::find(__pbegin, extent_v<_Dp>, __context_char_type{})) {
         return basic_format_arg<_Context>{
-            __arg, basic_string_view<__context_char_type>{__value, static_cast<size_t>(__pzero - __value)}};
-      else
-        // The behavior is undefined in this case.
-        return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__value, extent_v<_Dp>}};
+            __arg, basic_string_view<__context_char_type>{__pbegin, static_cast<size_t>(__pzero - __pbegin)}};
+      } else {
+        // Per [format.arg]/5, the behavior is undefined because the array is not null-terminated.
+        return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__pbegin, extent_v<_Dp>}};
+      }
     } else
       // When the _Traits or _Allocator are different an implicit conversion will fail.
       return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__value.data(), __value.size()}};

>From f7777e8c958ec78370a4de919d35f9b782f9f9e8 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Mon, 23 Dec 2024 00:35:31 +0800
Subject: [PATCH 4/9] Fix type of `__pzero`

---
 libcxx/include/__format/format_arg_store.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index a4144faa63b6e..1ab7c385d7039 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -199,7 +199,8 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
     // Using std::size on a character array will add the NUL-terminator to the size.
     if constexpr (__is_bounded_array_of<_Dp, __context_char_type>) {
       const auto __pbegin = std::begin(__value);
-      if (const _Dp* __pzero = char_traits<__context_char_type>::find(__pbegin, extent_v<_Dp>, __context_char_type{})) {
+      if (const __context_char_type* const __pzero =
+              char_traits<__context_char_type>::find(__pbegin, extent_v<_Dp>, __context_char_type{})) {
         return basic_format_arg<_Context>{
             __arg, basic_string_view<__context_char_type>{__pbegin, static_cast<size_t>(__pzero - __pbegin)}};
       } else {

>From 9a1911ac70f1145a0a21d76b8160fcd6695f41a8 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Sat, 22 Feb 2025 17:03:28 +0800
Subject: [PATCH 5/9] Hardening and test

---
 libcxx/include/__format/format_arg_store.h    | 15 ++++-----
 .../format.arg/assert.array.pass.cpp          | 33 +++++++++++++++++++
 2 files changed, 39 insertions(+), 9 deletions(-)
 create mode 100644 libcxx/test/libcxx/utilities/format/format.arguments/format.arg/assert.array.pass.cpp

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index 7f90c8b56c76e..8663fe0791600 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -198,15 +198,12 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
   else if constexpr (__arg == __arg_t::__string_view)
     // Using std::size on a character array will add the NUL-terminator to the size.
     if constexpr (__is_bounded_array_of<_Dp, __context_char_type>) {
-      const auto __pbegin = std::begin(__value);
-      if (const __context_char_type* const __pzero =
-              char_traits<__context_char_type>::find(__pbegin, extent_v<_Dp>, __context_char_type{})) {
-        return basic_format_arg<_Context>{
-            __arg, basic_string_view<__context_char_type>{__pbegin, static_cast<size_t>(__pzero - __pbegin)}};
-      } else {
-        // Per [format.arg]/5, the behavior is undefined because the array is not null-terminated.
-        return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__pbegin, extent_v<_Dp>}};
-      }
+      const __context_char_type* const __pbegin = std::begin(__value);
+      const __context_char_type* const __pzero =
+          char_traits<__context_char_type>::find(__pbegin, extent_v<_Dp>, __context_char_type{});
+      _LIBCPP_ASSERT_VALID_INPUT_RANGE(__pzero != nullptr, "formatting a non-null-terminated array");
+      return basic_format_arg<_Context>{
+          __arg, basic_string_view<__context_char_type>{__pbegin, static_cast<size_t>(__pzero - __pbegin)}};
     } else
       // When the _Traits or _Allocator are different an implicit conversion will fail.
       return basic_format_arg<_Context>{__arg, basic_string_view<__context_char_type>{__value.data(), __value.size()}};
diff --git a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/assert.array.pass.cpp b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/assert.array.pass.cpp
new file mode 100644
index 0000000000000..1e9b1d93eb06f
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/assert.array.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <format>
+
+// Formatting non-null-terminated character arrays.
+
+// REQUIRES: std-at-least-c++20, has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <format>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  {
+    const char non_null_terminated[3]{'1', '2', '3'};
+    TEST_LIBCPP_ASSERT_FAILURE(std::format("{}", non_null_terminated), "formatting a non-null-terminated array");
+  }
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  {
+    const wchar_t non_null_terminated[3]{L'1', L'2', L'3'};
+    TEST_LIBCPP_ASSERT_FAILURE(std::format(L"{}", non_null_terminated), "formatting a non-null-terminated array");
+  }
+#endif
+
+  return 0;
+}

>From 7f71c5b0fc16996a6ef5c9cdab2c7f07c4e61cdb Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Sat, 22 Feb 2025 17:03:57 +0800
Subject: [PATCH 6/9] Test non-strictly null terminated arrays

---
 .../test/std/utilities/format/format.functions/format_tests.h  | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index 34d4740d99b27..60abd4ac4e225 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h
@@ -3194,6 +3194,9 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
     // Contents after the embedded null character are discarded.
     CharT buffer[] = {CharT('a'), CharT('b'), CharT('c'), 0, CharT('d'), CharT('e'), CharT('f'), 0};
     check(SV("hello abc"), SV("hello {}"), buffer);
+    // Even when the last element of the array is not null character.
+    CharT buffer2[] = {CharT('a'), CharT('b'), CharT('c'), 0, CharT('d'), CharT('e'), CharT('f')};
+    check(SV("hello abc"), SV("hello {}"), buffer2);
   }
   {
     std::basic_string<CharT> data = STR("world");

>From 76225c71bcd78cfc04b1cb10fb981e0131c86e39 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Sun, 23 Feb 2025 11:16:16 +0800
Subject: [PATCH 7/9] Fix `formatter.char_array.pass.cpp`

---
 .../formatter.char_array.pass.cpp               | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
index 1b3ff52d22d58..4d59bbb4863b2 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
@@ -41,13 +41,10 @@ struct Tester {
   constexpr Tester(const char (&r)[N]) { __builtin_memcpy(text, r, N); }
   char text[N];
 
-  // The size of the array shouldn't include the NUL character.
-  static const std::size_t size = N - 1;
-
   template <class CharT>
   void
   test(const std::basic_string<CharT>& expected, const std::basic_string_view<CharT>& fmt, std::size_t offset) const {
-    using Str = CharT[size];
+    using Str = CharT[N];
     std::basic_format_parse_context<CharT> parse_ctx{fmt};
     std::formatter<Str, CharT> formatter;
     static_assert(std::semiregular<decltype(formatter)>);
@@ -60,13 +57,9 @@ struct Tester {
     auto out = std::back_inserter(result);
     using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
 
-    std::basic_string<CharT> buffer{text, text + N};
-    // Note not too found of this hack
-    Str* data = reinterpret_cast<Str*>(const_cast<CharT*>(buffer.c_str()));
-
     FormatCtxT format_ctx =
-        test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(*data));
-    formatter.format(*data, format_ctx);
+        test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(text));
+    formatter.format(text, format_ctx);
     assert(result == expected);
   }
 
@@ -118,8 +111,8 @@ template <class CharT>
 void test_array() {
   test_helper_wrapper<" azAZ09,./<>?">(STR(" azAZ09,./<>?"), STR("}"));
 
-  std::basic_string<CharT> s(CSTR("abc\0abc"), 7);
-  test_helper_wrapper<"abc\0abc">(s, STR("}"));
+  // Contents after embedded null terminator are not formatted.
+  test_helper_wrapper<"abc\0abc">(STR("abc"), STR("}"));
 
   test_helper_wrapper<"world">(STR("world"), STR("}"));
   test_helper_wrapper<"world">(STR("world"), STR("_>}"));

>From 4838f505691a51fa4b8c0be1f3f812ffd1906b9a Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Sun, 23 Feb 2025 19:37:30 +0800
Subject: [PATCH 8/9] Fix up `formatter.char_array.pass.cpp` with `wchar_t`

---
 .../formatter.char_array.pass.cpp             | 21 +++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
index 4d59bbb4863b2..bc056db9e254e 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.char_array.pass.cpp
@@ -54,12 +54,25 @@ struct Tester {
     assert(std::to_address(it) == std::to_address(fmt.end()) - offset);
 
     std::basic_string<CharT> result;
-    auto out = std::back_inserter(result);
+    auto out         = std::back_inserter(result);
     using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
 
-    FormatCtxT format_ctx =
-        test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(text));
-    formatter.format(text, format_ctx);
+    if constexpr (std::is_same_v<CharT, char>) {
+      FormatCtxT format_ctx =
+          test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(text));
+      formatter.format(text, format_ctx);
+    }
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+    else {
+      Str buffer;
+      for (std::size_t i = 0; i != N; ++i) {
+        buffer[i] = static_cast<CharT>(text[i]);
+      }
+      FormatCtxT format_ctx =
+          test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(buffer));
+      formatter.format(buffer, format_ctx);
+    }
+#endif
     assert(result == expected);
   }
 

>From 4b2f7eed3ae4e7c103d74955615ada4df90e9386 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Sun, 23 Feb 2025 23:12:34 +0800
Subject: [PATCH 9/9] Fix and harden `<__format/formatter_string.h>`

---
 libcxx/include/__format/formatter_string.h | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/__format/formatter_string.h b/libcxx/include/__format/formatter_string.h
index 30084e582214d..23a146d3a0001 100644
--- a/libcxx/include/__format/formatter_string.h
+++ b/libcxx/include/__format/formatter_string.h
@@ -10,6 +10,7 @@
 #ifndef _LIBCPP___FORMAT_FORMATTER_STRING_H
 #define _LIBCPP___FORMAT_FORMATTER_STRING_H
 
+#include <__assert>
 #include <__config>
 #include <__format/concepts.h>
 #include <__format/format_parse_context.h>
@@ -17,6 +18,7 @@
 #include <__format/formatter_output.h>
 #include <__format/parser_std_format_spec.h>
 #include <__format/write_escaped.h>
+#include <cstddef>
 #include <string>
 #include <string_view>
 
@@ -94,7 +96,9 @@ struct _LIBCPP_TEMPLATE_VIS formatter<_CharT[_Size], _CharT> : public __formatte
   template <class _FormatContext>
   _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator
   format(const _CharT (&__str)[_Size], _FormatContext& __ctx) const {
-    return _Base::format(basic_string_view<_CharT>(__str, _Size), __ctx);
+    const _CharT* const __pzero = char_traits<_CharT>::find(__str, _Size, _CharT{});
+    _LIBCPP_ASSERT_VALID_INPUT_RANGE(__pzero != nullptr, "formatting a non-null-terminated array");
+    return _Base::format(basic_string_view<_CharT>(__str, static_cast<size_t>(__pzero - __str)), __ctx);
   }
 };
 



More information about the libcxx-commits mailing list