Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a5ac299
Convert host_env Windows path/argv params from raw *const u16 to &Wid…
youknowone May 16, 2026
cea48cc
Migrate remaining winapi raw u16 pointer signatures to typed references
youknowone May 16, 2026
25a9d40
Migrate winreg pub unsafe fn string parameters to typed references
youknowone May 16, 2026
0acdcad
Add ToPyException impls for host_env error types (PyPy wrap_oserror a…
youknowone May 16, 2026
dc57418
Add CheckLibcResult helper and apply to socket/fcntl/shm/posix_wasi
youknowone May 16, 2026
2d4be2c
Add Win32 BOOL/HANDLE check helpers; apply check helpers across host_env
youknowone May 16, 2026
592e8b4
Apply Win32/libc check helpers to overlapped/testconsole/os.rs
youknowone May 16, 2026
86055d2
Apply Win32 check helpers to winapi.rs (partial)
youknowone May 16, 2026
fe71c58
Apply Win32 check helpers across more winapi.rs functions
youknowone May 16, 2026
a7c567a
Apply Win32 check helpers to nt.rs (partial)
youknowone May 16, 2026
5329cc5
Add CheckWin32Sentinel helper; apply to nt.rs INVALID_HANDLE_VALUE/IN…
youknowone May 16, 2026
f1532e2
Add OwnedHandle / HandleToOwned helper; apply to mmap create_named_ma…
youknowone May 16, 2026
e768278
Use OwnedHandle RAII in nt::pipe to eliminate manual cleanup on error…
youknowone May 16, 2026
b730f7e
Use OwnedHandle in nt::chmod_follow; hoist HandleToOwned import
youknowone May 16, 2026
67ece5b
Drop rustix dependency from vm crate
youknowone May 17, 2026
a61d9bd
Fix CI failures: cross-platform regressions
youknowone May 19, 2026
3320c2a
Fix CI failures: cfg gates and unused imports
youknowone May 19, 2026
e9ec6e8
Fix CI failures: rustfmt and windows unused import
youknowone May 19, 2026
ba3f4fa
Push remaining libc/extern callsites from vm into host_env
youknowone May 19, 2026
45c83dd
Move time tz state and winsound FFI into host_env
youknowone May 19, 2026
49c13f8
Migrate unsetenv to host_env::nt::wputenv; rustfmt
youknowone May 20, 2026
61e0fd4
Address PR review comments
youknowone May 20, 2026
783c6b5
Fix CI failures and address review follow-ups
youknowone May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fix CI failures and address review follow-ups
CI failures:
- rustfmt cleanup in exceptions.rs after the ReadlinkError change.
- vm/stdlib/os.rs: drop unused ToWideString import that the wputenv
  migration left behind.
- vm/stdlib/winsound.rs: replace explicit `&*buf` with `&buf` to
  satisfy clippy::explicit_auto_deref.
- Lib/test/test_format.py, Lib/test/test_types.py: drop the now-stale
  expectedFailureIfWindows decorators on the locale-format tests; the
  Windows path now reads real `localeconv` data via host_env so these
  tests pass.

Review follow-ups:
- host_env::winapi::create_process: switch the new buffer terminator
  checks from `assert!` to fallible validators returning
  `io::ErrorKind::InvalidInput`, so bad inputs stay recoverable at
  the API boundary.
- host_env::winsound::play_sound: reject `Memory(_)` together with
  `SND_ASYNC` (lifetime-unsafe) and `SND_MEMORY` without a
  `Memory(_)` source. Expand `PlaySoundError` into a variant enum.
- vm::stdlib::_winapi::CreateProcess: route the Win32 path/argv strings
  through `as_wtf8().to_wide_cstring()` like the rest of the Windows
  API surface; `expect_str()` could panic on Python strings containing
  lone surrogates.
  • Loading branch information
youknowone committed May 20, 2026
commit 783c6b5064978907b1b4a4cf7121d13eeaafcde7
1 change: 0 additions & 1 deletion Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@ def test_non_ascii(self):
self.assertEqual(format(1+2j, "\u2007^8"), "\u2007(1+2j)\u2007")
self.assertEqual(format(0j, "\u2007^4"), "\u20070j\u2007")

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: ',' not found in '123456789'")
def test_locale(self):
try:
oldloc = locale.setlocale(locale.LC_ALL)
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ def test(i, format_spec, result):
test(123456, "1=20", '11111111111111123456')
test(123456, "*=20", '**************123456')

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: '1,234.57' != '1234.57'")
@run_with_locale('LC_NUMERIC', 'en_US.UTF8', '')
def test_float__format__locale(self):
# test locale support for __format__ code 'n'
Expand All @@ -441,7 +440,6 @@ def test_float__format__locale(self):
self.assertEqual(locale.format_string('%g', x, grouping=True), format(x, 'n'))
self.assertEqual(locale.format_string('%.10g', x, grouping=True), format(x, '.10n'))

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: '123,456,789,012,345,678,901,234,567,890' != '123456789012345678901234567890'")
@run_with_locale('LC_NUMERIC', 'en_US.UTF8', '')
def test_int__format__locale(self):
# test locale support for __format__ code 'n' for integers
Expand Down
32 changes: 20 additions & 12 deletions crates/host_env/src/winapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,23 +219,31 @@ unsafe fn create_process_w_raw(
/// The buffer is passed `&mut [u16]` because `CreateProcessW` may modify it
/// in place.
#[inline]
fn assert_command_line_terminated(buf: &[u16]) {
assert!(
buf.last() == Some(&0),
"command_line buffer passed to create_process must be NUL-terminated",
);
fn validate_command_line_terminated(buf: &[u16]) -> io::Result<()> {
if buf.last() == Some(&0) {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"command_line buffer passed to create_process must be NUL-terminated",
))
}
}

/// Win32 `CreateProcessW` with `CREATE_UNICODE_ENVIRONMENT` requires
/// `lpEnvironment` to be a sequence of `KEY=value\0` strings followed by a
/// final terminating `\0` — i.e. the block ends with two consecutive zero
/// `u16`s.
#[inline]
fn assert_environment_block_terminated(buf: &[u16]) {
assert!(
buf.len() >= 2 && buf[buf.len() - 2..] == [0, 0],
"env block passed to create_process must end with a double NUL terminator",
);
fn validate_environment_block_terminated(buf: &[u16]) -> io::Result<()> {
if buf.len() >= 2 && buf[buf.len() - 2..] == [0, 0] {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"env block passed to create_process must end with a double NUL terminator",
))
}
}

