Skip to content

Fix Windows crash with non-ASCII locale thousands separators#188

Open
joffrey-b wants to merge 1 commit into
complexlogic:masterfrom
joffrey-b:fix-windows11-cli-crash
Open

Fix Windows crash with non-ASCII locale thousands separators#188
joffrey-b wants to merge 1 commit into
complexlogic:masterfrom
joffrey-b:fix-windows11-cli-crash

Conversation

@joffrey-b

@joffrey-b joffrey-b commented Jun 15, 2026

Copy link
Copy Markdown

Summary

rsgain crashes on Windows when run directly in a console on systems using a locale whose
thousands separator is a non-ASCII character. French is a confirmed example: Windows uses
NARROW NO-BREAK SPACE (U+202F) as the thousands separator, which triggers the crash.
The root cause is a bug in the Windows CRT's std::numpunct<char> facet. The fix is a
one-line guard that skips setting the global locale on Windows.

Closes #185

Root cause

Three things combine to produce the crash:

  1. At startup, main() sets the global C++ locale to the system locale
    (std::locale::global(std::locale(""))), enabling locale-aware number formatting.
  2. Several format strings use {:L} (e.g. the sample rate line
    "Stream #0: FLAC, 16 bit, 44 100 Hz, 2 ch"), which accesses the locale's
    std::numpunct<char> facet to retrieve the thousands separator character.
  3. On Windows, the CRT's numpunct<char>::thousands_sep() crashes (exception code
    0xC0000409, STATUS_STACK_BUFFER_OVERRUN in ucrtbase.dll) when the system locale
    defines a thousands separator that cannot be represented as a single char. French uses
    NARROW NO-BREAK SPACE (U+202F), which encodes to three bytes in UTF-8
    (\xE2\x80\xAF). The numpunct<char> facet, which can only return a single char,
    overruns an internal CRT buffer attempting to produce this character.

This explains all the observed symptoms:

  • Piping through Tee-Object works: stdout is not a console so the progress paths that
    call {:L} are not reached in the same way, and the crash does not manifest.
  • -q works: output_ok is suppressed entirely, so the {:L} format specifiers are
    never evaluated.
  • --version works: no {:L} format specifiers are involved.
  • The crash occurs immediately before or during the "Stream #0: ... {:L} Hz ..." output
    line: this is the first {:L} call that formats a number large enough (44100) to
    require a thousands separator.

Fix

Guard the std::locale::global call with #ifndef _WIN32. Without a system locale set,
{:L} falls back to the C locale which uses no thousands separator, so numpunct is
never called with a non-ASCII character on Windows.

This is a targeted workaround for a Windows CRT bug. The change is one line in
rsgain.cpp. No build system changes, no new dependencies. Locale-aware number
formatting ({:L}) continues to work correctly on Linux and macOS.

Changes

  • src/rsgain.cpp: Wrap std::locale::global(std::locale("")) in #ifndef _WIN32.
    A comment explains the reason.

Testing

Verified that rsgain easy <directory> no longer crashes on a French Windows 11 system
when run directly in PowerShell (real console). All files are scanned and tagged correctly.

Behaviour on Linux and macOS is unchanged.

@complexlogic

Copy link
Copy Markdown
Owner

Do you know if the bug has been reported in a public bug tracker somewhere? That way, I could keep track of it and remove this workaround when it's no longer necessary.

@joffrey-b

Copy link
Copy Markdown
Author

Yes, it's a known, currently-open defect class in Microsoft's own STL repository, though I haven't found a report that reproduces our exact crash (STATUS_STACK_BUFFER_OVERRUN). Here's what's tracked upstream:

microsoft/STL#6206, "LWG-4090 Underspecified use of locale facets for locale-dependent std::format" (opened 2026-04-03 by Stephan T. Lavavej, an MSVC STL maintainer, labeled LWG + format, still open)

A comment on that issue contains a repro of the same underlying defect:

locale::global(locale(""));
fputs(format("{:L}\n", 12345).c_str(), stdout);   // char version, broken
fputws(format(L"{:L}\n", 12345).c_str(), stdout); // wchar_t version, correct

With a UTF-8 codepage and a locale whose thousands separator is multi-byte (e.g. Polish NBSP U+00A0, or our French narrow NBSP U+202F), the char-based std::format/std::print path breaks because it assumes the separator fits in a single char. In Microsoft's repro this manifests as garbled output rather than a crash, our STATUS_STACK_BUFFER_OVERRUN is a more severe symptom of the same defect, likely depending on the exact code path (console write vs. string formatting) and CRT version.

There's also a related, closed issue, microsoft/STL#3562, where a maintainer confirms this is a by-design limitation of <locale>: "<locale> doesn't help by expecting a single char to be able to describe a separator."

So: same root-cause defect class as our crash (single-byte numpunct<char> facet can't hold a multi-byte separator), tracked upstream, unresolved. I'd link the workaround to microsoft/STL#6206 as the closest tracking issue, with the caveat that it isn't a byte-for-byte match for our specific crash, it's the same underlying limitation, not a confirmed duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI doesn't work on Windows 11

2 participants