[libcxx-commits] [libcxx] [libc++][filesystem] Fix truncation in vformat_string dynamic fallback (PR #203531)
Zibi Sarbinowski via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Aug 25 12:21:29 PDT 2026
https://github.com/zibi2 updated https://github.com/llvm/llvm-project/pull/203531
>From 2ac95ad348cc4d27e74705ed739144a1852ac0e3 Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Fri, 12 Jun 2026 09:44:06 -0400
Subject: [PATCH 1/2] [libc++][filesystem] Increase error message buffer size
to prevent truncation
---
libcxx/src/filesystem/format_string.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libcxx/src/filesystem/format_string.h b/libcxx/src/filesystem/format_string.h
index 8d17b027a6e31..ae43899a162cc 100644
--- a/libcxx/src/filesystem/format_string.h
+++ b/libcxx/src/filesystem/format_string.h
@@ -31,7 +31,7 @@ _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
namespace detail {
inline _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) string vformat_string(const char* msg, va_list ap) {
- array<char, 256> buf;
+ array<char, 1024> buf;
va_list apcopy;
va_copy(apcopy, ap);
>From c07159dca50cebcfe8530b2e06315b538b03426f Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Tue, 25 Aug 2026 15:16:46 -0400
Subject: [PATCH 2/2] [libc++][filesystem] Fix dynamic fallback in
vformat_string
The fallback path in vformat_string had two bugs:
1. resize_and_overwrite was called with 'size' (bytes excluding null),
so vsnprintf received n==size and could only write size-1 characters
plus the null terminator, truncating the last character. Fixed by
passing size+1 to give vsnprintf room for all 'size' characters plus
the null terminator.
2. The root cause of the original truncation was that the fallback path
was broken, not that the stack buffer was too small. Revert the buffer
back to 256 bytes since the dynamic fallback now works correctly for
messages exceeding the stack buffer size.
Note: ap is used directly in the fallback since only apcopy (not ap)
was consumed by the first vsnprintf call, so ap remains valid.
---
libcxx/src/filesystem/format_string.h | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/libcxx/src/filesystem/format_string.h b/libcxx/src/filesystem/format_string.h
index ae43899a162cc..4cb8e282b707a 100644
--- a/libcxx/src/filesystem/format_string.h
+++ b/libcxx/src/filesystem/format_string.h
@@ -31,7 +31,7 @@ _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
namespace detail {
inline _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) string vformat_string(const char* msg, va_list ap) {
- array<char, 1024> buf;
+ array<char, 256> buf;
va_list apcopy;
va_copy(apcopy, ap);
@@ -44,8 +44,10 @@ inline _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) string vformat_string(const ch
} else {
// we did not provide a long enough buffer on our first attempt. The
// return value is the number of bytes (excluding the null byte) that are
- // needed for formatting.
- result.resize_and_overwrite(size, [&](char* res, size_t n) { return ::vsnprintf(res, n, msg, ap); });
+ // needed for formatting. We pass size+1 so that vsnprintf has room to
+ // write all 'size' characters plus the null terminator. ap is still
+ // valid here since only apcopy (not ap) was passed to vsnprintf above.
+ result.resize_and_overwrite(size + 1, [&](char* res, size_t n) { return ::vsnprintf(res, n, msg, ap); });
_LIBCPP_ASSERT_INTERNAL(static_cast<size_t>(size) == result.size(),
"vsnprintf did not result in the same number of characters as the first attempt?");
}
More information about the libcxx-commits
mailing list