[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
Wed Oct 21 06:49:00 PDT 2026
https://github.com/zibi2 updated https://github.com/llvm/llvm-project/pull/203531
>From 69d8286f611fa5abce6f68df4a4df935858b1140 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/3] [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 48bdad9a120305..bf3c0c7695306e 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 ae9875b979bbfd8abfecf0cacc58b067f701c996 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/3] [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 bf3c0c7695306e..95fc3f378c59d5 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?");
}
>From 2c9e1b726ff5998dca39eac5a8fe175d7761233f Mon Sep 17 00:00:00 2001
From: Zibi Sarbinowski <zibi at ca.ibm.com>
Date: Wed, 21 Oct 2026 09:48:41 -0400
Subject: [PATCH 3/3] [libc++][filesystem] Pass n+1 to vsnprintf in
vformat_string fallback
std::string always has room for a null terminator past its size, so
there is no need to resize_and_overwrite to size+1. Resize to 'size'
and pass n+1 to vsnprintf instead, which lets it write all 'size'
characters plus the null terminator.
---
libcxx/src/filesystem/format_string.h | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/libcxx/src/filesystem/format_string.h b/libcxx/src/filesystem/format_string.h
index 95fc3f378c59d5..45b2310e8b4aa6 100644
--- a/libcxx/src/filesystem/format_string.h
+++ b/libcxx/src/filesystem/format_string.h
@@ -44,10 +44,11 @@ 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. 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); });
+ // needed for formatting. std::string always has room for a null terminator
+ // past its size, so we pass n+1 to vsnprintf to let it 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, [&](char* res, size_t n) { return ::vsnprintf(res, n + 1, 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