output_file(path, buffer_size = 0) allocated a zero-size buffer, but
ostream::grow can only make room by flushing, so a zero capacity is never
increased. push_back then writes past the allocation and leaves size()
above capacity(), after which append computes capacity_ - size_ as
SIZE_MAX and copies the rest of the output out of bounds. If the first
write is an append instead, the count is clamped to 0 and print never
returns.
Allocate at least one byte so a zero buffer size behaves like 1.
* Avoid narrowing duration day counts to the original representation
* Keep duration field values in the promoted representation
---------
Co-authored-by: Aikhan Jumashukurov <117706338+aikhanj@users.noreply.github.com>
* Fix generic path format when formatting to wide strings
get_path_string used the native or generic string computed by the path
formatter only when converting wchar_t to UTF-8 and fell back to
p.string<Char>() otherwise, so the 'g' specifier was ignored when the
output character type matched path::value_type. On Windows,
fmt::format(L"{:g}", path(L"C:\\foo")) returned L"C:\\foo" instead of
L"C:/foo". Use the passed string in that case, as write_escaped_path
already does.
* Keep generic path format when converting to wide strings on POSIX
The formatters for std::variant, std::monostate, std::expected and
std::unexpected wrote narrow string literals with detail::write<Char>,
which is ambiguous for wchar_t, so formatting these types into a wide
string failed to compile even though is_formattable reported them as
formattable. Use detail::string_literal like the std::optional
formatter does.
Compute the upper bound as 2^digits with std::ldexp instead of
static_cast<FromRep>(max) + 1, which is only exact when rounding to
nearest: with FE_UPWARD in an unoptimized build the bound became
2^63 + 2048 and a count of 2^63 reached the undefined conversion.
Truncate the count once and use it for both the check and the cast.
Skip the checks that need safe duration casts when
FMT_SAFE_DURATION_CAST is 0, where the NaN time point check failed.
Drop the 1e300 time point and two bool assertions, which don't catch
anything the others don't, and test max + 1 and NaN directly.
* fix unchecked float to integral conversion in detail::duration_cast
* Check the truncated count in float to integral duration_cast
* Keep the duration_cast fallback for custom representations
* Use an explicit rep in the custom_rep duration_cast test
std::chrono::seconds is duration<long> on 64-bit Linux so common_type<long,
custom_rep> had no specialization and chrono-test failed to compile there.
* Exclude bool from the checked float to integral duration_cast
Move writing of the formatted hexfloat into write_hexfloat, which only
depends on the character and iterator types, so that it is not duplicated
for each floating-point type, and mark decimal_point_impl noinline to
avoid inlining the std::locale lookup into every caller.
Compute the ISO week-based year and week number from the day of the year
of the Thursday in the current week instead of the weekday of December 31.
This only needs a leap year check, which works for negative years without
floored division, and is about 3 times faster.
For std::tm values with tm_wday and tm_yday inconsistent with tm_year,
%G, %g and %V now give the same results as strftime.
Replace the negative year tests with a year_month_day test that also
covers the leap year check and days_from_civil for negative years.
* Fix formatting of nullptr with FMT_COMPILE and to_string
std::nullptr_t is implicitly convertible to string_view and const char*,
so compiled format strings and fmt::to_string treated nullptr as a null
C string: FMT_COMPILE("{}!") crashed and FMT_COMPILE("{}") and to_string
threw "string pointer is null". Format it as a pointer ("0x0") like the
runtime API does.
* Write nullptr as a pointer in detail::write
detail::write<Char>(out, nullptr) picked the const Char* overload, so
fmt::to_string(nullptr) and FMT_COMPILE("{}") threw "string pointer is
null". Add a nullptr_t overload that forwards to the const void* one.
With the L flag, floating-point values are formatted with the locale's
decimal point, but the hexfloat presentation ('a'/'A') always emitted
'.'. Replace it with the locale decimal point when the value is
localized, like the other floating-point presentations do.
allocator<T>::allocate(n) computed n * sizeof(T) with only an FMT_ASSERT
guarding against overflow, which is a no-op under NDEBUG. On 32-bit targets
a width of 2^30 or more with a 4-byte character type, e.g.
fmt::format(L"{:>{}}", s, 1100000000), wraps the byte count around, and
basic_memory_buffer records a capacity much larger than the block it got,
so padding is written past the end of the allocation.
Make the check unconditional and throw std::bad_alloc, as std::allocator
did before it was replaced in 11.1.0. Move <new> back to format.h since the
header now throws std::bad_alloc.
iso_year_weeks computed the weekday of December 31 with truncating
division and a remainder that is negative for negative years, so it
returned the wrong number of ISO weeks for years before year 0. As a
result %V and %G/%g were wrong around the start and end of such years,
e.g. 1 January of year -1 was formatted as week 52 of year -2 instead
of week 53, and 31 December of year -2 as week 1 of year -1.
Use floored division and a non-negative remainder.
Claude-Session: https://claude.ai/code/session_01Y1b55sEL1gA1c3FCMcQCbm
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The year_month_day formatter only filled in tm_year, tm_mon and tm_mday,
so specifiers that depend on the weekday or the day of the year (%a, %A,
%u, %w, %j, %U, %W, %V, %G, %g) were formatted from zero-initialized
fields, e.g. every date was printed as a Sunday. Compute tm_wday and
tm_yday from the date before passing it to the tm formatter.
nested_format_specs::write formatted the body of a padded value into a
temporary buffer using a context of the same type as the outer one. This
only works when the outer context writes to an appender, so FMT_COMPILE
and format_to with other output iterators failed to compile even when no
width was given.
Use a buffer context that takes arguments and locale from the outer
context when the outer context doesn't write to an appender.
Fixes#4949.
Floor the seconds before calling gmtime instead of adjusting tm_sec, and
keep the subsecond remainder in the common duration type, which fixes
overflow for narrow reps such as duration<int16_t, std::micro>.
The sign is cleared for unsigned conversions regardless of whether the
'+' or the space flag was given, so "% o" and "% X" only repeat what
"%+o" and "%+X" already cover.
C only applies the '+' and space flags to signed conversions, so
fmt::sprintf("%+u", 42) should give "42" like printf does, not "+42".
The same goes for %o, %x and %X.
For numeric alignment write the 0x prefix directly to the output and
subtract it from the width instead of tracking the prefix length
separately. This makes the extra write_padded call and the intermediate
buffer copy unnecessary, restoring write_bytes.
Compilers are installed from third-party archives that periodically
return 50x or throttle to a crawl, which fails the build. Keep the
downloaded packages and restore them on the next run so that both apt
and the legacy wget-based installs work from disk instead of refetching.
apt, unlike apt-get, discards downloaded packages after installing them
unless the binary-specific Keep-Downloaded-Packages is set.
The cache is written once per key; bump the -v1 suffix to refresh it
after the package versions change.
Inline the SANITIZER_FLAGS expression into CXXFLAGS and drop CFLAGS.
c-test is the only C target and CMake links it with the C++ driver because
it depends on fmt-c, so the sanitizer runtime is still linked in.
operator= deallocates the destination's storage but leaves it pointing at
the freed block with a stale capacity and size. When the allocator doesn't
propagate and the two allocators are unequal, move_alloc copies into that
pointer rather than taking over the source's storage, so it writes to freed
memory and the destructor then frees the same block again.
Restore the inline buffer after deallocating and before calling move().
EDG over GCC likes to pretend to be GCC in a lot of macros,
but not all. It does not define `__OPTIMIZE__` ever.
So we would erroneously emit pragma optimize `Og`
even in optimized builds.
Drop the assertions that return from write_escaped_alternative before
reaching the advance_to call they were added to cover: the std::string
and char alternatives take the has_to_string_view and is_same_v<T, Char>
early returns, and expected<void, int> never calls the function at all.
All four are already checked in std-test.cc.
Merge format_unexpected into format_expected since both are guarded by
__cpp_lib_expected and the latter was a single assertion, and note that
std::unexpected has a formatter of its own so the adjacent unexpected(42)
checks are not duplicates.
* Fix null allocator reference in non-propagating move test
* Test ostream chunking with a real buffer
* Initialize the file pointer when constructing a buffer writer
* Enable ASan and UBSan in supported Linux CI builds
* Limit sanitizer CI to recent Clang and GCC configurations
Unify the duplicated trailing-zero computation in write_fixed and remove
the redundant guards around fill_n. Reduce the constexpr branch of
basic_memory_buffer::grow to a single construct loop that uses the
buffer's own allocator.
In tests, drop the MSVC version checks that FMT_USE_CONSTEVAL already
implies, replace a loop computing 2^-900 with the 0x1p-900 literal, and
report high_precision_long_double as skipped rather than passed when
long double is not an extended-precision type.
Hoist the loop-invariant is_constant_evaluated check into a guard, matching
the fill_n overload for pointers. This lets the runtime path return out
directly instead of relying on the scalar fallback being a no-op once n
reaches 0, and leaves the fallback call only in the branch that needs it.
Also cache buf.size() per iteration as buffer::append does, so the fill
pointer is computed after try_resize rather than depending on it not
reallocating.
The U+1FAE0 and U+31350 cases added in #4962 duplicate the is_printable
checks for the same code points. For a code point >= 0x80 needs_escape is
exactly !is_printable(cp), and the existing U+1F984 case already covers a
printable 4-byte sequence passing through unmodified.
GitHub is retiring the macOS 14 runner image on November 2nd, 2026, with
brownouts starting October 5th. Switch to macos-15, which is also arm64,
and pin it explicitly to match how the other workflows pin their images.
Also drop the Xcode 14.3 selection step: it was gated on macos-13, which
is no longer in the matrix and has already been retired.