Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,7 @@ def _acquire_release(lock, timeout, l=None, n=1):
for _ in range(n):
lock.release()

@unittest.skip("TODO: RUSTPYTHON; flaky test")
def test_repr_rlock(self):
if self.TYPE != 'processes':
self.skipTest('test not appropriate for {}'.format(self.TYPE))
Expand Down
37 changes: 37 additions & 0 deletions crates/stdlib/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ mod _locale {
convert::ToPyException,
function::OptionalArg,
};
#[cfg(windows)]
use windows_sys::Win32::Globalization::GetACP;

#[cfg(all(
unix,
Expand Down Expand Up @@ -260,4 +262,39 @@ mod _locale {
pystr_from_raw_cstr(vm, result)
}
}

/// Get the current locale encoding.
#[pyfunction]
fn getencoding() -> String {
#[cfg(windows)]
{
// On Windows, use GetACP() to get the ANSI code page
let acp = unsafe { GetACP() };
format!("cp{}", acp)
}
#[cfg(not(windows))]
{
// On Unix, use nl_langinfo(CODESET) or fallback to UTF-8
#[cfg(all(
unix,
not(any(target_os = "ios", target_os = "android", target_os = "redox"))
))]
{
unsafe {
let codeset = libc::nl_langinfo(libc::CODESET);
if !codeset.is_null()
&& let Ok(s) = CStr::from_ptr(codeset).to_str()
&& !s.is_empty()
{
return s.to_string();
}
}
"UTF-8".to_string()
}
#[cfg(any(target_os = "ios", target_os = "android", target_os = "redox"))]
{
"UTF-8".to_string()
}
}
}
}
183 changes: 181 additions & 2 deletions crates/stdlib/src/overlapped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ mod _overlapped {
// straight-forward port of Modules/overlapped.c

use crate::vm::{
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyBaseExceptionRef, PyBytesRef, PyType},
common::lock::PyMutex,
convert::{ToPyException, ToPyObject},
function::OptionalArg,
protocol::PyBuffer,
types::Constructor,
};
use windows_sys::Win32::{
Foundation::{self, GetLastError, HANDLE},
Networking::WinSock::SOCKADDR_IN6,
Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR_IN, SOCKADDR_IN6},
System::IO::OVERLAPPED,
};

Expand Down Expand Up @@ -153,6 +154,51 @@ mod _overlapped {
overlapped.Internal != (Foundation::STATUS_PENDING as usize)
}

/// Parse a SOCKADDR_IN6 (which can also hold IPv4 addresses) to a Python address tuple
fn unparse_address(
addr: &SOCKADDR_IN6,
_addr_len: libc::c_int,
vm: &VirtualMachine,
) -> PyObjectRef {
use crate::vm::convert::ToPyObject;

unsafe {
let family = addr.sin6_family;
if family == AF_INET {
// IPv4 address stored in SOCKADDR_IN6 structure
let addr_in = &*(addr as *const SOCKADDR_IN6 as *const SOCKADDR_IN);
let ip_bytes = addr_in.sin_addr.S_un.S_un_b;
let ip_str = format!(
"{}.{}.{}.{}",
ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4
);
let port = u16::from_be(addr_in.sin_port);
(ip_str, port).to_pyobject(vm)
} else if family == AF_INET6 {
// IPv6 address
let ip_bytes = addr.sin6_addr.u.Byte;
let ip_str = format!(
"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
u16::from_be_bytes([ip_bytes[0], ip_bytes[1]]),
u16::from_be_bytes([ip_bytes[2], ip_bytes[3]]),
u16::from_be_bytes([ip_bytes[4], ip_bytes[5]]),
u16::from_be_bytes([ip_bytes[6], ip_bytes[7]]),
u16::from_be_bytes([ip_bytes[8], ip_bytes[9]]),
u16::from_be_bytes([ip_bytes[10], ip_bytes[11]]),
u16::from_be_bytes([ip_bytes[12], ip_bytes[13]]),
u16::from_be_bytes([ip_bytes[14], ip_bytes[15]]),
);
let port = u16::from_be(addr.sin6_port);
let flowinfo = addr.sin6_flowinfo;
let scope_id = addr.Anonymous.sin6_scope_id;
(ip_str, port, flowinfo, scope_id).to_pyobject(vm)
} else {
// Unknown address family, return None
vm.ctx.none()
}
}
}

