diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 2e9d4c5e72..b8b005061f 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -36,6 +36,9 @@ FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \ FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR +# TODO: RUSTPYTHON remove this +from _ctypes import _non_existing_function + # WINOLEAPI -> HRESULT # WINOLEAPI_(type) # @@ -296,7 +299,9 @@ def create_unicode_buffer(init, size=None): return buf elif isinstance(init, int): _sys.audit("ctypes.create_unicode_buffer", None, init) - buftype = c_wchar * init + # XXX: RUSTPYTHON + # buftype = c_wchar * init + buftype = c_wchar.__mul__(init) buf = buftype() return buf raise TypeError(init) @@ -495,14 +500,15 @@ def WinError(code=None, descr=None): c_ssize_t = c_longlong # functions - from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr ## void *memmove(void *, const void *, size_t); -memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) +# XXX: RUSTPYTHON +# memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) ## void *memset(void *, int, size_t) -memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr) +# XXX: RUSTPYTHON +# memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr) def PYFUNCTYPE(restype, *argtypes): class CFunctionType(_CFuncPtr): @@ -511,11 +517,13 @@ class CFunctionType(_CFuncPtr): _flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI return CFunctionType -_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr) +# XXX: RUSTPYTHON +# _cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr) def cast(obj, typ): return _cast(obj, obj, typ) -_string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr) +# XXX: RUSTPYTHON +# _string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr) def string_at(ptr, size=-1): """string_at(addr[, size]) -> string @@ -527,7 +535,8 @@ def string_at(ptr, size=-1): except ImportError: pass else: - _wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr) + # XXX: RUSTPYTHON + # _wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr) def wstring_at(ptr, size=-1): """wstring_at(addr[, size]) -> string diff --git a/extra_tests/snippets/builtins_ctypes.py b/extra_tests/snippets/builtins_ctypes.py index c5c563a48e..2108ce41a4 100644 --- a/extra_tests/snippets/builtins_ctypes.py +++ b/extra_tests/snippets/builtins_ctypes.py @@ -33,13 +33,16 @@ def create_string_buffer(init, size=None): if size is None: size = len(init)+1 _sys.audit("ctypes.create_string_buffer", init, size) - buftype = c_char * size + buftype = c_char.__mul__(size) + print(type(c_char.__mul__(size))) + # buftype = c_char * size buf = buftype() buf.value = init return buf elif isinstance(init, int): _sys.audit("ctypes.create_string_buffer", None, init) - buftype = c_char * init + buftype = c_char.__mul__(init) + # buftype = c_char * init buf = buftype() return buf raise TypeError(init) @@ -260,8 +263,62 @@ def LoadLibrary(self, name): cdll = LibraryLoader(CDLL) +test_byte_array = create_string_buffer(b"Hello, World!\n") +assert test_byte_array._length_ == 15 + if _os.name == "posix" or _sys.platform == "darwin": pass else: + import os + libc = cdll.msvcrt - print("rand", libc.rand()) + libc.rand() + i = c_int(1) + print("start srand") + print(libc.srand(i)) + print(test_byte_array) + print(test_byte_array._type_) + # print("start printf") + # libc.printf(test_byte_array) + + # windows pip support + + def get_win_folder_via_ctypes(csidl_name: str) -> str: + """Get folder with ctypes.""" + # There is no 'CSIDL_DOWNLOADS'. + # Use 'CSIDL_PROFILE' (40) and append the default folder 'Downloads' instead. + # https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid + + import ctypes # noqa: PLC0415 + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + "CSIDL_PERSONAL": 5, + "CSIDL_MYPICTURES": 39, + "CSIDL_MYVIDEO": 14, + "CSIDL_MYMUSIC": 13, + "CSIDL_DOWNLOADS": 40, + "CSIDL_DESKTOPDIRECTORY": 16, + }.get(csidl_name) + if csidl_const is None: + msg = f"Unknown CSIDL name: {csidl_name}" + raise ValueError(msg) + + buf = ctypes.create_unicode_buffer(1024) + windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker + windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if it has high-bit chars. + if any(ord(c) > 255 for c in buf): # noqa: PLR2004 + buf2 = ctypes.create_unicode_buffer(1024) + if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + if csidl_name == "CSIDL_DOWNLOADS": + return os.path.join(buf.value, "Downloads") # noqa: PTH118 + + return buf.value + + # print(get_win_folder_via_ctypes("CSIDL_DOWNLOADS")) diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs index 99866bae70..b6fd150889 100644 --- a/vm/src/stdlib/ctypes.rs +++ b/vm/src/stdlib/ctypes.rs @@ -17,6 +17,7 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { extend_module!(vm, module, { "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PyCSimple::make_class(ctx), + "ArrayType" => array::PyCArrayType::make_class(ctx), "Array" => array::PyCArray::make_class(ctx), "CFuncPtr" => function::PyCFuncPtr::make_class(ctx), "_Pointer" => pointer::PyCPointer::make_class(ctx), @@ -37,7 +38,7 @@ pub(crate) mod _ctypes { use super::base::PyCSimple; use crate::builtins::PyTypeRef; use crate::class::StaticType; - use crate::function::{Either, OptionalArg}; + use crate::function::{Either, FuncArgs, OptionalArg}; use crate::stdlib::ctypes::library; use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine}; use crossbeam_utils::atomic::AtomicCell; @@ -124,11 +125,12 @@ pub(crate) mod _ctypes { "d" | "g" => mem::size_of::(), "?" | "B" => mem::size_of::(), "P" | "z" | "Z" => mem::size_of::(), + "O" => mem::size_of::(), _ => unreachable!(), } } - const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?"; + const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?O"; pub fn new_simple_type( cls: Either<&PyObjectRef, &PyTypeRef>, @@ -218,9 +220,14 @@ pub(crate) mod _ctypes { #[pyfunction(name = "POINTER")] pub fn pointer(_cls: PyTypeRef) {} - #[pyfunction] + #[pyfunction(name = "pointer")] pub fn pointer_fn(_inst: PyObjectRef) {} + #[pyfunction] + fn _pointer_type_cache() -> PyObjectRef { + todo!() + } + #[cfg(target_os = "windows")] #[pyfunction(name = "_check_HRESULT")] pub fn check_hresult(_self: PyObjectRef, hr: i32, _vm: &VirtualMachine) -> PyResult { @@ -243,6 +250,24 @@ pub(crate) mod _ctypes { } } + #[pyfunction] + fn byref(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + // TODO: RUSTPYTHON + Err(vm.new_value_error("not implemented".to_string())) + } + + #[pyfunction] + fn alignment(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + // TODO: RUSTPYTHON + Err(vm.new_value_error("not implemented".to_string())) + } + + #[pyfunction] + fn resize(_args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + // TODO: RUSTPYTHON + Err(vm.new_value_error("not implemented".to_string())) + } + #[pyfunction] fn get_errno() -> i32 { errno::errno().0 @@ -252,4 +277,41 @@ pub(crate) mod _ctypes { fn set_errno(value: i32) { errno::set_errno(errno::Errno(value)); } + + #[cfg(windows)] + #[pyfunction] + fn get_last_error() -> PyResult { + Ok(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + } + + #[cfg(windows)] + #[pyfunction] + fn set_last_error(value: u32) -> PyResult<()> { + unsafe { windows_sys::Win32::Foundation::SetLastError(value) }; + Ok(()) + } + + #[pyattr] + fn _memmove_addr(_vm: &VirtualMachine) -> usize { + let f = libc::memmove; + f as usize + } + + #[pyattr] + fn _memset_addr(_vm: &VirtualMachine) -> usize { + let f = libc::memset; + f as usize + } + + #[pyattr] + fn _string_at_addr(_vm: &VirtualMachine) -> usize { + let f = libc::strnlen; + f as usize + } + + #[pyattr] + fn _cast_addr(_vm: &VirtualMachine) -> usize { + // TODO: RUSTPYTHON + 0 + } } diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 8b023582c9..44d237cd7e 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -1,5 +1,107 @@ -#[pyclass(name = "Array", module = "_ctypes")] -pub struct PyCArray {} +use crate::builtins::PyBytes; +use crate::types::Callable; +use crate::{Py, PyObjectRef, PyPayload}; +use crate::{PyResult, VirtualMachine, builtins::PyTypeRef, types::Constructor}; +use crossbeam_utils::atomic::AtomicCell; +use rustpython_common::lock::PyRwLock; +use rustpython_vm::stdlib::ctypes::base::PyCSimple; -#[pyclass(flags(BASETYPE, IMMUTABLETYPE))] -impl PyCArray {} +// TODO: make it metaclass +#[pyclass(name = "ArrayType", module = "_ctypes")] +#[derive(PyPayload)] +pub struct PyCArrayType { + pub(super) inner: PyCArray, +} + +impl std::fmt::Debug for PyCArrayType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCArrayType") + .field("inner", &self.inner) + .finish() + } +} + +impl Callable for PyCArrayType { + type Args = (); + fn call(zelf: &Py, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + Ok(PyCArray { + typ: PyRwLock::new(zelf.inner.typ.read().clone()), + length: AtomicCell::new(zelf.inner.length.load()), + value: PyRwLock::new(zelf.inner.value.read().clone()), + } + .into_pyobject(vm)) + } +} + +impl Constructor for PyCArrayType { + type Args = PyObjectRef; + + fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { + unreachable!() + } +} + +#[pyclass(flags(IMMUTABLETYPE), with(Callable, Constructor))] +impl PyCArrayType {} + +#[pyclass(name = "Array", base = "PyCSimple", module = "_ctypes")] +#[derive(PyPayload)] +pub struct PyCArray { + pub(super) typ: PyRwLock, + pub(super) length: AtomicCell, + pub(super) value: PyRwLock, +} + +impl std::fmt::Debug for PyCArray { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyCArray") + .field("typ", &self.typ) + .field("length", &self.length) + .finish() + } +} + +impl Constructor for PyCArray { + type Args = (PyTypeRef, usize); + + fn py_new(_cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + Ok(Self { + typ: PyRwLock::new(args.0), + length: AtomicCell::new(args.1), + value: PyRwLock::new(vm.ctx.none()), + } + .into_pyobject(vm)) + } +} + +#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(Constructor))] +impl PyCArray { + #[pygetset(name = "_type_")] + fn typ(&self) -> PyTypeRef { + self.typ.read().clone() + } + + #[pygetset(name = "_length_")] + fn length(&self) -> usize { + self.length.load() + } + + #[pygetset] + fn value(&self) -> PyObjectRef { + self.value.read().clone() + } + + #[pygetset(setter)] + fn set_value(&self, value: PyObjectRef) { + *self.value.write() = value; + } +} + +impl PyCArray { + pub fn to_arg(&self, _vm: &VirtualMachine) -> PyResult { + let value = self.value.read(); + let py_bytes = value.payload::().unwrap(); + let bytes = py_bytes.as_ref().to_vec(); + Ok(libffi::middle::Arg::new(&bytes)) + } +} diff --git a/vm/src/stdlib/ctypes/base.rs b/vm/src/stdlib/ctypes/base.rs index 5c5396be29..d07ccba30b 100644 --- a/vm/src/stdlib/ctypes/base.rs +++ b/vm/src/stdlib/ctypes/base.rs @@ -1,3 +1,4 @@ +use super::array::{PyCArray, PyCArrayType}; use crate::builtins::PyType; use crate::builtins::{PyBytes, PyFloat, PyInt, PyNone, PyStr, PyTypeRef}; use crate::convert::ToPyObject; @@ -246,4 +247,63 @@ impl PyCSimple { zelf.value.store(content); Ok(()) } + + #[pyclassmethod] + fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + if n < 0 { + return Err(vm.new_value_error(format!("Array length must be >= 0, not {}", n))); + } + Ok(PyCArrayType { + inner: PyCArray { + typ: PyRwLock::new(cls), + length: AtomicCell::new(n as usize), + value: PyRwLock::new(vm.ctx.none()), + }, + } + .to_pyobject(vm)) + } + + #[pyclassmethod(magic)] + fn mul(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { + PyCSimple::repeat(cls, n, vm) + } +} + +impl PyCSimple { + pub fn to_arg( + &self, + ty: libffi::middle::Type, + vm: &VirtualMachine, + ) -> Option { + let value = unsafe { (*self.value.as_ptr()).clone() }; + if let Ok(i) = value.try_int(vm) { + let i = i.as_bigint(); + if ty.as_raw_ptr() == libffi::middle::Type::u8().as_raw_ptr() { + return i.to_u8().map(|r: u8| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::i8().as_raw_ptr() { + return i.to_i8().map(|r: i8| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::u16().as_raw_ptr() { + return i.to_u16().map(|r: u16| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::i16().as_raw_ptr() { + return i.to_i16().map(|r: i16| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::u32().as_raw_ptr() { + return i.to_u32().map(|r: u32| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::i32().as_raw_ptr() { + return i.to_i32().map(|r: i32| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::u64().as_raw_ptr() { + return i.to_u64().map(|r: u64| libffi::middle::Arg::new(&r)); + } else if ty.as_raw_ptr() == libffi::middle::Type::i64().as_raw_ptr() { + return i.to_i64().map(|r: i64| libffi::middle::Arg::new(&r)); + } else { + return None; + } + } + if let Ok(_f) = value.try_float(vm) { + todo!(); + } + if let Ok(_b) = value.try_to_bool(vm) { + todo!(); + } + None + } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 7d8dc0386a..c1c1230e03 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -2,6 +2,7 @@ use crate::builtins::{PyStr, PyTupleRef, PyTypeRef}; use crate::convert::ToPyObject; use crate::function::FuncArgs; use crate::stdlib::ctypes::PyCData; +use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str}; use crate::types::{Callable, Constructor}; use crate::{Py, PyObjectRef, PyResult, VirtualMachine}; @@ -16,6 +17,7 @@ use std::fmt::Debug; #[derive(Debug)] pub struct Function { + args: Vec, // TODO: no protection from use-after-free pointer: CodePtr, cif: Cif, @@ -38,8 +40,36 @@ impl Function { let args = args .iter() .map(|arg| { - if let Some(data) = arg.downcast_ref::() { - Ok(ffi_type_from_str(&data._type_).unwrap()) + if let Some(arg) = arg.payload_if_subclass::(vm) { + let converted = ffi_type_from_str(&arg._type_); + return match converted { + Some(t) => Ok(t), + None => Err(vm.new_type_error("Invalid type".to_string())), // TODO: add type name + }; + } + if let Some(arg) = arg.payload_if_subclass::(vm) { + let t = arg.typ.read(); + let ty_attributes = t.attributes.read(); + let ty_pystr = + ty_attributes + .get(vm.ctx.intern_str("_type_")) + .ok_or_else(|| { + vm.new_type_error("Expected a ctypes simple type".to_string()) + })?; + let ty_str = ty_pystr + .downcast_ref::() + .ok_or_else(|| { + vm.new_type_error("Expected a ctypes simple type".to_string()) + })? + .to_string(); + let converted = ffi_type_from_str(&ty_str); + match converted { + Some(_t) => { + // TODO: Use + Ok(Type::void()) + } + None => Err(vm.new_type_error("Invalid type".to_string())), // TODO: add type name + } } else { Err(vm.new_type_error("Expected a ctypes simple type".to_string())) } @@ -50,7 +80,7 @@ impl Function { library .get(terminated.as_bytes()) .map_err(|err| err.to_string()) - .map_err(|err| vm.new_value_error(err))? + .map_err(|err| vm.new_attribute_error(err))? }; let code_ptr = CodePtr(*pointer as *mut _); let return_type = match ret_type { @@ -60,8 +90,9 @@ impl Function { } None => Type::c_int(), }; - let cif = Cif::new(args, return_type); + let cif = Cif::new(args.clone(), return_type); Ok(Function { + args, cif, pointer: code_ptr, }) @@ -74,16 +105,19 @@ impl Function { ) -> PyResult { let args = args .into_iter() - .map(|arg| { - if let Some(data) = arg.downcast_ref::() { - dbg!(&data); - todo!("HANDLE ARGUMENTS") - } else { - Err(vm.new_type_error("Expected a ctypes simple type".to_string())) + .enumerate() + .map(|(count, arg)| { + // none type check + if let Some(d) = arg.payload_if_subclass::(vm) { + return Ok(d.to_arg(self.args[count].clone(), vm).unwrap()); + } + if let Some(d) = arg.payload_if_subclass::(vm) { + return Ok(d.to_arg(vm).unwrap()); } + Err(vm.new_type_error("Expected a ctypes simple type".to_string())) }) .collect::>>()?; - // TODO: FIX return type + // TODO: FIX return let result: i32 = unsafe { self.cif.call(self.pointer, &args) }; Ok(vm.ctx.new_int(result).into()) } @@ -151,7 +185,9 @@ impl Callable for PyCFuncPtr { let name = zelf.name.read(); let res_type = zelf._restype_.read(); let func = Function::load( - inner_lib.as_ref().unwrap(), + inner_lib + .as_ref() + .ok_or_else(|| vm.new_value_error("Library not found".to_string()))?, &name, &args.args, &res_type, @@ -173,4 +209,14 @@ impl PyCFuncPtr { fn set_name(&self, name: String) { *self.name.write() = name; } + + #[pygetset(name = "_restype_")] + fn restype(&self) -> Option { + self._restype_.read().as_ref().cloned() + } + + #[pygetset(name = "_restype_", setter)] + fn set_restype(&self, restype: PyTypeRef) { + *self._restype_.write() = Some(restype); + } } diff --git a/vm/src/stdlib/nt.rs b/vm/src/stdlib/nt.rs index ecc63e0aa6..48f1ab668b 100644 --- a/vm/src/stdlib/nt.rs +++ b/vm/src/stdlib/nt.rs @@ -34,6 +34,9 @@ pub(crate) mod module { #[pyattr] use libc::{O_BINARY, O_TEMPORARY}; + #[pyattr] + const _LOAD_LIBRARY_SEARCH_DEFAULT_DIRS: i32 = 4096; + #[pyfunction] pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult { let attr = unsafe { FileSystem::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) };