#[allow(
Expand All @@ -253,10 +261,10 @@ pub fn create_process(
handle_list: Option<Vec<usize>>,
) -> io::Result<ProcessInfo> {
if let Some(cmd) = command_line.as_deref() {
assert_command_line_terminated(cmd);
validate_command_line_terminated(cmd)?;
}
if let Some(env_block) = env {
assert_environment_block_terminated(env_block);
validate_environment_block_terminated(env_block)?;
}

let mut si: windows_sys::Win32::System::Threading::STARTUPINFOEXW =
Expand Down
33 changes: 30 additions & 3 deletions crates/host_env/src/winsound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ unsafe extern "system" {
fn MessageBeep(uType: u32) -> i32;
}

/// `SND_ASYNC` flag value from `mmsystem.h`.
const SND_ASYNC: u32 = 0x0001;
/// `SND_MEMORY` flag value from `mmsystem.h`.
const SND_MEMORY: u32 = 0x0004;

/// Source for a `PlaySound` call.
pub enum PlaySoundSource<'a> {
/// Stop currently playing sound (NULL `pszSound`).
Expand All @@ -23,19 +28,41 @@ pub enum PlaySoundSource<'a> {
}

/// Returns `Ok(())` when `PlaySoundW` returns non-zero, an error otherwise.
///
/// Rejects `Memory(_)` together with `SND_ASYNC` because async playback
/// requires the buffer to outlive the call; combining them with a borrowed
/// slice would let WinMM read freed memory.
pub fn play_sound(source: PlaySoundSource<'_>, flags: u32) -> Result<(), PlaySoundError> {
if matches!(source, PlaySoundSource::Memory(_)) && flags & SND_ASYNC != 0 {
return Err(PlaySoundError::MemoryAsyncRejected);
}
// `SND_MEMORY` requires a `Memory(_)` source; an empty pointer would
// dereference garbage.
if !matches!(source, PlaySoundSource::Memory(_)) && flags & SND_MEMORY != 0 {
return Err(PlaySoundError::MemoryFlagWithoutBuffer);
}
let ptr: *const u16 = match source {
PlaySoundSource::Stop => core::ptr::null(),
PlaySoundSource::Memory(buf) => buf.as_ptr().cast(),
PlaySoundSource::Name(s) => s.as_ptr(),
};
let ok = unsafe { PlaySoundW(ptr, 0, flags) };
if ok == 0 { Err(PlaySoundError) } else { Ok(()) }
if ok == 0 {
Err(PlaySoundError::CallFailed)
} else {
Ok(())
}
}