#[pyclass(with(Constructor))]
impl Overlapped {
#[pygetset]
Expand Down Expand Up @@ -259,6 +305,139 @@ mod _overlapped {
}
Ok(())
}

#[pymethod]
fn getresult(zelf: &Py<Self>, wait: OptionalArg<bool>, vm: &VirtualMachine) -> PyResult {
use windows_sys::Win32::Foundation::{
ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS,
};
use windows_sys::Win32::System::IO::GetOverlappedResult;

let mut inner = zelf.inner.lock();
let wait = wait.unwrap_or(false);

// Check operation state
if matches!(inner.data, OverlappedData::None) {
return Err(vm.new_value_error("operation not yet attempted".to_owned()));
}
if matches!(inner.data, OverlappedData::NotStarted) {
return Err(vm.new_value_error("operation failed to start".to_owned()));
}

// Get the result
let mut transferred: u32 = 0;
let ret = unsafe {
GetOverlappedResult(
inner.handle,
&inner.overlapped,
&mut transferred,
if wait { 1 } else { 0 },
)
};

let err = if ret != 0 {
ERROR_SUCCESS
} else {
unsafe { GetLastError() }
};
inner.error = err;

// Handle errors
match err {
ERROR_SUCCESS | ERROR_MORE_DATA => {}
ERROR_BROKEN_PIPE => {
// For read operations, broken pipe is acceptable
match &inner.data {
OverlappedData::Read(_) | OverlappedData::ReadInto(_) => {}
OverlappedData::ReadFrom(rf)
if rf.result.is(&vm.ctx.none())
|| rf.allocated_buffer.is(&vm.ctx.none()) =>
{
return Err(from_windows_err(err, vm));
}
OverlappedData::ReadFrom(_) => {}
OverlappedData::ReadFromInto(rfi) if rfi.result.is(&vm.ctx.none()) => {
return Err(from_windows_err(err, vm));
}
OverlappedData::ReadFromInto(_) => {}
_ => return Err(from_windows_err(err, vm)),
}
}
ERROR_IO_PENDING => {
return Err(from_windows_err(err, vm));
}
_ => return Err(from_windows_err(err, vm)),
}

// Return result based on operation type
match &inner.data {
OverlappedData::Read(buf) => {
// Resize buffer to actual bytes read
let bytes = buf.as_bytes();
let result = if transferred as usize != bytes.len() {
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
} else {
buf.clone()
};
Ok(result.into())
}
OverlappedData::ReadInto(_) => {
// Return number of bytes read
Ok(vm.ctx.new_int(transferred).into())
}
OverlappedData::Write(_) => {
// Return number of bytes written
Ok(vm.ctx.new_int(transferred).into())
}
OverlappedData::Accept(_) => {
// Return None for accept
Ok(vm.ctx.none())
}
OverlappedData::Connect => {
// Return None for connect
Ok(vm.ctx.none())
}
OverlappedData::Disconnect => {
// Return None for disconnect
Ok(vm.ctx.none())
}
OverlappedData::ConnectNamedPipe => {
// Return None for connect named pipe
Ok(vm.ctx.none())
}
OverlappedData::WaitNamedPipeAndConnect => {
// Return None
Ok(vm.ctx.none())
}
OverlappedData::ReadFrom(rf) => {
// Return (resized_buffer, (host, port)) tuple
let buf = rf
.allocated_buffer
.downcast_ref::<crate::vm::builtins::PyBytes>()
.unwrap();
let bytes = buf.as_bytes();
let resized_buf = if transferred as usize != bytes.len() {
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
} else {
buf.to_owned()
};
let addr_tuple = unparse_address(&rf.address, rf.address_length, vm);
Ok(vm
.ctx
.new_tuple(vec![resized_buf.into(), addr_tuple])
.into())
}
Comment on lines +412 to +429
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential panic from unwrap() on downcast.

Line 417 uses .unwrap() on downcast_ref::<PyBytes>(). If rf.allocated_buffer is not a PyBytes object (due to a logic error elsewhere), this will panic.

Consider using a safer pattern or adding a comment explaining why this is guaranteed to succeed.

🔎 Suggested safer alternative
-                    let buf = rf
-                        .allocated_buffer
-                        .downcast_ref::<crate::vm::builtins::PyBytes>()
-                        .unwrap();
+                    let buf = rf
+                        .allocated_buffer
+                        .downcast_ref::<crate::vm::builtins::PyBytes>()
+                        .ok_or_else(|| vm.new_type_error("expected bytes object".to_owned()))?;
📝 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.

Suggested change
OverlappedData::ReadFrom(rf) => {
// Return (resized_buffer, (host, port)) tuple
let buf = rf
.allocated_buffer
.downcast_ref::<crate::vm::builtins::PyBytes>()
.unwrap();
let bytes = buf.as_bytes();
let resized_buf = if transferred as usize != bytes.len() {
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
} else {
buf.to_owned()
};
let addr_tuple = unparse_address(&rf.address, rf.address_length, vm);
Ok(vm
.ctx
.new_tuple(vec![resized_buf.into(), addr_tuple])
.into())
}
OverlappedData::ReadFrom(rf) => {
// Return (resized_buffer, (host, port)) tuple
let buf = rf
.allocated_buffer
.downcast_ref::<crate::vm::builtins::PyBytes>()
.ok_or_else(|| vm.new_type_error("expected bytes object".to_owned()))?;
let bytes = buf.as_bytes();
let resized_buf = if transferred as usize != bytes.len() {
vm.ctx.new_bytes(bytes[..transferred as usize].to_vec())
} else {
buf.to_owned()
};
let addr_tuple = unparse_address(&rf.address, rf.address_length, vm);
Ok(vm
.ctx
.new_tuple(vec![resized_buf.into(), addr_tuple])
.into())
}
🤖 Prompt for AI Agents
In crates/stdlib/src/overlapped.rs around lines 412 to 429, the code uses
.unwrap() on rf.allocated_buffer.downcast_ref::<crate::vm::builtins::PyBytes>()
which can panic if the object is not PyBytes; replace the unwrap with a safe
check (e.g., match or if let) that handles the None case by returning a
Python-level error (TypeError or ValueError) via the vm or otherwise converting
the object to bytes safely, ensuring no panic; include a concise error message
describing the unexpected buffer type and proceed to construct resized_buf only
after confirming the downcast succeeded.

OverlappedData::ReadFromInto(rfi) => {
// Return (transferred, (host, port)) tuple
let addr_tuple = unparse_address(&rfi.address, rfi.address_length, vm);
Ok(vm
.ctx
.new_tuple(vec![vm.ctx.new_int(transferred).into(), addr_tuple])
.into())
}
_ => Ok(vm.ctx.none()),
}
}
}

impl Constructor for Overlapped {
Expand Down
35 changes: 34 additions & 1 deletion crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,40 @@ pub(super) mod _os {
#[pyfunction]
fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> {
env::set_current_dir(&path.path)
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?;

#[cfg(windows)]
{
// win32_wchdir()

// On Windows, set the per-drive CWD environment variable (=X:)
// This is required for GetFullPathNameW to work correctly with drive-relative paths

use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::System::Environment::SetEnvironmentVariableW;

if let Ok(cwd) = env::current_dir() {
let cwd_str = cwd.as_os_str();
let mut cwd_wide: Vec<u16> = cwd_str.encode_wide().collect();

// Check for UNC-like paths (\\server\share or //server/share)
// wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0
let is_unc_like_path = cwd_wide.len() >= 2
&& ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16)
|| (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16));

if !is_unc_like_path {
// Create env var name "=X:" where X is the drive letter
let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0];
cwd_wide.push(0); // null-terminate the path
unsafe {
SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr());
}
}
}
}

Ok(())
}

#[pyfunction]
Expand Down
Loading