From 79abbf0b29d5183b0885e45ea9991bd8d046d702 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Dec 2025 23:36:38 +0900 Subject: [PATCH] fix ctypes --- crates/vm/src/stdlib/ctypes.rs | 102 ++- crates/vm/src/stdlib/ctypes/array.rs | 44 +- crates/vm/src/stdlib/ctypes/base.rs | 74 ++- crates/vm/src/stdlib/ctypes/function.rs | 776 ++++++++++++++--------- crates/vm/src/stdlib/ctypes/library.rs | 108 +++- crates/vm/src/stdlib/ctypes/pointer.rs | 147 ++++- crates/vm/src/stdlib/ctypes/simple.rs | 187 ++++-- crates/vm/src/stdlib/ctypes/structure.rs | 6 - 8 files changed, 972 insertions(+), 472 deletions(-) diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 3fdb2df61..9b9222304 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -95,6 +95,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { pointer::PyCPointerType::make_class(ctx); structure::PyCStructType::make_class(ctx); union::PyCUnionType::make_class(ctx); + function::PyCFuncPtrType::make_class(ctx); extend_module!(vm, &module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), @@ -385,12 +386,8 @@ pub(crate) mod _ctypes { #[pyattr] const RTLD_GLOBAL: i32 = 0; - #[cfg(target_os = "windows")] #[pyattr] - const SIZEOF_TIME_T: usize = 8; - #[cfg(not(target_os = "windows"))] - #[pyattr] - const SIZEOF_TIME_T: usize = 4; + const SIZEOF_TIME_T: usize = std::mem::size_of::(); #[pyattr] const CTYPES_MAX_ARGCOUNT: usize = 1024; @@ -578,30 +575,42 @@ pub(crate) mod _ctypes { #[pyfunction(name = "dlopen")] fn load_library_unix( name: Option, - _load_flags: OptionalArg, + load_flags: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // TODO: audit functions first - // TODO: load_flags + // Default mode: RTLD_NOW | RTLD_LOCAL, always force RTLD_NOW + let mode = load_flags.unwrap_or(libc::RTLD_NOW | libc::RTLD_LOCAL) | libc::RTLD_NOW; + match name { Some(name) => { let cache = library::libcache(); let mut cache_write = cache.write(); let os_str = name.as_os_str(vm)?; - let (id, _) = cache_write.get_or_insert_lib(&*os_str, vm).map_err(|e| { - // Include filename in error message for better diagnostics - let name_str = os_str.to_string_lossy(); - vm.new_os_error(format!("{}: {}", name_str, e)) - })?; + let (id, _) = cache_write + .get_or_insert_lib_with_mode(&*os_str, mode, vm) + .map_err(|e| { + let name_str = os_str.to_string_lossy(); + vm.new_os_error(format!("{}: {}", name_str, e)) + })?; Ok(id) } None => { - // If None, call libc::dlopen(null, mode) to get the current process handle - let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_NOW) }; + // dlopen(NULL, mode) to get the current process handle (for pythonapi) + let handle = unsafe { libc::dlopen(std::ptr::null(), mode) }; if handle.is_null() { - return Err(vm.new_os_error("dlopen() error")); + let err = unsafe { libc::dlerror() }; + let msg = if err.is_null() { + "dlopen() error".to_string() + } else { + unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() } + }; + return Err(vm.new_os_error(msg)); } - Ok(handle as usize) + // Add to library cache so symbol lookup works + let cache = library::libcache(); + let mut cache_write = cache.write(); + let id = cache_write.insert_raw_handle(handle); + Ok(id) } } } @@ -614,6 +623,48 @@ pub(crate) mod _ctypes { Ok(()) } + #[cfg(not(windows))] + #[pyfunction] + fn dlclose(handle: usize, _vm: &VirtualMachine) -> PyResult<()> { + // Remove from cache, which triggers SharedLibrary drop. + // libloading::Library calls dlclose automatically on Drop. + let cache = library::libcache(); + let mut cache_write = cache.write(); + cache_write.drop_lib(handle); + Ok(()) + } + + #[cfg(not(windows))] + #[pyfunction] + fn dlsym( + handle: usize, + name: crate::builtins::PyStrRef, + vm: &VirtualMachine, + ) -> PyResult { + let symbol_name = std::ffi::CString::new(name.as_str()) + .map_err(|_| vm.new_value_error("symbol name contains null byte"))?; + + // Clear previous error + unsafe { libc::dlerror() }; + + let ptr = unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) }; + + // Check for error via dlerror first + let err = unsafe { libc::dlerror() }; + if !err.is_null() { + let msg = unsafe { std::ffi::CStr::from_ptr(err).to_string_lossy().into_owned() }; + return Err(vm.new_os_error(msg)); + } + + // Treat NULL symbol address as error + // This handles cases like GNU IFUNCs that resolve to NULL + if ptr.is_null() { + return Err(vm.new_os_error(format!("symbol '{}' not found", name.as_str()))); + } + + Ok(ptr as usize) + } + #[pyfunction(name = "POINTER")] fn create_pointer_type(cls: PyObjectRef, vm: &VirtualMachine) -> PyResult { use crate::builtins::PyStr; @@ -905,25 +956,24 @@ pub(crate) mod _ctypes { #[pyfunction] fn get_errno() -> i32 { - errno::errno().0 + super::function::get_errno_value() } #[pyfunction] - fn set_errno(value: i32) { - errno::set_errno(errno::Errno(value)); + fn set_errno(value: i32) -> i32 { + super::function::set_errno_value(value) } #[cfg(windows)] #[pyfunction] fn get_last_error() -> PyResult { - Ok(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + Ok(super::function::get_last_error_value()) } #[cfg(windows)] #[pyfunction] - fn set_last_error(value: u32) -> PyResult<()> { - unsafe { windows_sys::Win32::Foundation::SetLastError(value) }; - Ok(()) + fn set_last_error(value: u32) -> u32 { + super::function::set_last_error_value(value) } #[pyattr] @@ -1084,9 +1134,9 @@ pub(crate) mod _ctypes { ffi_args.push(Arg::new(val)); } - let cif = Cif::new(arg_types, Type::isize()); + let cif = Cif::new(arg_types, Type::c_int()); let code_ptr = CodePtr::from_ptr(func_addr as *const _); - let result: isize = unsafe { cif.call(code_ptr, &ffi_args) }; + let result: libc::c_int = unsafe { cif.call(code_ptr, &ffi_args) }; Ok(vm.ctx.new_int(result).into()) } diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 60e6516bf..208b3e3f4 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -1,9 +1,12 @@ use super::StgInfo; use super::base::{CDATA_BUFFER_METHODS, PyCData}; +use super::type_info; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, atomic_func, - builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, + builtins::{ + PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef, genericalias::PyGenericAlias, + }, class::StaticType, function::{ArgBytesLike, FuncArgs, PySetterValue}, protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods}, @@ -11,6 +14,19 @@ use crate::{ }; use num_traits::{Signed, ToPrimitive}; +/// Get itemsize from a PEP 3118 format string +/// Extracts the type code (last char after endianness prefix) and returns its size +fn get_size_from_format(fmt: &str) -> usize { + // Format is like "q", etc. - strip endianness prefix and get type code + let code = fmt + .trim_start_matches(['<', '>', '@', '=', '!', '&']) + .chars() + .next() + .map(|c| c.to_string()); + code.map(|c| type_info(&c).map(|t| t.size).unwrap_or(1)) + .unwrap_or(1) +} + /// Creates array type for (element_type, length) /// Uses _array_type_cache to ensure identical calls return the same type object pub(super) fn array_type_from_ctype( @@ -444,6 +460,11 @@ impl AsSequence for PyCArray { with(Constructor, AsSequence, AsBuffer) )] impl PyCArray { + #[pyclassmethod] + fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } + fn int_to_bytes(i: &malachite_bigint::BigInt, size: usize) -> Vec { // Try unsigned first (handles values like 0xFFFFFFFF that overflow signed) // then fall back to signed (handles negative values) @@ -1056,19 +1077,30 @@ impl AsBuffer for PyCArray { .expect("PyCArray type must have StgInfo"); let format = stg_info.format.clone(); let shape = stg_info.shape.clone(); - let element_size = stg_info.element_size; let desc = if let Some(fmt) = format && !shape.is_empty() { + // itemsize is the size of the base element type (item_info->size) + // For empty arrays, we still need the element size, not 0 + let total_elements: usize = shape.iter().product(); + let has_zero_dim = shape.contains(&0); + let itemsize = if total_elements > 0 && buffer_len > 0 { + buffer_len / total_elements + } else { + // For empty arrays, get itemsize from format type code + get_size_from_format(&fmt) + }; + // Build dim_desc from shape (C-contiguous: row-major order) // stride[i] = product(shape[i+1:]) * itemsize + // For empty arrays (any dimension is 0), all strides are 0 let mut dim_desc = Vec::with_capacity(shape.len()); - let mut stride = element_size as isize; + let mut stride = itemsize as isize; - // Calculate strides from innermost to outermost dimension for &dim_size in shape.iter().rev() { - dim_desc.push((dim_size, stride, 0)); + let current_stride = if has_zero_dim { 0 } else { stride }; + dim_desc.push((dim_size, current_stride, 0)); stride *= dim_size as isize; } dim_desc.reverse(); @@ -1076,7 +1108,7 @@ impl AsBuffer for PyCArray { BufferDescriptor { len: buffer_len, readonly: false, - itemsize: element_size, + itemsize, format: std::borrow::Cow::Owned(fmt), dim_desc, } diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 38c371346..44793a215 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -300,15 +300,27 @@ pub(super) fn get_field_format( big_endian: bool, vm: &VirtualMachine, ) -> String { + let endian_prefix = if big_endian { ">" } else { "<" }; + // 1. Check StgInfo for format if let Some(type_obj) = field_type.downcast_ref::() && let Some(stg_info) = type_obj.stg_info_opt() && let Some(fmt) = &stg_info.format { - // Handle endian prefix for simple types - if fmt.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; - return format!("{}{}", endian_prefix, fmt); + // For structures (T{...}), arrays ((n)...), and pointers (&...), return as-is + // These complex types have their own endianness markers inside + if fmt.starts_with('T') + || fmt.starts_with('(') + || fmt.starts_with('&') + || fmt.starts_with("X{") + { + return fmt.clone(); + } + + // For simple types, replace existing endian prefix with the correct one + let base_fmt = fmt.trim_start_matches(['<', '>', '@', '=', '!']); + if !base_fmt.is_empty() { + return format!("{}{}", endian_prefix, base_fmt); } return fmt.clone(); } @@ -318,8 +330,7 @@ pub(super) fn get_field_format( && let Some(type_str) = type_attr.downcast_ref::() { let s = type_str.as_str(); - if s.len() == 1 { - let endian_prefix = if big_endian { ">" } else { "<" }; + if !s.is_empty() { return format!("{}{}", endian_prefix, s); } return s.to_string(); @@ -1168,29 +1179,30 @@ impl PyCData { .ok_or_else(|| vm.new_value_error("Invalid library handle"))? }; - // Get symbol address using platform-specific API - let symbol_name = std::ffi::CString::new(name.as_str()) - .map_err(|_| vm.new_value_error("Invalid symbol name"))?; + // Look up the library in the cache and use lib.get() for symbol lookup + let library_cache = super::library::libcache().read(); + let library = library_cache + .get_lib(handle) + .ok_or_else(|| vm.new_value_error("Library not found"))?; + let inner_lib = library.lib.lock(); - #[cfg(windows)] - let ptr: *const u8 = unsafe { - match windows_sys::Win32::System::LibraryLoader::GetProcAddress( - handle as windows_sys::Win32::Foundation::HMODULE, - symbol_name.as_ptr() as *const u8, - ) { - Some(p) => p as *const u8, - None => std::ptr::null(), + let symbol_name_with_nul = format!("{}\0", name.as_str()); + let ptr: *const u8 = if let Some(lib) = &*inner_lib { + unsafe { + lib.get::<*const u8>(symbol_name_with_nul.as_bytes()) + .map(|sym| *sym) + .map_err(|_| { + vm.new_value_error(format!("symbol '{}' not found", name.as_str())) + })? } + } else { + return Err(vm.new_value_error("Library closed")); }; - #[cfg(not(windows))] - let ptr: *const u8 = - unsafe { libc::dlsym(handle as *mut libc::c_void, symbol_name.as_ptr()) as *const u8 }; - + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors if ptr.is_null() { - return Err( - vm.new_value_error(format!("symbol '{}' not found in library", name.as_str())) - ); + return Err(vm.new_value_error(format!("symbol '{}' not found", name.as_str()))); } // PyCData_AtAddress @@ -1593,7 +1605,7 @@ impl PyCField { /// PyCField_set #[pyslot] fn descr_set( - zelf: &crate::PyObject, + zelf: &PyObject, obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine, @@ -1804,7 +1816,7 @@ pub enum FfiArgValue { F64(f64), Pointer(usize), /// Pointer with owned data. The PyObjectRef keeps the pointed data alive. - OwnedPointer(usize, #[allow(dead_code)] crate::PyObjectRef), + OwnedPointer(usize, #[allow(dead_code)] PyObjectRef), } impl FfiArgValue { @@ -2145,6 +2157,16 @@ pub(super) fn read_ptr_from_buffer(buffer: &[u8]) -> usize { } } +/// Check if a type is a "simple instance" (direct subclass of a simple type) +/// Returns TRUE for c_int, c_void_p, etc. (simple types with _type_ attribute) +/// Returns FALSE for Structure, Array, POINTER(T), etc. +pub(super) fn is_simple_instance(typ: &Py) -> bool { + // _ctypes_simple_instance + // Check if the type's metaclass is PyCSimpleType + let metaclass = typ.class(); + metaclass.fast_issubclass(super::simple::PyCSimpleType::static_type()) +} + /// Set or initialize StgInfo on a type pub(super) fn set_or_init_stginfo(type_ref: &PyType, stg_info: StgInfo) { if type_ref.init_type_data(stg_info.clone()).is_err() diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 9bddb0ef0..04ff238eb 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -1,16 +1,19 @@ // spell-checker:disable use super::{ - _ctypes::CArgObject, PyCArray, PyCData, PyCPointer, PyCStructure, base::FfiArgValue, - simple::PyCSimple, type_info, + _ctypes::CArgObject, + PyCArray, PyCData, PyCPointer, PyCStructure, StgInfo, + base::{CDATA_BUFFER_METHODS, FfiArgValue, ParamFunc, StgInfoFlags}, + simple::PyCSimple, + type_info, }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyDict, PyNone, PyStr, PyTuple, PyType, PyTypeRef}, class::StaticType, - convert::ToPyObject, function::FuncArgs, - types::{Callable, Constructor, Representable}, + protocol::{BufferDescriptor, PyBuffer}, + types::{AsBuffer, Callable, Constructor, Initializer, Representable}, vm::thread::with_current_vm, }; use libffi::{ @@ -18,9 +21,10 @@ use libffi::{ middle::{Arg, Cif, Closure, CodePtr, Type}, }; use libloading::Symbol; -use num_traits::ToPrimitive; +use num_traits::{Signed, ToPrimitive}; use rustpython_common::lock::PyRwLock; -use std::ffi::{self, c_void}; +use std::borrow::Cow; +use std::ffi::c_void; use std::fmt::Debug; // Internal function addresses for special ctypes functions @@ -28,6 +32,90 @@ pub(super) const INTERNAL_CAST_ADDR: usize = 1; pub(super) const INTERNAL_STRING_AT_ADDR: usize = 2; pub(super) const INTERNAL_WSTRING_AT_ADDR: usize = 3; +// Thread-local errno storage for ctypes +std::thread_local! { + /// Thread-local storage for ctypes errno + /// This is separate from the system errno - ctypes swaps them during FFI calls + /// when use_errno=True is specified. + static CTYPES_LOCAL_ERRNO: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +/// Get ctypes thread-local errno value +pub(super) fn get_errno_value() -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| e.get()) +} + +/// Set ctypes thread-local errno value, returns old value +pub(super) fn set_errno_value(value: i32) -> i32 { + CTYPES_LOCAL_ERRNO.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore errno around FFI call (called when use_errno=True) +/// Before: restore thread-local errno to system +/// After: save system errno to thread-local +#[cfg(not(windows))] +fn swap_errno(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local errno to system + let saved = CTYPES_LOCAL_ERRNO.with(|e| e.get()); + errno::set_errno(errno::Errno(saved)); + + // Call the function + let result = f(); + + // After call: save system errno to thread-local + let new_error = errno::errno().0; + CTYPES_LOCAL_ERRNO.with(|e| e.set(new_error)); + + result +} + +#[cfg(windows)] +std::thread_local! { + /// Thread-local storage for ctypes last_error (Windows only) + static CTYPES_LOCAL_LAST_ERROR: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +#[cfg(windows)] +pub(super) fn get_last_error_value() -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()) +} + +#[cfg(windows)] +pub(super) fn set_last_error_value(value: u32) -> u32 { + CTYPES_LOCAL_LAST_ERROR.with(|e| { + let old = e.get(); + e.set(value); + old + }) +} + +/// Save and restore last_error around FFI call (called when use_last_error=True) +#[cfg(windows)] +fn save_and_restore_last_error(f: F) -> R +where + F: FnOnce() -> R, +{ + // Before call: restore thread-local last_error to Windows + let saved = CTYPES_LOCAL_LAST_ERROR.with(|e| e.get()); + unsafe { windows_sys::Win32::Foundation::SetLastError(saved) }; + + // Call the function + let result = f(); + + // After call: save Windows last_error to thread-local + let new_error = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + CTYPES_LOCAL_LAST_ERROR.with(|e| e.set(new_error)); + + result +} + type FP = unsafe extern "C" fn(); /// Get FFI type for a ctypes type code @@ -131,11 +219,19 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult direct value + // 7. Integer -> direct value (PyLong_AsVoidPtr behavior) if let Ok(int_val) = value.try_int(vm) { - return Ok(FfiArgValue::Pointer( - int_val.as_bigint().to_usize().unwrap_or(0), - )); + let bigint = int_val.as_bigint(); + // Negative values: use signed conversion (allows -1 as 0xFFFF...) + if bigint.is_negative() { + if let Some(signed_val) = bigint.to_isize() { + return Ok(FfiArgValue::Pointer(signed_val as usize)); + } + } else if let Some(unsigned_val) = bigint.to_usize() { + return Ok(FfiArgValue::Pointer(unsigned_val)); + } + // Value out of range - raise OverflowError + return Err(vm.new_overflow_error("int too large to convert to pointer".to_string())); } // 8. Check _as_parameter_ attribute ( recursive ConvParam) @@ -150,46 +246,86 @@ fn convert_to_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult PyResult<(Type, FfiArgValue)> { +/// Returns an Argument with FFI type, value, and optional keep object +fn conv_param(value: &PyObject, vm: &VirtualMachine) -> PyResult { // 1. CArgObject (from byref() or paramfunc) -> use stored type and value if let Some(carg) = value.downcast_ref::() { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } // 2. None -> NULL pointer if value.is(&vm.ctx.none) { - return Ok((Type::pointer(), FfiArgValue::Pointer(0))); + return Ok(Argument { + ffi_type: Type::pointer(), + keep: None, + value: FfiArgValue::Pointer(0), + }); } // 3. ctypes objects -> use paramfunc if let Ok(carg) = super::base::call_paramfunc(value, vm) { let ffi_type = ffi_type_from_tag(carg.tag); - return Ok((ffi_type, carg.value.clone())); + return Ok(Argument { + ffi_type, + keep: None, + value: carg.value.clone(), + }); } - // 4. Python str -> pointer (use internal UTF-8 buffer) + // 4. Python str -> wide string pointer (like PyUnicode_AsWideCharString) if let Some(s) = value.downcast_ref::() { - let addr = s.as_str().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + // Convert to null-terminated UTF-16 (wide string) + let wide: Vec = s + .as_str() + .encode_utf16() + .chain(std::iter::once(0)) + .collect(); + let wide_bytes: Vec = wide.iter().flat_map(|&x| x.to_ne_bytes()).collect(); + let keep = vm.ctx.new_bytes(wide_bytes); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } - // 9. Python bytes -> pointer to buffer + // 9. Python bytes -> null-terminated buffer pointer + // Need to ensure null termination like c_char_p if let Some(bytes) = value.downcast_ref::() { - let addr = bytes.as_bytes().as_ptr() as usize; - return Ok((Type::pointer(), FfiArgValue::Pointer(addr))); + let mut buffer = bytes.as_bytes().to_vec(); + buffer.push(0); // Add null terminator + let keep = vm.ctx.new_bytes(buffer); + let addr = keep.as_bytes().as_ptr() as usize; + return Ok(Argument { + ffi_type: Type::pointer(), + keep: Some(keep.into()), + value: FfiArgValue::Pointer(addr), + }); } // 10. Python int -> i32 (default integer type) if let Ok(int_val) = value.try_int(vm) { let val = int_val.as_bigint().to_i32().unwrap_or(0); - return Ok((Type::i32(), FfiArgValue::I32(val))); + return Ok(Argument { + ffi_type: Type::i32(), + keep: None, + value: FfiArgValue::I32(val), + }); } // 11. Python float -> f64 if let Ok(float_val) = value.try_float(vm) { - return Ok((Type::f64(), FfiArgValue::F64(float_val.to_f64()))); + return Ok(Argument { + ffi_type: Type::f64(), + keep: None, + value: FfiArgValue::F64(float_val.to_f64()), + }); } // 12. Check _as_parameter_ attribute @@ -245,7 +381,7 @@ impl ArgumentType for PyTypeRef { } fn convert_object(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // Call from_param first to convert the value (like CPython's callproc.c:1235) + // Call from_param first to convert the value // converter = PyTuple_GET_ITEM(argtypes, i); // v = PyObject_CallOneArg(converter, arg); let from_param = self @@ -264,11 +400,10 @@ impl ArgumentType for PyTypeRef { return Ok(FfiArgValue::Pointer(0)); } - // For pointer types (POINTER(T)), we need to pass the ADDRESS of the value's buffer + // For pointer types (POINTER(T)), we need to pass the pointer VALUE stored in buffer if self.fast_issubclass(PyCPointer::static_type()) { - if let Some(cdata) = converted.downcast_ref::() { - let addr = cdata.buffer.read().as_ptr() as usize; - return Ok(FfiArgValue::Pointer(addr)); + if let Some(pointer) = converted.downcast_ref::() { + return Ok(FfiArgValue::Pointer(pointer.get_ptr_value())); } return convert_to_pointer(&converted, vm); } @@ -305,12 +440,6 @@ impl ArgumentType for PyTypeRef { trait ReturnType { fn to_ffi_type(&self, vm: &VirtualMachine) -> Option; - #[allow(clippy::wrong_self_convention)] - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult>; } impl ReturnType for PyTypeRef { @@ -343,130 +472,56 @@ impl ReturnType for PyTypeRef { // Fallback to class name get_ffi_type(self.name().to_string().as_str()) } - - fn from_ffi_type( - &self, - value: *mut ffi::c_void, - vm: &VirtualMachine, - ) -> PyResult> { - // Get the type code from _type_ attribute (use get_attr to traverse MRO) - let type_code = self - .as_object() - .get_attr(vm.ctx.intern_str("_type_"), vm) - .ok() - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); - - let result = match type_code.as_deref() { - Some("b") => vm - .ctx - .new_int(unsafe { *(value as *const i8) } as i32) - .into(), - Some("B") => vm - .ctx - .new_int(unsafe { *(value as *const u8) } as i32) - .into(), - Some("c") => vm - .ctx - .new_bytes(vec![unsafe { *(value as *const u8) }]) - .into(), - Some("h") => vm - .ctx - .new_int(unsafe { *(value as *const i16) } as i32) - .into(), - Some("H") => vm - .ctx - .new_int(unsafe { *(value as *const u16) } as i32) - .into(), - Some("i") => vm.ctx.new_int(unsafe { *(value as *const i32) }).into(), - Some("I") => vm.ctx.new_int(unsafe { *(value as *const u32) }).into(), - Some("l") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_long) }) - .into(), - Some("L") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulong) }) - .into(), - Some("q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_longlong) }) - .into(), - Some("Q") => vm - .ctx - .new_int(unsafe { *(value as *const libc::c_ulonglong) }) - .into(), - Some("f") => vm - .ctx - .new_float(unsafe { *(value as *const f32) } as f64) - .into(), - Some("d") => vm.ctx.new_float(unsafe { *(value as *const f64) }).into(), - Some("P") | Some("z") | Some("Z") => { - vm.ctx.new_int(unsafe { *(value as *const usize) }).into() - } - Some("?") => vm - .ctx - .new_bool(unsafe { *(value as *const u8) } != 0) - .into(), - None => { - // No _type_ attribute - check for Structure/Array types - // GetResult: PyCData_FromBaseObj creates instance from memory - if let Some(stg_info) = self.stg_info_opt() { - let size = stg_info.size; - // Create instance of the ctypes type - let instance = self.as_object().call((), vm)?; - - // Copy return value memory into instance buffer - // Use a block to properly scope the borrow - { - let src = unsafe { std::slice::from_raw_parts(value as *const u8, size) }; - if let Some(cdata) = instance.downcast_ref::() { - let mut buffer = cdata.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(structure) = instance.downcast_ref::() { - let mut buffer = structure.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } else if let Some(array) = instance.downcast_ref::() { - let mut buffer = array.0.buffer.write(); - if buffer.len() >= size { - buffer.to_mut()[..size].copy_from_slice(src); - } - } - } - return Ok(Some(instance)); - } - // Not a ctypes type - call type with int result - return self - .as_object() - .call((unsafe { *(value as *const i32) },), vm) - .map(Some); - } - _ => return Err(vm.new_type_error("Unsupported return type")), - }; - Ok(Some(result)) - } } impl ReturnType for PyNone { fn to_ffi_type(&self, _vm: &VirtualMachine) -> Option { get_ffi_type("void") } +} - fn from_ffi_type( - &self, - _value: *mut ffi::c_void, - _vm: &VirtualMachine, - ) -> PyResult> { - Ok(None) +// PyCFuncPtrType - Metaclass for function pointer types +// PyCFuncPtrType_init + +#[pyclass(name = "PyCFuncPtrType", base = PyType, module = "_ctypes")] +#[derive(Debug)] +#[repr(transparent)] +pub(super) struct PyCFuncPtrType(PyType); + +impl Initializer for PyCFuncPtrType { + type Args = FuncArgs; + + fn init(zelf: PyRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult<()> { + let obj: PyObjectRef = zelf.clone().into(); + let new_type: PyTypeRef = obj + .downcast() + .map_err(|_| vm.new_type_error("expected type"))?; + + new_type.check_not_initialized(vm)?; + + let ptr_size = std::mem::size_of::(); + let mut stg_info = StgInfo::new(ptr_size, ptr_size); + stg_info.format = Some("X{}".to_string()); + stg_info.length = 1; + stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; + stg_info.paramfunc = ParamFunc::Pointer; // CFuncPtr is passed as a pointer + + let _ = new_type.init_type_data(stg_info); + Ok(()) } } +#[pyclass(flags(IMMUTABLETYPE), with(Initializer))] +impl PyCFuncPtrType {} + /// PyCFuncPtr - Function pointer instance /// Saved in _base.buffer -#[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] +#[pyclass( + module = "_ctypes", + name = "CFuncPtr", + base = PyCData, + metaclass = "PyCFuncPtrType" +)] #[repr(C)] pub(super) struct PyCFuncPtr { pub _base: PyCData, @@ -892,7 +947,13 @@ impl Constructor for PyCFuncPtr { .map_err(|err| err.to_string()) .map_err(|err| vm.new_attribute_error(err))? }; - *pointer as usize + let addr = *pointer as usize; + // dlsym can return NULL for symbols that resolve to NULL (e.g., GNU IFUNC) + // Treat NULL addresses as errors + if addr == 0 { + return Err(vm.new_attribute_error(format!("function '{}' not found", name))); + } + addr } else { 0 }; @@ -921,12 +982,17 @@ impl Constructor for PyCFuncPtr { // Get argument types and result type from the class let class_argtypes = cls.get_attr(vm.ctx.intern_str("_argtypes_")); let class_restype = cls.get_attr(vm.ctx.intern_str("_restype_")); + let class_flags = cls + .get_attr(vm.ctx.intern_str("_flags_")) + .and_then(|f| f.try_to_value::(vm).ok()) + .unwrap_or(0); // Create the thunk (C-callable wrapper for the Python function) let thunk = PyCThunk::new( first_arg.clone(), class_argtypes.clone(), class_restype.clone(), + class_flags, vm, )?; let code_ptr = thunk.code_ptr(); @@ -1060,13 +1126,16 @@ fn extract_call_info(zelf: &Py, vm: &VirtualMachine) -> PyResult().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())) - .is_some_and(|tc| matches!(tc.as_str(), "P" | "z" | "Z")); + .and_then(|t| { + t.stg_info_opt() + .map(|info| info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER)) + }) + .unwrap_or(false); Ok(CallInfo { explicit_arg_types, @@ -1178,13 +1247,18 @@ fn resolve_com_method( Ok((Some(CodePtr(fptr as *mut _)), true)) } -/// Prepared arguments for FFI call -struct PreparedArgs { - ffi_arg_types: Vec, - ffi_values: Vec, - out_buffers: Vec<(usize, PyObjectRef)>, +/// Single argument for FFI call +// struct argument +struct Argument { + ffi_type: Type, + value: FfiArgValue, + #[allow(dead_code)] + keep: Option, // Object to keep alive during call } +/// Out buffers for paramflags OUT parameters +type OutBuffers = Vec<(usize, PyObjectRef)>; + /// Get buffer address from a ctypes object fn get_buffer_addr(obj: &PyObjectRef) -> Option { obj.downcast_ref::() @@ -1213,18 +1287,16 @@ fn create_out_buffer(arg_type: &PyTypeRef, vm: &VirtualMachine) -> PyResult PyResult { - let results: Vec<(Type, FfiArgValue)> = args +fn build_callargs_no_argtypes( + args: &FuncArgs, + vm: &VirtualMachine, +) -> PyResult<(Vec, OutBuffers)> { + let arguments: Vec = args .args .iter() .map(|arg| conv_param(arg, vm)) .collect::>>()?; - let (ffi_arg_types, ffi_values) = results.into_iter().unzip(); - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } /// Build callargs for regular function with argtypes (no paramflags) @@ -1232,12 +1304,8 @@ fn build_callargs_simple( args: &FuncArgs, arg_types: &[PyTypeRef], vm: &VirtualMachine, -) -> PyResult { - let ffi_arg_types = arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::>>()?; - let ffi_values = args +) -> PyResult<(Vec, OutBuffers)> { + let arguments: Vec = args .args .iter() .enumerate() @@ -1245,14 +1313,16 @@ fn build_callargs_simple( let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - arg_type.convert_object(arg.clone(), vm) + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + Ok(Argument { + ffi_type, + keep: None, + value, + }) }) - .collect::, _>>()?; - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers: Vec::new(), - }) + .collect::>>()?; + Ok((arguments, Vec::new())) } /// Build callargs with paramflags (handles IN/OUT parameters) @@ -1262,27 +1332,21 @@ fn build_callargs_with_paramflags( paramflags: &ParsedParamFlags, skip_first_arg: bool, // true for COM methods vm: &VirtualMachine, -) -> PyResult { - let mut ffi_arg_types = Vec::new(); - let mut ffi_values = Vec::new(); +) -> PyResult<(Vec, OutBuffers)> { + let mut arguments = Vec::new(); let mut out_buffers = Vec::new(); // For COM methods, first arg is self (pointer) let mut caller_arg_idx = if skip_first_arg { - ffi_arg_types.push(Type::pointer()); if !args.args.is_empty() { - ffi_values.push(conv_param(&args.args[0], vm)?.1); + let arg = conv_param(&args.args[0], vm)?; + arguments.push(arg); } 1usize } else { 0usize }; - // Add FFI types for all argtypes - for arg_type in arg_types { - ffi_arg_types.push(ArgumentType::to_ffi_type(arg_type, vm)?); - } - // Process parameters based on paramflags for (param_idx, (direction, _name, default)) in paramflags.iter().enumerate() { let arg_type = arg_types @@ -1292,13 +1356,19 @@ fn build_callargs_with_paramflags( let is_out = (*direction & 2) != 0; // OUT flag let is_in = (*direction & 1) != 0 || *direction == 0; // IN flag or default + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + if is_out && !is_in { // Pure OUT parameter: create buffer, don't consume caller arg let buffer = create_out_buffer(arg_type, vm)?; let addr = get_buffer_addr(&buffer).ok_or_else(|| { vm.new_type_error("Cannot create OUT buffer for this type".to_string()) })?; - ffi_values.push(FfiArgValue::Pointer(addr)); + arguments.push(Argument { + ffi_type, + keep: None, + value: FfiArgValue::Pointer(addr), + }); out_buffers.push((param_idx, buffer)); } else { // IN or IN|OUT: get from caller args or default @@ -1315,15 +1385,16 @@ fn build_callargs_with_paramflags( // IN|OUT: track for return out_buffers.push((param_idx, arg.clone())); } - ffi_values.push(arg_type.convert_object(arg, vm)?); + let value = arg_type.convert_object(arg, vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } } - Ok(PreparedArgs { - ffi_arg_types, - ffi_values, - out_buffers, - }) + Ok((arguments, out_buffers)) } /// Build call arguments (main dispatcher) @@ -1333,7 +1404,7 @@ fn build_callargs( paramflags: Option<&ParsedParamFlags>, is_com_method: bool, vm: &VirtualMachine, -) -> PyResult { +) -> PyResult<(Vec, OutBuffers)> { let Some(ref arg_types) = call_info.explicit_arg_types else { // No argtypes: use ConvParam return build_callargs_no_argtypes(args, vm); @@ -1344,28 +1415,23 @@ fn build_callargs( build_callargs_with_paramflags(args, arg_types, pflags, is_com_method, vm) } else if is_com_method { // COM method without paramflags - let mut ffi_types = vec![Type::pointer()]; - ffi_types.extend( - arg_types - .iter() - .map(|t| ArgumentType::to_ffi_type(t, vm)) - .collect::>>()?, - ); - let mut ffi_vals = Vec::new(); + let mut arguments = Vec::new(); if !args.args.is_empty() { - ffi_vals.push(conv_param(&args.args[0], vm)?.1); + arguments.push(conv_param(&args.args[0], vm)?); } for (n, arg) in args.args.iter().skip(1).enumerate() { let arg_type = arg_types .get(n) .ok_or_else(|| vm.new_type_error("argument amount mismatch"))?; - ffi_vals.push(arg_type.convert_object(arg.clone(), vm)?); + let ffi_type = ArgumentType::to_ffi_type(arg_type, vm)?; + let value = arg_type.convert_object(arg.clone(), vm)?; + arguments.push(Argument { + ffi_type, + keep: None, + value, + }); } - Ok(PreparedArgs { - ffi_arg_types: ffi_types, - ffi_values: ffi_vals, - out_buffers: Vec::new(), - }) + Ok((arguments, Vec::new())) } else { // Regular function build_callargs_simple(args, arg_types, vm) @@ -1380,12 +1446,10 @@ enum RawResult { } /// Execute FFI call -fn ctypes_callproc(code_ptr: CodePtr, prepared: &PreparedArgs, call_info: &CallInfo) -> RawResult { - let cif = Cif::new( - prepared.ffi_arg_types.clone(), - call_info.ffi_return_type.clone(), - ); - let ffi_args: Vec = prepared.ffi_values.iter().map(|v| v.as_arg()).collect(); +fn ctypes_callproc(code_ptr: CodePtr, arguments: &[Argument], call_info: &CallInfo) -> RawResult { + let ffi_arg_types: Vec = arguments.iter().map(|a| a.ffi_type.clone()).collect(); + let cif = Cif::new(ffi_arg_types, call_info.ffi_return_type.clone()); + let ffi_args: Vec = arguments.iter().map(|a| a.value.as_arg()).collect(); if call_info.restype_is_none { unsafe { cif.call::<()>(code_ptr, &ffi_args) }; @@ -1438,73 +1502,118 @@ fn check_hresult(hresult: i32, zelf: &Py, vm: &VirtualMachine) -> Py } /// Convert raw FFI result to Python object +// = GetResult fn convert_raw_result( raw_result: &mut RawResult, call_info: &CallInfo, vm: &VirtualMachine, ) -> Option { - match raw_result { - RawResult::Void => None, + // Get result as bytes for type conversion + let (result_bytes, result_size) = match raw_result { + RawResult::Void => return None, RawResult::Pointer(ptr) => { - // Get type code from restype to determine conversion method - let type_code = call_info - .restype_obj - .as_ref() - .and_then(|t| t.clone().downcast::().ok()) - .and_then(|t| t.as_object().get_attr(vm.ctx.intern_str("_type_"), vm).ok()) - .and_then(|t| t.downcast_ref::().map(|s| s.to_string())); - - match type_code.as_deref() { - Some("z") => { - // c_char_p: NULL -> None, otherwise read C string -> bytes - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let cstr = unsafe { std::ffi::CStr::from_ptr(*ptr as _) }; - Some(vm.ctx.new_bytes(cstr.to_bytes().to_vec()).into()) - } - } - Some("Z") => { - // c_wchar_p: NULL -> None, otherwise read wide string -> str - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - let wstr_ptr = *ptr as *const libc::wchar_t; - let mut len = 0; - unsafe { - while *wstr_ptr.add(len) != 0 { - len += 1; - } - } - let slice = unsafe { std::slice::from_raw_parts(wstr_ptr, len) }; - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - Some(vm.ctx.new_str(s).into()) - } - } - _ => { - // c_void_p ("P") and other pointer types: NULL -> None, otherwise int - if *ptr == 0 { - Some(vm.ctx.none()) - } else { - Some(vm.ctx.new_int(*ptr).into()) - } - } - } + let bytes = ptr.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::()) } - RawResult::Value(val) => call_info - .restype_obj - .as_ref() - .and_then(|f| f.clone().downcast::().ok()) - .map(|f| { - f.from_ffi_type(val as *mut _ as *mut c_void, vm) - .ok() - .flatten() - }) - .unwrap_or_else(|| Some(vm.ctx.new_int(*val as usize).as_object().to_pyobject(vm))), + RawResult::Value(val) => { + let bytes = val.to_ne_bytes(); + (bytes.to_vec(), std::mem::size_of::()) + } + }; + + // 1. No restype → return as int + let restype = match &call_info.restype_obj { + None => { + // Default: return as int + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return Some(vm.ctx.new_int(val).into()); + } + Some(r) => r, + }; + + // 2. restype is None → return None + if restype.is(&vm.ctx.none()) { + return None; } + + // 3. Get restype as PyType + let restype_type = match restype.clone().downcast::() { + Ok(t) => t, + Err(_) => { + // Not a type, call it with int result + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype.call((val,), vm).ok(); + } + }; + + // 4. Get StgInfo + let stg_info = restype_type.stg_info_opt(); + + // No StgInfo → call restype with int + if stg_info.is_none() { + let val = match raw_result { + RawResult::Pointer(p) => *p as isize, + RawResult::Value(v) => *v as isize, + RawResult::Void => return None, + }; + return restype_type.as_object().call((val,), vm).ok(); + } + + let info = stg_info.unwrap(); + + // 5. Simple type with getfunc → use bytes_to_pyobject (info->getfunc) + // is_simple_instance returns TRUE for c_int, c_void_p, etc. + if super::base::is_simple_instance(&restype_type) { + return super::base::bytes_to_pyobject(&restype_type, &result_bytes, vm).ok(); + } + + // 6. Complex type → create ctypes instance (PyCData_FromBaseObj) + // This handles POINTER(T), Structure, Array, etc. + + // Special handling for POINTER(T) types - set pointer value directly + if info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER) + && info.proto.is_some() + && let RawResult::Pointer(ptr) = raw_result + && let Ok(instance) = restype_type.as_object().call((), vm) + { + if let Some(pointer) = instance.downcast_ref::() { + pointer.set_ptr_value(*ptr); + } + return Some(instance); + } + + // Create instance and copy result data + pycdata_from_ffi_result(&restype_type, &result_bytes, result_size, vm).ok() +} + +/// Create a ctypes instance from FFI result (PyCData_FromBaseObj equivalent) +fn pycdata_from_ffi_result( + typ: &PyTypeRef, + result_bytes: &[u8], + size: usize, + vm: &VirtualMachine, +) -> PyResult { + // Create instance + let instance = PyType::call(typ, ().into(), vm)?; + + // Copy result data into instance buffer + if let Some(cdata) = instance.downcast_ref::() { + let mut buffer = cdata.buffer.write(); + let copy_size = size.min(buffer.len()).min(result_bytes.len()); + if copy_size > 0 { + buffer.to_mut()[..copy_size].copy_from_slice(&result_bytes[..copy_size]); + } + } + + Ok(instance) } /// Extract values from OUT buffers @@ -1522,7 +1631,7 @@ fn extract_out_values( fn build_result( mut raw_result: RawResult, call_info: &CallInfo, - prepared: PreparedArgs, + out_buffers: OutBuffers, zelf: &Py, args: &FuncArgs, vm: &VirtualMachine, @@ -1552,11 +1661,11 @@ fn build_result( } // Handle OUT parameter return values - if prepared.out_buffers.is_empty() { + if out_buffers.is_empty() { return result.map(Ok).unwrap_or_else(|| Ok(vm.ctx.none())); } - let out_values = extract_out_values(prepared.out_buffers, vm); + let out_values = extract_out_values(out_buffers, vm); Ok(match <[PyObjectRef; 1]>::try_from(out_values) { Ok([single]) => single, Err(v) => PyTuple::new_ref(v, &vm.ctx).into(), @@ -1584,23 +1693,43 @@ impl Callable for PyCFuncPtr { let paramflags = parse_paramflags(zelf, vm)?; // 5. Build call arguments - let prepared = build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; + let (arguments, out_buffers) = + build_callargs(&args, &call_info, paramflags.as_ref(), is_com_method, vm)?; // 6. Get code pointer let code_ptr = match func_ptr.or_else(|| zelf.get_code_ptr()) { Some(cp) => cp, None => { debug_assert!(false, "NULL function pointer"); - // In release mode, this will crash like CPython + // In release mode, this will crash CodePtr(std::ptr::null_mut()) } }; - // 7. Call the function - let raw_result = ctypes_callproc(code_ptr, &prepared, &call_info); + // 7. Get flags to check for use_last_error/use_errno + let flags = PyCFuncPtr::_flags_(zelf, vm); - // 8. Build result - build_result(raw_result, &call_info, prepared, zelf, &args, vm) + // 8. Call the function (with use_last_error/use_errno handling) + #[cfg(not(windows))] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0 { + swap_errno(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + #[cfg(windows)] + let raw_result = { + if flags & super::base::StgInfoFlags::FUNCFLAG_USE_LASTERROR.bits() != 0 { + save_and_restore_last_error(|| ctypes_callproc(code_ptr, &arguments, &call_info)) + } else { + ctypes_callproc(code_ptr, &arguments, &call_info) + } + }; + + // 9. Build result + build_result(raw_result, &call_info, out_buffers, zelf, &args, vm) } } @@ -1614,7 +1743,36 @@ impl Representable for PyCFuncPtr { } } -#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable))] +// PyCData_NewGetBuffer +impl AsBuffer for PyCFuncPtr { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + // CFuncPtr types may not have StgInfo if PyCFuncPtrType metaclass is not used + // Use default values for function pointers: format="X{}", size=sizeof(pointer) + let (format, itemsize) = if let Some(stg_info) = zelf.class().stg_info_opt() { + ( + stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("X{}")), + stg_info.size, + ) + } else { + (Cow::Borrowed("X{}"), std::mem::size_of::()) + }; + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} + +#[pyclass(flags(BASETYPE), with(Callable, Constructor, Representable, AsBuffer))] impl PyCFuncPtr { // restype getter/setter #[pygetset] @@ -1685,7 +1843,6 @@ impl PyCFuncPtr { } // Fallback to StgInfo for native types - use super::base::StgInfoFlags; zelf.class() .stg_info_opt() .map(|stg| stg.flags.bits()) @@ -1708,7 +1865,9 @@ struct ThunkUserData { /// Argument types for conversion arg_types: Vec, /// Result type for conversion (None means void) - res_type: Option, + pub res_type: Option, + /// Function flags (FUNCFLAG_USE_ERRNO, etc.) + pub flags: u32, } /// Check if ty is a subclass of a simple type (like MyInt(c_int)). @@ -1758,11 +1917,23 @@ fn ffi_to_python(ty: &Py, ptr: *const c_void, vm: &VirtualMachine) -> Py len += 1; } let slice = std::slice::from_raw_parts(wstr_ptr, len); - let s: String = slice - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - vm.ctx.new_str(s).into() + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec = slice.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + vm.ctx.new_str(wtf8).into() + } + #[cfg(not(windows))] + { + let s: String = slice + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + vm.ctx.new_str(s).into() + } } } Some("P") => vm.ctx.new_int(*(ptr as *const usize)).into(), @@ -1865,6 +2036,16 @@ unsafe extern "C" fn thunk_callback( userdata: &ThunkUserData, ) { with_current_vm(|vm| { + // Swap errno before call if FUNCFLAG_USE_ERRNO is set + let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0; + let saved_errno = if use_errno { + let current = rustpython_common::os::get_errno(); + // TODO: swap with ctypes stored errno (thread-local) + Some(current) + } else { + None + }; + let py_args: Vec = userdata .arg_types .iter() @@ -1877,6 +2058,15 @@ unsafe extern "C" fn thunk_callback( let py_result = userdata.callable.call(py_args, vm); + // Swap errno back after call + if use_errno { + let _current = rustpython_common::os::get_errno(); + // TODO: store current errno to ctypes storage + if let Some(saved) = saved_errno { + rustpython_common::os::set_errno(saved); + } + } + // Call unraisable hook if exception occurred if let Err(exc) = &py_result { let repr = userdata @@ -1935,6 +2125,7 @@ impl PyCThunk { callable: PyObjectRef, arg_types: Option, res_type: Option, + flags: u32, vm: &VirtualMachine, ) -> PyResult { let arg_type_vec: Vec = match arg_types { @@ -1979,6 +2170,7 @@ impl PyCThunk { callable: callable.clone(), arg_types: arg_type_vec, res_type: res_type_ref, + flags, }); let userdata_ptr = Box::into_raw(userdata); let userdata_ref: &'static ThunkUserData = unsafe { &*userdata_ptr }; diff --git a/crates/vm/src/stdlib/ctypes/library.rs b/crates/vm/src/stdlib/ctypes/library.rs index ec8ca91af..7512ce29d 100644 --- a/crates/vm/src/stdlib/ctypes/library.rs +++ b/crates/vm/src/stdlib/ctypes/library.rs @@ -2,12 +2,14 @@ use crate::VirtualMachine; use libloading::Library; use rustpython_common::lock::{PyMutex, PyRwLock}; use std::collections::HashMap; -use std::ffi::{OsStr, c_void}; +use std::ffi::OsStr; use std::fmt; -use std::ptr::null; -pub(super) struct SharedLibrary { - pub(super) lib: PyMutex>, +#[cfg(unix)] +use libloading::os::unix::Library as UnixLibrary; + +pub struct SharedLibrary { + pub(crate) lib: PyMutex>, } impl fmt::Debug for SharedLibrary { @@ -17,18 +19,44 @@ impl fmt::Debug for SharedLibrary { } impl SharedLibrary { - fn new(name: impl AsRef) -> Result { + #[cfg(windows)] + pub fn new(name: impl AsRef) -> Result { Ok(SharedLibrary { lib: PyMutex::new(unsafe { Some(Library::new(name.as_ref())?) }), }) } - fn get_pointer(&self) -> usize { + #[cfg(unix)] + pub fn new_with_mode( + name: impl AsRef, + mode: i32, + ) -> Result { + Ok(SharedLibrary { + lib: PyMutex::new(Some(unsafe { + UnixLibrary::open(Some(name.as_ref()), mode)?.into() + })), + }) + } + + /// Create a SharedLibrary from a raw dlopen handle (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn from_raw_handle(handle: *mut libc::c_void) -> SharedLibrary { + SharedLibrary { + lib: PyMutex::new(Some(unsafe { UnixLibrary::from_raw(handle).into() })), + } + } + + /// Get the underlying OS handle (HMODULE on Windows, dlopen handle on Unix) + pub fn get_pointer(&self) -> usize { let lib_lock = self.lib.lock(); if let Some(l) = &*lib_lock { - l as *const Library as usize + // libloading::Library internally stores the OS handle directly + // On Windows: HMODULE (*mut c_void) + // On Unix: *mut c_void from dlopen + // We use transmute_copy to read the handle without consuming the Library + unsafe { std::mem::transmute_copy::(l) } } else { - null::() as usize + 0 } } @@ -36,16 +64,6 @@ impl SharedLibrary { let lib_lock = self.lib.lock(); lib_lock.is_none() } - - fn close(&self) { - *self.lib.lock() = None; - } -} - -impl Drop for SharedLibrary { - fn drop(&mut self) { - self.close(); - } } pub(super) struct ExternalLibs { @@ -63,6 +81,7 @@ impl ExternalLibs { self.libraries.get(&key) } + #[cfg(windows)] pub fn get_or_insert_lib( &mut self, library_path: impl AsRef, @@ -71,20 +90,53 @@ impl ExternalLibs { let new_lib = SharedLibrary::new(library_path)?; let key = new_lib.get_pointer(); - match self.libraries.get(&key) { - Some(l) => { - if l.is_closed() { - self.libraries.insert(key, new_lib); - } - } - _ => { - self.libraries.insert(key, new_lib); - } - }; + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + if should_use_cached { + // new_lib will be dropped, calling FreeLibrary (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + + self.libraries.insert(key, new_lib); Ok((key, self.libraries.get(&key).expect("just inserted"))) } + #[cfg(unix)] + pub fn get_or_insert_lib_with_mode( + &mut self, + library_path: impl AsRef, + mode: i32, + _vm: &VirtualMachine, + ) -> Result<(usize, &SharedLibrary), libloading::Error> { + let new_lib = SharedLibrary::new_with_mode(library_path, mode)?; + let key = new_lib.get_pointer(); + + // Check if library already exists and is not closed + let should_use_cached = self.libraries.get(&key).is_some_and(|l| !l.is_closed()); + + if should_use_cached { + // new_lib will be dropped, calling dlclose (decrements refcount) + // But library stays loaded because cached version maintains refcount + drop(new_lib); + return Ok((key, self.libraries.get(&key).expect("just checked"))); + } + + self.libraries.insert(key, new_lib); + Ok((key, self.libraries.get(&key).expect("just inserted"))) + } + + /// Insert a raw dlopen handle into the cache (for pythonapi / dlopen(NULL)) + #[cfg(unix)] + pub fn insert_raw_handle(&mut self, handle: *mut libc::c_void) -> usize { + let shared_lib = SharedLibrary::from_raw_handle(handle); + let key = handle as usize; + self.libraries.insert(key, shared_lib); + key + } + pub fn drop_lib(&mut self, key: usize) { self.libraries.remove(&key); } diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 3ee39af3a..ae97b741b 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -1,6 +1,7 @@ +use super::base::CDATA_BUFFER_METHODS; use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags}; -use crate::protocol::PyNumberMethods; -use crate::types::{AsNumber, Constructor, Initializer}; +use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; +use crate::types::{AsBuffer, AsNumber, Constructor, Initializer}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef}, @@ -8,6 +9,7 @@ use crate::{ function::{FuncArgs, OptionalArg}, }; use num_traits::ToPrimitive; +use std::borrow::Cow; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] #[derive(Debug)] @@ -42,11 +44,19 @@ impl Initializer for PyCPointerType { stg_info.length = 1; stg_info.flags |= StgInfoFlags::TYPEFLAG_ISPOINTER; - // Set format string: "&" - if let Some(ref proto) = stg_info.proto { - let item_info = proto.stg_info_opt().expect("proto has StgInfo"); + // Set format string: "&" or "&(shape)" for arrays + if let Some(ref proto) = stg_info.proto + && let Some(item_info) = proto.stg_info_opt() + { let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } let _ = new_type.init_type_data(stg_info); @@ -69,6 +79,15 @@ impl PyCPointerType { return Ok(value); } + // 1.5 CArgObject (from byref()) - check if underlying obj is instance of _type_ + if let Some(carg) = value.downcast_ref::() + && let Ok(type_attr) = cls.as_object().get_attr("_type_", vm) + && let Ok(type_ref) = type_attr.downcast::() + && carg.obj.is_instance(type_ref.as_object(), vm)? + { + return Ok(value); + } + // 2. If already an instance of the requested type, return it if value.is_instance(cls.as_object(), vm)? { return Ok(value); @@ -149,10 +168,17 @@ impl PyCPointerType { if let Some(mut stg_info) = zelf.get_type_data_mut::() { stg_info.proto = Some(typ_type.clone()); - // Update format string: "&" + // Update format string: "&" or "&(shape)" for arrays let item_info = typ_type.stg_info_opt().expect("proto has StgInfo"); let current_format = item_info.format.as_deref().unwrap_or("B"); - stg_info.format = Some(format!("&{}", current_format)); + // Include shape for array types in the pointer format + let shape_str = if !item_info.shape.is_empty() { + let dims: Vec = item_info.shape.iter().map(|d| d.to_string()).collect(); + format!("({})", dims.join(",")) + } else { + String::new() + }; + stg_info.format = Some(format!("&{}{}", shape_str, current_format)); } // 4. Set _type_ attribute on the pointer type @@ -233,7 +259,10 @@ impl Initializer for PyCPointer { } } -#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor, Initializer))] +#[pyclass( + flags(BASETYPE, IMMUTABLETYPE), + with(Constructor, Initializer, AsBuffer) +)] impl PyCPointer { /// Get the pointer value stored in buffer as usize pub fn get_ptr_value(&self) -> usize { @@ -605,20 +634,47 @@ impl PyCPointer { unsafe { let ptr = addr as *const u8; match type_code { + // Single-byte types don't need read_unaligned Some("c") => Ok(vm.ctx.new_bytes(vec![*ptr]).into()), - Some("b") => Ok(vm.ctx.new_int(*(ptr as *const i8) as i32).into()), + Some("b") => Ok(vm.ctx.new_int(*ptr as i8 as i32).into()), Some("B") => Ok(vm.ctx.new_int(*ptr as i32).into()), - Some("h") => Ok(vm.ctx.new_int(*(ptr as *const i16) as i32).into()), - Some("H") => Ok(vm.ctx.new_int(*(ptr as *const u16) as i32).into()), - Some("i") | Some("l") => Ok(vm.ctx.new_int(*(ptr as *const i32)).into()), - Some("I") | Some("L") => Ok(vm.ctx.new_int(*(ptr as *const u32)).into()), - Some("q") => Ok(vm.ctx.new_int(*(ptr as *const i64)).into()), - Some("Q") => Ok(vm.ctx.new_int(*(ptr as *const u64)).into()), - Some("f") => Ok(vm.ctx.new_float(*(ptr as *const f32) as f64).into()), - Some("d") | Some("g") => Ok(vm.ctx.new_float(*(ptr as *const f64)).into()), - Some("P") | Some("z") | Some("Z") => { - Ok(vm.ctx.new_int(*(ptr as *const usize)).into()) - } + // Multi-byte types need read_unaligned for safety on strict-alignment architectures + Some("h") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i16) as i32) + .into()), + Some("H") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u16) as i32) + .into()), + Some("i") | Some("l") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i32)) + .into()), + Some("I") | Some("L") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u32)) + .into()), + Some("q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const i64)) + .into()), + Some("Q") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const u64)) + .into()), + Some("f") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f32) as f64) + .into()), + Some("d") | Some("g") => Ok(vm + .ctx + .new_float(std::ptr::read_unaligned(ptr as *const f64)) + .into()), + Some("P") | Some("z") | Some("Z") => Ok(vm + .ctx + .new_int(std::ptr::read_unaligned(ptr as *const usize)) + .into()), _ => { // Default: read as bytes let bytes = std::slice::from_raw_parts(ptr, size).to_vec(); @@ -652,27 +708,37 @@ impl PyCPointer { "bytes/string or integer address expected".to_owned(), )); }; - *(ptr as *mut usize) = ptr_val; + std::ptr::write_unaligned(ptr as *mut usize, ptr_val); return Ok(()); } _ => {} } // Try to get value as integer + // Use write_unaligned for safety on strict-alignment architectures if let Ok(int_val) = value.try_int(vm) { let i = int_val.as_bigint(); match size { 1 => { - *ptr = i.to_u8().unwrap_or(0); + *ptr = i.to_u8().expect("int too large"); } 2 => { - *(ptr as *mut i16) = i.to_i16().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i16, + i.to_i16().expect("int too large"), + ); } 4 => { - *(ptr as *mut i32) = i.to_i32().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i32, + i.to_i32().expect("int too large"), + ); } 8 => { - *(ptr as *mut i64) = i.to_i64().unwrap_or(0); + std::ptr::write_unaligned( + ptr as *mut i64, + i.to_i64().expect("int too large"), + ); } _ => { let bytes = i.to_signed_bytes_le(); @@ -688,10 +754,10 @@ impl PyCPointer { let f = float_val.to_f64(); match size { 4 => { - *(ptr as *mut f32) = f as f32; + std::ptr::write_unaligned(ptr as *mut f32, f as f32); } 8 => { - *(ptr as *mut f64) = f; + std::ptr::write_unaligned(ptr as *mut f64, f); } _ => {} } @@ -712,3 +778,28 @@ impl PyCPointer { } } } + +impl AsBuffer for PyCPointer { + fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCPointer type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("&B")); + let itemsize = stg_info.size; + // Pointer types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); + Ok(buf) + } +} diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 1c0ec250d..803b38d6e 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -14,12 +14,47 @@ use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods}; use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable}; use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; use num_traits::ToPrimitive; +use std::borrow::Cow; use std::fmt::Debug; /// Valid type codes for ctypes simple types // spell-checker: disable-next-line pub(super) const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQPXOv?g"; +/// Convert ctypes type code to PEP 3118 format code. +/// Some ctypes codes need to be mapped to standard-size codes based on platform. +/// _ctypes_alloc_format_string_for_type +fn ctypes_code_to_pep3118(code: char) -> char { + match code { + // c_int: map based on sizeof(int) + 'i' if std::mem::size_of::() == 2 => 'h', + 'i' if std::mem::size_of::() == 4 => 'i', + 'i' if std::mem::size_of::() == 8 => 'q', + 'I' if std::mem::size_of::() == 2 => 'H', + 'I' if std::mem::size_of::() == 4 => 'I', + 'I' if std::mem::size_of::() == 8 => 'Q', + // c_long: map based on sizeof(long) + 'l' if std::mem::size_of::() == 4 => 'l', + 'l' if std::mem::size_of::() == 8 => 'q', + 'L' if std::mem::size_of::() == 4 => 'L', + 'L' if std::mem::size_of::() == 8 => 'Q', + // c_bool: map based on sizeof(bool) - typically 1 byte on all platforms + '?' if std::mem::size_of::() == 1 => '?', + '?' if std::mem::size_of::() == 2 => 'H', + '?' if std::mem::size_of::() == 4 => 'L', + '?' if std::mem::size_of::() == 8 => 'Q', + // Default: use the same code + _ => code, + } +} + +/// _ctypes_alloc_format_string_for_type +fn alloc_format_string_for_type(code: char, big_endian: bool) -> String { + let prefix = if big_endian { ">" } else { "<" }; + let pep_code = ctypes_code_to_pep3118(code); + format!("{}{}", prefix, pep_code) +} + /// Create a new simple type instance from a class fn new_simple_type( cls: Either<&PyObject, &Py>, @@ -165,6 +200,20 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul } // O_set: py_object accepts any Python object "O" => Ok(value.to_owned()), + // X_set: BSTR - same as Z (c_wchar_p), accepts None, int, or str + "X" => { + if value.is(&vm.ctx.none) + || value.downcast_ref_if_exact::(vm).is_some() + || value.downcast_ref_if_exact::(vm).is_some() + { + Ok(value.to_owned()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name() + ))) + } + } _ => { // "P" if value.downcast_ref_if_exact::(vm).is_some() @@ -313,7 +362,7 @@ impl PyCSimpleType { .to_pyobject(vm)); } // 2. Array/Pointer with c_wchar element type - if is_cwchar_array_or_pointer(&value, vm) { + if is_cwchar_array_or_pointer(&value, vm)? { return Ok(value); } // 3. CArgObject (byref(c_wchar(...))) @@ -521,13 +570,10 @@ impl Initializer for PyCSimpleType { let mut stg_info = StgInfo::new(size, align); // Set format for PEP 3118 buffer protocol - // Format is endian prefix + type code (e.g., "" - }; - stg_info.format = Some(format!("{}{}", endian_prefix, type_str)); + stg_info.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + cfg!(target_endian = "big"), + )); stg_info.paramfunc = super::base::ParamFunc::Simple; // Set TYPEFLAG_ISPOINTER for pointer types: z (c_char_p), Z (c_wchar_p), @@ -619,13 +665,14 @@ fn create_swapped_types( swapped_type.set_attr("_swappedbytes_", vm.ctx.none(), vm)?; // Update swapped type's StgInfo format to use opposite endian prefix - // Native uses '<' on little-endian, '>' on big-endian - // Swapped uses the opposite if let Ok(swapped_type_ref) = swapped_type.clone().downcast::() && let Some(mut sw_stg) = swapped_type_ref.get_type_data_mut::() { - let swapped_prefix = if is_little_endian { ">" } else { "<" }; - sw_stg.format = Some(format!("{}{}", swapped_prefix, type_str)); + // Swapped: little-endian system uses big-endian prefix and vice versa + sw_stg.format = Some(alloc_format_string_for_type( + type_str.chars().next().unwrap_or('?'), + is_little_endian, + )); } // Set attributes based on system byte order @@ -734,63 +781,56 @@ fn value_to_bytes_endian( } "b" => { // c_byte - signed char (1 byte) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i8; + let v = int_val.as_bigint().to_i128().expect("int too large") as i8; return vec![v as u8]; } vec![0] } "B" => { // c_ubyte - unsigned char (1 byte) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u8).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u8; return vec![v]; } vec![0] } "h" => { // c_short (2 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i16; + let v = int_val.as_bigint().to_i128().expect("int too large") as i16; return to_bytes!(v); } vec![0; 2] } "H" => { // c_ushort (2 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u16).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u16; return to_bytes!(v); } vec![0; 2] } "i" => { // c_int (4 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i32; + let v = int_val.as_bigint().to_i128().expect("int too large") as i32; return to_bytes!(v); } vec![0; 4] } "I" => { // c_uint (4 bytes) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u32).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u32; return to_bytes!(v); } vec![0; 4] } "l" => { // c_long (platform dependent) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as libc::c_long; + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_long; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::(); @@ -798,13 +838,8 @@ fn value_to_bytes_endian( } "L" => { // c_ulong (platform dependent) - // PyLong_AsUnsignedLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val - .as_bigint() - .to_i128() - .map(|n| n as libc::c_ulong) - .unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as libc::c_ulong; return to_bytes!(v); } const SIZE: usize = std::mem::size_of::(); @@ -812,18 +847,16 @@ fn value_to_bytes_endian( } "q" => { // c_longlong (8 bytes) - // PyLong_AsLongMask pattern: wrapping for overflow values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().unwrap_or(0) as i64; + let v = int_val.as_bigint().to_i128().expect("int too large") as i64; return to_bytes!(v); } vec![0; 8] } "Q" => { // c_ulonglong (8 bytes) - // PyLong_AsUnsignedLongLongMask: wrapping for negative values if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_i128().map(|n| n as u64).unwrap_or(0); + let v = int_val.as_bigint().to_i128().expect("int too large") as u64; return to_bytes!(v); } vec![0; 8] @@ -899,7 +932,10 @@ fn value_to_bytes_endian( "P" => { // c_void_p - pointer type (platform pointer size) if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -908,7 +944,10 @@ fn value_to_bytes_endian( // c_char_p - pointer to char (stores pointer value from int) // PyBytes case is handled in slot_new/set_value with make_z_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -917,7 +956,10 @@ fn value_to_bytes_endian( // c_wchar_p - pointer to wchar_t (stores pointer value from int) // PyStr case is handled in slot_new/set_value with make_wchar_buffer() if let Ok(int_val) = value.try_index(vm) { - let v = int_val.as_bigint().to_usize().unwrap_or(0); + let v = int_val + .as_bigint() + .to_usize() + .expect("int too large for pointer"); return to_bytes!(v); } vec![0; std::mem::size_of::()] @@ -939,7 +981,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(arr) = value.downcast_ref::() && let Some(info) = arr.class().stg_info_opt() && let Some(ref elem_type) = info.element_type - && let Some(elem_code) = elem_type.class().type_code(vm) + && let Some(elem_code) = elem_type.type_code(vm) { return elem_code == "c"; } @@ -947,7 +989,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { if let Some(ptr) = value.downcast_ref::() && let Some(info) = ptr.class().stg_info_opt() && let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { return proto_code == "c"; } @@ -955,25 +997,25 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { } /// Check if value is a c_wchar array or pointer(c_wchar) -fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool { +fn is_cwchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> PyResult { // Check Array with c_wchar element type if let Some(arr) = value.downcast_ref::() { - let info = arr.class().stg_info_opt().expect("array has StgInfo"); + let info = arr.class().stg_info(vm)?; let elem_type = info.element_type.as_ref().expect("array has element_type"); - if let Some(elem_code) = elem_type.class().type_code(vm) { - return elem_code == "u"; + if let Some(elem_code) = elem_type.type_code(vm) { + return Ok(elem_code == "u"); } } // Check Pointer to c_wchar if let Some(ptr) = value.downcast_ref::() { - let info = ptr.class().stg_info_opt().expect("pointer has StgInfo"); + let info = ptr.class().stg_info(vm)?; if let Some(ref proto) = info.proto - && let Some(proto_code) = proto.class().type_code(vm) + && let Some(proto_code) = proto.type_code(vm) { - return proto_code == "u"; + return Ok(proto_code == "u"); } } - false + Ok(false) } impl Constructor for PyCSimple { @@ -1121,15 +1163,27 @@ impl PyCSimple { return Ok(vm.ctx.none()); } // Read null-terminated wide string at the address + // Windows: wchar_t = u16 (UTF-16) -> use Wtf8Buf::from_wide for surrogate pairs + // Unix: wchar_t = i32 (UTF-32) -> convert via char::from_u32 unsafe { let w_ptr = ptr as *const libc::wchar_t; let len = libc::wcslen(w_ptr); let wchars = std::slice::from_raw_parts(w_ptr, len); - let s: String = wchars - .iter() - .filter_map(|&c| char::from_u32(c as u32)) - .collect(); - return Ok(vm.ctx.new_str(s).into()); + #[cfg(windows)] + { + use rustpython_common::wtf8::Wtf8Buf; + let wide: Vec = wchars.to_vec(); + let wtf8 = Wtf8Buf::from_wide(&wide); + return Ok(vm.ctx.new_str(wtf8).into()); + } + #[cfg(not(windows))] + { + let s: String = wchars + .iter() + .filter_map(|&c| char::from_u32(c as u32)) + .collect(); + return Ok(vm.ctx.new_str(s).into()); + } } } @@ -1349,12 +1403,25 @@ impl PyCSimple { impl AsBuffer for PyCSimple { fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - let buffer_len = zelf.0.buffer.read().len(); - let buf = PyBuffer::new( - zelf.to_owned().into(), - BufferDescriptor::simple(buffer_len, false), // readonly=false for ctypes - &CDATA_BUFFER_METHODS, - ); + let stg_info = zelf + .class() + .stg_info_opt() + .expect("PyCSimple type must have StgInfo"); + let format = stg_info + .format + .clone() + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed("B")); + let itemsize = stg_info.size; + // Simple types are scalars with ndim=0, shape=() + let desc = BufferDescriptor { + len: itemsize, + readonly: false, + itemsize, + format, + dim_desc: vec![], + }; + let buf = PyBuffer::new(zelf.to_owned().into(), desc, &CDATA_BUFFER_METHODS); Ok(buf) } } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 10b8812e4..1ca428669 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -601,12 +601,6 @@ impl PyCStructure { fn _b0_(&self) -> Option { self.0.base.read().clone() } - - #[pygetset] - fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return the _fields_ from the class, not instance - vm.ctx.none() - } } impl AsBuffer for PyCStructure {