Skip to content

fix(encoding): bound non-codecvt narrow/widen by str_size#1359

Merged
henryiii merged 1 commit into
mainfrom
fix/encoding-narrow-widen-size
Jul 1, 2026
Merged

fix(encoding): bound non-codecvt narrow/widen by str_size#1359
henryiii merged 1 commit into
mainfrom
fix/encoding-narrow-widen-size

Conversation

@henryiii

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

Bug

In include/CLI/impl/Encoding_inl.hpp, the non-codecvt branches of narrow_impl/widen_impl did (void)str_size; and relied on std::wcsrtombs/std::mbsrtowcs converting up to a NUL terminator. That is fine for the std::wstring/null-terminated overloads, but wrong for the size-taking overloads (narrow(const wchar_t *, std::size_t), the C++17 narrow(std::wstring_view) / widen(std::string_view), etc.):

  • A view into the middle of a larger buffer converted the entire remainder instead of str_size characters.
  • A view over a non-NUL-terminated buffer read out of bounds (UB).
  • Embedded NULs truncated the result.

The codecvt path already handled all of this correctly via to_bytes(str, str + str_size). This non-codecvt path becomes the default under C++26 (CLI11_HAS_CODECVT is 0 when CLI11_CPP26 is set, per include/CLI/Macros.hpp), so it matters increasingly.

Fix

In the non-codecvt branches, copy the input into a local, NUL-terminated buffer of exactly str_size characters and convert from that, so the conversion is bounded by str_size and never reads past the buffer. The existing locale scope_guard behavior and error reporting are preserved (offsets now reported relative to the local copy). C++11-compatible. Embedded NULs within the first str_size characters still terminate the C-style conversion early; this is documented in a comment as a best-effort limitation.

Testing (dual-path)

Added Encoding: WidenSize / Encoding: NarrowSize regression tests in tests/EncodingTest.cpp that:

  • convert a prefix of a longer buffer (e.g. widen(longer.c_str(), 6) / narrow(longer.c_str(), 6)) and assert only the prefix is converted;
  • convert a non-NUL-terminated buffer of exactly str_size chars (would read OOB under the old code).

Both the default (codecvt) build and a -DCMAKE_CXX_FLAGS=-DCLI11_HAS_CODECVT=0 build were configured and run; EncodingTest passes in both. The macro override was confirmed to select the non-codecvt branch (preprocessed output uses wcsrtombs/mbsrtowcs rather than to_bytes).

Part of #1357

The non-codecvt branches of narrow_impl/widen_impl discarded str_size and
relied on std::wcsrtombs/std::mbsrtowcs converting up to a NUL terminator.
This was wrong for the size-taking overloads (narrow(const wchar_t*, size),
the string_view overloads, etc.): a view into the middle of a buffer
converted the entire remainder, a view over a non-NUL-terminated buffer read
out of bounds, and embedded NULs truncated the result. The codecvt path
already handled this correctly via to_bytes(str, str + str_size).

Copy the input into a local NUL-terminated buffer of exactly str_size
characters so the conversion is bounded. Embedded NULs still terminate the
C-style conversion early; this is documented as a best-effort limitation.

This path becomes the default under C++26 (CLI11_HAS_CODECVT is 0 when
CLI11_CPP26 is set), so correctness here matters increasingly.

Add regression tests (Encoding: WidenSize / NarrowSize) that convert a prefix
of a longer buffer and a non-NUL-terminated buffer; they pass under both the
codecvt and non-codecvt (-DCLI11_HAS_CODECVT=0) paths.

Assisted-by: ClaudeCode:claude-opus-4-8
@henryiii

Copy link
Copy Markdown
Collaborator Author

I'm not on Windows, but the tests pass, so will undraft.

@henryiii henryiii marked this pull request as ready for review June 15, 2026 14:59
@henryiii henryiii merged commit 7796ad1 into main Jul 1, 2026
70 checks passed
@henryiii henryiii deleted the fix/encoding-narrow-widen-size branch July 1, 2026 20:14
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.

1 participant