/// `PlaySoundW` returned 0; there is no documented errno for this path.
#[derive(Debug, Clone, Copy)]
pub struct PlaySoundError;
pub enum PlaySoundError {
/// `PlaySoundW` returned 0; there is no documented errno for this path.
CallFailed,
/// `Memory(_)` source cannot be combined with `SND_ASYNC` in the safe API.
MemoryAsyncRejected,
/// `SND_MEMORY` set in `flags` but no `Memory(_)` buffer supplied.
MemoryFlagWithoutBuffer,
}

/// `Beep(freq, duration)`. `false` on failure.
#[must_use]
Expand Down
4 changes: 3 additions & 1 deletion crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,9 @@ impl ToPyException for rustpython_host_env::nt::ReadlinkError {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
match self {
Self::Io(err) => err.to_pyexception(vm),
Self::NotSymbolicLink => vm.new_os_error("The file or directory is not a reparse point"),
Self::NotSymbolicLink => {
vm.new_os_error("The file or directory is not a reparse point")
}
Self::InvalidReparseData => vm.new_os_error("Invalid reparse data"),
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Expand Down
14 changes: 6 additions & 8 deletions crates/vm/src/stdlib/_winapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,8 @@ mod _winapi {

let handle_list = get_handle_list(args.startup_info.get_attr("lpAttributeList", vm)?, vm)?;

let wcstring = |s: PyStrRef| {
widestring::WideCString::from_str(s.expect_str()).map_err(|err| err.to_pyexception(vm))
};

// Validate no embedded null bytes in command name and command line
// before handing the strings off; to_wide_cstring truncates at NUL.
if let Some(ref name) = args.name
&& name.as_bytes().contains(&0)
{
Expand All @@ -246,12 +243,13 @@ mod _winapi {
return Err(crate::exceptions::cstring_error(vm));
}

let app_name = args.name.map(wcstring).transpose()?;
let current_dir = args.current_dir.map(wcstring).transpose()?;
let wcstring = |s: PyStrRef| s.as_wtf8().to_wide_cstring();
let app_name = args.name.as_ref().map(|s| wcstring(s.clone()));
let current_dir = args.current_dir.as_ref().map(|s| wcstring(s.clone()));
let mut command_line = args
.command_line
.map(|s| wcstring(s).map(|w| w.into_vec_with_nul()))
.transpose()?;
.as_ref()
.map(|s| wcstring(s.clone()).into_vec_with_nul());

let procinfo = host_winapi::create_process(
app_name.as_deref(),
Expand Down
2 changes: 0 additions & 2 deletions crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ impl ToPyObject for crt_fd::Borrowed<'_> {
pub(super) mod _os {
use super::{DirFd, FollowSymlinks, SupportFunc};
use crate::host_env::fileutils::StatStruct;
#[cfg(windows)]
use crate::host_env::windows::ToWideString;
#[cfg(any(unix, windows))]
use crate::utils::ToCString;
use crate::{
Expand Down
11 changes: 6 additions & 5 deletions crates/vm/src/stdlib/winsound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ mod winsound {
rustpython_host_env::winsound::PlaySoundError,
) -> crate::builtins::PyBaseExceptionRef
+ '_ {
|_| vm.new_runtime_error("Failed to play sound")
use rustpython_host_env::winsound::PlaySoundError::*;
|err| match err {
MemoryAsyncRejected => vm.new_runtime_error("Cannot play asynchronously from memory"),
MemoryFlagWithoutBuffer | CallFailed => vm.new_runtime_error("Failed to play sound"),
}
}

#[pyfunction]
Expand All @@ -87,14 +91,11 @@ mod winsound {
}

if flags & SND_MEMORY != 0 {
if flags & SND_ASYNC != 0 {
return Err(vm.new_runtime_error("Cannot play asynchronously from memory"));
}
let buffer = PyBuffer::try_from_borrowed_object(vm, &sound)?;
let buf = buffer
.as_contiguous()
.ok_or_else(|| vm.new_type_error("a bytes-like object is required, not 'str'"))?;
return play_sound(PlaySoundSource::Memory(&*buf), flags).map_err(map_play_err(vm));
return play_sound(PlaySoundSource::Memory(&buf), flags).map_err(map_play_err(vm));
}

if sound.downcastable::<PyBytes>() {
Expand Down
Loading