fix ctypes

This commit is contained in:
Jeong YunWon authored and Jeong, YunWon committed 2025-12-24 00:13:30 +09:00
1 parent 4bf0bac52d
commit 79abbf0b29
8 files changed
+972 -472

No files matched your search

+76 -26
View File
@@ -95,6 +95,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
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::<libc::time_t>();
#[pyattr]
const CTYPES_MAX_ARGCOUNT: usize = 1024;
@@ -578,30 +575,42 @@ pub(crate) mod _ctypes {
#[pyfunction(name = "dlopen")]
fn load_library_unix(
name: Option<crate::function::FsPath>,
_load_flags: OptionalArg<i32>,
load_flags: OptionalArg<i32>,
vm: &VirtualMachine,
) -> PyResult<usize> {
// 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<usize> {
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<u32> {
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())
}
+38 -6
View File
@@ -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 "<f", ">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<u8> {
// 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,
}
+48 -26
View File
@@ -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::<PyType>()
&& 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::<PyStr>()
{
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<PyObjectRef>,
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<PyType>) -> 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()
File diff suppressed because it is too large. Load diff
+80 -28
View File
@@ -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<Option<Library>>,
#[cfg(unix)]
use libloading::os::unix::Library as UnixLibrary;
pub struct SharedLibrary {
pub(crate) lib: PyMutex<Option<Library>>,
}
impl fmt::Debug for SharedLibrary {
@@ -17,18 +19,44 @@ impl fmt::Debug for SharedLibrary {
}
impl SharedLibrary {
fn new(name: impl AsRef<OsStr>) -> Result<SharedLibrary, libloading::Error> {
#[cfg(windows)]
pub fn new(name: impl AsRef<OsStr>) -> Result<SharedLibrary, libloading::Error> {
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<OsStr>,
mode: i32,
) -> Result<SharedLibrary, libloading::Error> {
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::<Library, usize>(l) }
} else {
null::<c_void>() 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<OsStr>,
@@ -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<OsStr>,
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);
}
+119 -28
View File
@@ -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: "&<element_format>"
if let Some(ref proto) = stg_info.proto {
let item_info = proto.stg_info_opt().expect("proto has StgInfo");
// Set format string: "&<element_format>" or "&(shape)<element_format>" 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<String> = 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::<super::_ctypes::CArgObject>()
&& let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(type_ref) = type_attr.downcast::<PyType>()
&& 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::<StgInfo>() {
stg_info.proto = Some(typ_type.clone());
// Update format string: "&<element_format>"
// Update format string: "&<element_format>" or "&(shape)<element_format>" 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<String> = 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<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
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)
}
}
+127 -60
View File
@@ -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::<std::ffi::c_int>() == 2 => 'h',
'i' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'i',
'i' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'q',
'I' if std::mem::size_of::<std::ffi::c_int>() == 2 => 'H',
'I' if std::mem::size_of::<std::ffi::c_int>() == 4 => 'I',
'I' if std::mem::size_of::<std::ffi::c_int>() == 8 => 'Q',
// c_long: map based on sizeof(long)
'l' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'l',
'l' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'q',
'L' if std::mem::size_of::<std::ffi::c_long>() == 4 => 'L',
'L' if std::mem::size_of::<std::ffi::c_long>() == 8 => 'Q',
// c_bool: map based on sizeof(bool) - typically 1 byte on all platforms
'?' if std::mem::size_of::<bool>() == 1 => '?',
'?' if std::mem::size_of::<bool>() == 2 => 'H',
'?' if std::mem::size_of::<bool>() == 4 => 'L',
'?' if std::mem::size_of::<bool>() == 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<PyType>>,
@@ -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::<PyInt>(vm).is_some()
|| value.downcast_ref_if_exact::<PyStr>(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::<PyInt>(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., "<i" for little-endian int)
let endian_prefix = if cfg!(target_endian = "little") {
"<"
} else {
">"
};
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::<PyType>()
&& let Some(mut sw_stg) = swapped_type_ref.get_type_data_mut::<StgInfo>()
{
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::<libc::c_long>();
@@ -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::<libc::c_ulong>();
@@ -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::<usize>()]
@@ -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::<usize>()]
@@ -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::<usize>()]
@@ -939,7 +981,7 @@ fn is_cchar_array_or_pointer(value: &PyObject, vm: &VirtualMachine) -> bool {
if let Some(arr) = value.downcast_ref::<PyCArray>()
&& 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::<PyCPointer>()
&& 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<bool> {
// Check Array with c_wchar element type
if let Some(arr) = value.downcast_ref::<PyCArray>() {
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::<PyCPointer>() {
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<u16> = 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<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
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)
}
}
-6
View File
@@ -601,12 +601,6 @@ impl PyCStructure {
fn _b0_(&self) -> Option<PyObjectRef> {
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 {