Skip to content

gh-69573: Check the return value of _putch() and _putwch() in msvcrt#154364

Open
gianghungtien wants to merge 1 commit into
python:mainfrom
gianghungtien:gh-69573-msvcrt-putch-check-return
Open

gh-69573: Check the return value of _putch() and _putwch() in msvcrt#154364
gianghungtien wants to merge 1 commit into
python:mainfrom
gianghungtien:gh-69573-msvcrt-putch-check-return

Conversation

@gianghungtien

@gianghungtien gianghungtien commented Jul 21, 2026

Copy link
Copy Markdown

msvcrt.putch() and msvcrt.putwch() discarded the value returned by the underlying _putch() / _putwch() CRT functions, so a failed write was silently ignored. This has been the case since the module was written, and it is inconsistent with ungetch() / ungetwch() in the same module, which already check their return value and raise OSError.

The most common failure mode is a process with no console attached (for example running under pythonw.exe, or a child created with DETACHED_PROCESS). In that situation _putch() returns EOF and _putwch() returns WEOF without writing anything, and the caller had no way to notice.

Why not just PyErr_SetFromErrno()

Copying the existing ungetch() pattern verbatim would have been wrong here. I checked what the UCRT actually reports on this failure, in a process started without a console:

_putwch  ret=65535 (WEOF)  errno=0  GetLastError=0
_putch   ret=-1    (EOF)   errno=0  GetLastError=0

The CRT short-circuits on the missing console handle and records no error information at all, so PyErr_SetFromErrno() on its own would produce a misleading [Errno 0] exception. The fix therefore uses the errno-based message when an error code is available, and falls back to a generic OSError("write to console failed") when it is not.

For reference, _ungetch() succeeds without a console (it only fills a CRT-internal pushback buffer), which is why that function never exposed this problem.

Tests

  • test_putch_without_console runs a child process with the DETACHED_PROCESS creation flag (no console at all) and asserts that both functions raise OSError. It needs no gui resource, so it runs by default. Verified that it fails against an unpatched build (msvcrt.putch() did not raise OSError) and passes with the fix.
  • The existing test_putch / test_putwch could previously never fail, since the functions ignored all errors. Now that they can raise, they are guarded with a requires_console skip so they do not spuriously fail when the test suite itself is run without a console.

PCbuild\build.bat -p x64 -c Release compiles clean (0 warnings), and python -m test test_msvcrt passes (14 run, 3 skipped for the gui resource).

Compatibility

This turns a silent no-op into a raised exception, which is a behaviour change. It matches what the issue asks for and what the module already does elsewhere; putch()/putwch() are documented only as writing to the console, and code that relied on the silent failure was not writing anything anyway. Happy to adjust the scope or the error message if reviewers prefer something different.

The getch() / getche() / getwch() / getwche() family has the same underlying problem, but changing those affects return values rather than just error reporting, so I left them out to keep this change reviewable. Glad to follow up separately if wanted.

…svcrt

msvcrt.putch() and msvcrt.putwch() discarded the value returned by the
underlying CRT functions, so a failure was silently ignored.  The most
common case is a process that has no console attached, for example when
running under pythonw.exe: _putch() then returns EOF and _putwch()
returns WEOF without writing anything.

Both functions now raise OSError on failure, which is consistent with
ungetch() and ungetwch() in the same module.  The CRT sets neither errno
nor the Windows last error for this failure, so a generic OSError is
raised when no error code is available.
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #33688936 | 📁 Comparing eb26e24 against main (bfd774d)

  🔍 Preview build  

2 files changed
± library/msvcrt.html
± whatsnew/changelog.html

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant