ffi: No interior NULs (part 1) - #8245
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughWindows and WTF-8 FFI string handling now uses fallible, interior-NUL-aware conversions. Host APIs, registry bindings, path operations, ctypes helpers, and Python exception mapping were updated to propagate conversion failures. ChangesFallible encoding primitives
Host conversion contracts
Windows API propagation
Registry integration
VM integration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant PythonVM
participant Wtf8
participant HostWindowsAPI
PythonVM->>Wtf8: encode_wide_ffi()
Wtf8-->>PythonVM: UTF-16 units or InteriorNulError
PythonVM->>HostWindowsAPI: call with validated WideCString
HostWindowsAPI-->>PythonVM: API result or conversion error
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/host_env/src/fileutils.rs (1)
80-97: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winAdd the missing
Ok(())and propagate the result
crates/host_env/src/fileutils.rs:76-97currently ends with(), even though the signature returnsResult<(), io::Error>, so this won’t compile. Thecrates/host_env/src/nt.rs:831,836,857call sites also discard the returnedResult, which dropspath.to_wide()?failures; thread it through with?.🤖 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/fileutils.rs` around lines 80 - 97, The helper in fileutils.rs returns Result<(), io::Error> but currently falls off the end with unit, so add an explicit Ok(()) after the permission update logic. Also make the nt.rs callers that use this helper propagate its Result instead of ignoring it, so failures from path.to_wide()? are not dropped; update the call sites around the file metadata handling to use ? and thread the error upward.crates/vm/src/stdlib/_ctypes/base.rs (1)
1596-1596: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winHandle the fallible
str_to_wchar_bytesresult here. This call still destructures aResult;?only works onceInteriorNulErroris mapped intoPyBaseExceptionRef, so either add that conversion or convert the error explicitly at this call site.🤖 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/_ctypes/base.rs` at line 1596, The call in the wchar conversion path is still destructuring a fallible `str_to_wchar_bytes` result directly, so update the logic around `str_to_wchar_bytes` to properly handle its `Result` before destructuring. In the `_ctypes::base` code path that builds the wide string buffer, either add a conversion from `InteriorNulError` into `PyBaseExceptionRef` so `?` can be used cleanly, or explicitly map the error at the call site before extracting `holder` and `ptr`.
🤖 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/ctypes.rs`:
- Around line 542-546: The vec_into_bytes helper currently reinterprets the
original allocation with Vec::from_raw_parts, which can deallocate with the
wrong layout for wide-string buffers. Update vec_into_bytes in ctypes.rs to copy
the bytes out of the source Vec<T> instead of casting the allocation; keep the
existing size_of::<T>() guard, but replace the raw-parts reconstruction with a
safe byte copy approach so the returned Vec<u8> owns a correctly laid-out
allocation.
In `@crates/host_env/src/windows.rs`:
- Around line 413-417: The `to_wide` method in `windows.rs` is using `io::Error`
as a direct mapper, which won’t compile in this context. Update the
`WideCString::from_os_str(self)` error handling to use `io::Error::other`,
matching the pattern used by the other conversion methods, while keeping the
rest of `to_wide` unchanged.
- Around line 428-435: The Wtf8 implementation of ToWideString is incomplete and
uses the wrong encoder for the checked Result-based API. In the ToWideString
impl for Wtf8, add the missing to_wide_cstring method alongside to_wide and
to_wide_with_nul, and update all three methods to use encode_wide_ffi() so they
return Result<Vec<u16>, io::Error> correctly and reject interior NULs. Keep the
fix localized to the Wtf8 trait implementation in windows.rs.
In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Line 158: In the ctypes conversion helpers, the `?` operator is being used on
errors that do not automatically convert into `PyBaseExceptionRef`, so fix the
error handling in the functions that call
`rustpython_host_env::ctypes::utf16z_bytes` and `null_terminated_bytes` by
explicitly mapping those `InteriorNulError` and `NulError` values into the
Python exception type before propagating them. Apply the same change in the
corresponding logic in `function.rs` and `base.rs`, keeping the conversion
localized near the existing `utf16z_bytes` / `null_terminated_bytes` calls.
In `@crates/wtf8/src/lib.rs`:
- Around line 915-916: The doc comment on encode_wide_ffi names the wrong
encoding: it currently says the function converts to potentially ill-formed
UTF-8, but this helper returns potentially ill-formed UTF-16 wide code units
like encode_wide. Update the comment text for encode_wide_ffi to describe UTF-16
instead of UTF-8, keeping the note about checking for interior NULs.
---
Outside diff comments:
In `@crates/host_env/src/fileutils.rs`:
- Around line 80-97: The helper in fileutils.rs returns Result<(), io::Error>
but currently falls off the end with unit, so add an explicit Ok(()) after the
permission update logic. Also make the nt.rs callers that use this helper
propagate its Result instead of ignoring it, so failures from path.to_wide()?
are not dropped; update the call sites around the file metadata handling to use
? and thread the error upward.
In `@crates/vm/src/stdlib/_ctypes/base.rs`:
- Line 1596: The call in the wchar conversion path is still destructuring a
fallible `str_to_wchar_bytes` result directly, so update the logic around
`str_to_wchar_bytes` to properly handle its `Result` before destructuring. In
the `_ctypes::base` code path that builds the wide string buffer, either add a
conversion from `InteriorNulError` into `PyBaseExceptionRef` so `?` can be used
cleanly, or explicitly map the error at the call site before extracting `holder`
and `ptr`.
🪄 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: de55e068-6ea7-4cd4-8e9e-98e767127b8a
📒 Files selected for processing (6)
crates/host_env/src/ctypes.rscrates/host_env/src/fileutils.rscrates/host_env/src/windows.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/wtf8/src/lib.rs
ec9ad96 to
ee369e2
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/host_env/src/ctypes.rs (1)
1095-1097: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd tests for the new fallible
null_terminated_bytes.This is a security-critical function (interior NUL detection for FFI), but the test module has no coverage for it. Consider adding tests for: valid input without NULs, input containing an interior NUL (should return
Err(NulError)), and empty input.🧪 Suggested tests
#[test] fn null_terminated_bytes_valid() { assert_eq!( null_terminated_bytes(b"hello").unwrap(), b"hello\0" ); } #[test] fn null_terminated_bytes_interior_nul_rejected() { assert!(null_terminated_bytes(b"hel\0lo").is_err()); } #[test] fn null_terminated_bytes_empty() { assert_eq!(null_terminated_bytes(b"").unwrap(), b"\0"); }🤖 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/ctypes.rs` around lines 1095 - 1097, Add test coverage for the fallible null_terminated_bytes helper in ctypes.rs, since it now performs FFI-safe interior NUL validation via CString::new. Extend the existing test module with cases for valid non-NUL input, input containing an interior NUL that must return Err(NulError), and empty input; use the null_terminated_bytes function name directly so the tests clearly target the new behavior.
🤖 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/ctypes.rs`:
- Around line 2-3: The `CString` import in `ctypes.rs` is incorrectly gated with
`#[cfg(unix)]` even though `null_terminated_bytes` uses `CString`
unconditionally and is called from `ensure_z_null_terminated` in `base.rs` and
`conv_param` in `function.rs` on all targets. Remove the unix-only cfg from the
`CString` import so `null_terminated_bytes` can compile on non-unix platforms as
well, and make sure the identifier references in `ctypes.rs` remain valid
without any platform-specific gating.
---
Nitpick comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 1095-1097: Add test coverage for the fallible
null_terminated_bytes helper in ctypes.rs, since it now performs FFI-safe
interior NUL validation via CString::new. Extend the existing test module with
cases for valid non-NUL input, input containing an interior NUL that must return
Err(NulError), and empty input; use the null_terminated_bytes function name
directly so the tests clearly target the new behavior.
🪄 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: 2c9c6278-53c9-4285-ac5d-4a5a4b8a716e
📒 Files selected for processing (6)
crates/host_env/src/ctypes.rscrates/host_env/src/fileutils.rscrates/host_env/src/windows.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/wtf8/src/lib.rs
💤 Files with no reviewable changes (1)
- crates/wtf8/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- crates/host_env/src/fileutils.rs
- crates/vm/src/stdlib/_ctypes/function.rs
- crates/vm/src/stdlib/_ctypes/base.rs
- crates/host_env/src/windows.rs
ee369e2 to
20fd74b
Compare
20fd74b to
d41d6a4
Compare
b3816bb to
9b0f70c
Compare
08865bf to
b7ce43b
Compare
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/stdlib/src/overlapped.rs (1)
213-225: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winMap
InteriorNulErrorto a Python exception explicitly.Type error:
collect::<Result<_, _>>()produces anInteriorNulErroron failure, but?attempts to implicitly convert it toPyBaseExceptionRef(the error type ofPyResult). Since there is no automaticFromconversion, this will cause a compilation error. You must explicitly map the error.🐛 Proposed fix
2 => { // IPv4: (host, port) let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?; let port: u16 = addr_obj[1].clone().try_to_value(vm)?; - let host_wide: Vec<u16> = - host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?; + let host_wide: Vec<u16> = host.as_wtf8() + .encode_wide_ffi() + .collect::<Result<_, _>>() + .map_err(|e| e.to_pyexception(vm))?; host_overlapped::parse_address_v4_wide(&host_wide, port) .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } 4 => { // IPv6: (host, port, flowinfo, scope_id) let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?; let port: u16 = addr_obj[1].clone().try_to_value(vm)?; let flowinfo: u32 = addr_obj[2].clone().try_to_value(vm)?; let scope_id: u32 = addr_obj[3].clone().try_to_value(vm)?; - let host_wide: Vec<u16> = - host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?; + let host_wide: Vec<u16> = host.as_wtf8() + .encode_wide_ffi() + .collect::<Result<_, _>>() + .map_err(|e| e.to_pyexception(vm))?; host_overlapped::parse_address_v6_wide(&host_wide, port, flowinfo, scope_id) .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) }🤖 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/stdlib/src/overlapped.rs` around lines 213 - 225, Update the IPv4 and IPv6 host encoding in the address-parsing function to explicitly map `InteriorNulError` from `encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6 parsing flow.
♻️ Duplicate comments (1)
crates/host_env/src/windows.rs (1)
426-427: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winComplete
to_wide_cstring;WideCString::from_cannot compile.Collect the checked UTF-16 units and construct a
WideCStringwith the constructor supported by the repository’s pinnedwidestringversion.As per coding guidelines, follow default rustfmt style and run
cargo clippy, fixing introduced warnings before completion.#!/bin/bash set -euo pipefail rg -n -C3 'name = "widestring"|widestring\s*=' Cargo.lock Cargo.toml rg -n -C3 'WideCString::from_(vec|vec_with_nul|os_str|str)' --type rust .🤖 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/windows.rs` around lines 426 - 427, Complete the to_wide_cstring method by collecting the validated UTF-16 units and constructing WideCString with a constructor available in the repository’s pinned widestring version, replacing the incomplete WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy, resolving any warnings introduced by this change.Source: Coding guidelines
🤖 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/ctypes.rs`:
- Around line 533-535: Update wchar_null_terminated_bytes to use
encode_wide_ffi() instead of casting code points directly to WChar, preserving
non-BMP characters as surrogate pairs on 16-bit targets while retaining the
existing null-terminated byte iteration behavior.
In `@crates/host_env/src/nt.rs`:
- Line 220: Update downstream callers of `access`, `test_file_type_by_name`, and
`test_file_exists_by_name` to handle their `Result<bool, io::Error>` returns. In
the VM stdlib `access` binding, map I/O errors to the appropriate Python
exception; for the internal file-testing helpers, convert errors to `false` with
`.unwrap_or(false)` while preserving existing boolean behavior.
- Line 986: Update the return statement in the surrounding function to return
the boolean value as a successful Result, matching its Result<bool, io::Error>
return type; preserve the existing false outcome.
In `@crates/host_env/src/windows.rs`:
- Around line 403-405: Update the remaining Windows callers of
ToWideString::to_wide_with_nul and ToWideString::to_wide_cstring to handle their
Result values explicitly. Propagate or otherwise handle conversion errors in the
callers in windows.rs, winsound, and winreg, preserving each call site’s
existing success behavior and avoiding infallible assumptions.
In `@crates/host_env/src/winreg.rs`:
- Around line 512-515: Update expand_environment_strings to stop calling
into_vec_with_nul on the borrowed input; pass input.as_ptr() directly to
ExpandEnvironmentStringsW and remove the unnecessary wide_input allocation while
preserving the existing expansion behavior.
- Around line 353-355: Update the error mapping in the wide_sub_key conversion
within the relevant registry query flow so map_err returns a concrete
QueryStringError::Utf16 instance containing the conversion error, rather than
the tuple variant constructor. Preserve the existing QueryStringError return
path and propagate the original FromUtf16Error value.
- Around line 464-471: Update set_default_value to explicitly map the
to_wide_cstring ContainsNul failure into io::Error before using ?, then update
the SetValue caller to handle the Result<u32, io::Error> contract instead of
comparing the result directly with zero; preserve the existing success and
Windows error-code behavior.
In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 779-780: Validate the function symbol name before constructing the
terminated string in the surrounding function of the lookup_function_symbol_addr
call. Reject names containing interior NUL bytes and return the existing error
path, then preserve the current format!("{name}\0") lookup flow for valid names.
In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 844-846: Update the error mapping in the `WideCString::from_str`
conversion within the surrounding winreg function to pass the available `vm`
context to `to_pyexception`, matching the existing `expand_environment_strings`
mapping. Leave the successful conversion and environment expansion behavior
unchanged.
In `@crates/wtf8/src/lib.rs`:
- Around line 1532-1548: Reject every source NUL immediately in the encoder
iterator, returning InteriorNulError and setting the iterator’s completion state
so iteration cannot resume. Apply this change at crates/wtf8/src/lib.rs lines
1532-1548 and crates/host_env/src/ctypes.rs lines 545-557, preserving the
synthesized terminator behavior while removing the scan-and-accept path.
- Around line 1557-1560: Update the size_hint method to account for the
iterator’s possible terminator output and early termination on interior-NUL
errors; do not forward self.iter.size_hint() unchanged. Return bounds that never
overstate the minimum or maximum number of items the iterator can emit,
preserving the appropriate unbounded case.
---
Outside diff comments:
In `@crates/stdlib/src/overlapped.rs`:
- Around line 213-225: Update the IPv4 and IPv6 host encoding in the
address-parsing function to explicitly map `InteriorNulError` from
`encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception
type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6
parsing flow.
---
Duplicate comments:
In `@crates/host_env/src/windows.rs`:
- Around line 426-427: Complete the to_wide_cstring method by collecting the
validated UTF-16 units and constructing WideCString with a constructor available
in the repository’s pinned widestring version, replacing the incomplete
WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy,
resolving any warnings introduced by this change.
🪄 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: b91be2b8-7920-42f4-ab35-75fc3a170cc4
📒 Files selected for processing (18)
crates/host_env/src/ctypes.rscrates/host_env/src/fileutils.rscrates/host_env/src/nt.rscrates/host_env/src/overlapped.rscrates/host_env/src/winapi.rscrates/host_env/src/windows.rscrates/host_env/src/winreg.rscrates/stdlib/src/overlapped.rscrates/vm/src/exceptions.rscrates/vm/src/stdlib/_ctypes/array.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/vm/src/stdlib/_ctypes/pointer.rscrates/vm/src/stdlib/_ctypes/simple.rscrates/vm/src/stdlib/_winapi.rscrates/vm/src/stdlib/nt.rscrates/vm/src/stdlib/winreg.rscrates/wtf8/src/lib.rs
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/stdlib/src/overlapped.rs (1)
213-225: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winMap
InteriorNulErrorto a Python exception explicitly.Type error:
collect::<Result<_, _>>()produces anInteriorNulErroron failure, but?attempts to implicitly convert it toPyBaseExceptionRef(the error type ofPyResult). Since there is no automaticFromconversion, this will cause a compilation error. You must explicitly map the error.🐛 Proposed fix
2 => { // IPv4: (host, port) let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?; let port: u16 = addr_obj[1].clone().try_to_value(vm)?; - let host_wide: Vec<u16> = - host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?; + let host_wide: Vec<u16> = host.as_wtf8() + .encode_wide_ffi() + .collect::<Result<_, _>>() + .map_err(|e| e.to_pyexception(vm))?; host_overlapped::parse_address_v4_wide(&host_wide, port) .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } 4 => { // IPv6: (host, port, flowinfo, scope_id) let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?; let port: u16 = addr_obj[1].clone().try_to_value(vm)?; let flowinfo: u32 = addr_obj[2].clone().try_to_value(vm)?; let scope_id: u32 = addr_obj[3].clone().try_to_value(vm)?; - let host_wide: Vec<u16> = - host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?; + let host_wide: Vec<u16> = host.as_wtf8() + .encode_wide_ffi() + .collect::<Result<_, _>>() + .map_err(|e| e.to_pyexception(vm))?; host_overlapped::parse_address_v6_wide(&host_wide, port, flowinfo, scope_id) .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) }🤖 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/stdlib/src/overlapped.rs` around lines 213 - 225, Update the IPv4 and IPv6 host encoding in the address-parsing function to explicitly map `InteriorNulError` from `encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6 parsing flow.
♻️ Duplicate comments (1)
crates/host_env/src/windows.rs (1)
426-427: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winComplete
to_wide_cstring;WideCString::from_cannot compile.Collect the checked UTF-16 units and construct a
WideCStringwith the constructor supported by the repository’s pinnedwidestringversion.As per coding guidelines, follow default rustfmt style and run
cargo clippy, fixing introduced warnings before completion.#!/bin/bash set -euo pipefail rg -n -C3 'name = "widestring"|widestring\s*=' Cargo.lock Cargo.toml rg -n -C3 'WideCString::from_(vec|vec_with_nul|os_str|str)' --type rust .🤖 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/windows.rs` around lines 426 - 427, Complete the to_wide_cstring method by collecting the validated UTF-16 units and constructing WideCString with a constructor available in the repository’s pinned widestring version, replacing the incomplete WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy, resolving any warnings introduced by this change.Source: Coding guidelines
🤖 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/ctypes.rs`:
- Around line 533-535: Update wchar_null_terminated_bytes to use
encode_wide_ffi() instead of casting code points directly to WChar, preserving
non-BMP characters as surrogate pairs on 16-bit targets while retaining the
existing null-terminated byte iteration behavior.
In `@crates/host_env/src/nt.rs`:
- Line 220: Update downstream callers of `access`, `test_file_type_by_name`, and
`test_file_exists_by_name` to handle their `Result<bool, io::Error>` returns. In
the VM stdlib `access` binding, map I/O errors to the appropriate Python
exception; for the internal file-testing helpers, convert errors to `false` with
`.unwrap_or(false)` while preserving existing boolean behavior.
- Line 986: Update the return statement in the surrounding function to return
the boolean value as a successful Result, matching its Result<bool, io::Error>
return type; preserve the existing false outcome.
In `@crates/host_env/src/windows.rs`:
- Around line 403-405: Update the remaining Windows callers of
ToWideString::to_wide_with_nul and ToWideString::to_wide_cstring to handle their
Result values explicitly. Propagate or otherwise handle conversion errors in the
callers in windows.rs, winsound, and winreg, preserving each call site’s
existing success behavior and avoiding infallible assumptions.
In `@crates/host_env/src/winreg.rs`:
- Around line 512-515: Update expand_environment_strings to stop calling
into_vec_with_nul on the borrowed input; pass input.as_ptr() directly to
ExpandEnvironmentStringsW and remove the unnecessary wide_input allocation while
preserving the existing expansion behavior.
- Around line 353-355: Update the error mapping in the wide_sub_key conversion
within the relevant registry query flow so map_err returns a concrete
QueryStringError::Utf16 instance containing the conversion error, rather than
the tuple variant constructor. Preserve the existing QueryStringError return
path and propagate the original FromUtf16Error value.
- Around line 464-471: Update set_default_value to explicitly map the
to_wide_cstring ContainsNul failure into io::Error before using ?, then update
the SetValue caller to handle the Result<u32, io::Error> contract instead of
comparing the result directly with zero; preserve the existing success and
Windows error-code behavior.
In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 779-780: Validate the function symbol name before constructing the
terminated string in the surrounding function of the lookup_function_symbol_addr
call. Reject names containing interior NUL bytes and return the existing error
path, then preserve the current format!("{name}\0") lookup flow for valid names.
In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 844-846: Update the error mapping in the `WideCString::from_str`
conversion within the surrounding winreg function to pass the available `vm`
context to `to_pyexception`, matching the existing `expand_environment_strings`
mapping. Leave the successful conversion and environment expansion behavior
unchanged.
In `@crates/wtf8/src/lib.rs`:
- Around line 1532-1548: Reject every source NUL immediately in the encoder
iterator, returning InteriorNulError and setting the iterator’s completion state
so iteration cannot resume. Apply this change at crates/wtf8/src/lib.rs lines
1532-1548 and crates/host_env/src/ctypes.rs lines 545-557, preserving the
synthesized terminator behavior while removing the scan-and-accept path.
- Around line 1557-1560: Update the size_hint method to account for the
iterator’s possible terminator output and early termination on interior-NUL
errors; do not forward self.iter.size_hint() unchanged. Return bounds that never
overstate the minimum or maximum number of items the iterator can emit,
preserving the appropriate unbounded case.
---
Outside diff comments:
In `@crates/stdlib/src/overlapped.rs`:
- Around line 213-225: Update the IPv4 and IPv6 host encoding in the
address-parsing function to explicitly map `InteriorNulError` from
`encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception
type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6
parsing flow.
---
Duplicate comments:
In `@crates/host_env/src/windows.rs`:
- Around line 426-427: Complete the to_wide_cstring method by collecting the
validated UTF-16 units and constructing WideCString with a constructor available
in the repository’s pinned widestring version, replacing the incomplete
WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy,
resolving any warnings introduced by this change.
🪄 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: b91be2b8-7920-42f4-ab35-75fc3a170cc4
📒 Files selected for processing (18)
crates/host_env/src/ctypes.rscrates/host_env/src/fileutils.rscrates/host_env/src/nt.rscrates/host_env/src/overlapped.rscrates/host_env/src/winapi.rscrates/host_env/src/windows.rscrates/host_env/src/winreg.rscrates/stdlib/src/overlapped.rscrates/vm/src/exceptions.rscrates/vm/src/stdlib/_ctypes/array.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/vm/src/stdlib/_ctypes/pointer.rscrates/vm/src/stdlib/_ctypes/simple.rscrates/vm/src/stdlib/_winapi.rscrates/vm/src/stdlib/nt.rscrates/vm/src/stdlib/winreg.rscrates/wtf8/src/lib.rs
🛑 Comments failed to post (11)
crates/host_env/src/ctypes.rs (1)
533-535: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail rg -n -C3 '\b(type WChar|WCHAR_SIZE|wchar_to_bytes|wchar_null_terminated_bytes)\b' \ crates/host_env/src/ctypes.rs rg -n -C3 '\bencode_wide(_ffi)?\s*\(' crates/host_env/src/ctypes.rs crates/wtf8/src/lib.rsRepository: RustPython/RustPython
Length of output: 5652
🏁 Script executed:
#!/bin/bash set -euo pipefail sed -n '520,590p' crates/host_env/src/ctypes.rs printf '\n---\n' sed -n '900,940p' crates/wtf8/src/lib.rs printf '\n---\n' sed -n '1,220p' crates/wtf8/src/lib.rs | sed -n '1,40p'Repository: RustPython/RustPython
Length of output: 5517
🏁 Script executed:
#!/bin/bash set -euo pipefail python3 - <<'PY' from pathlib import Path text = Path('crates/wtf8/src/lib.rs').read_text() for needle in ['struct EncodeWide', 'impl Iterator for EncodeWide', 'struct EncodeWideForFfi', 'impl Iterator for EncodeWideForFfi']: idx = text.find(needle) print('\n###', needle, idx) if idx != -1: start = text.rfind('\n', 0, idx-200) end = text.find('\n\n', idx) print(text[idx:idx+1400]) PYRepository: RustPython/RustPython
Length of output: 5913
🏁 Script executed:
#!/bin/bash set -euo pipefail rg -n -C4 'EncodeWide|EncodeWideForFfi|encode_wide\(' crates/wtf8/src/lib.rsRepository: RustPython/RustPython
Length of output: 2350
Use
encode_wide_ffi()here. Casting code points directly toWChardrops non-BMP characters on 16-bit targets instead of emitting surrogate pairs.🤖 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/ctypes.rs` around lines 533 - 535, Update wchar_null_terminated_bytes to use encode_wide_ffi() instead of casting code points directly to WChar, preserving non-BMP characters as surrogate pairs on 16-bit targets while retaining the existing null-terminated byte iteration behavior.crates/host_env/src/nt.rs (2)
220-220: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
Update downstream callers to handle the new
Resultreturn type.Changing the return type of
access(as well astest_file_type_by_nameandtest_file_exists_by_name) toResult<bool, io::Error>causes compilation errors in downstream consumers likecrates/vm/src/stdlib/nt.rs, which still expect abool.You need to update the callers to handle the
Result. For instance, incrates/vm/src/stdlib/nt.rs, map the error to a Python exception foraccess, and use.unwrap_or(false)for the internal file testing helpers.🤖 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/nt.rs` at line 220, Update downstream callers of `access`, `test_file_type_by_name`, and `test_file_exists_by_name` to handle their `Result<bool, io::Error>` returns. In the VM stdlib `access` binding, map I/O errors to the appropriate Python exception; for the internal file-testing helpers, convert errors to `false` with `.unwrap_or(false)` while preserving existing boolean behavior.
986-986: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Wrap the return value in
Ok.Type mismatch: the function returns
Result<bool, io::Error>, so returning(false)will cause a compilation error.🐛 Proposed fix
- return (false); + return Ok(false);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.return Ok(false);🤖 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/nt.rs` at line 986, Update the return statement in the surrounding function to return the boolean value as a successful Result, matching its Result<bool, io::Error> return type; preserve the existing false outcome.crates/host_env/src/windows.rs (1)
403-405: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail rg -n -C4 --type rust '\.to_wide_(with_nul|cstring)\s*\(' .Repository: RustPython/RustPython
Length of output: 47727
🏁 Script executed:
#!/bin/bash set -euo pipefail wc -l crates/host_env/src/windows.rs sed -n '1,260p' crates/host_env/src/windows.rs printf '\n---SNIP---\n' sed -n '260,520p' crates/host_env/src/windows.rsRepository: RustPython/RustPython
Length of output: 13806
🏁 Script executed:
#!/bin/bash set -euo pipefail # Show all call sites that may still rely on the old infallible behavior. rg -n -C2 --type rust 'to_wide_with_nul\(\)|to_wide_cstring\(' crates/host_env crates/vmRepository: RustPython/RustPython
Length of output: 30715
Propagate the fallible wide-string conversions to the remaining Windows callers. Several sites still treat
to_wide_with_nul()/to_wide_cstring()as infallible (crates/host_env/src/windows.rs:157,184,crates/vm/src/stdlib/winsound.rs:148,crates/vm/src/stdlib/winreg.rs:744,763), so the Windows build still breaks until each one handles theResult.🤖 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/windows.rs` around lines 403 - 405, Update the remaining Windows callers of ToWideString::to_wide_with_nul and ToWideString::to_wide_cstring to handle their Result values explicitly. Propagate or otherwise handle conversion errors in the callers in windows.rs, winsound, and winreg, preserving each call site’s existing success behavior and avoiding infallible assumptions.crates/host_env/src/winreg.rs (3)
353-355: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Pass an error instance rather than a variant constructor.
Type error:
QueryStringError::Utf16is a tuple variant that expects aFromUtf16Errorargument. Passing the variant name without an argument tomap_errreturns a function pointer rather than an error instance, causing a compilation failure. Consider mapping to an appropriate Windows error code instead.🐛 Proposed fix
let wide_sub_key = sub_key .to_wide_cstring() - .map_err(|_| QueryStringError::Utf16)?; + .map_err(|_| QueryStringError::Code(windows_sys::Win32::Foundation::ERROR_INVALID_DATA))?;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.let wide_sub_key = sub_key .to_wide_cstring() .map_err(|_| QueryStringError::Code(windows_sys::Win32::Foundation::ERROR_INVALID_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/host_env/src/winreg.rs` around lines 353 - 355, Update the error mapping in the wide_sub_key conversion within the relevant registry query flow so map_err returns a concrete QueryStringError::Utf16 instance containing the conversion error, rather than the tuple variant constructor. Preserve the existing QueryStringError return path and propagate the original FromUtf16Error value.
464-471: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
Map
ContainsNulerror and update downstream consumers.Two issues exist here:
sub_key.to_wide_cstring()returnsResult<WideCString, ContainsNul<u16>>, which cannot be implicitly converted toio::Errorvia?. You must explicitly map the error.- The signature change of
set_default_valuetoResult<u32, io::Error>breaks the downstream callerSetValueincrates/vm/src/stdlib/winreg.rs(which expects a rawu32error code to performif res == 0). You will need to update the caller to match the newResult.🐛 Proposed fix for the local type error
pub fn set_default_value( hkey: Registry::HKEY, sub_key: &OsStr, typ: u32, value: &OsStr, ) -> Result<u32, io::Error> { let child_key = if !sub_key.is_empty() { - let wide_sub_key = sub_key.to_wide_cstring()?; + let wide_sub_key = sub_key.to_wide_cstring().map_err(io::Error::other)?; let mut out_key = core::ptr::null_mut();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.pub fn set_default_value( hkey: Registry::HKEY, sub_key: &OsStr, typ: u32, value: &OsStr, ) -> Result<u32, io::Error> { let child_key = if !sub_key.is_empty() { let wide_sub_key = sub_key.to_wide_cstring().map_err(io::Error::other)?;🤖 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/winreg.rs` around lines 464 - 471, Update set_default_value to explicitly map the to_wide_cstring ContainsNul failure into io::Error before using ?, then update the SetValue caller to handle the Result<u32, io::Error> contract instead of comparing the result directly with zero; preserve the existing success and Windows error-code behavior.
512-515: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Avoid taking ownership of a borrowed reference and eliminate unnecessary allocations.
Type error:
into_vec_with_nulconsumes aWideCStringby value, butinputis a reference (&WideCStr). This will fail to compile because you cannot move out of a shared reference.Since
ExpandEnvironmentStringsWonly requires a pointer, you can avoid allocating a newVecentirely by passinginput.as_ptr()directly.🐛 Proposed fix
pub fn expand_environment_strings( input: &WideCStr, ) -> Result<String, ExpandEnvironmentStringsError> { - let wide_input = input.into_vec_with_nul(); let required_size = unsafe { - Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0) + Environment::ExpandEnvironmentStringsW(input.as_ptr(), core::ptr::null_mut(), 0) }; if required_size == 0 { return Err(ExpandEnvironmentStringsError::Os); } let mut out = vec![0u16; required_size as usize]; let written = unsafe { - Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size) + Environment::ExpandEnvironmentStringsW(input.as_ptr(), out.as_mut_ptr(), required_size) };Also applies to: 524-526
🤖 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/winreg.rs` around lines 512 - 515, Update expand_environment_strings to stop calling into_vec_with_nul on the borrowed input; pass input.as_ptr() directly to ExpandEnvironmentStringsW and remove the unnecessary wide_input allocation while preserving the existing expansion behavior.crates/vm/src/stdlib/_ctypes/function.rs (1)
779-780: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Validate interior NULs for function symbol names.
Using
format!("{name}\0")allows any interior NUL bytes innameto pass through into the resulting byte slice, which can cause the underlying C-style API (e.g.,dlsymorGetProcAddress) to truncate the string and look up an unintended symbol. Since this PR aims to secure FFI paths against interior NULs, you should validatenameas well.🛡️ Proposed fix to prevent FFI string truncation
- let terminated = format!("{name}\0"); + let terminated = rustpython_host_env::ctypes::null_terminated_bytes(name.as_bytes()) + .map_err(|e| e.to_pyexception(vm))?; let ptr_val = match rustpython_host_env::ctypes::lookup_function_symbol_addr( handle .to_usize() .ok_or_else(|| vm.new_value_error("Invalid handle"))?, - terminated.as_bytes(), + &terminated, ) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.let terminated = rustpython_host_env::ctypes::null_terminated_bytes(name.as_bytes()) .map_err(|e| e.to_pyexception(vm))?; let ptr_val = match rustpython_host_env::ctypes::lookup_function_symbol_addr( handle .to_usize() .ok_or_else(|| vm.new_value_error("Invalid handle"))?, &terminated, ) {🤖 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/_ctypes/function.rs` around lines 779 - 780, Validate the function symbol name before constructing the terminated string in the surrounding function of the lookup_function_symbol_addr call. Reject names containing interior NUL bytes and return the existing error path, then preserve the current format!("{name}\0") lookup flow for valid names.crates/vm/src/stdlib/winreg.rs (1)
844-846: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Provide the
vmcontext parameter.Type error:
to_pyexception()requires thevmparameter (&VirtualMachine) to instantiate the Python exception. This will cause a compilation error.🐛 Proposed fix
fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult<String> { - let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception())?; + let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception(vm))?; host_winreg::expand_environment_strings(&i).map_err(|err| err.to_pyexception(vm)) }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult<String> { let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception(vm))?; host_winreg::expand_environment_strings(&i).map_err(|err| err.to_pyexception(vm)) }🤖 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/winreg.rs` around lines 844 - 846, Update the error mapping in the `WideCString::from_str` conversion within the surrounding winreg function to pass the available `vm` context to `to_pyexception`, matching the existing `expand_environment_strings` mapping. Leave the successful conversion and environment expansion behavior unchanged.crates/wtf8/src/lib.rs (2)
1532-1548: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Reject all source NULs consistently. Both encoders synthesize their own terminator, so every NUL found in the input is interior and must fail.
crates/wtf8/src/lib.rs#L1532-L1548: returnInteriorNulErrorimmediately for any source NUL and mark the iterator complete.crates/host_env/src/ctypes.rs#L545-L557: apply the same rule and prevent iteration from resuming after the error.📍 Affects 2 files
crates/wtf8/src/lib.rs#L1532-L1548(this comment)crates/host_env/src/ctypes.rs#L545-L557🤖 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/wtf8/src/lib.rs` around lines 1532 - 1548, Reject every source NUL immediately in the encoder iterator, returning InteriorNulError and setting the iterator’s completion state so iteration cannot resume. Apply this change at crates/wtf8/src/lib.rs lines 1532-1548 and crates/host_env/src/ctypes.rs lines 545-557, preserving the synthesized terminator behavior while removing the scan-and-accept path.
1557-1560: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the iterator’s
size_hint.The iterator can emit an additional terminator, while an early interior-NUL error can produce fewer items than the wrapped iterator’s lower bound. Forwarding the original hint violates both bounds.
Proposed fix
fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() + if self.complete { + return (0, Some(0)); + } + let (_, upper) = self.iter.size_hint(); + (1, upper.and_then(|len| len.checked_add(1))) }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.#[inline] fn size_hint(&self) -> (usize, Option<usize>) { if self.complete { return (0, Some(0)); } let (_, upper) = self.iter.size_hint(); (1, upper.and_then(|len| len.checked_add(1))) }🤖 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/wtf8/src/lib.rs` around lines 1557 - 1560, Update the size_hint method to account for the iterator’s possible terminator output and early termination on interior-NUL errors; do not forward self.iter.size_hint() unchanged. Return bounds that never overstate the minimum or maximum number of items the iterator can emit, preserving the appropriate unbounded case.
b7ce43b to
6f595cd
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
crates/host_env/src/winreg.rs (3)
509-524: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winFix compilation error:
into_vec_with_nulconsumes by value.
into_vec_with_nulis a method onWideCStringand cannot be called on a reference&WideCStr. SinceExpandEnvironmentStringsWonly requires a pointer, you can passinput.as_ptr()directly and avoid the allocation.🐛 Proposed fix
pub fn expand_environment_strings( input: &widestring::WideCStr, ) -> Result<String, ExpandEnvironmentStringsError> { - let wide_input = input.into_vec_with_nul(); let required_size = unsafe { - Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0) + Environment::ExpandEnvironmentStringsW(input.as_ptr(), core::ptr::null_mut(), 0) }; if required_size == 0 { return Err(ExpandEnvironmentStringsError::Os); } let mut out = vec![0u16; required_size as usize]; let written = unsafe { - Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size) + Environment::ExpandEnvironmentStringsW(input.as_ptr(), out.as_mut_ptr(), required_size) };🤖 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/winreg.rs` around lines 509 - 524, Update expand_environment_strings to stop calling the consuming into_vec_with_nul method on the borrowed input; pass input.as_ptr() directly to both Environment::ExpandEnvironmentStringsW calls and remove the unnecessary allocation.
463-507: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winFix unresolved variable, return type mismatch, and incorrect argument type.
The parameter was renamed to
wide_sub_keybut the body referencessub_key. Additionally, the function is declared to returnu32but attempts to returnOk(res)at the end.🐛 Proposed fix
pub fn set_default_value( hkey: Registry::HKEY, wide_sub_key: &widestring::WideCStr, typ: u32, wide_value: &widestring::WideCStr, ) -> u32 { - let child_key = if !sub_key.is_empty() { + let child_key = if !wide_sub_key.is_empty() { let mut out_key = core::ptr::null_mut(); let res = unsafe { create_key_ex( hkey, - &wide_sub_key, + wide_sub_key, 0, core::ptr::null_mut(), 0, Registry::KEY_SET_VALUE, core::ptr::null(), &mut out_key, core::ptr::null_mut(), ) }; if res != 0 { return res; } Some(out_key) } else { None }; let target_key = child_key.unwrap_or(hkey); let res = unsafe { set_value_ex( target_key, None, typ, wide_value.as_ptr() as *const u8, (wide_value.len() * 2) as u32, ) }; if let Some(ck) = child_key { close_key(ck); } - Ok(res) + res }🤖 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/winreg.rs` around lines 463 - 507, Update set_default_value to use the existing wide_sub_key parameter instead of the unresolved sub_key reference, pass the expected key type to create_key_ex, and return the u32 result directly rather than wrapping it in Ok. Preserve the existing child-key creation, value-setting, and cleanup flow.
350-362: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winFix unresolved variable and incorrect argument type.
The parameter was renamed to
wide_sub_key, but the body still referencessub_key. Additionally,open_key_exshould take the unwrappedsub_keyfrom theif letbinding rather than theOptionwrapperwide_sub_key.🐛 Proposed fix
pub fn query_default_value( hkey: Registry::HKEY, wide_sub_key: Option<&widestring::WideCStr>, ) -> Result<String, QueryStringError> { - let child_key = if let Some(sub_key) = sub_key.filter(|s| !s.is_empty()) { + let child_key = if let Some(sub_key) = wide_sub_key.filter(|s| !s.is_empty()) { let mut out_key = core::ptr::null_mut(); let res = unsafe { open_key_ex( hkey, - &wide_sub_key, + Some(sub_key), 0, Registry::KEY_QUERY_VALUE, &mut out_key, ) };🤖 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/winreg.rs` around lines 350 - 362, In the child-key handling branch of the registry query function, use the bound `sub_key` value instead of the renamed `wide_sub_key` variable. Pass this unwrapped `sub_key` directly to `open_key_ex`, preserving the existing filtering of empty subkeys and the surrounding registry logic.crates/vm/src/stdlib/winreg.rs (1)
616-623: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winPass the converted wide strings to
set_default_value.The local variables
wide_sub_keyandwide_valuewere created but not passed toset_default_value, causing a type mismatch since the host function signature was updated to expect&WideCStr.🐛 Proposed fix
- let wide_sub_key = WideCString::from_str(sub_key)?; - let wide_value = WideCString::from_str(value)?; + let wide_sub_key = WideCString::from_str(sub_key).map_err(|e| e.to_pyexception(vm))?; + let wide_value = WideCString::from_str(value).map_err(|e| e.to_pyexception(vm))?; let res = host_winreg::set_default_value( hkey, - std::ffi::OsStr::new(&sub_key), + &wide_sub_key, typ, - std::ffi::OsStr::new(&value), + &wide_value, );🤖 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/winreg.rs` around lines 616 - 623, Update the set_default_value call in the winreg flow to pass references to the already-created wide_sub_key and wide_value variables instead of constructing OsStr values from sub_key and value. Preserve the existing typ and hkey arguments and rely on the WideCString conversions already performed.
🤖 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/ctypes.rs`:
- Around line 543-586: Update wchar_ffi_bytes so supplementary code points are
encoded as UTF-16 surrogate pairs when WChar is 2 bytes, reusing
Wtf8::encode_wide_ffi or the equivalent platform-specific path; retain the
existing direct conversion for wider WChar representations and preserve NUL
termination/interior-NUL handling.
In `@crates/host_env/src/nt.rs`:
- Line 239: Map every to_wide_with_nul conversion error to the surrounding I/O
error type before applying ?, using io::Error::other at
crates/host_env/src/nt.rs lines 239, 386, 419, 451, 618, 656, 1383, 1421, 1449,
1466, and 1598; at lines 1281-1284, map it to
ReadlinkError::Io(io::Error::other(e)).
- Line 944: Update test_file_type_by_name to return result directly instead of
wrapping it in Ok, matching the function’s bool return type and preserving the
computed value.
In `@crates/vm/src/stdlib/_ctypes/base.rs`:
- Line 398: Make the shared wchar conversion helper in
crates/vm/src/stdlib/_ctypes/base.rs fallible and reject interior NULs by
returning the existing InteriorNulError. Update the function.rs conversion path
to use the checked helper and propagate that error, preserving valid wchar
conversions; apply the corresponding changes at base.rs lines 398-398 and
function.rs lines 153-153.
In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 569-570: Map all Windows registry wide-string conversion errors
into PyException with the current vm context. In crates/vm/src/stdlib/winreg.rs
lines 569-570, map WideCString::from_str errors via to_pyexception(vm); at lines
577-578, append the same map_err before ?; at line 587, pass vm to
to_wide_cstring; and at lines 848-849, pass vm to the existing exception
mapping.
---
Outside diff comments:
In `@crates/host_env/src/winreg.rs`:
- Around line 509-524: Update expand_environment_strings to stop calling the
consuming into_vec_with_nul method on the borrowed input; pass input.as_ptr()
directly to both Environment::ExpandEnvironmentStringsW calls and remove the
unnecessary allocation.
- Around line 463-507: Update set_default_value to use the existing wide_sub_key
parameter instead of the unresolved sub_key reference, pass the expected key
type to create_key_ex, and return the u32 result directly rather than wrapping
it in Ok. Preserve the existing child-key creation, value-setting, and cleanup
flow.
- Around line 350-362: In the child-key handling branch of the registry query
function, use the bound `sub_key` value instead of the renamed `wide_sub_key`
variable. Pass this unwrapped `sub_key` directly to `open_key_ex`, preserving
the existing filtering of empty subkeys and the surrounding registry logic.
In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 616-623: Update the set_default_value call in the winreg flow to
pass references to the already-created wide_sub_key and wide_value variables
instead of constructing OsStr values from sub_key and value. Preserve the
existing typ and hkey arguments and rely on the WideCString conversions already
performed.
🪄 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: 8a3654d2-a5a6-4359-be3e-6a7b6a4155d1
📒 Files selected for processing (15)
crates/host_env/src/ctypes.rscrates/host_env/src/fileutils.rscrates/host_env/src/nt.rscrates/host_env/src/overlapped.rscrates/host_env/src/winapi.rscrates/host_env/src/windows.rscrates/host_env/src/winreg.rscrates/stdlib/src/overlapped.rscrates/vm/src/exceptions.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/vm/src/stdlib/_winapi.rscrates/vm/src/stdlib/nt.rscrates/vm/src/stdlib/winreg.rscrates/wtf8/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (8)
- crates/vm/src/exceptions.rs
- crates/host_env/src/fileutils.rs
- crates/stdlib/src/overlapped.rs
- crates/vm/src/stdlib/nt.rs
- crates/host_env/src/winapi.rs
- crates/wtf8/src/lib.rs
- crates/vm/src/stdlib/_winapi.rs
- crates/host_env/src/windows.rs
| /// Encode buffer as a wide string but yield bytes instead of u16. | ||
| /// | ||
| /// The resulting bytes buffer is suitable for FFI. It is NUL capped without interior | ||
| /// NULs. | ||
| pub fn wchar_ffi_bytes(s: &Wtf8) -> impl Iterator<Item = Result<u8, InteriorNulError>> { | ||
| let mut iter = s.code_points().map(|cp| cp.to_u32() as WChar); | ||
| let mut pending: Option<array::IntoIter<_, _>> = None; | ||
| let mut complete = false; | ||
|
|
||
| iter::from_fn(move || { | ||
| if let Some(pending) = pending.as_mut() | ||
| && let Some(next) = pending.next() | ||
| { | ||
| return Some(Ok(next)); | ||
| } | ||
|
|
||
| match iter.next() { | ||
| Some(0) => { | ||
| core::hint::cold_path(); | ||
| complete = true; | ||
|
|
||
| for next in iter.by_ref() { | ||
| if next != 0 { | ||
| return Some(Err(InteriorNulError)); | ||
| } | ||
| } | ||
|
|
||
| pending = Some((0 as WChar).to_ne_bytes().into_iter()); | ||
| } | ||
| Some(next) => { | ||
| pending = Some(next.to_ne_bytes().into_iter()); | ||
| } | ||
| None if !complete => { | ||
| complete = true; | ||
| pending = Some((0 as WChar).to_ne_bytes().into_iter()); | ||
| } | ||
| None => { | ||
| return None; | ||
| } | ||
| } | ||
|
|
||
| pending.as_mut().unwrap().next().map(Result::Ok) | ||
| }) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
Fix code point truncation when WChar is 2 bytes.
The wchar_ffi_bytes function truncates supplementary code points by casting them directly to WChar (cp.to_u32() as WChar). As correctly noted in the nearby wchar_null_terminated_bytes function, on platforms where WChar is 2 bytes (e.g., Windows), this will truncate supplementary characters instead of encoding them as surrogate pairs, leading to invalid UTF-16 data and potential data corruption.
Please conditionally compile this function or use conditional logic to call s.encode_wide_ffi() on platforms where size_of::<WChar>() == 2 to yield properly encoded bytes.
🤖 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/ctypes.rs` around lines 543 - 586, Update wchar_ffi_bytes
so supplementary code points are encoded as UTF-16 surrogate pairs when WChar is
2 bytes, reusing Wtf8::encode_wide_ffi or the equivalent platform-specific path;
retain the existing direct conversion for wider WChar representations and
preserve NUL termination/interior-NUL handling.
| }; | ||
|
|
||
| let wide_path = path.as_os_str().to_wide_with_nul(); | ||
| let wide_path = path.as_os_str().to_wide_with_nul()?; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Map wide-string conversion errors to io::Error.
The to_wide_with_nul() method returns Result<Vec<u16>, ContainsNul<u16>>. Because ContainsNul does not implicitly convert to std::io::Error via the ? operator, these calls fail to compile. Map the error to io::Error before applying ?.
crates/host_env/src/nt.rs#L239-L239: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L386-L386: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L419-L419: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L451-L451: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L618-L618: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L656-L656: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L1281-L1284: Map the error to the enum variant using.map_err(|e| ReadlinkError::Io(io::Error::other(e)))?.crates/host_env/src/nt.rs#L1383-L1383: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L1421-L1421: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L1449-L1449: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L1466-L1466: Map the error using.map_err(io::Error::other)?.crates/host_env/src/nt.rs#L1598-L1598: Map the error using.map_err(io::Error::other)?.
📍 Affects 1 file
crates/host_env/src/nt.rs#L239-L239(this comment)crates/host_env/src/nt.rs#L386-L386crates/host_env/src/nt.rs#L419-L419crates/host_env/src/nt.rs#L451-L451crates/host_env/src/nt.rs#L618-L618crates/host_env/src/nt.rs#L656-L656crates/host_env/src/nt.rs#L1281-L1284crates/host_env/src/nt.rs#L1383-L1383crates/host_env/src/nt.rs#L1421-L1421crates/host_env/src/nt.rs#L1449-L1449crates/host_env/src/nt.rs#L1466-L1466crates/host_env/src/nt.rs#L1598-L1598
🤖 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/nt.rs` at line 239, Map every to_wide_with_nul conversion
error to the surrounding I/O error type before applying ?, using
io::Error::other at crates/host_env/src/nt.rs lines 239, 386, 419, 451, 618,
656, 1383, 1421, 1449, 1466, and 1598; at lines 1281-1284, map it to
ReadlinkError::Io(io::Error::other(e)).
| let result = test_file_type_by_handle(handle, tested_type, false); | ||
| unsafe { CloseHandle(handle) }; | ||
| return result; | ||
| return Ok(result); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Fix return type mismatch.
The function test_file_type_by_name returns a bool, but this line attempts to return Ok(result). This will fail to compile.
🐛 Proposed fix
- return Ok(result);
+ return result;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return Ok(result); | |
| return result; |
🤖 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/nt.rs` at line 944, Update test_file_type_by_name to
return result directly instead of wrapping it in Ok, matching the function’s
bool return type and preserving the computed value.
| /// Convert str to null-terminated wchar_t buffer. Returns (PyBytes holder, pointer). | ||
| pub(super) fn str_to_wchar_bytes(s: &Wtf8, vm: &VirtualMachine) -> (PyObjectRef, usize) { | ||
| let bytes = rustpython_host_env::ctypes::wchar_null_terminated_bytes(s); | ||
| let bytes = wchar_null_terminated_bytes(s); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- base.rs outline ---\n'
ast-grep outline crates/vm/src/stdlib/_ctypes/base.rs --view expanded || true
printf '\n--- function.rs outline ---\n'
ast-grep outline crates/vm/src/stdlib/_ctypes/function.rs --view expanded || true
printf '\n--- relevant snippets ---\n'
sed -n '340,430p' crates/vm/src/stdlib/_ctypes/base.rs
printf '\n'
sed -n '120,190p' crates/vm/src/stdlib/_ctypes/function.rs
printf '\n--- search for wchar helpers / InteriorNulError / utf16z_bytes / wchar_null_terminated_bytes ---\n'
rg -n "wchar_null_terminated_bytes|utf16z_bytes|InteriorNulError|wchar_ffi_bytes|str_to_wchar_bytes" crates/vm/src -SRepository: RustPython/RustPython
Length of output: 250
🏁 Script executed:
sed -n '340,430p' crates/vm/src/stdlib/_ctypes/base.rs && printf '\n---\n' && sed -n '120,190p' crates/vm/src/stdlib/_ctypes/function.rs && printf '\n---\n' && rg -n "wchar_null_terminated_bytes|utf16z_bytes|InteriorNulError|wchar_ffi_bytes|str_to_wchar_bytes" crates/vm/src -SRepository: RustPython/RustPython
Length of output: 7477
🏁 Script executed:
pwd && ls -1 && git ls-files crates/vm/src/stdlib/_ctypes | sed -n '1,40p'Repository: RustPython/RustPython
Length of output: 817
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in crates/vm/src/stdlib/_ctypes/simple.rs crates/vm/src/stdlib/_ctypes/pointer.rs crates/vm/src/stdlib/_ctypes/array.rs crates/vm/src/stdlib/_ctypes/base.rs; do
echo "--- $f ---"
rg -n -C 2 "str_to_wchar_bytes|utf16z_bytes" "$f" || true
echo
doneRepository: RustPython/RustPython
Length of output: 2922
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the host_env ctypes helpers if vendored in this repo, and inspect NUL-related APIs.
fd -a "ctypes.rs" . || true
fd -a "host_env" . || true
rg -n "fn wchar_null_terminated_bytes|fn utf16z_bytes|InteriorNulError|null_terminated_bytes" . -SRepository: RustPython/RustPython
Length of output: 2308
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '520,590p' crates/host_env/src/ctypes.rs
printf '\n---\n'
sed -n '1148,1185p' crates/host_env/src/ctypes.rs
printf '\n---\n'
sed -n '1190,1215p' crates/vm/src/exceptions.rsRepository: RustPython/RustPython
Length of output: 4068
Make ctypes wchar conversion reject interior NULs.
The shared _ctypes wchar path still accepts embedded NULs, so native callees can see truncated strings. Make the helper fallible and switch the function.rs path to the checked API that raises InteriorNulError.
📍 Affects 2 files
crates/vm/src/stdlib/_ctypes/base.rs#L398-L398(this comment)crates/vm/src/stdlib/_ctypes/function.rs#L153-L153
🤖 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/_ctypes/base.rs` at line 398, Make the shared wchar
conversion helper in crates/vm/src/stdlib/_ctypes/base.rs fallible and reject
interior NULs by returning the existing InteriorNulError. Update the function.rs
conversion path to use the checked helper and propagate that error, preserving
valid wchar conversions; apply the corresponding changes at base.rs lines
398-398 and function.rs lines 153-153.
| let sub_key = sub_key.map(WideCString::from_str).transpose()?; | ||
| host_winreg::query_default_value(hkey, sub_key.as_deref()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Map wide-string conversion errors to PyException.
Calls to WideCString::from_str(...) return a ContainsNul error, and to_wide_cstring() requires the vm context. Without explicitly mapping these errors to PyException via .map_err(|e| e.to_pyexception(vm)) (or passing vm to to_wide_cstring), the ? operator fails to compile because ContainsNul cannot be implicitly converted to PyResult.
crates/vm/src/stdlib/winreg.rs#L569-L570: Change the mapping to.map(|s| WideCString::from_str(s).map_err(|e| e.to_pyexception(vm))).crates/vm/src/stdlib/winreg.rs#L577-L578: Append.map_err(|e| e.to_pyexception(vm))?to the conversion call.crates/vm/src/stdlib/winreg.rs#L587-L587: Add the missingvmargument to the method call:.to_wide_cstring(vm)?.crates/vm/src/stdlib/winreg.rs#L848-L849: Pass thevmcontext to the exception mapping:.map_err(|err| err.to_pyexception(vm))?.
📍 Affects 1 file
crates/vm/src/stdlib/winreg.rs#L569-L570(this comment)crates/vm/src/stdlib/winreg.rs#L577-L578crates/vm/src/stdlib/winreg.rs#L587-L587crates/vm/src/stdlib/winreg.rs#L848-L849
🤖 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/winreg.rs` around lines 569 - 570, Map all Windows
registry wide-string conversion errors into PyException with the current vm
context. In crates/vm/src/stdlib/winreg.rs lines 569-570, map
WideCString::from_str errors via to_pyexception(vm); at lines 577-578, append
the same map_err before ?; at line 587, pass vm to to_wide_cstring; and at lines
848-849, pass vm to the existing exception mapping.
Interior NULs is a security hazard for C-style strings. A NUL byte truncates a string which can lead the caller and callee to see two different strings. It can cause path traversal attacks where a path in Python looks complete but it is interpreted differently through FFI. RustPython needs to handle this for some of its C-API as well as raw libc or Windows calls. Both Rust's standard library as well as Rustix handle interior NULs for us with CStrings, so this mostly affects Windows or areas where we have raw bytes that weren't checked by CString. Finally, this PR is non-exhaustive. I will have to rely heavily on CodeRabbit to help lint it to ensure that interior NUL checks are only introduced for FFI and not outside of it. Most of RustPython seems to handle interior NULs already due to CString as well as WideCString. **AI disclosure:** I relied on AI to ensure I'm solving this problem correctly. Mainly, I used it to check if the FFI functions I'm modifying need to handle interior NULs. **Sources:** * https://owasp.org/www-community/attacks/Embedding_Null_Code * python/cpython#11656 Assisted-by: Codex:gpt-5.4
6f595cd to
f25dc9f
Compare
Part of RustPython#8245 to reduce the amount of work needed to review. I simplified the embedded nul errors by forwarding to the implementations in `vm::exceptions`.
Part of RustPython#8245 to reduce the amount of work needed to review. I simplified the embedded nul errors by forwarding to the implementations in `vm::exceptions`.
Part of RustPython#8245 to reduce the amount of work needed to review. I simplified the embedded nul errors by forwarding to the implementations in `vm::exceptions`.
Part of RustPython#8245 to reduce the amount of work needed to review. I simplified the embedded nul errors by forwarding to the implementations in `vm::exceptions`.
Part of #8245 to reduce the amount of work needed to review. I simplified the embedded nul errors by forwarding to the implementations in `vm::exceptions`.
(This is WIP; the Windows code doesn't even compile yet 😆 Opening as placeholder + CodeRabbit lints )
Interior NULs is a security hazard for C-style strings. A NUL byte truncates a string which can lead the caller and callee to see two different strings. It can cause path traversal attacks where a path in Python looks complete but it is interpreted differently through FFI.
RustPython needs to handle this for some of its C-API as well as raw libc or Windows calls. Both Rust's standard library as well as Rustix handle interior NULs for us with CStrings, so this mostly affects Windows or areas where we have raw bytes that weren't checked by CString.
Finally, this PR is non-exhaustive. I will have to rely heavily on CodeRabbit to help lint it to ensure that interior NUL checks are only introduced for FFI and not outside of it. Most of RustPython seems to handle interior NULs already due to CString as well as WideCString.
AI disclosure: I relied on AI to ensure I'm solving this problem correctly. Mainly, I used it to check if the FFI functions I'm modifying need to handle interior NULs.
Sources:
Assisted-by: Codex:gpt-5.4
Summary
Summary by CodeRabbit