Skip to content

more hostenv isolation#7886

Merged
youknowone merged 23 commits into
RustPython:mainfrom
youknowone:hostenv
May 20, 2026
Merged

more hostenv isolation#7886
youknowone merged 23 commits into
RustPython:mainfrom
youknowone:hostenv

Conversation

@youknowone
Copy link
Copy Markdown
Member

@youknowone youknowone commented May 16, 2026

Summary by CodeRabbit

  • New Features

    • Added OS utilities: secure urandom and process abort wrappers; Windows sound controls (play/beep/message); POSIX timezone helpers.
  • Refactor

    • Standardized native error handling for more consistent failures across platforms.
    • Safer wide-string handling and typed Windows APIs for more robust interop.
    • Consolidated Python exception conversions for more uniform error reporting.
  • API

    • Several Windows-facing APIs updated to use typed wide-string inputs (minor signature changes).

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3d828481-da38-4495-9d98-d5e2a6c30bd4

📥 Commits

Reviewing files that changed from the base of the PR and between 61e0fd4 and 783c6b5.

⛔ Files ignored due to path filters (2)
  • Lib/test/test_format.py is excluded by !Lib/**
  • Lib/test/test_types.py is excluded by !Lib/**
📒 Files selected for processing (6)
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/winsound.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/winsound.rs
💤 Files with no reviewable changes (1)
  • crates/vm/src/stdlib/os.rs

📝 Walkthrough

Walkthrough

Introduces centralized libc/Win32 return-value helpers, converts many host_env wrappers to use these helpers, modernizes wide-string APIs to WideCStr/owned handles, adds small OS utilities (tz, winsound, urandom, abort, write_stderr_raw, strerror_string), and updates stdlib/vm error conversions to use ToPyException.

Changes

Unified refactor (single cohesive cohort)

Layer / File(s) Summary
Helpers, host_env refactors, and stdlib integrations
crates/host_env/src/{os.rs,windows.rs,winapi.rs,nt.rs,winreg.rs,mmap.rs,overlapped.rs,testconsole.rs,multiprocessing.rs,fcntl.rs,msvcrt.rs,posix_wasi.rs,posix.rs,signal.rs,socket.rs,resource.rs,shm.rs,errno.rs,time.rs,winsound.rs,io.rs,lib.rs}, crates/stdlib/src/*, crates/vm/src/*, crates/host_env/Cargo.toml, crates/vm/Cargo.toml
Adds CheckLibc/CheckLibcZero and CheckWin32* helpers, WideCStr/HandleToOwned APIs, utilities (tz, winsound, urandom, abort, write_stderr_raw, strerror_string), converts many raw UTF-16 pointer APIs to &WideCStr/Option<&WideCStr>, replaces manual sentinel/last_os_error checks with .check_libc_neg()? / .check_win32_bool()?, and updates stdlib/vm sites to use to_pyexception(vm) conversions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh
  • coolreader18

Poem

🐰 I hopped through C returns and Win32 calls,

Replaced the checks with helpers — neat and small;
Wide strings now safer where raw pointers sprawled,
Errors fold to Python with one friendly call,
A rabbit cheers: tidy code for one and all!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@youknowone youknowone force-pushed the hostenv branch 2 times, most recently from e96a933 to 9788fcf Compare May 19, 2026 06:21
@youknowone youknowone marked this pull request as ready for review May 19, 2026 12:10
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/_winapi.rs (1)

233-254: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use WTF-8 conversion for CreateProcess arguments to support surrogate paths.

The current wcstring closure uses WideCString::from_str(s.expect_str()), which narrows to UTF-8-only strings. This is inconsistent with the rest of the file, which uses as_wtf8().to_wide_cstring() to preserve WTF-8 representations (including surrogates) for Windows paths. The expect_str() call will panic on valid Windows inputs containing unpaired surrogates that RustPython can represent.

Use as_wtf8().to_wide_cstring() directly, and remove the .transpose()? error handling since this method returns WideCString directly rather than a Result.

Proposed fix
-        let wcstring = |s: PyStrRef| {
-            widestring::WideCString::from_str(s.expect_str()).map_err(|err| err.to_pyexception(vm))
-        };
+        let wcstring = |s: PyStrRef| s.as_wtf8().to_wide_cstring();
@@
-        let app_name = args.name.map(wcstring).transpose()?;
-        let current_dir = args.current_dir.map(wcstring).transpose()?;
+        let app_name = args.name.map(wcstring);
+        let current_dir = args.current_dir.map(wcstring);
         let mut command_line = args
             .command_line
-            .map(|s| wcstring(s).map(|w| w.into_vec_with_nul()))
-            .transpose()?;
+            .map(|s| wcstring(s).into_vec_with_nul());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_winapi.rs` around lines 233 - 254, The wcstring closure
currently uses WideCString::from_str(s.expect_str()), which rejects WTF-8
surrogate data; replace it by calling s.as_wtf8().to_wide_cstring() so
CreateProcess args preserve surrogate paths, and update the call sites (where
app_name = args.name.map(wcstring).transpose()? , current_dir =
args.current_dir.map(wcstring).transpose()? and command_line mapping) to account
for to_wide_cstring() returning WideCString directly (remove the unnecessary
.transpose()? / Result-handling and adjust the command_line mapping to convert
the WideCString into a NUL-terminated Vec using into_vec_with_nul()) so no
panics occur for unpaired surrogates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/winapi.rs`:
- Around line 190-215: create_process_w_raw currently forwards command_line and
env slices straight to CreateProcessW which can read past the slice if they are
not properly UTF-16 NUL-terminated; add input validation in
create_process_w_raw: if command_line.is_some() ensure the slice is non-empty
and its last u16 == 0 (single NUL terminator) and return an
Err(io::Error::new(..., "unterminated command_line")) if not; for env.is_some()
ensure the slice is either at least two u16 long and the last two u16 values are
0 (double NUL terminator) and return Err(...) if not (alternatively treat an
empty env slice as invalid or require callers to pass None); perform these
checks before calling CreateProcessW so the API contract is enforced for any
internal caller of create_process_w_raw.

In `@crates/stdlib/src/overlapped.rs`:
- Around line 1254-1258: The code currently uses
widestring::WideCString::from_str_truncate when constructing name_wide for
create_event_w, which silently truncates on embedded NULs; change this to the
fallible WideCString::from_str and propagate its error as a Python ValueError
via the same ToPyException conversion pattern used in _winapi.rs so callers see
the invalid-name error instead of silently opening a wrong kernel object; update
the code paths around name_wide (the variable and its use in
host_winapi::create_event_w) to return/convert the WideCString::from_str error
into a ToPyException ValueError before calling create_event_w, keeping the
existing set_from_windows_err mapping for host_winapi errors.

---

Outside diff comments:
In `@crates/vm/src/stdlib/_winapi.rs`:
- Around line 233-254: The wcstring closure currently uses
WideCString::from_str(s.expect_str()), which rejects WTF-8 surrogate data;
replace it by calling s.as_wtf8().to_wide_cstring() so CreateProcess args
preserve surrogate paths, and update the call sites (where app_name =
args.name.map(wcstring).transpose()? , current_dir =
args.current_dir.map(wcstring).transpose()? and command_line mapping) to account
for to_wide_cstring() returning WideCString directly (remove the unnecessary
.transpose()? / Result-handling and adjust the command_line mapping to convert
the WideCString into a NUL-terminated Vec using into_vec_with_nul()) so no
panics occur for unpaired surrogates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 129f362b-0538-4aea-89ee-e7696c96b622

📥 Commits

Reviewing files that changed from the base of the PR and between 5394129 and 43c137e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • crates/host_env/src/fcntl.rs
  • crates/host_env/src/io_unsupported.rs
  • crates/host_env/src/mmap.rs
  • crates/host_env/src/msvcrt.rs
  • crates/host_env/src/multiprocessing.rs
  • crates/host_env/src/nt.rs
  • crates/host_env/src/os.rs
  • crates/host_env/src/overlapped.rs
  • crates/host_env/src/posix_wasi.rs
  • crates/host_env/src/resource.rs
  • crates/host_env/src/shm.rs
  • crates/host_env/src/signal.rs
  • crates/host_env/src/socket.rs
  • crates/host_env/src/testconsole.rs
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/windows.rs
  • crates/host_env/src/winreg.rs
  • crates/host_env/src/wmi.rs
  • crates/stdlib/src/fcntl.rs
  • crates/stdlib/src/multiprocessing.rs
  • crates/stdlib/src/overlapped.rs
  • crates/stdlib/src/socket.rs
  • crates/vm/Cargo.toml
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/_io.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/_wmi.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/winreg.rs
💤 Files with no reviewable changes (1)
  • crates/vm/Cargo.toml

Comment thread crates/host_env/src/winapi.rs
Comment thread crates/stdlib/src/overlapped.rs Outdated
youknowone added 20 commits May 20, 2026 09:02
Remove unused IntoPyException impl for rustix::io::Errno and the
rustix entry in crates/vm/Cargo.toml. rustix is now only depended on
by host_env.
- winapi.rs: pass None to create_event_w; the recent Option<&WideCStr>
  migration left one call site still passing a raw null pointer.
- exceptions.rs: gate ToPyException for LockfError with
  cfg(any(unix, target_os = "wasi")), matching host_env::fcntl's own
  cfg. The previous cfg let it compile on wasm32-unknown-unknown where
  host_env::fcntl does not exist.
- io_unsupported.rs: derive Eq on FileMode alongside PartialEq to
  satisfy clippy::derive_partial_eq_without_eq.
- exceptions.rs: gate ToPyException for LockfError with
  cfg(all(unix, not(target_os = "redox"))) to match the type's own
  cfg in host_env/src/fcntl.rs (LockfError is not built on wasi).
- signal.rs: CheckLibcResult is only used in unix-gated functions;
  split import so it is not pulled in for windows.
- mmap.rs: remove CheckWin32Handle from imports; no longer used after
  switching to HandleToOwned-based RAII.
- overlapped.rs: remove INVALID_HANDLE_VALUE from connect_pipe import;
  the call now uses .check_valid().
- signal.rs: reorder cfg-gated imports per rustfmt.
- socket.rs: gate ToPyException import to cfg(all(unix, not(target_os = "redox")));
  it is only used inside sendmsg which has the same gate, so it was unused
  on windows.
Add host_env wrappers and replace the corresponding vm call sites:
- host_env::errno::strerror_string for libc::strerror
- host_env::io::write_stderr_raw for libc::write(STDERR_FILENO,...)
- host_env::locale::localeconv_data reused from vm::format
- host_env::os::abort for the inline abort extern
- host_env::os::urandom wraps getrandom; getrandom moves from vm to host_env
- host_env::posix::lchmod for the macOS/BSD lchmod extern
- host_env::posix::fcopyfile for the macOS fcopyfile extern
- host_env::nt::wputenv for the Windows _wputenv extern

vm/format.rs's get_locale_info now uses host_env on both unix and windows
instead of the unix-only libc::localeconv path.
- host_env::time::tz: wraps the libc tzset/timezone/daylight/tzname
  globals on non-msvc, non-wasm32 targets. vm::stdlib::time now reads
  these via the typed wrappers instead of declaring its own externs.
- host_env::winsound (windows): exposes PlaySoundW (via a typed
  PlaySoundSource enum), Beep, and MessageBeep. vm::stdlib::winsound
  drops its inline FFI block and routes through host_env.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/vm/src/stdlib/_winapi.rs (1)

233-254: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align CreateProcess string conversion with the rest of the Windows API surface.

The wcstring closure at line 234 uses WideCString::from_str(s.expect_str()), but the rest of this refactor standardized Win32 inputs to as_wtf8().to_wide_cstring() to handle Python str values with lone surrogates. This makes CreateProcess the inconsistent outlier for name, command_line, and current_dir, and expect_str() can panic on inputs the other Windows APIs now accept safely.

Since null byte validation is already performed in lines 237–249, the closure can be simplified to directly call to_wide_cstring() without the error-mapping wrapper:

Suggested fix
-        let wcstring = |s: PyStrRef| {
-            widestring::WideCString::from_str(s.expect_str()).map_err(|err| err.to_pyexception(vm))
-        };
+        let wcstring = |s: PyStrRef| s.as_wtf8().to_wide_cstring();

Then apply the pattern to all three usages:

-        let app_name = args.name.map(wcstring).transpose()?;
-        let current_dir = args.current_dir.map(wcstring).transpose()?;
+        let app_name = args.name.map(wcstring);
+        let current_dir = args.current_dir.map(wcstring);
         let mut command_line = args
             .command_line
-            .map(|s| wcstring(s).map(|w| w.into_vec_with_nul()))
-            .transpose()?;
+            .map(|s| wcstring(s).into_vec_with_nul());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_winapi.rs` around lines 233 - 254, The CreateProcess
conversion uses WideCString::from_str with s.expect_str(), which is inconsistent
with other Win32 APIs and can panic on lone surrogates; change the wcstring
closure to call s.as_wtf8().to_wide_cstring() (dropping expect_str and the
manual map_err wrapper) and then apply that wcstring to args.name,
args.command_line, and args.current_dir usages (the existing null-byte checks
remain). Ensure you keep the same transpose/? flow for app_name, current_dir,
and command_line so error handling is preserved.
crates/vm/src/stdlib/winsound.rs (1)

159-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative beep durations before casting to u32.

Lines 165-168 cast args.duration directly to u32. A call like winsound.Beep(440, -1) turns into 4_294_967_295 ms instead of failing fast.

Suggested fix
     if !(37..=32767).contains(&args.frequency) {
         return Err(vm.new_value_error("frequency must be in 37 thru 32767"));
     }
+    if args.duration < 0 {
+        return Err(vm.new_value_error("duration must be non-negative"));
+    }

     if rustpython_host_env::winsound::beep(args.frequency as u32, args.duration as u32) {
         Ok(())
     } else {
         Err(vm.new_runtime_error("Failed to beep"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/winsound.rs` around lines 159 - 169, The Beep wrapper
currently casts args.duration to u32 without rejecting negatives, allowing
values like -1 to wrap to large u32; update the Beep function to validate
args.duration is non-negative (and optionally within a sensible max) before
casting, returning vm.new_value_error on negative durations; locate the check
near the existing frequency validation in Beep (using BeepArgs and
VirtualMachine) and perform the duration check prior to calling
rustpython_host_env::winsound::beep(args.frequency as u32, args.duration as
u32).
crates/vm/src/stdlib/nt.rs (1)

1068-1075: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep filename on non-Io readlink failures.

Line 1075 converts the remaining ReadlinkError variants straight into a Python exception, but those variants no longer have access to path. That drops OSError.filename/message context for path-specific failures that are not plain io::Errors. Please keep explicit path-aware mapping here instead of the catch-all conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/nt.rs` around lines 1068 - 1075, The non-Io
ReadlinkError variants in fn readlink currently call err.to_pyexception(vm)
which loses the original path context; update the match arm for Err(err) (from
host_nt::readlink / host_nt::ReadlinkError) to map each non-Io variant into an
OSError that retains the filename by using OSErrorBuilder::with_filename(&err,
path.clone(), vm) (or the appropriate constructor that accepts the error and
path) instead of calling to_pyexception directly, so that OSError.filename and
the message include the original path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/vm/src/exceptions.rs`:
- Around line 1507-1513: In the ToPyException impl for
rustpython_host_env::nt::ReadlinkError, change the NotSymbolicLink arm to return
an OSError instead of a ValueError: replace the call to vm.new_value_error("not
a symbolic link") with vm.new_os_error("not a symbolic link") so that
ReadlinkError::NotSymbolicLink maps to an OSError-family exception (keep the
existing error message text).

In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 258-270: The error path currently returns a generic OSError string
for host_winreg::connect_registry failures; replace that with the Win32-backed
exception helper by calling the existing os_error_from_windows_code function
with the registry error code and vm so the Python-visible exception contains the
proper Win32 error code and message (i.e., in the branch where res != 0, return
Err(os_error_from_windows_code(res, vm)) instead of
vm.new_os_error(format!(...))). Ensure you reference
host_winreg::connect_registry, the res variable, and PyHkey::new when making the
change.

---

Outside diff comments:
In `@crates/vm/src/stdlib/_winapi.rs`:
- Around line 233-254: The CreateProcess conversion uses WideCString::from_str
with s.expect_str(), which is inconsistent with other Win32 APIs and can panic
on lone surrogates; change the wcstring closure to call
s.as_wtf8().to_wide_cstring() (dropping expect_str and the manual map_err
wrapper) and then apply that wcstring to args.name, args.command_line, and
args.current_dir usages (the existing null-byte checks remain). Ensure you keep
the same transpose/? flow for app_name, current_dir, and command_line so error
handling is preserved.

In `@crates/vm/src/stdlib/nt.rs`:
- Around line 1068-1075: The non-Io ReadlinkError variants in fn readlink
currently call err.to_pyexception(vm) which loses the original path context;
update the match arm for Err(err) (from host_nt::readlink /
host_nt::ReadlinkError) to map each non-Io variant into an OSError that retains
the filename by using OSErrorBuilder::with_filename(&err, path.clone(), vm) (or
the appropriate constructor that accepts the error and path) instead of calling
to_pyexception directly, so that OSError.filename and the message include the
original path.

In `@crates/vm/src/stdlib/winsound.rs`:
- Around line 159-169: The Beep wrapper currently casts args.duration to u32
without rejecting negatives, allowing values like -1 to wrap to large u32;
update the Beep function to validate args.duration is non-negative (and
optionally within a sensible max) before casting, returning vm.new_value_error
on negative durations; locate the check near the existing frequency validation
in Beep (using BeepArgs and VirtualMachine) and perform the duration check prior
to calling rustpython_host_env::winsound::beep(args.frequency as u32,
args.duration as u32).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d7bacdfe-cfe5-47b1-9a04-ff3bf7040d5e

📥 Commits

Reviewing files that changed from the base of the PR and between 43c137e and 45c83dd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • crates/host_env/Cargo.toml
  • crates/host_env/src/errno.rs
  • crates/host_env/src/fcntl.rs
  • crates/host_env/src/io.rs
  • crates/host_env/src/io_unsupported.rs
  • crates/host_env/src/lib.rs
  • crates/host_env/src/mmap.rs
  • crates/host_env/src/msvcrt.rs
  • crates/host_env/src/multiprocessing.rs
  • crates/host_env/src/nt.rs
  • crates/host_env/src/os.rs
  • crates/host_env/src/overlapped.rs
  • crates/host_env/src/posix.rs
  • crates/host_env/src/posix_wasi.rs
  • crates/host_env/src/resource.rs
  • crates/host_env/src/shm.rs
  • crates/host_env/src/signal.rs
  • crates/host_env/src/socket.rs
  • crates/host_env/src/testconsole.rs
  • crates/host_env/src/time.rs
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/windows.rs
  • crates/host_env/src/winreg.rs
  • crates/host_env/src/winsound.rs
  • crates/host_env/src/wmi.rs
  • crates/stdlib/src/fcntl.rs
  • crates/stdlib/src/multiprocessing.rs
  • crates/stdlib/src/overlapped.rs
  • crates/stdlib/src/socket.rs
  • crates/vm/Cargo.toml
  • crates/vm/src/exceptions.rs
  • crates/vm/src/format.rs
  • crates/vm/src/stdlib/_io.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/_wmi.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/winreg.rs
  • crates/vm/src/stdlib/winsound.rs
  • crates/vm/src/vm/mod.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/host_env/src/io_unsupported.rs

Comment thread crates/vm/src/exceptions.rs
Comment thread crates/vm/src/stdlib/winreg.rs Outdated
- vm::stdlib::os::unsetenv had a second _wputenv call site that still
  referenced the removed inline extern. Route it through
  host_env::nt::wputenv like putenv.
- rustfmt fixups in exceptions.rs (boolean chain layout) and the two
  winsound files.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/host_env/src/winsound.rs (1)

19-20: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Reject SND_MEMORY | SND_ASYNC combination in the safe wrapper.

When SND_MEMORY is combined with SND_ASYNC on Win32 PlaySoundW, the caller must keep the sound buffer valid until playback completes. The current play_sound() accepts arbitrary flags, allowing another caller to combine these flags and return while WinMM still holds a pointer into borrowed Rust memory. Add validation to reject this unsafe combination.

♻️ Proposed fix
+const SND_ASYNC: u32 = 0x0001;
+
 /// Returns `Ok(())` when `PlaySoundW` returns non-zero, an error otherwise.
 pub fn play_sound(source: PlaySoundSource<'_>, flags: u32) -> Result<(), PlaySoundError> {
+    if matches!(source, PlaySoundSource::Memory(_)) && flags & SND_ASYNC != 0 {
+        return Err(PlaySoundError);
+    }
+
     let ptr: *const u16 = match source {
         PlaySoundSource::Stop => core::ptr::null(),
         PlaySoundSource::Memory(buf) => buf.as_ptr().cast(),
         PlaySoundSource::Name(s) => s.as_ptr(),
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winsound.rs` around lines 19 - 20, The safe wrapper must
prevent the unsafe combination SND_MEMORY | SND_ASYNC because PlaySoundW
requires the buffer to outlive async playback; update the play_sound() path that
handles the Memory(&'a [u8]) variant to detect when flags include both
SND_MEMORY and SND_ASYNC and return an Err (or other safe error) instead of
calling into WinMM, rejecting the call; reference the Memory enum variant and
the play_sound() function to locate the check and ensure the validation runs
before any FFI call so no borrowed buffer is handed to PlaySoundW when async is
requested.
♻️ Duplicate comments (1)
crates/vm/src/exceptions.rs (1)

1509-1514: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Map NotSymbolicLink to OSError.

Line 1513 still returns ValueError, but the non-symlink readlink failure path should stay in the OSError family so callers can handle it consistently.

In CPython, what exception type does `os.readlink()` raise when the target path is not a symbolic link?
💡 Proposed fix
 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_value_error("not a symbolic link"),
+            Self::NotSymbolicLink => vm.new_os_error("not a symbolic link"),
             Self::InvalidReparseData => vm.new_os_error("Invalid reparse data"),
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/exceptions.rs` around lines 1509 - 1514, The ToPyException impl
for rustpython_host_env::nt::ReadlinkError maps the NotSymbolicLink variant to a
ValueError; change that mapping so NotSymbolicLink returns an OSError instead
(use vm.new_os_error with an appropriate message like "not a symbolic link") in
the impl for rustpython_host_env::nt::ReadlinkError so callers of readlink
remain in the OSError family.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/host_env/src/winsound.rs`:
- Around line 19-20: The safe wrapper must prevent the unsafe combination
SND_MEMORY | SND_ASYNC because PlaySoundW requires the buffer to outlive async
playback; update the play_sound() path that handles the Memory(&'a [u8]) variant
to detect when flags include both SND_MEMORY and SND_ASYNC and return an Err (or
other safe error) instead of calling into WinMM, rejecting the call; reference
the Memory enum variant and the play_sound() function to locate the check and
ensure the validation runs before any FFI call so no borrowed buffer is handed
to PlaySoundW when async is requested.

---

Duplicate comments:
In `@crates/vm/src/exceptions.rs`:
- Around line 1509-1514: The ToPyException impl for
rustpython_host_env::nt::ReadlinkError maps the NotSymbolicLink variant to a
ValueError; change that mapping so NotSymbolicLink returns an OSError instead
(use vm.new_os_error with an appropriate message like "not a symbolic link") in
the impl for rustpython_host_env::nt::ReadlinkError so callers of readlink
remain in the OSError family.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2c77d80f-66ad-46eb-b7e8-6aaae57605d2

📥 Commits

Reviewing files that changed from the base of the PR and between 45c83dd and 49c13f8.

📒 Files selected for processing (4)
  • crates/host_env/src/winsound.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/winsound.rs

- host_env::winapi::create_process: assert that the command_line buffer
  is NUL-terminated and that the env block ends with a double-NUL,
  matching the Win32 CreateProcessW contract.
- stdlib::overlapped CreateEvent: replace WideCString::from_str_truncate
  with the fallible from_str(), so embedded NULs in the event name
  surface as ValueError instead of being silently truncated.
- vm::exceptions::ReadlinkError::NotSymbolicLink now maps to OSError
  (matches Win32 ERROR_NOT_A_REPARSE_POINT semantics) rather than
  ValueError.
- winreg::ConnectRegistry: route the non-zero return through the
  existing os_error_from_windows_code helper so the resulting exception
  carries the real winerror/message instead of a generic OSError.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/host_env/src/winapi.rs (1)

221-239: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return InvalidInput here instead of panicking.

Lines 221-239 and Lines 255-260 turn malformed command_line / env buffers into assert! panics, even though create_process() returns io::Result. Please make these validators fallible and propagate io::ErrorKind::InvalidInput so bad inputs stay recoverable at the API boundary.

Suggested fix
 #[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",
+        ))
+    }
 }
 
 #[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",
+        ))
+    }
 }
@@
     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)?;
     }

As per coding guidelines, Follow Rust best practices for error handling and memory management.

Also applies to: 255-260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winapi.rs` around lines 221 - 239, Replace the panicking
validators assert_command_line_terminated and
assert_environment_block_terminated with fallible versions that return
io::Result<()> (or an appropriate Result type) and on invalid input return
Err(io::Error::new(io::ErrorKind::InvalidInput, "<descriptive message>"))
instead of assert!. Update all call sites (e.g., where create_process
constructs/validates command_line and env buffers and the code paths indicated
near the other validator at lines 255-260) to propagate the Result (using ? or
map_err) so malformed buffers produce a recoverable InvalidInput io::Error
rather than panicking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@crates/host_env/src/winapi.rs`:
- Around line 221-239: Replace the panicking validators
assert_command_line_terminated and assert_environment_block_terminated with
fallible versions that return io::Result<()> (or an appropriate Result type) and
on invalid input return Err(io::Error::new(io::ErrorKind::InvalidInput,
"<descriptive message>")) instead of assert!. Update all call sites (e.g., where
create_process constructs/validates command_line and env buffers and the code
paths indicated near the other validator at lines 255-260) to propagate the
Result (using ? or map_err) so malformed buffers produce a recoverable
InvalidInput io::Error rather than panicking.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b0db8cb2-3907-4994-aca4-6cbac65d9354

📥 Commits

Reviewing files that changed from the base of the PR and between 49c13f8 and 61e0fd4.

📒 Files selected for processing (4)
  • crates/host_env/src/winapi.rs
  • crates/stdlib/src/overlapped.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/winreg.rs

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.
@github-actions
Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_format.py (TODO: 6)

dependencies:

dependent tests: (no tests depend on format)

[x] lib: cpython/Lib/types.py
[ ] test: cpython/Lib/test/test_types.py (TODO: 6)

dependencies:

  • types

dependent tests: (55 tests)

  • types: test_annotationlib test_ast test_asyncgen test_asyncio test_builtin test_call test_code test_collections test_compile test_compiler_assemble test_coroutines test_descr test_dis test_doctest test_dtrace test_dynamicclassattribute test_email test_enum test_exception_group test_fstring test_funcattrs test_generators test_genericalias test_global test_hmac test_importlib test_inspect test_listcomps test_marshal test_monitoring test_opcache test_optimizer test_os test_positional_only_arg test_pprint test_pyclbr test_pydoc test_raise test_string test_subclassinit test_subprocess test_tempfile test_threading test_trace test_traceback test_type_aliases test_type_annotations test_type_params test_types test_typing test_unittest test_userdict test_xml_etree test_xml_etree_c test_xxlimited

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone youknowone merged commit ae3804f into RustPython:main May 20, 2026
27 checks passed
@youknowone youknowone deleted the hostenv branch May 20, 2026 07:16
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