From 41b30de5ea7045e9d6e58b67f18856fc602dccd5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 8 Aug 2021 04:22:11 +0900 Subject: [PATCH 01/56] metaclass support for #[pyclass] macro --- derive/src/pyclass.rs | 32 +++++++++++++++++++++++++++----- derive/src/util.rs | 6 +++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index cd5c759fbc..838b677f79 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -142,6 +142,7 @@ fn generate_class_def( name: &str, module_name: Option<&str>, base: Option, + metaclass: Option, attrs: &[Attribute], ) -> std::result::Result { let doc = if let Some(doc) = attrs.doc() { @@ -175,8 +176,6 @@ fn generate_class_def( ) .into()); } - let base = base.map(|name| Ident::new(&name, ident.span())); - let base_class = if is_pystruct { quote! { fn static_baseclass() -> &'static ::rustpython_vm::builtins::PyTypeRef { @@ -184,11 +183,24 @@ fn generate_class_def( rustpython_vm::builtins::PyTuple::static_type() } } - } else if let Some(base) = base { + } else if let Some(typ) = base { + let typ = Ident::new(&typ, ident.span()); quote! { fn static_baseclass() -> &'static ::rustpython_vm::builtins::PyTypeRef { use rustpython_vm::StaticType; - #base::static_type() + #typ::static_type() + } + } + } else { + quote!() + }; + + let meta_class = if let Some(typ) = metaclass { + let typ = Ident::new(&typ, ident.span()); + quote! { + fn static_metaclass() -> &'static ::rustpython_vm::builtins::PyTypeRef { + use rustpython_vm::StaticType; + #typ::static_type() } } } else { @@ -211,6 +223,8 @@ fn generate_class_def( &CELL } + #meta_class + #base_class } }; @@ -227,7 +241,15 @@ pub(crate) fn impl_pyclass( let class_name = class_meta.class_name()?; let module_name = class_meta.module()?; let base = class_meta.base()?; - let class_def = generate_class_def(ident, &class_name, module_name.as_deref(), base, attrs)?; + let metaclass = class_meta.metaclass()?; + let class_def = generate_class_def( + ident, + &class_name, + module_name.as_deref(), + base, + metaclass, + attrs, + )?; let ret = quote! { #item diff --git a/derive/src/util.rs b/derive/src/util.rs index 756478f201..90cc16e708 100644 --- a/derive/src/util.rs +++ b/derive/src/util.rs @@ -233,7 +233,7 @@ impl ItemMeta for SimpleItemMeta { pub(crate) struct ClassItemMeta(ItemMetaInner); impl ItemMeta for ClassItemMeta { - const ALLOWED_NAMES: &'static [&'static str] = &["module", "name", "base"]; + const ALLOWED_NAMES: &'static [&'static str] = &["module", "name", "base", "metaclass"]; fn from_inner(inner: ItemMetaInner) -> Self { Self(inner) @@ -272,6 +272,10 @@ impl ClassItemMeta { self.inner()._optional_str("base") } + pub fn metaclass(&self) -> Result> { + self.inner()._optional_str("metaclass") + } + pub fn module(&self) -> Result> { const KEY: &str = "module"; let inner = self.inner(); From b4a1b8070166084d2baed25c5cedc5842b0e617f Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveira Date: Wed, 28 Oct 2020 23:52:58 -0300 Subject: [PATCH 02/56] Initial ctypes module structure with dlopen and dlsym --- Cargo.lock | 11 +++++++++ Lib/ctypes/__init__.py | 12 ++++++++++ vm/Cargo.toml | 2 ++ vm/src/stdlib/ctypes.rs | 49 +++++++++++++++++++++++++++++++++++++++++ vm/src/stdlib/mod.rs | 5 +++++ 5 files changed, 79 insertions(+) create mode 100644 Lib/ctypes/__init__.py create mode 100644 vm/src/stdlib/ctypes.rs diff --git a/Cargo.lock b/Cargo.lock index d885b52242..6415e2e2fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,16 @@ dependencies = [ "make-cmd", ] +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "libz-sys" version = "1.1.3" @@ -1904,6 +1914,7 @@ dependencies = [ "itertools", "lexical-core", "libc", + "libloading", "libz-sys", "log", "md-5", diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py new file mode 100644 index 0000000000..959e5c33c5 --- /dev/null +++ b/Lib/ctypes/__init__.py @@ -0,0 +1,12 @@ +from _ctypes import dlopen, dlsym + +class LibraryLoader: + def __init__(self, dll_path): + self._dll_path = dll_path + self._dll = dlopen(dll_path) + + def tt(self): + dlsym(self._dll, "hello") + + +cdll = LibraryLoader diff --git a/vm/Cargo.toml b/vm/Cargo.toml index a452bcdbbb..8317d4d000 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -114,6 +114,8 @@ flamer = { version = "0.4", optional = true } rustpython-common = { path = "../common" } +libloading = "0.6" + [target.'cfg(unix)'.dependencies] exitcode = "1.1.2" uname = "0.1.1" diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs new file mode 100644 index 0000000000..dabf036fd2 --- /dev/null +++ b/vm/src/stdlib/ctypes.rs @@ -0,0 +1,49 @@ +use crate::pyobject::PyObjectRef; +use crate::VirtualMachine; + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let module = _ctypes::make_module(vm); + module +} + +#[pymodule] +mod _ctypes { + extern crate libloading; + use crate::builtins::pystr::PyStrRef; + use crate::builtins::pytype::PyTypeRef; + use crate::pyobject::{PyObjectRef, PyResult, PyValue}; + use crate::VirtualMachine; + + // #[pyclass()] + #[derive(Debug)] + struct SharedLibrary { + lib: libloading::Library, + } + + impl PyValue for SharedLibrary { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + &vm.ctx.types.object_type + } + } + + #[pyfunction] + fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { + let shared_lib = SharedLibrary { + lib: libloading::Library::new(lib_path.as_ref()).expect("Failed to load library"), + }; + Ok(vm.new_pyobj(shared_lib)) + } + + #[pyfunction] + fn dlsym(handle: PyObjectRef, func_name: PyStrRef) { + if let Some(slib) = handle.payload::() { + unsafe { + let func: libloading::Symbol = slib + .lib + .get(func_name.as_ref().as_bytes()) + .expect("Failed to get func"); + func(); + } + } + } +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 8f4cb7d92c..52d29298ce 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -12,6 +12,7 @@ mod bisect; mod codecs; mod collections; mod csv; +mod ctypes; mod dis; mod errno; mod functools; @@ -152,6 +153,10 @@ pub fn get_module_inits() -> StdlibMap { { os::MODULE_NAME => os::make_module, } + #[cfg(any(unix, windows, target_os = "wasi"))] + { + "_ctypes" => ctypes::make_module, + } #[cfg(any(unix, target_os = "wasi"))] { "fcntl" => fcntl::make_module, From b9ada0a0e16a0f21e99ad24adc18680fa5d7e94e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 31 Oct 2020 08:43:38 -0300 Subject: [PATCH 03/56] Refactoring ctypes module to a PyPy-like file tree --- vm/src/stdlib/ctypes.rs | 49 ------------------------------- vm/src/stdlib/ctypes/array.rs | 0 vm/src/stdlib/ctypes/basics.rs | 0 vm/src/stdlib/ctypes/common.rs | 0 vm/src/stdlib/ctypes/ctypes.rs | 0 vm/src/stdlib/ctypes/dll.rs | 36 +++++++++++++++++++++++ vm/src/stdlib/ctypes/function.rs | 0 vm/src/stdlib/ctypes/mod.rs | 15 ++++++++++ vm/src/stdlib/ctypes/pointer.rs | 0 vm/src/stdlib/ctypes/primitive.rs | 0 vm/src/stdlib/ctypes/structure.rs | 0 vm/src/stdlib/ctypes/union.rs | 0 12 files changed, 51 insertions(+), 49 deletions(-) delete mode 100644 vm/src/stdlib/ctypes.rs create mode 100644 vm/src/stdlib/ctypes/array.rs create mode 100644 vm/src/stdlib/ctypes/basics.rs create mode 100644 vm/src/stdlib/ctypes/common.rs create mode 100644 vm/src/stdlib/ctypes/ctypes.rs create mode 100644 vm/src/stdlib/ctypes/dll.rs create mode 100644 vm/src/stdlib/ctypes/function.rs create mode 100644 vm/src/stdlib/ctypes/mod.rs create mode 100644 vm/src/stdlib/ctypes/pointer.rs create mode 100644 vm/src/stdlib/ctypes/primitive.rs create mode 100644 vm/src/stdlib/ctypes/structure.rs create mode 100644 vm/src/stdlib/ctypes/union.rs diff --git a/vm/src/stdlib/ctypes.rs b/vm/src/stdlib/ctypes.rs deleted file mode 100644 index dabf036fd2..0000000000 --- a/vm/src/stdlib/ctypes.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::pyobject::PyObjectRef; -use crate::VirtualMachine; - -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let module = _ctypes::make_module(vm); - module -} - -#[pymodule] -mod _ctypes { - extern crate libloading; - use crate::builtins::pystr::PyStrRef; - use crate::builtins::pytype::PyTypeRef; - use crate::pyobject::{PyObjectRef, PyResult, PyValue}; - use crate::VirtualMachine; - - // #[pyclass()] - #[derive(Debug)] - struct SharedLibrary { - lib: libloading::Library, - } - - impl PyValue for SharedLibrary { - fn class(vm: &VirtualMachine) -> &PyTypeRef { - &vm.ctx.types.object_type - } - } - - #[pyfunction] - fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let shared_lib = SharedLibrary { - lib: libloading::Library::new(lib_path.as_ref()).expect("Failed to load library"), - }; - Ok(vm.new_pyobj(shared_lib)) - } - - #[pyfunction] - fn dlsym(handle: PyObjectRef, func_name: PyStrRef) { - if let Some(slib) = handle.payload::() { - unsafe { - let func: libloading::Symbol = slib - .lib - .get(func_name.as_ref().as_bytes()) - .expect("Failed to get func"); - func(); - } - } - } -} diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/ctypes.rs b/vm/src/stdlib/ctypes/ctypes.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs new file mode 100644 index 0000000000..a42bf6b012 --- /dev/null +++ b/vm/src/stdlib/ctypes/dll.rs @@ -0,0 +1,36 @@ +extern crate libloading; +use crate::builtins::pystr::PyStrRef; +use crate::builtins::pytype::PyTypeRef; +use crate::pyobject::{PyObjectRef, PyResult, PyValue}; +use crate::VirtualMachine; + +// #[pyclass()] +#[derive(Debug)] +struct SharedLibrary { + lib: libloading::Library, +} + +impl PyValue for SharedLibrary { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + &vm.ctx.types.object_type + } +} + +pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { + let shared_lib = SharedLibrary { + lib: libloading::Library::new(lib_path.as_ref()).expect("Failed to load library"), + }; + Ok(vm.new_pyobj(shared_lib)) +} + +pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef) { + if let Some(slib) = handle.payload::() { + unsafe { + let func: libloading::Symbol = slib + .lib + .get(func_name.as_ref().as_bytes()) + .expect("Failed to get func"); + func(); + } + } +} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs new file mode 100644 index 0000000000..9ecdaaa11a --- /dev/null +++ b/vm/src/stdlib/ctypes/mod.rs @@ -0,0 +1,15 @@ +use crate::pyobject::PyObjectRef; +use crate::VirtualMachine; + +mod dll; + +use crate::stdlib::ctypes::dll::*; + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "_ctypes", { + "dlopen" => ctx.new_function(dlopen), + "dlsym" => ctx.new_function(dlsym), + }) +} diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/structure.rs b/vm/src/stdlib/ctypes/structure.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vm/src/stdlib/ctypes/union.rs b/vm/src/stdlib/ctypes/union.rs new file mode 100644 index 0000000000..e69de29bb2 From 1fa7e806b28dc4b782ffd8c043166fd10f0c31d6 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveira Date: Sat, 31 Oct 2020 18:23:29 -0300 Subject: [PATCH 04/56] Add __getattr__ to deal with attributes at runtime --- Lib/ctypes/__init__.py | 12 +++++++++--- vm/src/stdlib/ctypes/dll.rs | 15 ++++++++------- vm/src/stdlib/ctypes/function.rs | 28 ++++++++++++++++++++++++++++ vm/src/stdlib/ctypes/mod.rs | 5 +++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 959e5c33c5..e58e74d4a3 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,12 +1,18 @@ -from _ctypes import dlopen, dlsym +from _ctypes import dlopen, dlsym, CFuncPtr class LibraryLoader: def __init__(self, dll_path): self._dll_path = dll_path self._dll = dlopen(dll_path) - def tt(self): - dlsym(self._dll, "hello") + def __getattr__(self, attr): + if attr.startswith('_'): + raise AttributeError(attr) + func_ptr = dlsym(self._dll, attr) + if not func_ptr: + raise AttributeError("{}: undefined symbol: {}".format(self._dll_path, attr)) + + return lambda: func_ptr.call() cdll = LibraryLoader diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index a42bf6b012..7aa1e7b384 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -4,7 +4,8 @@ use crate::builtins::pytype::PyTypeRef; use crate::pyobject::{PyObjectRef, PyResult, PyValue}; use crate::VirtualMachine; -// #[pyclass()] +use crate::stdlib::ctypes::function::PyCFuncPtr; + #[derive(Debug)] struct SharedLibrary { lib: libloading::Library, @@ -23,14 +24,14 @@ pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { Ok(vm.new_pyobj(shared_lib)) } -pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef) { +pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef, vm: &VirtualMachine) -> PyResult { if let Some(slib) = handle.payload::() { unsafe { - let func: libloading::Symbol = slib - .lib - .get(func_name.as_ref().as_bytes()) - .expect("Failed to get func"); - func(); + match slib.lib.get(func_name.as_ref().as_bytes()) { + Ok(func) => return Ok(vm.new_pyobj(PyCFuncPtr::new(*func))), + Err(_) => return Ok(vm.ctx.none()), + } } } + Ok(vm.ctx.none()) } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index e69de29bb2..5bc5ff102e 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -0,0 +1,28 @@ +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType}; +use crate::VirtualMachine; + +#[pyclass(module = "_ctypes", name = "CFuncPtr")] +#[derive(Debug)] +pub struct PyCFuncPtr { + ext_func: extern "C" fn(), +} + +impl PyValue for PyCFuncPtr { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +#[pyimpl] +impl PyCFuncPtr { + #[inline] + pub fn new(ext_func: extern "C" fn()) -> PyCFuncPtr { + PyCFuncPtr { ext_func } + } + + #[pymethod] + pub fn call(&self) { + (self.ext_func)(); + } +} diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 9ecdaaa11a..a507c7b551 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -1,9 +1,12 @@ +use crate::pyobject::PyClassImpl; use crate::pyobject::PyObjectRef; use crate::VirtualMachine; mod dll; +mod function; use crate::stdlib::ctypes::dll::*; +use crate::stdlib::ctypes::function::*; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -11,5 +14,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "_ctypes", { "dlopen" => ctx.new_function(dlopen), "dlsym" => ctx.new_function(dlsym), + // "CFuncPtr" => ctx.new_class("CFuncPtr", &ctx.types.object_type, Default::default()) + "CFuncPtr" => PyCFuncPtr::make_class(ctx), }) } From b30dbae99f7ae569a91043a3a1d1e91eb8f3ec89 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 2 Nov 2020 18:20:24 -0300 Subject: [PATCH 05/56] Adding a very basic and initial implementation of _SimpleCData and FunctionProxy & friends --- Lib/ctypes/__init__.py | 86 ++++++++++++++++++---- vm/Cargo.toml | 4 +- vm/src/stdlib/ctypes/basics.rs | 33 +++++++++ vm/src/stdlib/ctypes/common.rs | 116 ++++++++++++++++++++++++++++++ vm/src/stdlib/ctypes/ctypes.rs | 0 vm/src/stdlib/ctypes/dll.rs | 39 +++++----- vm/src/stdlib/ctypes/function.rs | 101 +++++++++++++++++++++++--- vm/src/stdlib/ctypes/mod.rs | 10 ++- vm/src/stdlib/ctypes/primitive.rs | 48 +++++++++++++ 9 files changed, 394 insertions(+), 43 deletions(-) delete mode 100644 vm/src/stdlib/ctypes/ctypes.rs diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index e58e74d4a3..23e9e569bc 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,18 +1,78 @@ -from _ctypes import dlopen, dlsym, CFuncPtr +import os, sys -class LibraryLoader: - def __init__(self, dll_path): - self._dll_path = dll_path - self._dll = dlopen(dll_path) +from _ctypes import CFuncPtr as _CFuncPtr +from _ctypes import dlopen as _dlopen - def __getattr__(self, attr): - if attr.startswith('_'): - raise AttributeError(attr) - func_ptr = dlsym(self._dll, attr) - if not func_ptr: - raise AttributeError("{}: undefined symbol: {}".format(self._dll_path, attr)) +class CDLL(object): + """An instance of this class represents a loaded dll/shared + library, exporting functions using the standard C calling + convention (named 'cdecl' on Windows). + The exported functions can be accessed as attributes, or by + indexing with the function name. Examples: + .qsort -> callable object + ['qsort'] -> callable object + Calling the functions releases the Python GIL during the call and + reacquires it afterwards. + """ + # default values for repr + _name = '' + _handle = 0 + _FuncPtr = None - return lambda: func_ptr.call() + def __init__(self, name, handle=None): + self._name = name + class _FuncPtr(_CFuncPtr): + _restype_ = self._func_restype_ -cdll = LibraryLoader + self._FuncPtr = _FuncPtr + + if handle is None: + self._handle = _dlopen(self._name) + else: + self._handle = handle + + def __repr__(self): + return "<%s '%s', handle %x at %#x>" % \ + (self.__class__.__name__, self._name, + (self._handle & (sys.maxsize*2 + 1)), + id(self) & (sys.maxsize*2 + 1)) + + def __getattr__(self, name): + if name.startswith('__') and name.endswith('__'): + raise AttributeError(name) + + func = self.__getitem__(name) + setattr(self, name, func) + + return func + + def __getitem__(self, name_or_ordinal): + func = self._FuncPtr((name_or_ordinal, self)) + + if not isinstance(name_or_ordinal, int): + func.__name__ = name_or_ordinal + + return func + +class LibraryLoader(object): + def __init__(self, dlltype): + self._dlltype = dlltype + + def __getattr__(self, name): + if name[0] == '_': + raise AttributeError(name) + + dll = self._dlltype(name) + setattr(self, name, dll) + + return dll + + def __getitem__(self, name): + return getattr(self, name) + + def LoadLibrary(self, name): + return self._dlltype(name) + + +cdll = LibraryLoader(CDLL) \ No newline at end of file diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 8317d4d000..7b1838bb48 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -114,7 +114,9 @@ flamer = { version = "0.4", optional = true } rustpython-common = { path = "../common" } -libloading = "0.6" +lazy_static = "1.4.0" +libloading = "0.6.5" +libffi = "1.0.0" [target.'cfg(unix)'.dependencies] exitcode = "1.1.2" diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index e69de29bb2..29808a2b81 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -0,0 +1,33 @@ +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType,PyResult}; +use crate::VirtualMachine; + +use crate::stdlib::ctypes::common::CDataObject; + +#[pyclass(module = "_ctypes", name = "_CData", base = "CDataObject")] +#[derive(Debug)] +pub struct PyCData { + +} + +impl PyValue for PyCData { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +#[pyimpl] +impl PyCData { + #[inline] + pub fn new() -> PyCData { + PyCData { + + } + } + + #[pymethod(name = "__init__")] + fn init(&self, vm: &VirtualMachine) -> PyResult<()> { + Ok(()) + } + +} \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index e69de29bb2..e5cd684c62 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -0,0 +1,116 @@ +extern crate lazy_static; +extern crate libloading; + +use libloading::{Library}; +use ::std::{sync::Arc,collections::HashMap}; + +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType, PyRef, PyObjectRef, PyResult}; +use crate::VirtualMachine; +#[derive(Copy, Clone)] +pub enum Value { + Bool, + Char, + Wchar, + Byte, + Ubyte, + Short, + UShort, + Int, + UInt, + Long, + LongLong, + ULong, + ULL, + SizeT, + SsizeT, + Float, + Double, + // LongDoudle(...) ???, + CharP, + WcharP, + Void, +} + +pub struct ExternalFunctions { + functions: HashMap>, + libraries: HashMap>, +} + +impl ExternalFunctions { + pub fn call_function(&self, function: &str, arguments: Vec, vm: &VirtualMachine) -> PyResult { + match self.functions.get(function){ + Some(func_proxy) => func_proxy.call(arguments), + _ => Err(vm.new_runtime_error(format!("Function {} not found",function))) + } + } + + pub unsafe fn get_or_insert_lib(&mut self, library_path: String) -> Result<&Library,libloading::Error>{ + + let library = self.libraries.entry(library_path).or_insert({ + Arc::new(Library::new(library_path)?) + }); + + Ok(library.as_ref()) + } + + + pub fn get_or_insert_fn(&mut self, func_name: String, library_path: String, vm: &VirtualMachine) -> PyResult<&mut Arc>{ + let f_name = format!("{}_{}",library_path, func_name); + + match self.libraries.get(&library_path) { + Some(library) => Ok(self.functions.entry(f_name).or_insert({ + Arc::new(FunctionProxy {_name: f_name, _lib: *library}) + })), + _ => Err(vm.new_runtime_error(format!("Library of path {} not found",library_path))) + } + } +} + +lazy_static::lazy_static! { + pub static ref FUNCTIONS: ExternalFunctions = ExternalFunctions { + functions:HashMap::new(), + libraries:HashMap::new() + }; +} + +#[derive(Debug,Clone)] +pub struct FunctionProxy { + _name: String, + _lib: Arc, +} + +impl FunctionProxy { + // fn call(&self, args: Vec) -> PyResult { + + // } + +} + +impl PyValue for FunctionProxy { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + &vm.ctx.types.object_type + } +} + + + +#[pyclass(module = false, name = "_CDataObject")] +#[derive(Debug)] +pub struct CDataObject; + +pub type CDataObjectRef = PyRef; + +impl PyValue for CDataObject { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + + +#[pyimpl(flags(BASETYPE))] +impl CDataObject{ + // A lot of the logic goes in this trait + // There's also other traits that should have different implementations for some functions + // present here +} \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/ctypes.rs b/vm/src/stdlib/ctypes/ctypes.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 7aa1e7b384..80365a5dce 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,14 +1,15 @@ -extern crate libloading; +use crate::builtins::tuple::PyTupleRef; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; -use crate::pyobject::{PyObjectRef, PyResult, PyValue}; +use crate::pyobject::{PyObjectRef, PyResult, PyValue, PyRef}; use crate::VirtualMachine; use crate::stdlib::ctypes::function::PyCFuncPtr; +use crate::stdlib::ctypes::common::{FUNCTIONS}; #[derive(Debug)] struct SharedLibrary { - lib: libloading::Library, + lib: &'static libloading::Library, } impl PyValue for SharedLibrary { @@ -17,21 +18,23 @@ impl PyValue for SharedLibrary { } } -pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let shared_lib = SharedLibrary { - lib: libloading::Library::new(lib_path.as_ref()).expect("Failed to load library"), - }; +pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { + let library = unsafe { FUNCTIONS.get_or_insert_lib(lib_path.to_string()).expect("Failed to load library")}; + + let shared_lib = SharedLibrary { lib : library }; + Ok(vm.new_pyobj(shared_lib)) } -pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef, vm: &VirtualMachine) -> PyResult { - if let Some(slib) = handle.payload::() { - unsafe { - match slib.lib.get(func_name.as_ref().as_bytes()) { - Ok(func) => return Ok(vm.new_pyobj(PyCFuncPtr::new(*func))), - Err(_) => return Ok(vm.ctx.none()), - } - } - } - Ok(vm.ctx.none()) -} + +// pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef, argtypes: Option, restype:Option, vm: &VirtualMachine) -> PyResult { +// if let Some(slib) = handle.payload::() { +// unsafe { +// match slib.lib.get(func_name.as_ref().as_bytes()) { +// Ok(func) => return Ok(vm.new_pyobj(PyCFuncPtr::new(*func))), +// Err(e) => return Ok(vm.ctx.none()), +// } +// } +// } +// Ok(vm.ctx.none()) +// } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 5bc5ff102e..396a92386c 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,11 +1,24 @@ +use libloading::{Library}; +use ::std::{sync::Arc}; + use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType}; +use crate::builtins::tuple::PyTupleRef; +use crate::builtins::list::PyListRef; +use crate::builtins::pystr::{PyStr, PyStrRef}; +use crate::pyobject::{PyValue, StaticType, PyRef, PyResult, PyObjectRef}; + use crate::VirtualMachine; -#[pyclass(module = "_ctypes", name = "CFuncPtr")] +use crate::stdlib::ctypes::common::{CDataObject,FunctionProxy}; + + +#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { - ext_func: extern "C" fn(), + __name__: Option, + _argtypes_: Vec>, + _restype_: Option>, + ext_func: Option, } impl PyValue for PyCFuncPtr { @@ -16,13 +29,81 @@ impl PyValue for PyCFuncPtr { #[pyimpl] impl PyCFuncPtr { - #[inline] - pub fn new(ext_func: extern "C" fn()) -> PyCFuncPtr { - PyCFuncPtr { ext_func } - } + // If a subclass fill the parameters, construct new objects with it + // Else use default + + // #[inline] + // pub fn new() -> PyCFuncPtr { + // PyCFuncPtr { + // __name__: None, + // _argtypes_: Vec::new(), + // _restype_: None, + // ext_func: None, + // } + + // } + + // #[pyproperty] + // pub fn __name__(&self) -> PyResult { + // self.__name__.into() + // } + + // #[pyproperty(setter)] + // pub fn set___name__(&self, _name: PyStrRef) { + // self.__name__ = _name.to_string(); + // } + + // #[pyproperty] + // pub fn argtypes(&self) -> Option { + // // I think this needs to return a tuple reference to the objects that have CDataObject implementations + // // This kind off also wrong in the CPython's way, they allow classes with _as_parameter_ object attribute... + // convert_to_tuple(self._argtypes_) + // } - #[pymethod] + // #[pyproperty(setter)] + // pub fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) { + // if vm.isinstance(argtypes, PyListRef) || vm.isinstance(argtypes, PyTupleRef) { + // let args: Vec> = vm.extract_elements(argtypes).into_iter().filter(|obj|vm.isinstance(obj, CDataObjectRef)).collect(); + + // if args.len() > 0 { + // self._argtypes_ = args; + // } else { + // // Exception here + // // Err(vm.new_value_error("")) + // } + // } + + // // Exception here + // // Err(vm.new_type_error("")) + // } + + // #[pyproperty] + // pub fn restype(&self) -> Option { + // convert_to_tuple(self._restype_) + // } + + // #[pyproperty(setter)] + // pub fn set_restype(&self, restype: PyTupleRef) { + // self._restype_ = convert_from_tuple(restype) + // } + + #[pymethod(name = "__call__")] pub fn call(&self) { - (self.ext_func)(); + if self.__name__.is_none() { + // Exception here + // Err(vm.new_value_error("")) + } + + if self._argtypes_.len() == 0 { + // Exception here + // Err(vm.new_value_error("")) + } + + if self._restype_.is_none() { + // Exception here + // Err(vm.new_value_error("")) + } + + // Make the function call here } -} +} \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index a507c7b551..cee10f0987 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -3,10 +3,16 @@ use crate::pyobject::PyObjectRef; use crate::VirtualMachine; mod dll; +mod basics; +mod common; mod function; +mod primitive; use crate::stdlib::ctypes::dll::*; +use crate::stdlib::ctypes::basics::*; use crate::stdlib::ctypes::function::*; +use crate::stdlib::ctypes::primitive::*; + pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -14,7 +20,9 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "_ctypes", { "dlopen" => ctx.new_function(dlopen), "dlsym" => ctx.new_function(dlsym), - // "CFuncPtr" => ctx.new_class("CFuncPtr", &ctx.types.object_type, Default::default()) + "CFuncPtr" => PyCFuncPtr::make_class(ctx), + "_CData" => PyCData::make_class(ctx), + "_SimpleCData" => PySimpleType::make_class(ctx) }) } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index e69de29bb2..5dfdfe91b1 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -0,0 +1,48 @@ +use crate::builtins::PyTypeRef; +use crate::builtins::pystr::PyStrRef; +use crate::pyobject::{PyValue, StaticType, PyResult}; +use crate::VirtualMachine; + +use crate::stdlib::ctypes::common::CDataObject; + +const SIMPLE_TYPE_CHARS: &'static str = "cbBhHiIlLdfuzZqQP?g"; + + +#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "CDataObject")] +#[derive(Debug)] +pub struct PySimpleType { + _type_: PyStrRef, +} + +impl PyValue for PySimpleType { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +#[pyimpl] +impl PySimpleType { + #[inline] + pub fn new(_type: PyStrRef,vm: &VirtualMachine) -> PyResult { + // Needs to force the existence of _type_ + // Does it need to be here? + // Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) + + let s_type = _type.to_string(); + + if s_type.len() != 1{ + Err(vm.new_attribute_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) + } + + else { + + if SIMPLE_TYPE_CHARS.contains(s_type.as_str()){ + Ok(PySimpleType {_type_ : _type}) + } + + else { + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of '{}'.",SIMPLE_TYPE_CHARS))) + } + } + } +} \ No newline at end of file From 89587f063c6a10c762afbbbaaa32b5e9804149b1 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveira Date: Tue, 3 Nov 2020 19:33:37 -0300 Subject: [PATCH 06/56] Add PyFuncPtr tp_new from DLL and basic tp_call --- Lib/ctypes/__init__.py | 12 ++- vm/src/stdlib/ctypes/basics.rs | 13 +-- vm/src/stdlib/ctypes/common.rs | 67 ++++++++------- vm/src/stdlib/ctypes/dll.rs | 38 +++++---- vm/src/stdlib/ctypes/function.rs | 133 +++++++++++++++++++++--------- vm/src/stdlib/ctypes/mod.rs | 9 +- vm/src/stdlib/ctypes/primitive.rs | 33 ++++---- 7 files changed, 183 insertions(+), 122 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 23e9e569bc..fa7f5df6e9 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -4,6 +4,7 @@ from _ctypes import dlopen as _dlopen class CDLL(object): + """An instance of this class represents a loaded dll/shared library, exporting functions using the standard C calling convention (named 'cdecl' on Windows). @@ -23,7 +24,7 @@ def __init__(self, name, handle=None): self._name = name class _FuncPtr(_CFuncPtr): - _restype_ = self._func_restype_ + pass self._FuncPtr = _FuncPtr @@ -44,15 +45,15 @@ def __getattr__(self, name): func = self.__getitem__(name) setattr(self, name, func) - + return func def __getitem__(self, name_or_ordinal): - func = self._FuncPtr((name_or_ordinal, self)) + func = self._FuncPtr(name_or_ordinal, self) if not isinstance(name_or_ordinal, int): func.__name__ = name_or_ordinal - + return func class LibraryLoader(object): @@ -73,6 +74,3 @@ def __getitem__(self, name): def LoadLibrary(self, name): return self._dlltype(name) - - -cdll = LibraryLoader(CDLL) \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 29808a2b81..27ed3607bb 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,14 +1,12 @@ use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType,PyResult}; +use crate::pyobject::{PyResult, PyValue, StaticType}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::CDataObject; #[pyclass(module = "_ctypes", name = "_CData", base = "CDataObject")] #[derive(Debug)] -pub struct PyCData { - -} +pub struct PyCData {} impl PyValue for PyCData { fn class(vm: &VirtualMachine) -> &PyTypeRef { @@ -20,14 +18,11 @@ impl PyValue for PyCData { impl PyCData { #[inline] pub fn new() -> PyCData { - PyCData { - - } + PyCData {} } #[pymethod(name = "__init__")] fn init(&self, vm: &VirtualMachine) -> PyResult<()> { Ok(()) } - -} \ No newline at end of file +} diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index e5cd684c62..58df216a08 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -1,17 +1,17 @@ extern crate lazy_static; extern crate libloading; -use libloading::{Library}; -use ::std::{sync::Arc,collections::HashMap}; +use ::std::{collections::HashMap, sync::Arc}; +use libloading::Library; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType, PyRef, PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; #[derive(Copy, Clone)] pub enum Value { - Bool, - Char, - Wchar, + Bool, + Char, + Wchar, Byte, Ubyte, Short, @@ -38,43 +38,58 @@ pub struct ExternalFunctions { } impl ExternalFunctions { - pub fn call_function(&self, function: &str, arguments: Vec, vm: &VirtualMachine) -> PyResult { - match self.functions.get(function){ + pub fn call_function( + &self, + function: &str, + arguments: Vec, + vm: &VirtualMachine, + ) -> PyResult { + match self.functions.get(function) { Some(func_proxy) => func_proxy.call(arguments), - _ => Err(vm.new_runtime_error(format!("Function {} not found",function))) + _ => Err(vm.new_runtime_error(format!("Function {} not found", function))), } } - pub unsafe fn get_or_insert_lib(&mut self, library_path: String) -> Result<&Library,libloading::Error>{ - - let library = self.libraries.entry(library_path).or_insert({ - Arc::new(Library::new(library_path)?) - }); - + pub unsafe fn get_or_insert_lib( + &mut self, + library_path: String, + ) -> Result<&Library, libloading::Error> { + let library = self + .libraries + .entry(library_path) + .or_insert({ Arc::new(Library::new(library_path)?) }); + Ok(library.as_ref()) } - - pub fn get_or_insert_fn(&mut self, func_name: String, library_path: String, vm: &VirtualMachine) -> PyResult<&mut Arc>{ - let f_name = format!("{}_{}",library_path, func_name); + pub fn get_or_insert_fn( + &mut self, + func_name: String, + library_path: String, + vm: &VirtualMachine, + ) -> PyResult<&mut Arc> { + let f_name = format!("{}_{}", library_path, func_name); match self.libraries.get(&library_path) { Some(library) => Ok(self.functions.entry(f_name).or_insert({ - Arc::new(FunctionProxy {_name: f_name, _lib: *library}) + Arc::new(FunctionProxy { + _name: f_name, + _lib: *library, + }) })), - _ => Err(vm.new_runtime_error(format!("Library of path {} not found",library_path))) + _ => Err(vm.new_runtime_error(format!("Library of path {} not found", library_path))), } } } lazy_static::lazy_static! { pub static ref FUNCTIONS: ExternalFunctions = ExternalFunctions { - functions:HashMap::new(), + functions:HashMap::new(), libraries:HashMap::new() }; } -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct FunctionProxy { _name: String, _lib: Arc, @@ -84,7 +99,6 @@ impl FunctionProxy { // fn call(&self, args: Vec) -> PyResult { // } - } impl PyValue for FunctionProxy { @@ -93,8 +107,6 @@ impl PyValue for FunctionProxy { } } - - #[pyclass(module = false, name = "_CDataObject")] #[derive(Debug)] pub struct CDataObject; @@ -107,10 +119,9 @@ impl PyValue for CDataObject { } } - #[pyimpl(flags(BASETYPE))] -impl CDataObject{ +impl CDataObject { // A lot of the logic goes in this trait // There's also other traits that should have different implementations for some functions // present here -} \ No newline at end of file +} diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 80365a5dce..fcaee2999e 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,14 +1,14 @@ -use crate::builtins::tuple::PyTupleRef; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; -use crate::pyobject::{PyObjectRef, PyResult, PyValue, PyRef}; +use crate::builtins::tuple::PyTupleRef; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue}; use crate::VirtualMachine; +use crate::stdlib::ctypes::common::FUNCTIONS; use crate::stdlib::ctypes::function::PyCFuncPtr; -use crate::stdlib::ctypes::common::{FUNCTIONS}; #[derive(Debug)] -struct SharedLibrary { +pub struct SharedLibrary { lib: &'static libloading::Library, } @@ -19,22 +19,24 @@ impl PyValue for SharedLibrary { } pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let library = unsafe { FUNCTIONS.get_or_insert_lib(lib_path.to_string()).expect("Failed to load library")}; + let library = unsafe { + FUNCTIONS + .get_or_insert_lib(lib_path.to_string()) + .expect("Failed to load library") + }; - let shared_lib = SharedLibrary { lib : library }; + let shared_lib = SharedLibrary { lib: library }; Ok(vm.new_pyobj(shared_lib)) } - -// pub fn dlsym(handle: PyObjectRef, func_name: PyStrRef, argtypes: Option, restype:Option, vm: &VirtualMachine) -> PyResult { -// if let Some(slib) = handle.payload::() { -// unsafe { -// match slib.lib.get(func_name.as_ref().as_bytes()) { -// Ok(func) => return Ok(vm.new_pyobj(PyCFuncPtr::new(*func))), -// Err(e) => return Ok(vm.ctx.none()), -// } -// } -// } -// Ok(vm.ctx.none()) -// } +pub fn dlsym( + slib: &SharedLibrary, + func_name: PyStrRef, +) -> Result { + unsafe { + slib.lib + .get(func_name.as_ref().as_bytes()) + .map(|func| *func) + } +} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 396a92386c..f06940b6e3 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,23 +1,33 @@ -use libloading::{Library}; -use ::std::{sync::Arc}; +use ::std::sync::Arc; +use libloading::Library; -use crate::builtins::PyTypeRef; -use crate::builtins::tuple::PyTupleRef; use crate::builtins::list::PyListRef; use crate::builtins::pystr::{PyStr, PyStrRef}; -use crate::pyobject::{PyValue, StaticType, PyRef, PyResult, PyObjectRef}; +use crate::builtins::tuple::PyTupleRef; +use crate::builtins::PyTypeRef; +use crate::function::FuncArgs; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{CDataObject,FunctionProxy}; +use crate::stdlib::ctypes::common::{CDataObject, FunctionProxy}; + +use crate::slots::Callable; +use crate::stdlib::ctypes::dll::{dlsym, SharedLibrary}; +extern crate libffi; + +use libffi::middle::*; + +use std::borrow::Borrow; #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { - __name__: Option, + _name_: Option, _argtypes_: Vec>, _restype_: Option>, + _callable_: Option, ext_func: Option, } @@ -27,21 +37,18 @@ impl PyValue for PyCFuncPtr { } } -#[pyimpl] +#[pyimpl(with(Callable), flags(BASETYPE))] impl PyCFuncPtr { - // If a subclass fill the parameters, construct new objects with it - // Else use default - - // #[inline] - // pub fn new() -> PyCFuncPtr { - // PyCFuncPtr { - // __name__: None, - // _argtypes_: Vec::new(), - // _restype_: None, - // ext_func: None, - // } - - // } + #[inline] + pub fn new() -> PyCFuncPtr { + PyCFuncPtr { + _name_: None, + _argtypes_: Vec::new(), + _restype_: None, + _callable_: None, + ext_func: None, + } + } // #[pyproperty] // pub fn __name__(&self) -> PyResult { @@ -57,7 +64,7 @@ impl PyCFuncPtr { // pub fn argtypes(&self) -> Option { // // I think this needs to return a tuple reference to the objects that have CDataObject implementations // // This kind off also wrong in the CPython's way, they allow classes with _as_parameter_ object attribute... - // convert_to_tuple(self._argtypes_) + // convert_to_tuple(self._argtypes_) // } // #[pyproperty(setter)] @@ -69,7 +76,7 @@ impl PyCFuncPtr { // self._argtypes_ = args; // } else { // // Exception here - // // Err(vm.new_value_error("")) + // // Err(vm.new_value_error("")) // } // } @@ -79,7 +86,7 @@ impl PyCFuncPtr { // #[pyproperty] // pub fn restype(&self) -> Option { - // convert_to_tuple(self._restype_) + // convert_to_tuple(self._restype_) // } // #[pyproperty(setter)] @@ -87,23 +94,75 @@ impl PyCFuncPtr { // self._restype_ = convert_from_tuple(restype) // } - #[pymethod(name = "__call__")] - pub fn call(&self) { - if self.__name__.is_none() { - // Exception here - // Err(vm.new_value_error("")) + // #[pymethod(name = "__call__")] + // pub fn call(&self) { + + // } + + // @TODO: Needs to check and implement other forms of new + #[pyslot] + fn tp_new( + cls: PyTypeRef, + func_name: PyStrRef, + dll: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + Self::from_dll(cls, func_name, dll, vm) + } + + /// Returns a PyCFuncPtr from a Python DLL object + /// # Arguments + /// + /// * `func_name` - A string that names the function symbol + /// * `dll` - A Python object with _handle attribute of tpye SharedLibrary + /// + fn from_dll( + cls: PyTypeRef, + func_name: PyStrRef, + dll: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + if let Ok(h) = vm.get_attribute(dll.clone(), "_handle") { + if let Some(handle) = h.payload::() { + if let Ok(ext_func) = dlsym(handle, func_name) { + return PyCFuncPtr { + _name_: None, + _argtypes_: Vec::new(), + _restype_: None, + _callable_: Some(ext_func), + ext_func: None, + } + .into_ref_with_type(vm, cls); + } + } } - if self._argtypes_.len() == 0 { - // Exception here - // Err(vm.new_value_error("")) + PyCFuncPtr { + _name_: None, + _argtypes_: Vec::new(), + _restype_: None, + _callable_: None, + ext_func: None, } + .into_ref_with_type(vm, cls) + } +} - if self._restype_.is_none() { - // Exception here - // Err(vm.new_value_error("")) +impl Callable for PyCFuncPtr { + // @TODO: Build args e result before calling. + fn call(zelf: &PyRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let c_args = vec![Type::pointer(), Type::c_int()]; + let cif = Cif::new(c_args.into_iter(), Type::c_int()); + + unsafe { + if let Some(ext_func) = zelf._callable_ { + let n: *mut std::ffi::c_void = cif.call( + CodePtr(ext_func as *mut _), + &[arg(&String::from("Hello")), arg(&2)], + ); + } } - // Make the function call here + Ok(vm.ctx.none()) } -} \ No newline at end of file +} diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index cee10f0987..fc57cca79d 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -2,25 +2,24 @@ use crate::pyobject::PyClassImpl; use crate::pyobject::PyObjectRef; use crate::VirtualMachine; -mod dll; mod basics; mod common; +mod dll; mod function; mod primitive; -use crate::stdlib::ctypes::dll::*; use crate::stdlib::ctypes::basics::*; +use crate::stdlib::ctypes::dll::*; use crate::stdlib::ctypes::function::*; use crate::stdlib::ctypes::primitive::*; - pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; py_module!(vm, "_ctypes", { "dlopen" => ctx.new_function(dlopen), - "dlsym" => ctx.new_function(dlsym), - + // "dlsym" => ctx.new_function(dlsym), + "CFuncPtr" => PyCFuncPtr::make_class(ctx), "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PySimpleType::make_class(ctx) diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 5dfdfe91b1..daa08dfc6d 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,13 +1,12 @@ -use crate::builtins::PyTypeRef; use crate::builtins::pystr::PyStrRef; -use crate::pyobject::{PyValue, StaticType, PyResult}; +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyResult, PyValue, StaticType}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::CDataObject; const SIMPLE_TYPE_CHARS: &'static str = "cbBhHiIlLdfuzZqQP?g"; - #[pyclass(module = "_ctypes", name = "_SimpleCData", base = "CDataObject")] #[derive(Debug)] pub struct PySimpleType { @@ -23,26 +22,24 @@ impl PyValue for PySimpleType { #[pyimpl] impl PySimpleType { #[inline] - pub fn new(_type: PyStrRef,vm: &VirtualMachine) -> PyResult { + pub fn new(_type: PyStrRef, vm: &VirtualMachine) -> PyResult { // Needs to force the existence of _type_ // Does it need to be here? // Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) - - let s_type = _type.to_string(); - - if s_type.len() != 1{ - Err(vm.new_attribute_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) - } - else { - - if SIMPLE_TYPE_CHARS.contains(s_type.as_str()){ - Ok(PySimpleType {_type_ : _type}) - } + let s_type = _type.to_string(); - else { + if s_type.len() != 1 { + Err(vm.new_attribute_error( + "class must define a '_type_' attribute which must be a string of length 1" + .to_string(), + )) + } else { + if SIMPLE_TYPE_CHARS.contains(s_type.as_str()) { + Ok(PySimpleType { _type_: _type }) + } else { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of '{}'.",SIMPLE_TYPE_CHARS))) - } + } } } -} \ No newline at end of file +} From b4ddf4db211962605634b9ffee1606a2637fd3d4 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Wed, 4 Nov 2020 18:56:16 -0300 Subject: [PATCH 07/56] Adding basic functions to call libffi-rs --- vm/src/stdlib/ctypes/basics.rs | 9 +- vm/src/stdlib/ctypes/common.rs | 130 +++++++++------- vm/src/stdlib/ctypes/dll.rs | 39 +++-- vm/src/stdlib/ctypes/function.rs | 241 +++++++++++++++++------------- vm/src/stdlib/ctypes/primitive.rs | 4 +- 5 files changed, 239 insertions(+), 184 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 27ed3607bb..e00c7fff3c 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -6,7 +6,9 @@ use crate::stdlib::ctypes::common::CDataObject; #[pyclass(module = "_ctypes", name = "_CData", base = "CDataObject")] #[derive(Debug)] -pub struct PyCData {} +pub struct PyCData { + _type_: String, +} impl PyValue for PyCData { fn class(vm: &VirtualMachine) -> &PyTypeRef { @@ -16,11 +18,6 @@ impl PyValue for PyCData { #[pyimpl] impl PyCData { - #[inline] - pub fn new() -> PyCData { - PyCData {} - } - #[pymethod(name = "__init__")] fn init(&self, vm: &VirtualMachine) -> PyResult<()> { Ok(()) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 58df216a08..07c60b6709 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -1,35 +1,40 @@ extern crate lazy_static; +extern crate libffi; extern crate libloading; use ::std::{collections::HashMap, sync::Arc}; +use libffi::{low, middle}; use libloading::Library; +use crate::builtins::pystr::PyStrRef; use crate::builtins::PyTypeRef; use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; -#[derive(Copy, Clone)] -pub enum Value { - Bool, - Char, - Wchar, - Byte, - Ubyte, - Short, - UShort, - Int, - UInt, - Long, - LongLong, - ULong, - ULL, - SizeT, - SsizeT, - Float, - Double, - // LongDoudle(...) ???, - CharP, - WcharP, - Void, + +pub const SIMPLE_TYPE_CHARS: &'static str = "cbBhHiIlLdfuzZqQP?g"; + +pub fn convert_type(ty: &str) -> middle::Type { + match ty { + "?" => middle::Type::c_uchar(), + "c" => middle::Type::c_schar(), + "u" => middle::Type::c_int(), + "b" => middle::Type::i8(), + "B" => middle::Type::c_uchar(), + "h" => middle::Type::c_ushort(), + "H" => middle::Type::u16(), + "i" => middle::Type::c_int(), + "I" => middle::Type::c_uint(), + "l" => middle::Type::c_long(), + "q" => middle::Type::c_longlong(), + "L" => middle::Type::c_ulong(), + "Q" => middle::Type::c_ulonglong(), + "f" => middle::Type::f32(), + "d" => middle::Type::f64(), + "g" => middle::Type::longdouble(), + "z" => middle::Type::pointer(), + "Z" => middle::Type::pointer(), + "P" => middle::Type::void(), + } } pub struct ExternalFunctions { @@ -38,47 +43,35 @@ pub struct ExternalFunctions { } impl ExternalFunctions { - pub fn call_function( - &self, - function: &str, - arguments: Vec, - vm: &VirtualMachine, - ) -> PyResult { - match self.functions.get(function) { - Some(func_proxy) => func_proxy.call(arguments), - _ => Err(vm.new_runtime_error(format!("Function {} not found", function))), - } - } - pub unsafe fn get_or_insert_lib( &mut self, library_path: String, - ) -> Result<&Library, libloading::Error> { + ) -> Result<&mut Arc, libloading::Error> { let library = self .libraries .entry(library_path) - .or_insert({ Arc::new(Library::new(library_path)?) }); + .or_insert(Arc::new(Library::new(library_path)?)); - Ok(library.as_ref()) + Ok(library) } pub fn get_or_insert_fn( &mut self, func_name: String, library_path: String, + library: Arc, vm: &VirtualMachine, - ) -> PyResult<&mut Arc> { + ) -> PyResult> { let f_name = format!("{}_{}", library_path, func_name); - match self.libraries.get(&library_path) { - Some(library) => Ok(self.functions.entry(f_name).or_insert({ - Arc::new(FunctionProxy { - _name: f_name, - _lib: *library, - }) - })), - _ => Err(vm.new_runtime_error(format!("Library of path {} not found", library_path))), - } + Ok(self + .functions + .entry(f_name) + .or_insert(Arc::new(FunctionProxy { + _name: f_name, + _lib: library, + })) + .clone()) } } @@ -96,9 +89,40 @@ pub struct FunctionProxy { } impl FunctionProxy { - // fn call(&self, args: Vec) -> PyResult { + #[inline] + pub fn get_name(&self) -> String { + return self._name; + } + + #[inline] + pub fn get_lib(&self) -> &Library { + self._lib.as_ref() + } - // } + pub fn call( + &self, + c_args: Vec, + restype: Option, + arg_vec: Vec, + ptr_fn: Option<*const i32>, + vm: &VirtualMachine, + ) { + let cas_ret = restype + .and_then(|r| Some(r.to_string().as_str())) + .unwrap_or("P"); + + let cif = middle::Cif::new(c_args.into_iter(), convert_type(cas_ret)); + + if ptr_fn.is_some() { + // Here it needs a type to return + unsafe { + cif.call( + middle::CodePtr::from_ptr(ptr_fn.unwrap() as *const _ as *const libc::c_void), + arg_vec.as_slice(), + ) + } + } + } } impl PyValue for FunctionProxy { @@ -109,13 +133,11 @@ impl PyValue for FunctionProxy { #[pyclass(module = false, name = "_CDataObject")] #[derive(Debug)] -pub struct CDataObject; - -pub type CDataObjectRef = PyRef; +pub struct CDataObject {} impl PyValue for CDataObject { fn class(vm: &VirtualMachine) -> &PyTypeRef { - Self::static_type() + Self::init_bare_type() } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index fcaee2999e..d0e9c63f59 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,15 +1,26 @@ +use ::std::sync::Arc; + use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; -use crate::builtins::tuple::PyTupleRef; -use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyObjectRef, PyResult, PyValue}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::FUNCTIONS; -use crate::stdlib::ctypes::function::PyCFuncPtr; #[derive(Debug)] pub struct SharedLibrary { - lib: &'static libloading::Library, + _name: String, + lib: &'static mut Arc, +} + +impl SharedLibrary { + pub fn get_name(&self) -> String { + self._name + } + + pub fn get_lib(&self) -> Arc { + self.lib.clone() + } } impl PyValue for SharedLibrary { @@ -25,18 +36,16 @@ pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult .expect("Failed to load library") }; - let shared_lib = SharedLibrary { lib: library }; - - Ok(vm.new_pyobj(shared_lib)) + Ok(vm.new_pyobj(SharedLibrary { + _name: lib_path.to_string(), + lib: library, + })) } pub fn dlsym( - slib: &SharedLibrary, - func_name: PyStrRef, -) -> Result { - unsafe { - slib.lib - .get(func_name.as_ref().as_bytes()) - .map(|func| *func) - } + slib: &libloading::Library, + func_name: String, +) -> Result<*const i32, libloading::Error> { + // This need some tweaks + unsafe { slib.get(func_name.as_bytes())?.into_raw() as *const _ } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index f06940b6e3..8fe49114ba 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,34 +1,30 @@ +extern crate libffi; + use ::std::sync::Arc; -use libloading::Library; -use crate::builtins::list::PyListRef; use crate::builtins::pystr::{PyStr, PyStrRef}; -use crate::builtins::tuple::PyTupleRef; use crate::builtins::PyTypeRef; use crate::function::FuncArgs; -use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol}; +use crate::pyobject::{ + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol, +}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{CDataObject, FunctionProxy}; +use crate::stdlib::ctypes::common::{ + convert_type, CDataObject, FunctionProxy, FUNCTIONS, SIMPLE_TYPE_CHARS, +}; use crate::slots::Callable; use crate::stdlib::ctypes::dll::{dlsym, SharedLibrary}; -extern crate libffi; - -use libffi::middle::*; - -use std::borrow::Borrow; - #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { - _name_: Option, - _argtypes_: Vec>, - _restype_: Option>, - _callable_: Option, - ext_func: Option, + _name_: String, + _argtypes_: Vec, + _restype_: Option, + _callable_: Arc, } impl PyValue for PyCFuncPtr { @@ -39,130 +35,163 @@ impl PyValue for PyCFuncPtr { #[pyimpl(with(Callable), flags(BASETYPE))] impl PyCFuncPtr { - #[inline] - pub fn new() -> PyCFuncPtr { - PyCFuncPtr { - _name_: None, - _argtypes_: Vec::new(), - _restype_: None, - _callable_: None, - ext_func: None, + #[pyproperty(name = "_argtypes_")] + fn get_argtypes(&self) -> PyObjectRef { + self._argtypes_ + } + + #[pyproperty(name = "_restype_")] + fn get_restype(&self) -> PyObjectRef { + self._restype_ + } + + #[pyproperty(name = "_argtypes_", setter)] + fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if vm.isinstance(&argtypes, &vm.ctx.types.list_type).is_ok() + || vm.isinstance(&argtypes, &vm.ctx.types.tuple_type).is_ok() + { + let args: Vec = vm.extract_elements(&argtypes).unwrap(); + + let c_args: Result, _> = args + .iter() + .enumerate() + .map(|(idx, inner_obj)| { + match vm.isinstance(inner_obj, CDataObject::static_type()) { + Ok(bollean) => match vm.get_attribute(*inner_obj, "_type_") { + Ok(_type_) + if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => + { + Ok(_type_) + } + Ok(_type_) => { + Err(vm.new_attribute_error("invalid _type_ value".to_string())) + } + Err(e) => { + Err(vm.new_attribute_error("atribute _type_ not found".to_string())) + } + }, + Err(exception) => Err(vm.new_type_error(format!( + "object at {} is not an instance of _CDataObject, type {} found", + idx, + inner_obj.class() + ))), + } + }) + .collect(); + + self._argtypes_.clear(); + self._argtypes_ + .extend(c_args?.iter().map(|obj| obj.to_string().as_ref())); + + Ok(vm.ctx.none()) + } else { + Err(vm.new_type_error(format!( + "argtypes must be Tuple or List, {} found.", + argtypes.class() + ))) } } - // #[pyproperty] - // pub fn __name__(&self) -> PyResult { - // self.__name__.into() - // } - - // #[pyproperty(setter)] - // pub fn set___name__(&self, _name: PyStrRef) { - // self.__name__ = _name.to_string(); - // } - - // #[pyproperty] - // pub fn argtypes(&self) -> Option { - // // I think this needs to return a tuple reference to the objects that have CDataObject implementations - // // This kind off also wrong in the CPython's way, they allow classes with _as_parameter_ object attribute... - // convert_to_tuple(self._argtypes_) - // } - - // #[pyproperty(setter)] - // pub fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) { - // if vm.isinstance(argtypes, PyListRef) || vm.isinstance(argtypes, PyTupleRef) { - // let args: Vec> = vm.extract_elements(argtypes).into_iter().filter(|obj|vm.isinstance(obj, CDataObjectRef)).collect(); - - // if args.len() > 0 { - // self._argtypes_ = args; - // } else { - // // Exception here - // // Err(vm.new_value_error("")) - // } - // } - - // // Exception here - // // Err(vm.new_type_error("")) - // } - - // #[pyproperty] - // pub fn restype(&self) -> Option { - // convert_to_tuple(self._restype_) - // } - - // #[pyproperty(setter)] - // pub fn set_restype(&self, restype: PyTupleRef) { - // self._restype_ = convert_from_tuple(restype) - // } - - // #[pymethod(name = "__call__")] - // pub fn call(&self) { - - // } + #[pyproperty(name = "_restype_", setter)] + fn set_restype(restype: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match vm.isinstance(&restype, CDataObject::static_type()) { + Ok(bollean) => match vm.get_attribute(restype, "_type_") { + Ok(_type_) if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => Ok(_type_), + Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), + Err(e) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), + }, + Err(exception) => Err(vm.new_type_error(format!( + "value is not an instance of _CDataObject, type {} found", + restype.class() + ))), + } + } // @TODO: Needs to check and implement other forms of new #[pyslot] fn tp_new( cls: PyTypeRef, func_name: PyStrRef, - dll: PyObjectRef, + arg: PyObjectRef, vm: &VirtualMachine, ) -> PyResult> { - Self::from_dll(cls, func_name, dll, vm) + match vm.get_attribute(cls.as_object().to_owned(), "_argtypes_") { + Ok(inner) => Self::from_dll(cls, func_name, arg, vm), + Err(e) => Err(vm.new_type_error( + "cannot construct instance of this class: no argtypes".to_string(), + )), + } } /// Returns a PyCFuncPtr from a Python DLL object /// # Arguments /// /// * `func_name` - A string that names the function symbol - /// * `dll` - A Python object with _handle attribute of tpye SharedLibrary + /// * `dll` - A Python object with _handle attribute of type SharedLibrary /// fn from_dll( cls: PyTypeRef, func_name: PyStrRef, - dll: PyObjectRef, + arg: PyObjectRef, vm: &VirtualMachine, ) -> PyResult> { - if let Ok(h) = vm.get_attribute(dll.clone(), "_handle") { + if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Some(handle) = h.payload::() { - if let Ok(ext_func) = dlsym(handle, func_name) { - return PyCFuncPtr { - _name_: None, - _argtypes_: Vec::new(), - _restype_: None, - _callable_: Some(ext_func), - ext_func: None, - } - .into_ref_with_type(vm, cls); + PyCFuncPtr { + _name_: func_name.to_string(), + _argtypes_: Vec::new(), + _restype_: None, + _callable_: FUNCTIONS.get_or_insert_fn( + func_name.to_string(), + handle.get_name(), + handle.get_lib(), + vm, + )?, } + .into_ref_with_type(vm, cls) + } else { + Err(vm.new_type_error(format!( + "_handle must be SharedLibrary not {}", + arg.class().name + ))) } + } else { + Err(vm.new_attribute_error( + "positional argument 2 must have _handle attribute".to_string(), + )) } - - PyCFuncPtr { - _name_: None, - _argtypes_: Vec::new(), - _restype_: None, - _callable_: None, - ext_func: None, - } - .into_ref_with_type(vm, cls) } } impl Callable for PyCFuncPtr { // @TODO: Build args e result before calling. fn call(zelf: &PyRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let c_args = vec![Type::pointer(), Type::c_int()]; - let cif = Cif::new(c_args.into_iter(), Type::c_int()); - - unsafe { - if let Some(ext_func) = zelf._callable_ { - let n: *mut std::ffi::c_void = cif.call( - CodePtr(ext_func as *mut _), - &[arg(&String::from("Hello")), arg(&2)], - ); - } + if args.args.len() != zelf._argtypes_.len() { + return Err(vm.new_runtime_error(format!( + "invalid number of arguments, required {}, but {} found", + zelf._argtypes_.len(), + args.args.len() + ))); } - Ok(vm.ctx.none()) + // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ + // Something similar to the set of _argtypes_ + // arg_vec = ... + + // This is not optimal, but I can't simply store a vector of middle::Type inside PyCFuncPtr + let c_args = zelf + ._argtypes_ + .iter() + .map(|str_type| convert_type(str_type.to_string().as_str())) + .collect(); + + let ptr_fn = dlsym(zelf._callable_.get_lib(), zelf._callable_.get_name()).ok(); + let ret = zelf + ._callable_ + .call(c_args, zelf._restype_, arg_vec, ptr_fn, vm); + + // Needs to convert ret back to an object + + Ok(vm.new_pyobj(ret)) } } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index daa08dfc6d..416c92fb2d 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -3,9 +3,7 @@ use crate::builtins::PyTypeRef; use crate::pyobject::{PyResult, PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::CDataObject; - -const SIMPLE_TYPE_CHARS: &'static str = "cbBhHiIlLdfuzZqQP?g"; +use crate::stdlib::ctypes::common::{CDataObject, SIMPLE_TYPE_CHARS}; #[pyclass(module = "_ctypes", name = "_SimpleCData", base = "CDataObject")] #[derive(Debug)] From 9177473c757a97db91bfcf14ba144e9e18b40c25 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveira Date: Thu, 5 Nov 2020 02:19:42 -0300 Subject: [PATCH 08/56] Fix compile errors --- vm/src/stdlib/ctypes/basics.rs | 4 +- vm/src/stdlib/ctypes/common.rs | 146 +++++++++++++----------------- vm/src/stdlib/ctypes/dll.rs | 60 +++++------- vm/src/stdlib/ctypes/function.rs | 89 +++++++++--------- vm/src/stdlib/ctypes/primitive.rs | 10 +- 5 files changed, 142 insertions(+), 167 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index e00c7fff3c..8ab4b6b32c 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -11,7 +11,7 @@ pub struct PyCData { } impl PyValue for PyCData { - fn class(vm: &VirtualMachine) -> &PyTypeRef { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() } } @@ -19,7 +19,7 @@ impl PyValue for PyCData { #[pyimpl] impl PyCData { #[pymethod(name = "__init__")] - fn init(&self, vm: &VirtualMachine) -> PyResult<()> { + fn init(&self, _vm: &VirtualMachine) -> PyResult<()> { Ok(()) } } diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 07c60b6709..add38f631c 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,16 +2,18 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, sync::Arc}; -use libffi::{low, middle}; +use ::std::{collections::HashMap}; + +use libffi::middle; use libloading::Library; -use crate::builtins::pystr::PyStrRef; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType}; +use crate::pyobject::{PyValue, StaticType}; +use crate::common::lock::PyRwLock; +use crate::common::rc::PyRc; use crate::VirtualMachine; -pub const SIMPLE_TYPE_CHARS: &'static str = "cbBhHiIlLdfuzZqQP?g"; +pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; pub fn convert_type(ty: &str) -> middle::Type { match ty { @@ -33,110 +35,90 @@ pub fn convert_type(ty: &str) -> middle::Type { "g" => middle::Type::longdouble(), "z" => middle::Type::pointer(), "Z" => middle::Type::pointer(), - "P" => middle::Type::void(), + "P" | _ => middle::Type::void(), } } -pub struct ExternalFunctions { - functions: HashMap>, - libraries: HashMap>, +pub fn lib_call( + c_args: Vec, + restype: middle::Type, + arg_vec: Vec, + ptr_fn: Option<*const i32>, + _vm: &VirtualMachine, +) { + + let cif = middle::Cif::new(c_args.into_iter(), restype); + + if ptr_fn.is_some() { + // Here it needs a type to return + unsafe { + cif.call( + middle::CodePtr::from_ptr(ptr_fn.unwrap() as *const _ as *const libc::c_void), + arg_vec.as_slice(), + ) + } + } } -impl ExternalFunctions { - pub unsafe fn get_or_insert_lib( - &mut self, - library_path: String, - ) -> Result<&mut Arc, libloading::Error> { - let library = self - .libraries - .entry(library_path) - .or_insert(Arc::new(Library::new(library_path)?)); +#[pyclass(module = false, name = "SharedLibrary")] +#[derive(Debug)] +pub struct SharedLibrary { + _name: String, + lib: Library, +} - Ok(library) +impl SharedLibrary { + pub fn get_name(&self) -> &String { + &self._name } - pub fn get_or_insert_fn( - &mut self, - func_name: String, - library_path: String, - library: Arc, - vm: &VirtualMachine, - ) -> PyResult> { - let f_name = format!("{}_{}", library_path, func_name); - - Ok(self - .functions - .entry(f_name) - .or_insert(Arc::new(FunctionProxy { - _name: f_name, - _lib: library, - })) - .clone()) + pub fn get_lib(&self) -> &Library { + &self.lib } } -lazy_static::lazy_static! { - pub static ref FUNCTIONS: ExternalFunctions = ExternalFunctions { - functions:HashMap::new(), - libraries:HashMap::new() - }; +impl PyValue for SharedLibrary { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + &vm.ctx.types.object_type + } } -#[derive(Debug, Clone)] -pub struct FunctionProxy { - _name: String, - _lib: Arc, +pub struct ExternalFunctions { + libraries: HashMap>, } -impl FunctionProxy { - #[inline] - pub fn get_name(&self) -> String { - return self._name; - } - - #[inline] - pub fn get_lib(&self) -> &Library { - self._lib.as_ref() +impl ExternalFunctions { + pub fn new() -> Self { + Self { + libraries: HashMap::new() + } } - pub fn call( - &self, - c_args: Vec, - restype: Option, - arg_vec: Vec, - ptr_fn: Option<*const i32>, - vm: &VirtualMachine, - ) { - let cas_ret = restype - .and_then(|r| Some(r.to_string().as_str())) - .unwrap_or("P"); - - let cif = middle::Cif::new(c_args.into_iter(), convert_type(cas_ret)); - - if ptr_fn.is_some() { - // Here it needs a type to return - unsafe { - cif.call( - middle::CodePtr::from_ptr(ptr_fn.unwrap() as *const _ as *const libc::c_void), - arg_vec.as_slice(), - ) - } - } + pub unsafe fn get_or_insert_lib( + &mut self, + library_path: &str, + ) -> Result, libloading::Error> { + let library = self + .libraries + .entry(library_path.to_string()) + .or_insert(PyRc::new(SharedLibrary{_name:library_path.to_string(), + lib:(Library::new(library_path)?)})); + + Ok(library.clone()) } } -impl PyValue for FunctionProxy { - fn class(vm: &VirtualMachine) -> &PyTypeRef { - &vm.ctx.types.object_type - } +lazy_static::lazy_static! { + pub static ref FUNCTIONS: PyRwLock = PyRwLock::new(ExternalFunctions::new()); } + #[pyclass(module = false, name = "_CDataObject")] #[derive(Debug)] pub struct CDataObject {} impl PyValue for CDataObject { - fn class(vm: &VirtualMachine) -> &PyTypeRef { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::init_bare_type() } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index d0e9c63f59..d07e38e2f7 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,51 +1,41 @@ -use ::std::sync::Arc; +extern crate libloading; +use crate::common::rc::PyRc; use crate::builtins::pystr::PyStrRef; -use crate::builtins::pytype::PyTypeRef; -use crate::pyobject::{PyObjectRef, PyResult, PyValue}; +use crate::pyobject::{PyRef, PyObjectRef, PyResult}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::FUNCTIONS; +use crate::stdlib::ctypes::common::{SharedLibrary,FUNCTIONS}; -#[derive(Debug)] -pub struct SharedLibrary { - _name: String, - lib: &'static mut Arc, -} - -impl SharedLibrary { - pub fn get_name(&self) -> String { - self._name - } - - pub fn get_lib(&self) -> Arc { - self.lib.clone() - } -} - -impl PyValue for SharedLibrary { - fn class(vm: &VirtualMachine) -> &PyTypeRef { - &vm.ctx.types.object_type - } -} pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { let library = unsafe { FUNCTIONS - .get_or_insert_lib(lib_path.to_string()) + .write() + .get_or_insert_lib(lib_path.as_ref()) .expect("Failed to load library") }; - Ok(vm.new_pyobj(SharedLibrary { - _name: lib_path.to_string(), - lib: library, - })) + let box_arc = Box::new(PyRc::as_ptr(&library)); + let f_lib = unsafe { box_arc.read() }; + Ok(vm.new_pyobj(f_lib)) } pub fn dlsym( - slib: &libloading::Library, - func_name: String, -) -> Result<*const i32, libloading::Error> { - // This need some tweaks - unsafe { slib.get(func_name.as_bytes())?.into_raw() as *const _ } + slib: PyRef, + func_name: PyStrRef, + vm: &VirtualMachine +) -> PyResult<*const i32> { + + let ptr_res = unsafe { + slib.get_lib() + .get(func_name.as_ref().as_bytes()) + .map(|f| *f) + }; + + if ptr_res.is_err() { + Err(vm.new_runtime_error(format!("Error while opening symbol {}",func_name.as_ref()))) + } else { + Ok(ptr_res.unwrap()) + } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 8fe49114ba..923cae1055 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,34 +1,34 @@ extern crate libffi; -use ::std::sync::Arc; - -use crate::builtins::pystr::{PyStr, PyStrRef}; -use crate::builtins::PyTypeRef; +use ::std::{mem}; +use crate::builtins::pystr::{PyStr,PyStrRef}; +use crate::builtins::{PyTypeRef}; +use crate::common::rc::PyRc; use crate::function::FuncArgs; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol, }; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{ - convert_type, CDataObject, FunctionProxy, FUNCTIONS, SIMPLE_TYPE_CHARS, + lib_call, convert_type, CDataObject, SharedLibrary, SIMPLE_TYPE_CHARS }; use crate::slots::Callable; -use crate::stdlib::ctypes::dll::{dlsym, SharedLibrary}; +use crate::stdlib::ctypes::dll::{dlsym}; #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { _name_: String, - _argtypes_: Vec, - _restype_: Option, - _callable_: Arc, + _argtypes_: Vec, + _restype_: Box, + _handle: PyRef } impl PyValue for PyCFuncPtr { - fn class(vm: &VirtualMachine) -> &PyTypeRef { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() } } @@ -36,28 +36,28 @@ impl PyValue for PyCFuncPtr { #[pyimpl(with(Callable), flags(BASETYPE))] impl PyCFuncPtr { #[pyproperty(name = "_argtypes_")] - fn get_argtypes(&self) -> PyObjectRef { - self._argtypes_ + fn argtypes(&self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.new_list(self._argtypes_.clone()) } #[pyproperty(name = "_restype_")] - fn get_restype(&self) -> PyObjectRef { - self._restype_ + fn restype(&self, vm: &VirtualMachine) -> PyObjectRef { + PyStr::from(self._restype_.as_str()).into_object(vm) } #[pyproperty(name = "_argtypes_", setter)] - fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if vm.isinstance(&argtypes, &vm.ctx.types.list_type).is_ok() || vm.isinstance(&argtypes, &vm.ctx.types.tuple_type).is_ok() { let args: Vec = vm.extract_elements(&argtypes).unwrap(); - let c_args: Result, _> = args + let c_args: Result, _> = args .iter() .enumerate() .map(|(idx, inner_obj)| { match vm.isinstance(inner_obj, CDataObject::static_type()) { - Ok(bollean) => match vm.get_attribute(*inner_obj, "_type_") { + Ok(_) => match vm.get_attribute(inner_obj.clone(), "_type_") { Ok(_type_) if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { @@ -66,11 +66,11 @@ impl PyCFuncPtr { Ok(_type_) => { Err(vm.new_attribute_error("invalid _type_ value".to_string())) } - Err(e) => { + Err(_) => { Err(vm.new_attribute_error("atribute _type_ not found".to_string())) } }, - Err(exception) => Err(vm.new_type_error(format!( + Err(_) => Err(vm.new_type_error(format!( "object at {} is not an instance of _CDataObject, type {} found", idx, inner_obj.class() @@ -80,10 +80,10 @@ impl PyCFuncPtr { .collect(); self._argtypes_.clear(); - self._argtypes_ - .extend(c_args?.iter().map(|obj| obj.to_string().as_ref())); + self._argtypes_.extend(c_args?.into_iter()); + + Ok(()) - Ok(vm.ctx.none()) } else { Err(vm.new_type_error(format!( "argtypes must be Tuple or List, {} found.", @@ -93,14 +93,22 @@ impl PyCFuncPtr { } #[pyproperty(name = "_restype_", setter)] - fn set_restype(restype: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { match vm.isinstance(&restype, CDataObject::static_type()) { - Ok(bollean) => match vm.get_attribute(restype, "_type_") { - Ok(_type_) if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => Ok(_type_), + Ok(_) => match vm.get_attribute(restype, "_type_") { + Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? && + _type_.to_string().len() == 1 && + SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { + + let old = self._restype_.as_mut(); + let new = _type_.to_string(); + mem::replace(old, new); + Ok(()) + } Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), - Err(e) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), + Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), }, - Err(exception) => Err(vm.new_type_error(format!( + Err(_) => Err(vm.new_type_error(format!( "value is not an instance of _CDataObject, type {} found", restype.class() ))), @@ -116,8 +124,8 @@ impl PyCFuncPtr { vm: &VirtualMachine, ) -> PyResult> { match vm.get_attribute(cls.as_object().to_owned(), "_argtypes_") { - Ok(inner) => Self::from_dll(cls, func_name, arg, vm), - Err(e) => Err(vm.new_type_error( + Ok(_) => Self::from_dll(cls, func_name, arg, vm), + Err(_) => Err(vm.new_type_error( "cannot construct instance of this class: no argtypes".to_string(), )), } @@ -137,16 +145,12 @@ impl PyCFuncPtr { ) -> PyResult> { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Some(handle) = h.payload::() { + PyCFuncPtr { _name_: func_name.to_string(), _argtypes_: Vec::new(), - _restype_: None, - _callable_: FUNCTIONS.get_or_insert_fn( - func_name.to_string(), - handle.get_name(), - handle.get_lib(), - vm, - )?, + _restype_: Box::new("".to_string()), + _handle: handle.into_ref(vm) } .into_ref_with_type(vm, cls) } else { @@ -185,12 +189,13 @@ impl Callable for PyCFuncPtr { .map(|str_type| convert_type(str_type.to_string().as_str())) .collect(); - let ptr_fn = dlsym(zelf._callable_.get_lib(), zelf._callable_.get_name()).ok(); - let ret = zelf - ._callable_ - .call(c_args, zelf._restype_, arg_vec, ptr_fn, vm); + let arg_vec = Vec::new(); - // Needs to convert ret back to an object + let ret_type = convert_type(zelf._restype_.to_string().as_ref()); + + let name_py_ref = PyStr::from(&zelf._name_).into_ref(vm); + let ptr_fn = dlsym(zelf._handle.into_ref(vm), name_py_ref,vm).ok(); + let ret = lib_call(c_args, ret_type, arg_vec, ptr_fn, vm); Ok(vm.new_pyobj(ret)) } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 416c92fb2d..b9bd8084ee 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -12,7 +12,7 @@ pub struct PySimpleType { } impl PyValue for PySimpleType { - fn class(vm: &VirtualMachine) -> &PyTypeRef { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() } } @@ -32,12 +32,10 @@ impl PySimpleType { "class must define a '_type_' attribute which must be a string of length 1" .to_string(), )) + } else if SIMPLE_TYPE_CHARS.contains(s_type.as_str()) { + Ok(PySimpleType { _type_: _type }) } else { - if SIMPLE_TYPE_CHARS.contains(s_type.as_str()) { - Ok(PySimpleType { _type_: _type }) - } else { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of '{}'.",SIMPLE_TYPE_CHARS))) - } + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of '{}'.",SIMPLE_TYPE_CHARS))) } } } From 14b08b99fc3189d8a158c551ab4a3df6b4958b43 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 7 Nov 2020 11:54:53 -0300 Subject: [PATCH 09/56] Changing some pieces of SharedLibrary --- vm/src/stdlib/ctypes/common.rs | 32 ++++++++++++---------- vm/src/stdlib/ctypes/dll.rs | 46 ++++++++++++++------------------ vm/src/stdlib/ctypes/function.rs | 39 +++++++++++++-------------- vm/src/stdlib/ctypes/mod.rs | 2 +- 4 files changed, 57 insertions(+), 62 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index add38f631c..dc53779ecc 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,15 +2,15 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap}; +use ::std::collections::HashMap; use libffi::middle; use libloading::Library; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType}; use crate::common::lock::PyRwLock; use crate::common::rc::PyRc; +use crate::pyobject::{PyObjectRef, PyValue, StaticType}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -46,7 +46,6 @@ pub fn lib_call( ptr_fn: Option<*const i32>, _vm: &VirtualMachine, ) { - let cif = middle::Cif::new(c_args.into_iter(), restype); if ptr_fn.is_some() { @@ -60,16 +59,22 @@ pub fn lib_call( } } -#[pyclass(module = false, name = "SharedLibrary")] #[derive(Debug)] pub struct SharedLibrary { - _name: String, + path_name: String, lib: Library, } impl SharedLibrary { + pub fn new(name: &str) -> Result { + Ok(SharedLibrary { + path_name: name.to_string(), + lib: Library::new(name.to_string())?, + }) + } + pub fn get_name(&self) -> &String { - &self._name + &self.path_name } pub fn get_lib(&self) -> &Library { @@ -90,29 +95,28 @@ pub struct ExternalFunctions { impl ExternalFunctions { pub fn new() -> Self { Self { - libraries: HashMap::new() + libraries: HashMap::new(), } } pub unsafe fn get_or_insert_lib( &mut self, library_path: &str, - ) -> Result, libloading::Error> { + vm: &VirtualMachine, + ) -> Result { let library = self .libraries .entry(library_path.to_string()) - .or_insert(PyRc::new(SharedLibrary{_name:library_path.to_string(), - lib:(Library::new(library_path)?)})); - - Ok(library.clone()) + .or_insert(PyRc::new(SharedLibrary::new(library_path)?)); + + Ok(library.clone().into_object(vm)) } } lazy_static::lazy_static! { - pub static ref FUNCTIONS: PyRwLock = PyRwLock::new(ExternalFunctions::new()); + pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalFunctions::new()); } - #[pyclass(module = false, name = "_CDataObject")] #[derive(Debug)] pub struct CDataObject {} diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index d07e38e2f7..58e04c8ccc 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,41 +1,35 @@ extern crate libloading; -use crate::common::rc::PyRc; use crate::builtins::pystr::PyStrRef; -use crate::pyobject::{PyRef, PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{SharedLibrary,FUNCTIONS}; - +use crate::stdlib::ctypes::common::{SharedLibrary, CDATACACHE}; pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { let library = unsafe { - FUNCTIONS + CDATACACHE .write() - .get_or_insert_lib(lib_path.as_ref()) + .get_or_insert_lib(lib_path.as_ref(), vm) .expect("Failed to load library") }; - - let box_arc = Box::new(PyRc::as_ptr(&library)); - let f_lib = unsafe { box_arc.read() }; - Ok(vm.new_pyobj(f_lib)) + Ok(library) } -pub fn dlsym( - slib: PyRef, - func_name: PyStrRef, - vm: &VirtualMachine -) -> PyResult<*const i32> { - - let ptr_res = unsafe { - slib.get_lib() - .get(func_name.as_ref().as_bytes()) - .map(|f| *f) - }; - - if ptr_res.is_err() { - Err(vm.new_runtime_error(format!("Error while opening symbol {}",func_name.as_ref()))) - } else { - Ok(ptr_res.unwrap()) +pub fn dlsym(slib: PyObjectRef, func_name: PyStrRef, vm: &VirtualMachine) -> PyResult<*const i32> { + // cast to PyRef + match vm.cast(slib, SharedLibrary) { + Ok(lib) => { + let ptr_res = unsafe { lib.get_lib().get(func_name.as_ref().as_bytes()).map(|f| *f) }; + if ptr_res.is_err() { + Err(vm.new_runtime_error(format!( + "Error while opening symbol {}", + func_name.as_ref() + ))) + } else { + Ok(ptr_res.unwrap()) + } + } + Err(_) => Err(vm.new_value_error("argument slib is not a valid SharedLibrary".to_string())), } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 923cae1055..da74d2335e 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,30 +1,28 @@ extern crate libffi; -use ::std::{mem}; +use ::std::mem; -use crate::builtins::pystr::{PyStr,PyStrRef}; -use crate::builtins::{PyTypeRef}; +use crate::builtins::pystr::{PyStr, PyStrRef}; +use crate::builtins::PyTypeRef; use crate::common::rc::PyRc; use crate::function::FuncArgs; -use crate::pyobject::{ - PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol, -}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{ - lib_call, convert_type, CDataObject, SharedLibrary, SIMPLE_TYPE_CHARS + convert_type, lib_call, CDataObject, SharedLibrary, SIMPLE_TYPE_CHARS, }; use crate::slots::Callable; -use crate::stdlib::ctypes::dll::{dlsym}; +use crate::stdlib::ctypes::dll::dlsym; #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { - _name_: String, - _argtypes_: Vec, - _restype_: Box, - _handle: PyRef + pub _name_: String, + pub _argtypes_: Vec, + pub _restype_: Box, + _handle: PyRef, } impl PyValue for PyCFuncPtr { @@ -83,7 +81,6 @@ impl PyCFuncPtr { self._argtypes_.extend(c_args?.into_iter()); Ok(()) - } else { Err(vm.new_type_error(format!( "argtypes must be Tuple or List, {} found.", @@ -96,10 +93,11 @@ impl PyCFuncPtr { fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { match vm.isinstance(&restype, CDataObject::static_type()) { Ok(_) => match vm.get_attribute(restype, "_type_") { - Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? && - _type_.to_string().len() == 1 && - SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { - + Ok(_type_) + if vm.isinstance(&_type_, &vm.ctx.types.str_type)? + && _type_.to_string().len() == 1 + && SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => + { let old = self._restype_.as_mut(); let new = _type_.to_string(); mem::replace(old, new); @@ -145,12 +143,11 @@ impl PyCFuncPtr { ) -> PyResult> { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Some(handle) = h.payload::() { - PyCFuncPtr { _name_: func_name.to_string(), _argtypes_: Vec::new(), _restype_: Box::new("".to_string()), - _handle: handle.into_ref(vm) + _handle: handle.into_ref(vm), } .into_ref_with_type(vm, cls) } else { @@ -192,9 +189,9 @@ impl Callable for PyCFuncPtr { let arg_vec = Vec::new(); let ret_type = convert_type(zelf._restype_.to_string().as_ref()); - + let name_py_ref = PyStr::from(&zelf._name_).into_ref(vm); - let ptr_fn = dlsym(zelf._handle.into_ref(vm), name_py_ref,vm).ok(); + let ptr_fn = dlsym(zelf._handle.into_ref(vm), name_py_ref, vm).ok(); let ret = lib_call(c_args, ret_type, arg_vec, ptr_fn, vm); Ok(vm.new_pyobj(ret)) diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index fc57cca79d..54216650f1 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -17,7 +17,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; py_module!(vm, "_ctypes", { - "dlopen" => ctx.new_function(dlopen), + // "dlopen" => ctx.new_function(dlopen), // "dlsym" => ctx.new_function(dlsym), "CFuncPtr" => PyCFuncPtr::make_class(ctx), From 8adf4dd008faa18ab42b23e253d6c0ec74106243 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 8 Nov 2020 15:10:15 -0300 Subject: [PATCH 10/56] Fixing some ref problems in functions.rs --- vm/src/stdlib/ctypes/common.rs | 51 +++++++++++------------- vm/src/stdlib/ctypes/dll.rs | 38 +++++++++++------- vm/src/stdlib/ctypes/function.rs | 67 ++++++++++++++++++-------------- 3 files changed, 85 insertions(+), 71 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index dc53779ecc..ba050a63eb 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -10,18 +10,16 @@ use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::common::rc::PyRc; -use crate::pyobject::{PyObjectRef, PyValue, StaticType}; +use crate::pyobject::{PyObjectRc, PyValue, StaticType}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; pub fn convert_type(ty: &str) -> middle::Type { match ty { - "?" => middle::Type::c_uchar(), "c" => middle::Type::c_schar(), "u" => middle::Type::c_int(), "b" => middle::Type::i8(), - "B" => middle::Type::c_uchar(), "h" => middle::Type::c_ushort(), "H" => middle::Type::u16(), "i" => middle::Type::c_int(), @@ -33,8 +31,8 @@ pub fn convert_type(ty: &str) -> middle::Type { "f" => middle::Type::f32(), "d" => middle::Type::f64(), "g" => middle::Type::longdouble(), - "z" => middle::Type::pointer(), - "Z" => middle::Type::pointer(), + "?" | "B" => middle::Type::c_uchar(), + "z" | "Z" => middle::Type::pointer(), "P" | _ => middle::Type::void(), } } @@ -58,13 +56,20 @@ pub fn lib_call( } } } - +#[pyclass(module = false, name = "SharedLibrary")] #[derive(Debug)] pub struct SharedLibrary { path_name: String, lib: Library, } +impl PyValue for SharedLibrary { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +#[pyimpl(flags(BASETYPE))] impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { @@ -73,18 +78,8 @@ impl SharedLibrary { }) } - pub fn get_name(&self) -> &String { - &self.path_name - } - - pub fn get_lib(&self) -> &Library { - &self.lib - } -} - -impl PyValue for SharedLibrary { - fn class(vm: &VirtualMachine) -> &PyTypeRef { - &vm.ctx.types.object_type + pub fn get_sym(&self, name: &str) -> Result<*const i32, libloading::Error> { + unsafe { self.lib.get(name.as_bytes()).map(|f| *f) } } } @@ -99,11 +94,11 @@ impl ExternalFunctions { } } - pub unsafe fn get_or_insert_lib( - &mut self, - library_path: &str, - vm: &VirtualMachine, - ) -> Result { + pub unsafe fn get_or_insert_lib<'a, 'b>( + &'b mut self, + library_path: &'a str, + vm: &'a VirtualMachine, + ) -> Result { let library = self .libraries .entry(library_path.to_string()) @@ -113,17 +108,13 @@ impl ExternalFunctions { } } -lazy_static::lazy_static! { - pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalFunctions::new()); -} - #[pyclass(module = false, name = "_CDataObject")] #[derive(Debug)] pub struct CDataObject {} impl PyValue for CDataObject { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::init_bare_type() + Self::static_metaclass() } } @@ -133,3 +124,7 @@ impl CDataObject { // There's also other traits that should have different implementations for some functions // present here } + +lazy_static::lazy_static! { + pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalFunctions::new()); +} diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 58e04c8ccc..4d18426dc5 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,33 +1,45 @@ extern crate libloading; use crate::builtins::pystr::PyStrRef; -use crate::pyobject::{PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, StaticType}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{SharedLibrary, CDATACACHE}; -pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let library = unsafe { +pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult { + // Match this error first + let lib_str_path = match vm.isinstance(&lib_path, &vm.ctx.types.str_type) { + Ok(_) => Ok(lib_path.to_string()), + Err(e) => Err(e), + }?; + + let result = unsafe { CDATACACHE .write() - .get_or_insert_lib(lib_path.as_ref(), vm) - .expect("Failed to load library") + .get_or_insert_lib(lib_str_path.as_ref(), vm) }; - Ok(library) + + match result { + Ok(lib) => Ok(lib), + Err(_) => Err(vm.new_os_error(format!( + "{} : cannot open shared object file: No such file or directory", + lib_path.to_string() + ))), + } } -pub fn dlsym(slib: PyObjectRef, func_name: PyStrRef, vm: &VirtualMachine) -> PyResult<*const i32> { - // cast to PyRef - match vm.cast(slib, SharedLibrary) { +pub fn dlsym(slib: PyObjectRc, func_name: PyStrRef, vm: &VirtualMachine) -> PyResult<*const i32> { + // match vm.isinstance(&slib, &SharedLibrary::static_type()) { + match slib.downcast::() { Ok(lib) => { - let ptr_res = unsafe { lib.get_lib().get(func_name.as_ref().as_bytes()).map(|f| *f) }; - if ptr_res.is_err() { + if let Ok(ptr) = lib.get_sym(func_name.as_ref()) { + Ok(ptr) + } else { + // @TODO: Change this error message Err(vm.new_runtime_error(format!( "Error while opening symbol {}", func_name.as_ref() ))) - } else { - Ok(ptr_res.unwrap()) } } Err(_) => Err(vm.new_value_error("argument slib is not a valid SharedLibrary".to_string())), diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index da74d2335e..6dda58b332 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,12 +1,11 @@ extern crate libffi; -use ::std::mem; - use crate::builtins::pystr::{PyStr, PyStrRef}; use crate::builtins::PyTypeRef; -use crate::common::rc::PyRc; +use crate::common::lock::PyRwLock; + use crate::function::FuncArgs; -use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, StaticType, TypeProtocol}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{ @@ -20,9 +19,9 @@ use crate::stdlib::ctypes::dll::dlsym; #[derive(Debug)] pub struct PyCFuncPtr { pub _name_: String, - pub _argtypes_: Vec, - pub _restype_: Box, - _handle: PyRef, + pub _argtypes_: PyRwLock>, + pub _restype_: PyRwLock, + _handle: PyObjectRc, } impl PyValue for PyCFuncPtr { @@ -35,12 +34,12 @@ impl PyValue for PyCFuncPtr { impl PyCFuncPtr { #[pyproperty(name = "_argtypes_")] fn argtypes(&self, vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.new_list(self._argtypes_.clone()) + vm.ctx.new_list(self._argtypes_.read().clone()) } #[pyproperty(name = "_restype_")] fn restype(&self, vm: &VirtualMachine) -> PyObjectRef { - PyStr::from(self._restype_.as_str()).into_object(vm) + PyStr::from(self._restype_.read().as_str()).into_object(vm) } #[pyproperty(name = "_argtypes_", setter)] @@ -68,23 +67,23 @@ impl PyCFuncPtr { Err(vm.new_attribute_error("atribute _type_ not found".to_string())) } }, + // @TODO: Needs to return the name of the type, not String(inner_obj) Err(_) => Err(vm.new_type_error(format!( "object at {} is not an instance of _CDataObject, type {} found", idx, - inner_obj.class() + inner_obj.to_string() ))), } }) .collect(); - self._argtypes_.clear(); - self._argtypes_.extend(c_args?.into_iter()); - + self._argtypes_.write().clear(); + self._argtypes_.write().extend(c_args?.into_iter()); Ok(()) } else { Err(vm.new_type_error(format!( "argtypes must be Tuple or List, {} found.", - argtypes.class() + argtypes.to_string() ))) } } @@ -98,17 +97,25 @@ impl PyCFuncPtr { && _type_.to_string().len() == 1 && SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { - let old = self._restype_.as_mut(); - let new = _type_.to_string(); - mem::replace(old, new); + // SAFETY: Values in _type_ are valid utf-8 + unsafe { + self._restype_.write().as_mut_vec().clear(); + + self._restype_ + .write() + .as_mut_vec() + .extend(_type_.to_string().as_mut_vec().iter()) + }; + Ok(()) } Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), }, + // @TODO: Needs to return the name of the type, not String(inner_obj) Err(_) => Err(vm.new_type_error(format!( "value is not an instance of _CDataObject, type {} found", - restype.class() + restype.to_string() ))), } } @@ -142,18 +149,19 @@ impl PyCFuncPtr { vm: &VirtualMachine, ) -> PyResult> { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { - if let Some(handle) = h.payload::() { + if let Ok(handle) = h.downcast::() { PyCFuncPtr { _name_: func_name.to_string(), - _argtypes_: Vec::new(), - _restype_: Box::new("".to_string()), - _handle: handle.into_ref(vm), + _argtypes_: PyRwLock::new(Vec::new()), + _restype_: PyRwLock::new("".to_string()), + _handle: handle.into_object().clone(), } .into_ref_with_type(vm, cls) } else { + // @TODO: Needs to return the name of the type, not String(inner_obj) Err(vm.new_type_error(format!( "_handle must be SharedLibrary not {}", - arg.class().name + arg.to_string() ))) } } else { @@ -165,12 +173,11 @@ impl PyCFuncPtr { } impl Callable for PyCFuncPtr { - // @TODO: Build args e result before calling. fn call(zelf: &PyRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - if args.args.len() != zelf._argtypes_.len() { + if args.args.len() != zelf._argtypes_.read().len() { return Err(vm.new_runtime_error(format!( "invalid number of arguments, required {}, but {} found", - zelf._argtypes_.len(), + zelf._argtypes_.read().len(), args.args.len() ))); } @@ -178,20 +185,20 @@ impl Callable for PyCFuncPtr { // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ // Something similar to the set of _argtypes_ // arg_vec = ... + let arg_vec = Vec::new(); // This is not optimal, but I can't simply store a vector of middle::Type inside PyCFuncPtr let c_args = zelf ._argtypes_ + .read() .iter() .map(|str_type| convert_type(str_type.to_string().as_str())) .collect(); - let arg_vec = Vec::new(); - - let ret_type = convert_type(zelf._restype_.to_string().as_ref()); + let ret_type = convert_type(zelf._restype_.read().as_ref()); let name_py_ref = PyStr::from(&zelf._name_).into_ref(vm); - let ptr_fn = dlsym(zelf._handle.into_ref(vm), name_py_ref, vm).ok(); + let ptr_fn = dlsym(zelf._handle.clone(), name_py_ref, vm).ok(); let ret = lib_call(c_args, ret_type, arg_vec, ptr_fn, vm); Ok(vm.new_pyobj(ret)) From 4341c97ddbfd061156a2f50b8b60f2eb312b76d1 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveira Date: Tue, 10 Nov 2020 13:20:01 -0300 Subject: [PATCH 11/56] Cahnge PyRc to PyRef in data cache --- vm/src/stdlib/ctypes/common.rs | 13 +++++++------ vm/src/stdlib/ctypes/dll.rs | 12 +++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index ba050a63eb..78ec73a3b2 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -10,7 +10,7 @@ use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::common::rc::PyRc; -use crate::pyobject::{PyObjectRc, PyValue, StaticType}; +use crate::pyobject::{PyObjectRc, PyValue, StaticType, PyRef}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -56,6 +56,7 @@ pub fn lib_call( } } } + #[pyclass(module = false, name = "SharedLibrary")] #[derive(Debug)] pub struct SharedLibrary { @@ -84,7 +85,7 @@ impl SharedLibrary { } pub struct ExternalFunctions { - libraries: HashMap>, + pub libraries: HashMap>, } impl ExternalFunctions { @@ -94,17 +95,17 @@ impl ExternalFunctions { } } - pub unsafe fn get_or_insert_lib<'a, 'b>( + pub fn get_or_insert_lib<'a, 'b>( &'b mut self, library_path: &'a str, vm: &'a VirtualMachine, - ) -> Result { + ) -> Result<&PyRef, libloading::Error> { let library = self .libraries .entry(library_path.to_string()) - .or_insert(PyRc::new(SharedLibrary::new(library_path)?)); + .or_insert(SharedLibrary::new(library_path)?.into_ref(vm)); - Ok(library.clone().into_object(vm)) + Ok(library) } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 4d18426dc5..3664a8a98f 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,7 +1,7 @@ extern crate libloading; use crate::builtins::pystr::PyStrRef; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, StaticType}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, PyValue}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{SharedLibrary, CDATACACHE}; @@ -13,14 +13,12 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult Err(e), }?; - let result = unsafe { - CDATACACHE - .write() - .get_or_insert_lib(lib_str_path.as_ref(), vm) - }; + let mut data_cache = CDATACACHE.write(); + + let result = data_cache.get_or_insert_lib(lib_str_path.as_ref(), vm); match result { - Ok(lib) => Ok(lib), + Ok(lib) => Ok(lib.clone().into_object()), Err(_) => Err(vm.new_os_error(format!( "{} : cannot open shared object file: No such file or directory", lib_path.to_string() From eb8541c91fc514694c0f36c38b6df30ac221d258 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Tue, 10 Nov 2020 15:32:27 -0300 Subject: [PATCH 12/56] Fixing arg type casting --- vm/src/stdlib/ctypes/common.rs | 18 +++---- vm/src/stdlib/ctypes/dll.rs | 19 +++++--- vm/src/stdlib/ctypes/function.rs | 82 +++++++++++++++++++------------- vm/src/stdlib/ctypes/mod.rs | 4 +- 4 files changed, 73 insertions(+), 50 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 78ec73a3b2..fa314e4cfa 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -9,8 +9,7 @@ use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::common::rc::PyRc; -use crate::pyobject::{PyObjectRc, PyValue, StaticType, PyRef}; +use crate::pyobject::{PyValue, StaticType, PyRef, PyObjectRef}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -41,19 +40,23 @@ pub fn lib_call( c_args: Vec, restype: middle::Type, arg_vec: Vec, - ptr_fn: Option<*const i32>, + wrapped_ptr: Option, _vm: &VirtualMachine, -) { +) -> Option { let cif = middle::Cif::new(c_args.into_iter(), restype); - if ptr_fn.is_some() { + if wrapped_ptr.is_some() { // Here it needs a type to return unsafe { + let ptr_fn = &wrapped_ptr.unwrap() as *const _ as *const isize; + cif.call( - middle::CodePtr::from_ptr(ptr_fn.unwrap() as *const _ as *const libc::c_void), + middle::CodePtr::from_ptr(ptr_fn as *const libc::c_void), arg_vec.as_slice(), ) } + } else { + None } } @@ -70,7 +73,6 @@ impl PyValue for SharedLibrary { } } -#[pyimpl(flags(BASETYPE))] impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { @@ -79,7 +81,7 @@ impl SharedLibrary { }) } - pub fn get_sym(&self, name: &str) -> Result<*const i32, libloading::Error> { + pub fn get_sym(&self, name: &str) -> Result<*const isize, libloading::Error> { unsafe { self.lib.get(name.as_bytes()).map(|f| *f) } } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 3664a8a98f..dfd4993212 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,7 +1,7 @@ extern crate libloading; -use crate::builtins::pystr::PyStrRef; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, PyValue}; +use crate::builtins::pystr::{PyStr}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{SharedLibrary, CDATACACHE}; @@ -26,17 +26,24 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult PyResult<*const i32> { +pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResult { // match vm.isinstance(&slib, &SharedLibrary::static_type()) { + if !vm.isinstance(&func, &vm.ctx.types.str_type)? { + return Err(vm.new_value_error("argument func_name must be str".to_string())); + } + + let func_name = func.downcast::().unwrap().as_ref(); + match slib.downcast::() { Ok(lib) => { - if let Ok(ptr) = lib.get_sym(func_name.as_ref()) { - Ok(ptr) + if let Ok(ptr) = lib.get_sym(func_name) { + Ok(vm.new_pyobj(ptr as isize)) + } else { // @TODO: Change this error message Err(vm.new_runtime_error(format!( "Error while opening symbol {}", - func_name.as_ref() + func_name ))) } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 6dda58b332..aedcbb3d2d 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,5 +1,7 @@ extern crate libffi; +use libffi::middle::Arg; + use crate::builtins::pystr::{PyStr, PyStrRef}; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; @@ -15,6 +17,36 @@ use crate::stdlib::ctypes::common::{ use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; +fn map_types_to_res(args: &Vec, vm: &VirtualMachine) -> PyResult>{ + args + .iter() + .enumerate() + .map(|(idx, inner_obj)| { + match vm.isinstance(inner_obj, CDataObject::static_type()) { + Ok(_) => match vm.get_attribute(inner_obj.clone(), "_type_") { + Ok(_type_) + if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => + { + Ok(_type_) + } + Ok(_type_) => { + Err(vm.new_attribute_error("invalid _type_ value".to_string())) + } + Err(_) => { + Err(vm.new_attribute_error("atribute _type_ not found".to_string())) + } + }, + // @TODO: Needs to return the name of the type, not String(inner_obj) + Err(_) => Err(vm.new_type_error(format!( + "object at {} is not an instance of _CDataObject, type {} found", + idx, + inner_obj.to_string() + ))), + } + }) + .collect() +} + #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] #[derive(Debug)] pub struct PyCFuncPtr { @@ -49,37 +81,12 @@ impl PyCFuncPtr { { let args: Vec = vm.extract_elements(&argtypes).unwrap(); - let c_args: Result, _> = args - .iter() - .enumerate() - .map(|(idx, inner_obj)| { - match vm.isinstance(inner_obj, CDataObject::static_type()) { - Ok(_) => match vm.get_attribute(inner_obj.clone(), "_type_") { - Ok(_type_) - if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => - { - Ok(_type_) - } - Ok(_type_) => { - Err(vm.new_attribute_error("invalid _type_ value".to_string())) - } - Err(_) => { - Err(vm.new_attribute_error("atribute _type_ not found".to_string())) - } - }, - // @TODO: Needs to return the name of the type, not String(inner_obj) - Err(_) => Err(vm.new_type_error(format!( - "object at {} is not an instance of _CDataObject, type {} found", - idx, - inner_obj.to_string() - ))), - } - }) - .collect(); + let c_args = map_types_to_res(&args,vm); self._argtypes_.write().clear(); self._argtypes_.write().extend(c_args?.into_iter()); Ok(()) + } else { Err(vm.new_type_error(format!( "argtypes must be Tuple or List, {} found.", @@ -183,9 +190,13 @@ impl Callable for PyCFuncPtr { } // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ - // Something similar to the set of _argtypes_ - // arg_vec = ... - let arg_vec = Vec::new(); + let arg_vec = map_types_to_res(&args.args, vm)? + .iter() + .map(|arg| + Arg::new(&convert_type(vm.get_attribute(arg.clone(), "_type_").unwrap().to_string().as_ref())) + ) + .collect(); + // This is not optimal, but I can't simply store a vector of middle::Type inside PyCFuncPtr let c_args = zelf @@ -196,11 +207,14 @@ impl Callable for PyCFuncPtr { .collect(); let ret_type = convert_type(zelf._restype_.read().as_ref()); - - let name_py_ref = PyStr::from(&zelf._name_).into_ref(vm); + + let name_py_ref = PyStr::from(&zelf._name_).into_object(vm); let ptr_fn = dlsym(zelf._handle.clone(), name_py_ref, vm).ok(); let ret = lib_call(c_args, ret_type, arg_vec, ptr_fn, vm); - - Ok(vm.new_pyobj(ret)) + + match ret { + Some(value) => Ok(vm.new_pyobj(value)), + _ => Ok(vm.ctx.none()) + } } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 54216650f1..4e461e8eba 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -17,8 +17,8 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; py_module!(vm, "_ctypes", { - // "dlopen" => ctx.new_function(dlopen), - // "dlsym" => ctx.new_function(dlsym), + "dlopen" => ctx.new_function(dlopen), + "dlsym" => ctx.new_function(dlsym), "CFuncPtr" => PyCFuncPtr::make_class(ctx), "_CData" => PyCData::make_class(ctx), From e38b0a84c522026c9d5e35f7fc31f29ba3d6cd38 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 12 Nov 2020 18:00:20 -0300 Subject: [PATCH 13/56] Refactoring PyCFuncPtr --- vm/src/stdlib/ctypes/common.rs | 197 ++++++++++++++++++++++++------- vm/src/stdlib/ctypes/dll.rs | 16 ++- vm/src/stdlib/ctypes/function.rs | 133 +++++++++++---------- 3 files changed, 230 insertions(+), 116 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index fa314e4cfa..cdf9b0cb58 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,64 +2,167 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::collections::HashMap; +use ::std::{collections::HashMap, mem, os::raw::*}; +use libffi::low::{ + call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, types, CodePtr, + Error as FFIError, +}; use libffi::middle; + use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{PyValue, StaticType, PyRef, PyObjectRef}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyValue, StaticType}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; -pub fn convert_type(ty: &str) -> middle::Type { - match ty { - "c" => middle::Type::c_schar(), - "u" => middle::Type::c_int(), - "b" => middle::Type::i8(), - "h" => middle::Type::c_ushort(), - "H" => middle::Type::u16(), - "i" => middle::Type::c_int(), - "I" => middle::Type::c_uint(), - "l" => middle::Type::c_long(), - "q" => middle::Type::c_longlong(), - "L" => middle::Type::c_ulong(), - "Q" => middle::Type::c_ulonglong(), - "f" => middle::Type::f32(), - "d" => middle::Type::f64(), - "g" => middle::Type::longdouble(), - "?" | "B" => middle::Type::c_uchar(), - "z" | "Z" => middle::Type::pointer(), - "P" | _ => middle::Type::void(), +macro_rules! ffi_type { + ($name: ident) => { + middle::Type::$name().as_raw_ptr() + }; +} + +macro_rules! match_ffi_type { + ( + $pointer: expr, + + $( + $($type: ident)|+ => $body: expr + )+ + ) => { + match $pointer { + $( + $( + t if t == ffi_type!($type) => { $body } + )+ + )+ + _ => unreachable!() + } + }; + ( + $kind: expr, + + $( + $($type: tt)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { ffi_type!($body) } + )+ + )+ + _ => ffi_type!(void) + } } } -pub fn lib_call( - c_args: Vec, - restype: middle::Type, - arg_vec: Vec, - wrapped_ptr: Option, - _vm: &VirtualMachine, -) -> Option { - let cif = middle::Cif::new(c_args.into_iter(), restype); - - if wrapped_ptr.is_some() { - // Here it needs a type to return - unsafe { - let ptr_fn = &wrapped_ptr.unwrap() as *const _ as *const isize; +fn ffi_to_rust(ty: *mut ffi_type) -> NativeType { + match_ffi_type!( + ty, + c_schar => NativeType::Byte(ty as i8) + c_int => NativeType::Int(ty as i32) + c_short => NativeType::Short(ty as i16) + c_ushort => NativeType::UShort(ty as u16) + c_uint => NativeType::UInt(ty as u32) + c_long => NativeType::Long(ty as i64) + c_longlong => NativeType::LongLong(ty as i128) + c_ulong => NativeType::ULong(ty as u64) + c_ulonglong => NativeType::ULL(ty as u128) + f32 => NativeType::Float(unsafe{*(ty as *mut f32)}) + f64 => NativeType::Double(unsafe {*(ty as *mut f64)}) + longdouble => NativeType::LongDouble(unsafe {*(ty as *mut f64)}) + c_uchar => NativeType::UByte(ty as u8) + pointer => NativeType::Pointer(ty as *mut c_void) + void => NativeType::Void + ) +} + +fn str_to_type(ty: &str) -> *mut ffi_type { + match_ffi_type!( + ty, + "c" => c_schar + "u" => c_int + "b" => i8 + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => f32 + "d" => f64 + "g" => longdouble + "?" | "B" => c_uchar + "z" | "Z" => pointer + "P" => void + ) +} + +enum NativeType { + Byte(i8), + Short(i16), + UShort(u16), + Int(i32), + UInt(u32), + Long(i64), + LongLong(i128), + ULong(u64), + ULL(u128), + Float(f32), + Double(f64), + LongDouble(f64), + UByte(u8), + Pointer(*mut c_void), + Void, +} - cif.call( - middle::CodePtr::from_ptr(ptr_fn as *const libc::c_void), - arg_vec.as_slice(), - ) +#[derive(Debug)] +pub struct Function { + pointer: mem::MaybeUninit, + cif: ffi_cif, + arguments: Vec<*mut ffi_type>, + return_type: *mut ffi_type, +} + +impl Function { + pub fn new( + fn_ptr: mem::MaybeUninit, + arguments: Vec, + return_type: &str, + ) -> Function { + Function { + pointer: fn_ptr, + cif: Default::default(), + arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), + + return_type: str_to_type(return_type), } - } else { - None + } + pub fn set_args(&mut self, args: Vec) { + self.arguments = args.iter().map(|s| str_to_type(s.as_str())).collect(); + } + + pub fn set_ret(&mut self, ret: &str) { + self.return_type = str_to_type(ret); + } + + pub fn call( + &self, + arg_ptrs: Vec, + vm: &VirtualMachine, + ) -> Result { } } +unsafe impl Send for Function {} +unsafe impl Sync for Function {} + #[pyclass(module = false, name = "SharedLibrary")] #[derive(Debug)] pub struct SharedLibrary { @@ -81,16 +184,20 @@ impl SharedLibrary { }) } - pub fn get_sym(&self, name: &str) -> Result<*const isize, libloading::Error> { - unsafe { self.lib.get(name.as_bytes()).map(|f| *f) } + pub fn get_sym(&self, name: &str) -> Result<*mut c_void, libloading::Error> { + unsafe { + self.lib + .get(name.as_bytes()) + .map(|f: libloading::Symbol<*mut c_void>| *f) + } } } -pub struct ExternalFunctions { +pub struct ExternalLibs { pub libraries: HashMap>, } -impl ExternalFunctions { +impl ExternalLibs { pub fn new() -> Self { Self { libraries: HashMap::new(), @@ -129,5 +236,5 @@ impl CDataObject { } lazy_static::lazy_static! { - pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalFunctions::new()); + pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalLibs::new()); } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index dfd4993212..198fb1fa47 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,6 +1,8 @@ extern crate libloading; -use crate::builtins::pystr::{PyStr}; +use ::std::os::raw::c_void; + +use crate::builtins::pystr::PyStr; use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult}; use crate::VirtualMachine; @@ -30,21 +32,17 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul // match vm.isinstance(&slib, &SharedLibrary::static_type()) { if !vm.isinstance(&func, &vm.ctx.types.str_type)? { return Err(vm.new_value_error("argument func_name must be str".to_string())); - } - + } + let func_name = func.downcast::().unwrap().as_ref(); match slib.downcast::() { Ok(lib) => { if let Ok(ptr) = lib.get_sym(func_name) { - Ok(vm.new_pyobj(ptr as isize)) - + Ok(vm.new_pyobj(unsafe { &mut *ptr } as *const c_void as usize)) } else { // @TODO: Change this error message - Err(vm.new_runtime_error(format!( - "Error while opening symbol {}", - func_name - ))) + Err(vm.new_runtime_error(format!("Error while opening symbol {}", func_name))) } } Err(_) => Err(vm.new_value_error("argument slib is not a valid SharedLibrary".to_string())), diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index aedcbb3d2d..74d07e4477 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,6 +1,6 @@ extern crate libffi; -use libffi::middle::Arg; +use std::{mem, os::raw::c_void}; use crate::builtins::pystr::{PyStr, PyStrRef}; use crate::builtins::PyTypeRef; @@ -10,41 +10,32 @@ use crate::function::FuncArgs; use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{ - convert_type, lib_call, CDataObject, SharedLibrary, SIMPLE_TYPE_CHARS, -}; +use crate::stdlib::ctypes::common::{CDataObject, Function, SharedLibrary, SIMPLE_TYPE_CHARS}; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; -fn map_types_to_res(args: &Vec, vm: &VirtualMachine) -> PyResult>{ - args - .iter() - .enumerate() - .map(|(idx, inner_obj)| { - match vm.isinstance(inner_obj, CDataObject::static_type()) { - Ok(_) => match vm.get_attribute(inner_obj.clone(), "_type_") { - Ok(_type_) - if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => - { - Ok(_type_) - } - Ok(_type_) => { - Err(vm.new_attribute_error("invalid _type_ value".to_string())) - } - Err(_) => { - Err(vm.new_attribute_error("atribute _type_ not found".to_string())) - } - }, - // @TODO: Needs to return the name of the type, not String(inner_obj) - Err(_) => Err(vm.new_type_error(format!( - "object at {} is not an instance of _CDataObject, type {} found", - idx, - inner_obj.to_string() - ))), - } - }) - .collect() +fn map_types_to_res(args: &Vec, vm: &VirtualMachine) -> PyResult> { + args.iter() + .enumerate() + .map(|(idx, inner_obj)| { + match vm.isinstance(inner_obj, CDataObject::static_type()) { + Ok(_) => match vm.get_attribute(inner_obj.clone(), "_type_") { + Ok(_type_) if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { + Ok(_type_) + } + Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), + Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), + }, + // @TODO: Needs to return the name of the type, not String(inner_obj) + Err(_) => Err(vm.new_type_error(format!( + "object at {} is not an instance of _CDataObject, type {} found", + idx, + inner_obj.to_string() + ))), + } + }) + .collect() } #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] @@ -52,8 +43,9 @@ fn map_types_to_res(args: &Vec, vm: &VirtualMachine) -> PyResult>, - pub _restype_: PyRwLock, + pub _restype_: PyRwLock>, _handle: PyObjectRc, + _f: PyRwLock>, } impl PyValue for PyCFuncPtr { @@ -71,7 +63,7 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_")] fn restype(&self, vm: &VirtualMachine) -> PyObjectRef { - PyStr::from(self._restype_.read().as_str()).into_object(vm) + self._restype_.read().as_ref().clone() } #[pyproperty(name = "_argtypes_", setter)] @@ -81,12 +73,27 @@ impl PyCFuncPtr { { let args: Vec = vm.extract_elements(&argtypes).unwrap(); - let c_args = map_types_to_res(&args,vm); + let c_args = map_types_to_res(&args, vm); self._argtypes_.write().clear(); self._argtypes_.write().extend(c_args?.into_iter()); - Ok(()) + let fn_ptr = self._f.write().as_mut(); + + let str_types: Result, _> = c_args? + .iter() + .map(|obj| { + if let Ok(attr) = vm.get_attribute(obj.clone(), "_type_") { + Ok(attr.to_string()) + } else { + Err(()) + } + }) + .collect(); + + fn_ptr.set_args(str_types.unwrap()); + + Ok(()) } else { Err(vm.new_type_error(format!( "argtypes must be Tuple or List, {} found.", @@ -104,15 +111,12 @@ impl PyCFuncPtr { && _type_.to_string().len() == 1 && SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { - // SAFETY: Values in _type_ are valid utf-8 - unsafe { - self._restype_.write().as_mut_vec().clear(); + let dest = self._restype_.write().as_mut(); + let src = restype.clone(); + mem::replace(dest, src); - self._restype_ - .write() - .as_mut_vec() - .extend(_type_.to_string().as_mut_vec().iter()) - }; + let fn_ptr = self._f.write().as_mut(); + fn_ptr.set_ret(_type_.to_string().as_str()); Ok(()) } @@ -122,7 +126,7 @@ impl PyCFuncPtr { // @TODO: Needs to return the name of the type, not String(inner_obj) Err(_) => Err(vm.new_type_error(format!( "value is not an instance of _CDataObject, type {} found", - restype.to_string() + restype ))), } } @@ -160,8 +164,13 @@ impl PyCFuncPtr { PyCFuncPtr { _name_: func_name.to_string(), _argtypes_: PyRwLock::new(Vec::new()), - _restype_: PyRwLock::new("".to_string()), + _restype_: PyRwLock::new(Box::new(vm.ctx.none())), _handle: handle.into_object().clone(), + _f: PyRwLock::new(Box::new(Function::new( + mem::MaybeUninit::::uninit(), + Vec::new(), + "P", + ))), } .into_ref_with_type(vm, cls) } else { @@ -190,31 +199,31 @@ impl Callable for PyCFuncPtr { } // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ - let arg_vec = map_types_to_res(&args.args, vm)? - .iter() - .map(|arg| - Arg::new(&convert_type(vm.get_attribute(arg.clone(), "_type_").unwrap().to_string().as_ref())) - ) - .collect(); - + let arg_vec = map_types_to_res(&args.args, vm)?; // This is not optimal, but I can't simply store a vector of middle::Type inside PyCFuncPtr - let c_args = zelf + let c_args: Result, ()> = zelf ._argtypes_ .read() .iter() - .map(|str_type| convert_type(str_type.to_string().as_str())) + .map(|obj| { + if let Ok(attr) = vm.get_attribute(obj.clone(), "_type_") { + Ok(attr.to_string()) + } else { + Err(()) + } + }) .collect(); - let ret_type = convert_type(zelf._restype_.read().as_ref()); - + let ret_type = zelf._restype_.read().as_ref(); + let name_py_ref = PyStr::from(&zelf._name_).into_object(vm); let ptr_fn = dlsym(zelf._handle.clone(), name_py_ref, vm).ok(); - let ret = lib_call(c_args, ret_type, arg_vec, ptr_fn, vm); - - match ret { - Some(value) => Ok(vm.new_pyobj(value)), - _ => Ok(vm.ctx.none()) + + let res = zelf._f.read().call(arg_vec, vm); + + if let Ok(value) = res { + } else { } } } From c2df5dede89a615031838764561e386168cbaab4 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 12 Nov 2020 18:52:43 -0300 Subject: [PATCH 14/56] Moving dlsym to from_dll --- vm/src/stdlib/ctypes/common.rs | 8 ++++++-- vm/src/stdlib/ctypes/dll.rs | 2 +- vm/src/stdlib/ctypes/function.rs | 19 +++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index cdf9b0cb58..72c7d72cf9 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -124,7 +124,7 @@ enum NativeType { #[derive(Debug)] pub struct Function { - pointer: mem::MaybeUninit, + pointer: mem::MaybeUninit<*mut c_void>, cif: ffi_cif, arguments: Vec<*mut ffi_type>, return_type: *mut ffi_type, @@ -132,7 +132,7 @@ pub struct Function { impl Function { pub fn new( - fn_ptr: mem::MaybeUninit, + fn_ptr: mem::MaybeUninit<*mut c_void>, arguments: Vec, return_type: &str, ) -> Function { @@ -148,6 +148,10 @@ impl Function { self.arguments = args.iter().map(|s| str_to_type(s.as_str())).collect(); } + pub fn set_fn_ptr(&mut self, ptr: mem::MaybeUninit<*mut c_void>) { + self.pointer = ptr; + } + pub fn set_ret(&mut self, ret: &str) { self.return_type = str_to_type(ret); } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 198fb1fa47..c1509f080c 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -39,7 +39,7 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul match slib.downcast::() { Ok(lib) => { if let Ok(ptr) = lib.get_sym(func_name) { - Ok(vm.new_pyobj(unsafe { &mut *ptr } as *const c_void as usize)) + Ok(vm.new_pyobj(unsafe { &mut *ptr } as *const c_void as isize)) } else { // @TODO: Change this error message Err(vm.new_runtime_error(format!("Error while opening symbol {}", func_name))) diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 74d07e4477..0ca08021ca 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -161,15 +161,25 @@ impl PyCFuncPtr { ) -> PyResult> { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Ok(handle) = h.downcast::() { + let handle_obj = handle.into_object(); + let ptr_fn = dlsym(handle_obj.clone(), func_name.into_object(), vm).ok(); + + let fn_ptr = match ptr_fn { + Some(py_obj) => { + // Cast py_obj to isize and then to *mut c_void + } + _ => mem::MaybeUninit::<*mut c_void>::uninit(), + }; + PyCFuncPtr { _name_: func_name.to_string(), _argtypes_: PyRwLock::new(Vec::new()), _restype_: PyRwLock::new(Box::new(vm.ctx.none())), - _handle: handle.into_object().clone(), + _handle: handle_obj.clone(), _f: PyRwLock::new(Box::new(Function::new( - mem::MaybeUninit::::uninit(), + fn_ptr, Vec::new(), - "P", + "P", // put a default here ))), } .into_ref_with_type(vm, cls) @@ -217,9 +227,6 @@ impl Callable for PyCFuncPtr { let ret_type = zelf._restype_.read().as_ref(); - let name_py_ref = PyStr::from(&zelf._name_).into_object(vm); - let ptr_fn = dlsym(zelf._handle.clone(), name_py_ref, vm).ok(); - let res = zelf._f.read().call(arg_vec, vm); if let Ok(value) = res { From fa6b19b304afa6dde9959fe996fcd194f5b54423 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 13 Nov 2020 14:52:36 -0300 Subject: [PATCH 15/56] Adding proper *mut c_void casting --- vm/src/stdlib/ctypes/common.rs | 41 +++++++++++++++++++------------- vm/src/stdlib/ctypes/dll.rs | 20 ++++++++++------ vm/src/stdlib/ctypes/function.rs | 13 ++++------ 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 72c7d72cf9..65d66e7971 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -5,16 +5,15 @@ extern crate libloading; use ::std::{collections::HashMap, mem, os::raw::*}; use libffi::low::{ - call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, types, CodePtr, + call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, Error as FFIError, }; use libffi::middle; - use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyValue, StaticType}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyValue, StaticType, TryFromObject}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -124,18 +123,15 @@ enum NativeType { #[derive(Debug)] pub struct Function { - pointer: mem::MaybeUninit<*mut c_void>, + pointer: *mut c_void, cif: ffi_cif, arguments: Vec<*mut ffi_type>, return_type: *mut ffi_type, + // @TODO: Do we need to free the memory of these ffi_type? } impl Function { - pub fn new( - fn_ptr: mem::MaybeUninit<*mut c_void>, - arguments: Vec, - return_type: &str, - ) -> Function { + pub fn new(fn_ptr: *mut c_void, arguments: Vec, return_type: &str) -> Function { Function { pointer: fn_ptr, cif: Default::default(), @@ -148,10 +144,6 @@ impl Function { self.arguments = args.iter().map(|s| str_to_type(s.as_str())).collect(); } - pub fn set_fn_ptr(&mut self, ptr: mem::MaybeUninit<*mut c_void>) { - self.pointer = ptr; - } - pub fn set_ret(&mut self, ret: &str) { self.return_type = str_to_type(ret); } @@ -172,6 +164,7 @@ unsafe impl Sync for Function {} pub struct SharedLibrary { path_name: String, lib: Library, + is_open_g: Box, } impl PyValue for SharedLibrary { @@ -185,6 +178,7 @@ impl SharedLibrary { Ok(SharedLibrary { path_name: name.to_string(), lib: Library::new(name.to_string())?, + is_open_g: Box::new(true), }) } @@ -195,6 +189,19 @@ impl SharedLibrary { .map(|f: libloading::Symbol<*mut c_void>| *f) } } + + pub fn is_open(&self) -> bool { + self.is_open_g.as_ref().clone() + } + + pub fn close(&self) -> Result<(), libloading::Error> { + if let Err(e) = self.lib.close() { + Err(e) + } else { + mem::replace(self.is_open_g.as_mut(), false); + Ok(()) + } + } } pub struct ExternalLibs { @@ -208,10 +215,10 @@ impl ExternalLibs { } } - pub fn get_or_insert_lib<'a, 'b>( - &'b mut self, - library_path: &'a str, - vm: &'a VirtualMachine, + pub fn get_or_insert_lib( + &mut self, + library_path: &str, + vm: &VirtualMachine, ) -> Result<&PyRef, libloading::Error> { let library = self .libraries diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index c1509f080c..318a65741f 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,8 +1,7 @@ extern crate libloading; -use ::std::os::raw::c_void; - use crate::builtins::pystr::PyStr; +use crate::builtins::PyInt; use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult}; use crate::VirtualMachine; @@ -28,23 +27,30 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult PyResult { +pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResult { // match vm.isinstance(&slib, &SharedLibrary::static_type()) { if !vm.isinstance(&func, &vm.ctx.types.str_type)? { - return Err(vm.new_value_error("argument func_name must be str".to_string())); + return Err(vm.new_value_error("second argument (func) must be str".to_string())); } let func_name = func.downcast::().unwrap().as_ref(); match slib.downcast::() { Ok(lib) => { - if let Ok(ptr) = lib.get_sym(func_name) { - Ok(vm.new_pyobj(unsafe { &mut *ptr } as *const c_void as isize)) + if !lib.is_open() { + Err(vm.new_runtime_error(format!("Library is closed."))) + } else if let Ok(ptr) = lib.get_sym(func_name) { + Ok(PyInt::from(ptr as *const _ as usize)) } else { // @TODO: Change this error message Err(vm.new_runtime_error(format!("Error while opening symbol {}", func_name))) } } - Err(_) => Err(vm.new_value_error("argument slib is not a valid SharedLibrary".to_string())), + Err(_) => { + Err(vm + .new_value_error("first argument (slib) is not a valid SharedLibrary".to_string())) + } } } + +pub fn dlclose() {} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 0ca08021ca..0365771216 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -7,7 +7,9 @@ use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::function::FuncArgs; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType}; +use crate::pyobject::{ + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, +}; use crate::VirtualMachine; use crate::stdlib::ctypes::common::{CDataObject, Function, SharedLibrary, SIMPLE_TYPE_CHARS}; @@ -162,14 +164,9 @@ impl PyCFuncPtr { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Ok(handle) = h.downcast::() { let handle_obj = handle.into_object(); - let ptr_fn = dlsym(handle_obj.clone(), func_name.into_object(), vm).ok(); + let ptr_fn = dlsym(handle_obj.clone(), func_name.into_object(), vm)?; - let fn_ptr = match ptr_fn { - Some(py_obj) => { - // Cast py_obj to isize and then to *mut c_void - } - _ => mem::MaybeUninit::<*mut c_void>::uninit(), - }; + let fn_ptr = usize::try_from_object(vm, ptr_fn.into_object(vm))? as *mut c_void; PyCFuncPtr { _name_: func_name.to_string(), From 7d220c333afe2e3afcde0417ed013693568ac43b Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 13 Nov 2020 15:56:35 -0300 Subject: [PATCH 16/56] Adding 'reopen' lib --- vm/src/stdlib/ctypes/common.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 65d66e7971..5bcdc9301b 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -225,7 +225,20 @@ impl ExternalLibs { .entry(library_path.to_string()) .or_insert(SharedLibrary::new(library_path)?.into_ref(vm)); - Ok(library) + if !library.is_open() { + if let Some(l) = self.libraries.insert( + library_path.to_string(), + SharedLibrary::new(library_path)?.into_ref(vm), + ) { + // Ok(self.libraries.get_mut(library_path.to_string())) + Ok(&l) + } else { + // @TODO: What this error should be? + Err(libloading::Error::DlOpenUnknown) + } + } else { + Ok(library) + } } } From 0a9209576d9960102349cc0c14ed9f88fe718244 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 13 Nov 2020 19:28:58 -0300 Subject: [PATCH 17/56] Adding function call --- vm/src/stdlib/ctypes/common.rs | 157 +++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 48 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 5bcdc9301b..794c8a96cd 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -13,7 +13,7 @@ use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyValue, StaticType, TryFromObject}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -59,27 +59,6 @@ macro_rules! match_ffi_type { } } -fn ffi_to_rust(ty: *mut ffi_type) -> NativeType { - match_ffi_type!( - ty, - c_schar => NativeType::Byte(ty as i8) - c_int => NativeType::Int(ty as i32) - c_short => NativeType::Short(ty as i16) - c_ushort => NativeType::UShort(ty as u16) - c_uint => NativeType::UInt(ty as u32) - c_long => NativeType::Long(ty as i64) - c_longlong => NativeType::LongLong(ty as i128) - c_ulong => NativeType::ULong(ty as u64) - c_ulonglong => NativeType::ULL(ty as u128) - f32 => NativeType::Float(unsafe{*(ty as *mut f32)}) - f64 => NativeType::Double(unsafe {*(ty as *mut f64)}) - longdouble => NativeType::LongDouble(unsafe {*(ty as *mut f64)}) - c_uchar => NativeType::UByte(ty as u8) - pointer => NativeType::Pointer(ty as *mut c_void) - void => NativeType::Void - ) -} - fn str_to_type(ty: &str) -> *mut ffi_type { match_ffi_type!( ty, @@ -103,56 +82,138 @@ fn str_to_type(ty: &str) -> *mut ffi_type { ) } -enum NativeType { - Byte(i8), - Short(i16), - UShort(u16), - Int(i32), - UInt(u32), - Long(i64), - LongLong(i128), - ULong(u64), - ULL(u128), - Float(f32), - Double(f64), - LongDouble(f64), - UByte(u8), - Pointer(*mut c_void), - Void, -} - #[derive(Debug)] pub struct Function { - pointer: *mut c_void, + pointer: *const c_void, cif: ffi_cif, arguments: Vec<*mut ffi_type>, - return_type: *mut ffi_type, + return_type: Box<*mut ffi_type>, // @TODO: Do we need to free the memory of these ffi_type? } impl Function { - pub fn new(fn_ptr: *mut c_void, arguments: Vec, return_type: &str) -> Function { + pub fn new(fn_ptr: *const c_void, arguments: Vec, return_type: &str) -> Function { Function { pointer: fn_ptr, cif: Default::default(), arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), - return_type: str_to_type(return_type), + return_type: Box::new(str_to_type(return_type)), } } pub fn set_args(&mut self, args: Vec) { - self.arguments = args.iter().map(|s| str_to_type(s.as_str())).collect(); + self.arguments.clear(); + self.arguments + .extend(args.iter().map(|s| str_to_type(s.as_str()))); } pub fn set_ret(&mut self, ret: &str) { - self.return_type = str_to_type(ret); + mem::replace(self.return_type.as_mut(), str_to_type(ret)); } pub fn call( - &self, + &mut self, arg_ptrs: Vec, vm: &VirtualMachine, - ) -> Result { + ) -> PyResult { + let mut return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; + + let result = unsafe { + prep_cif( + &mut self.cif, + ABI, + self.arguments.len(), + return_type, + self.arguments.as_mut_ptr(), + ) + }; + + if let Err(FFIError::Typedef) = result { + return Err(vm.new_runtime_error( + "The type representation is invalid or unsupported".to_string(), + )); + } else if let Err(FFIError::Abi) = result { + return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); + } + + let cif_ptr = &self.cif as *const _ as *mut _; + let fun_ptr = CodePtr::from_ptr(self.pointer); + let mut args_ptr = self + .arguments + .iter_mut() + .map(|p: &mut *mut ffi_type| p as *mut _ as *mut c_void) + .collect() + .as_mut_ptr(); + + let ret_ptr = unsafe { + match_ffi_type!( + return_type, + c_schar => { + let r: c_schar = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i8) + } + c_int => { + let r: c_int = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i32) + } + c_short => { + let r: c_short = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i16) + } + c_ushort => { + let r: c_ushort = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u16) + } + c_uint => { + let r: c_uint = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u32) + } + c_long => { + let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u64) + } + c_longlong => { + let r: c_longlong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i64) + // vm.new_pyobj(r as i128) + } + c_ulong => { + let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u64) + } + c_ulonglong => { + let r: c_ulonglong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u64) + // vm.new_pyobj(r as u128) + } + f32 => { + let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as f32) + } + f64 => { + let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as f64) + } + longdouble => { + let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as f64) + } + c_uchar => { + let r: c_uchar = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u8) + } + pointer => { + let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as *const _ as usize) + } + void => { + let r: c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.ctx.none() + } + ) + }; + + Ok(ret_ptr) } } From 8ceffd9f327a5eefda6c929ab7f3758f7e4fd0d9 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 14 Nov 2020 16:16:15 -0300 Subject: [PATCH 18/56] Fixing clippy warnings --- vm/src/stdlib/ctypes/common.rs | 161 ++++++++++++++++++++----------- vm/src/stdlib/ctypes/dll.rs | 28 ++++-- vm/src/stdlib/ctypes/function.rs | 46 +++------ vm/src/stdlib/ctypes/mod.rs | 1 + 4 files changed, 139 insertions(+), 97 deletions(-) diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 794c8a96cd..efed6392e7 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,7 +2,7 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, mem, os::raw::*}; +use ::std::{collections::HashMap, mem, os::raw::*, ptr}; use libffi::low::{ call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, @@ -10,10 +10,13 @@ use libffi::low::{ }; use libffi::middle; use libloading::Library; +use num_bigint::BigInt; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType}; +use crate::pyobject::{ + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, +}; use crate::VirtualMachine; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; @@ -82,6 +85,60 @@ fn str_to_type(ty: &str) -> *mut ffi_type { ) } +fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> *mut c_void { + match_ffi_type!( + unsafe { *ty }, + c_schar => { + let mut r = i8::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + + } + c_int => { + let mut r = i32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_short => { + let mut r = i16::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_ushort => { + let mut r = u16::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_uint => { + let mut r = u32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + //@ TODO: Convert c*longlong from BigInt? + c_long | c_longlong => { + let mut r = i64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_ulong | c_ulonglong => { + let mut r = u64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + f32 => { + let mut r = f32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + f64 | longdouble=> { + let mut r = f64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_uchar => { + let mut r = u8::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + pointer => { + usize::try_from_object(vm, obj).unwrap() as *mut c_void + } + void => { + ptr::null_mut() + } + ) +} + #[derive(Debug)] pub struct Function { pointer: *const c_void, @@ -116,7 +173,7 @@ impl Function { arg_ptrs: Vec, vm: &VirtualMachine, ) -> PyResult { - let mut return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; + let return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; let result = unsafe { prep_cif( @@ -136,14 +193,18 @@ impl Function { return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); } + let mut argument_pointers: Vec<*mut c_void> = arg_ptrs + .iter() + .zip(self.arguments.iter_mut()) + .map(|(o, t)| { + let tt: *mut *mut ffi_type = t; + py_to_ffi(tt, o.clone(), vm) + }) + .collect(); + let cif_ptr = &self.cif as *const _ as *mut _; let fun_ptr = CodePtr::from_ptr(self.pointer); - let mut args_ptr = self - .arguments - .iter_mut() - .map(|p: &mut *mut ffi_type| p as *mut _ as *mut c_void) - .collect() - .as_mut_ptr(); + let args_ptr = argument_pointers.as_mut_ptr(); let ret_ptr = unsafe { match_ffi_type!( @@ -170,12 +231,11 @@ impl Function { } c_long => { let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u64) + vm.new_pyobj(r as i64) } c_longlong => { let r: c_longlong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as i64) - // vm.new_pyobj(r as i128) + vm.new_pyobj(BigInt::from(r as i128)) } c_ulong => { let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); @@ -183,18 +243,13 @@ impl Function { } c_ulonglong => { let r: c_ulonglong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u64) - // vm.new_pyobj(r as u128) + vm.new_pyobj(BigInt::from(r as u128)) } f32 => { let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as f32) } - f64 => { - let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as f64) - } - longdouble => { + f64 | longdouble => { let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as f64) } @@ -207,7 +262,6 @@ impl Function { vm.new_pyobj(r as *const _ as usize) } void => { - let r: c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.ctx.none() } ) @@ -224,8 +278,7 @@ unsafe impl Sync for Function {} #[derive(Debug)] pub struct SharedLibrary { path_name: String, - lib: Library, - is_open_g: Box, + lib: Option, } impl PyValue for SharedLibrary { @@ -238,35 +291,36 @@ impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { path_name: name.to_string(), - lib: Library::new(name.to_string())?, - is_open_g: Box::new(true), + lib: Some(Library::new(name.to_string())?), }) } - pub fn get_sym(&self, name: &str) -> Result<*mut c_void, libloading::Error> { + pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> { + let inner = if let Some(ref inner) = self.lib { + inner + } else { + return Err("The library has been closed".to_string()); + }; + unsafe { - self.lib + inner .get(name.as_bytes()) .map(|f: libloading::Symbol<*mut c_void>| *f) + .map_err(|err| err.to_string()) } } - pub fn is_open(&self) -> bool { - self.is_open_g.as_ref().clone() + pub fn is_closed(&self) -> bool { + self.lib.is_none() } - pub fn close(&self) -> Result<(), libloading::Error> { - if let Err(e) = self.lib.close() { - Err(e) - } else { - mem::replace(self.is_open_g.as_mut(), false); - Ok(()) - } + pub fn close(&mut self) { + drop(self.lib.take()); } } pub struct ExternalLibs { - pub libraries: HashMap>, + libraries: HashMap>, } impl ExternalLibs { @@ -281,25 +335,24 @@ impl ExternalLibs { library_path: &str, vm: &VirtualMachine, ) -> Result<&PyRef, libloading::Error> { - let library = self - .libraries - .entry(library_path.to_string()) - .or_insert(SharedLibrary::new(library_path)?.into_ref(vm)); - - if !library.is_open() { - if let Some(l) = self.libraries.insert( - library_path.to_string(), - SharedLibrary::new(library_path)?.into_ref(vm), - ) { - // Ok(self.libraries.get_mut(library_path.to_string())) - Ok(&l) - } else { - // @TODO: What this error should be? - Err(libloading::Error::DlOpenUnknown) + match self.libraries.get(&library_path.to_string()) { + Some(l) => { + if l.is_closed() { + self.libraries.insert( + library_path.to_string(), + SharedLibrary::new(library_path)?.into_ref(vm), + ); + } } - } else { - Ok(library) - } + _ => { + self.libraries.insert( + library_path.to_string(), + SharedLibrary::new(library_path)?.into_ref(vm), + ); + } + }; + + Ok(self.libraries.get(&library_path.to_string()).unwrap()) } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 318a65741f..538187d896 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -32,18 +32,15 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul if !vm.isinstance(&func, &vm.ctx.types.str_type)? { return Err(vm.new_value_error("second argument (func) must be str".to_string())); } - - let func_name = func.downcast::().unwrap().as_ref(); + let str_ref = func.downcast::().unwrap(); + let func_name = str_ref.as_ref(); match slib.downcast::() { Ok(lib) => { - if !lib.is_open() { - Err(vm.new_runtime_error(format!("Library is closed."))) - } else if let Ok(ptr) = lib.get_sym(func_name) { - Ok(PyInt::from(ptr as *const _ as usize)) - } else { - // @TODO: Change this error message - Err(vm.new_runtime_error(format!("Error while opening symbol {}", func_name))) + match lib.get_sym(func_name) { + Ok(ptr) => Ok(PyInt::from(ptr as *const _ as usize)), + Err(e) => Err(vm + .new_runtime_error(format!("Error while opening symbol {}: {}", func_name, e))), } } Err(_) => { @@ -53,4 +50,15 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul } } -pub fn dlclose() {} +pub fn dlclose(slib: PyObjectRc, vm: &VirtualMachine) -> PyResult { + match slib.downcast::() { + Ok(lib) => { + lib.close(); + Ok(vm.ctx.none()) + } + Err(_) => { + Err(vm + .new_value_error("first argument (slib) is not a valid SharedLibrary".to_string())) + } + } +} diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 0365771216..893544de5b 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -17,7 +17,7 @@ use crate::stdlib::ctypes::common::{CDataObject, Function, SharedLibrary, SIMPLE use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; -fn map_types_to_res(args: &Vec, vm: &VirtualMachine) -> PyResult> { +fn map_types_to_res(args: &[PyObjectRc], vm: &VirtualMachine) -> PyResult> { args.iter() .enumerate() .map(|(idx, inner_obj)| { @@ -75,14 +75,15 @@ impl PyCFuncPtr { { let args: Vec = vm.extract_elements(&argtypes).unwrap(); - let c_args = map_types_to_res(&args, vm); + let c_args = map_types_to_res(&args, vm)?; self._argtypes_.write().clear(); - self._argtypes_.write().extend(c_args?.into_iter()); + self._argtypes_.write().extend(c_args.clone().into_iter()); - let fn_ptr = self._f.write().as_mut(); + let mut f_guard = self._f.write(); + let fn_ptr = f_guard.as_mut(); - let str_types: Result, _> = c_args? + let str_types: Result, _> = c_args .iter() .map(|obj| { if let Ok(attr) = vm.get_attribute(obj.clone(), "_type_") { @@ -107,17 +108,17 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_", setter)] fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { match vm.isinstance(&restype, CDataObject::static_type()) { - Ok(_) => match vm.get_attribute(restype, "_type_") { + Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? && _type_.to_string().len() == 1 && SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { - let dest = self._restype_.write().as_mut(); - let src = restype.clone(); - mem::replace(dest, src); + let mut r_guard = self._restype_.write(); + mem::replace(r_guard.as_mut(), restype.clone()); - let fn_ptr = self._f.write().as_mut(); + let mut a_guard = self._f.write(); + let fn_ptr = a_guard.as_mut(); fn_ptr.set_ret(_type_.to_string().as_str()); Ok(()) @@ -164,8 +165,7 @@ impl PyCFuncPtr { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { if let Ok(handle) = h.downcast::() { let handle_obj = handle.into_object(); - let ptr_fn = dlsym(handle_obj.clone(), func_name.into_object(), vm)?; - + let ptr_fn = dlsym(handle_obj.clone(), func_name.clone().into_object(), vm)?; let fn_ptr = usize::try_from_object(vm, ptr_fn.into_object(vm))? as *mut c_void; PyCFuncPtr { @@ -208,26 +208,6 @@ impl Callable for PyCFuncPtr { // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ let arg_vec = map_types_to_res(&args.args, vm)?; - // This is not optimal, but I can't simply store a vector of middle::Type inside PyCFuncPtr - let c_args: Result, ()> = zelf - ._argtypes_ - .read() - .iter() - .map(|obj| { - if let Ok(attr) = vm.get_attribute(obj.clone(), "_type_") { - Ok(attr.to_string()) - } else { - Err(()) - } - }) - .collect(); - - let ret_type = zelf._restype_.read().as_ref(); - - let res = zelf._f.read().call(arg_vec, vm); - - if let Ok(value) = res { - } else { - } + zelf._f.write().as_mut().call(arg_vec, vm) } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 4e461e8eba..8fb4dec981 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -19,6 +19,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "_ctypes", { "dlopen" => ctx.new_function(dlopen), "dlsym" => ctx.new_function(dlsym), + "dlclose" => ctx.new_function(dlclose), "CFuncPtr" => PyCFuncPtr::make_class(ctx), "_CData" => PyCData::make_class(ctx), From 4c585abf359482eb54de48fdc6ade39f1bf5708e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 15 Nov 2020 13:14:40 -0300 Subject: [PATCH 19/56] Fixing dangling ref --- vm/src/stdlib/ctypes/basics.rs | 2 +- vm/src/stdlib/ctypes/common.rs | 22 +++++++++++++--------- vm/src/stdlib/ctypes/dll.rs | 1 - 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 8ab4b6b32c..2951412a60 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -4,7 +4,7 @@ use crate::VirtualMachine; use crate::stdlib::ctypes::common::CDataObject; -#[pyclass(module = "_ctypes", name = "_CData", base = "CDataObject")] +#[pyclass(module = "_ctypes", name = "_CData")] #[derive(Debug)] pub struct PyCData { _type_: String, diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index efed6392e7..eaecb0c290 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -274,16 +274,15 @@ impl Function { unsafe impl Send for Function {} unsafe impl Sync for Function {} -#[pyclass(module = false, name = "SharedLibrary")] #[derive(Debug)] pub struct SharedLibrary { path_name: String, - lib: Option, + lib: PyRwLock>, } impl PyValue for SharedLibrary { fn class(vm: &VirtualMachine) -> &PyTypeRef { - Self::static_type() + &vm.ctx.types.object_type } } @@ -291,12 +290,13 @@ impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { path_name: name.to_string(), - lib: Some(Library::new(name.to_string())?), + lib: PyRwLock::new(Some(Library::new(name.to_string())?)), }) } pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> { - let inner = if let Some(ref inner) = self.lib { + let guard = self.lib.read(); + let inner = if let Some(inner) = guard.as_ref() { inner } else { return Err("The library has been closed".to_string()); @@ -311,11 +311,11 @@ impl SharedLibrary { } pub fn is_closed(&self) -> bool { - self.lib.is_none() + self.lib.read().is_none() } - pub fn close(&mut self) { - drop(self.lib.take()); + pub fn close(&self) { + drop(self.lib.write().take()); } } @@ -362,12 +362,16 @@ pub struct CDataObject {} impl PyValue for CDataObject { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_metaclass() + Self::static_type() } } #[pyimpl(flags(BASETYPE))] impl CDataObject { + #[pyslot] + fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + CDataObject {}.into_ref_with_type(vm, cls) + } // A lot of the logic goes in this trait // There's also other traits that should have different implementations for some functions // present here diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 538187d896..185986399e 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -28,7 +28,6 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult PyResult { - // match vm.isinstance(&slib, &SharedLibrary::static_type()) { if !vm.isinstance(&func, &vm.ctx.types.str_type)? { return Err(vm.new_value_error("second argument (func) must be str".to_string())); } From b09ebe09cfca5523db8fd2577520fcafb37d39d9 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 19 Nov 2020 15:03:38 -0300 Subject: [PATCH 20/56] Starting primitive types impl --- vm/src/stdlib/ctypes/basics.rs | 29 +++++---- vm/src/stdlib/ctypes/common.rs | 38 ++--------- vm/src/stdlib/ctypes/function.rs | 104 ++++++++++++++---------------- vm/src/stdlib/ctypes/mod.rs | 2 - vm/src/stdlib/ctypes/primitive.rs | 93 +++++++++++++++++++------- 5 files changed, 140 insertions(+), 126 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 2951412a60..c278b89d98 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,25 +1,28 @@ use crate::builtins::PyTypeRef; -use crate::pyobject::{PyResult, PyValue, StaticType}; +use crate::pyobject::{PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::CDataObject; - -#[pyclass(module = "_ctypes", name = "_CData")] +// This class is the equivalent of PyCData_Type on tp_base for +// PyCStructType_Type, UnionType_Type, PyCPointerType_Type +// PyCArrayType_Type, PyCSimpleType_Type, PyCFuncPtrType_Type +#[pyclass(module = false, name = "_CData")] #[derive(Debug)] -pub struct PyCData { - _type_: String, -} +pub struct PyCData {} impl PyValue for PyCData { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_type() + Self::static_baseclass() } } -#[pyimpl] +#[pyimpl(flags(BASETYPE))] impl PyCData { - #[pymethod(name = "__init__")] - fn init(&self, _vm: &VirtualMachine) -> PyResult<()> { - Ok(()) - } + // A lot of the logic goes in this trait + // There's also other traits that should have different implementations for some functions + // present here + + // #[pyslot] + // fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + // PyCData {}.into_ref_with_type(vm, cls) + // } } diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index eaecb0c290..9fd56d2342 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,7 +2,7 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, mem, os::raw::*, ptr}; +use ::std::{collections::HashMap, os::raw::*, ptr}; use libffi::low::{ call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, @@ -14,13 +14,9 @@ use num_bigint::BigInt; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, -}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; use crate::VirtualMachine; -pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; - macro_rules! ffi_type { ($name: ident) => { middle::Type::$name().as_raw_ptr() @@ -57,12 +53,12 @@ macro_rules! match_ffi_type { t if t == $type => { ffi_type!($body) } )+ )+ - _ => ffi_type!(void) + _ => unreachable!() } } } -fn str_to_type(ty: &str) -> *mut ffi_type { +pub fn str_to_type(ty: &str) -> *mut ffi_type { match_ffi_type!( ty, "c" => c_schar @@ -91,7 +87,6 @@ fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> * c_schar => { let mut r = i8::try_from_object(vm, obj).unwrap(); &mut r as *mut _ as *mut c_void - } c_int => { let mut r = i32::try_from_object(vm, obj).unwrap(); @@ -145,7 +140,6 @@ pub struct Function { cif: ffi_cif, arguments: Vec<*mut ffi_type>, return_type: Box<*mut ffi_type>, - // @TODO: Do we need to free the memory of these ffi_type? } impl Function { @@ -165,7 +159,8 @@ impl Function { } pub fn set_ret(&mut self, ret: &str) { - mem::replace(self.return_type.as_mut(), str_to_type(ret)); + (*self.return_type.as_mut()) = str_to_type(ret); + // mem::replace(self.return_type.as_mut(), str_to_type(ret)); } pub fn call( @@ -356,27 +351,6 @@ impl ExternalLibs { } } -#[pyclass(module = false, name = "_CDataObject")] -#[derive(Debug)] -pub struct CDataObject {} - -impl PyValue for CDataObject { - fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_type() - } -} - -#[pyimpl(flags(BASETYPE))] -impl CDataObject { - #[pyslot] - fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { - CDataObject {}.into_ref_with_type(vm, cls) - } - // A lot of the logic goes in this trait - // There's also other traits that should have different implementations for some functions - // present here -} - lazy_static::lazy_static! { pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalLibs::new()); } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 893544de5b..7243aad84b 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,18 +1,21 @@ extern crate libffi; -use std::{mem, os::raw::c_void}; +use std::{fmt, os::raw::c_void}; -use crate::builtins::pystr::{PyStr, PyStrRef}; +use crossbeam_utils::atomic::AtomicCell; + +use crate::builtins::pystr::PyStrRef; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::function::FuncArgs; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{CDataObject, Function, SharedLibrary, SIMPLE_TYPE_CHARS}; +use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::common::{Function, SharedLibrary}; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; @@ -21,33 +24,32 @@ fn map_types_to_res(args: &[PyObjectRc], vm: &VirtualMachine) -> PyResult match vm.get_attribute(inner_obj.clone(), "_type_") { - Ok(_type_) if SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => { - Ok(_type_) - } - Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), - Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), - }, - // @TODO: Needs to return the name of the type, not String(inner_obj) + match vm.isinstance(inner_obj, PyCData::static_type()) { + // @TODO: checks related to _type_ are temporary + Ok(_) => Ok(vm.get_attribute(inner_obj.clone(), "_type_").unwrap()), Err(_) => Err(vm.new_type_error(format!( - "object at {} is not an instance of _CDataObject, type {} found", + "object at {} is not an instance of _CData, type {} found", idx, - inner_obj.to_string() + inner_obj.class().name ))), } }) .collect() } -#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "CDataObject")] -#[derive(Debug)] +#[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] pub struct PyCFuncPtr { pub _name_: String, - pub _argtypes_: PyRwLock>, - pub _restype_: PyRwLock>, + pub _argtypes_: AtomicCell>, + pub _restype_: AtomicCell, _handle: PyObjectRc, - _f: PyRwLock>, + _f: PyRwLock, +} + +impl fmt::Debug for PyCFuncPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCFuncPtr {{ _name_, _argtypes_, _restype_}}") + } } impl PyValue for PyCFuncPtr { @@ -60,12 +62,13 @@ impl PyValue for PyCFuncPtr { impl PyCFuncPtr { #[pyproperty(name = "_argtypes_")] fn argtypes(&self, vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.new_list(self._argtypes_.read().clone()) + vm.ctx + .new_list(unsafe { &*self._argtypes_.as_ptr() }.clone()) } #[pyproperty(name = "_restype_")] - fn restype(&self, vm: &VirtualMachine) -> PyObjectRef { - self._restype_.read().as_ref().clone() + fn restype(&self, _vm: &VirtualMachine) -> PyObjectRef { + unsafe { &*self._restype_.as_ptr() }.clone() } #[pyproperty(name = "_argtypes_", setter)] @@ -77,11 +80,7 @@ impl PyCFuncPtr { let c_args = map_types_to_res(&args, vm)?; - self._argtypes_.write().clear(); - self._argtypes_.write().extend(c_args.clone().into_iter()); - - let mut f_guard = self._f.write(); - let fn_ptr = f_guard.as_mut(); + self._argtypes_.store(c_args.clone()); let str_types: Result, _> = c_args .iter() @@ -94,6 +93,7 @@ impl PyCFuncPtr { }) .collect(); + let mut fn_ptr = self._f.write(); fn_ptr.set_args(str_types.unwrap()); Ok(()) @@ -107,29 +107,23 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_", setter)] fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - match vm.isinstance(&restype, CDataObject::static_type()) { + match vm.isinstance(&restype, PyCData::static_type()) { + // @TODO: checks related to _type_ are temporary Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { - Ok(_type_) - if vm.isinstance(&_type_, &vm.ctx.types.str_type)? - && _type_.to_string().len() == 1 - && SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) => - { - let mut r_guard = self._restype_.write(); - mem::replace(r_guard.as_mut(), restype.clone()); - - let mut a_guard = self._f.write(); - let fn_ptr = a_guard.as_mut(); + Ok(_type_) => { + self._restype_.store(restype.clone()); + + let mut fn_ptr = self._f.write(); fn_ptr.set_ret(_type_.to_string().as_str()); Ok(()) } - Ok(_type_) => Err(vm.new_attribute_error("invalid _type_ value".to_string())), Err(_) => Err(vm.new_attribute_error("atribute _type_ not found".to_string())), }, - // @TODO: Needs to return the name of the type, not String(inner_obj) + Err(_) => Err(vm.new_type_error(format!( - "value is not an instance of _CDataObject, type {} found", - restype + "value is not an instance of _CData, type {} found", + restype.class().name ))), } } @@ -145,7 +139,7 @@ impl PyCFuncPtr { match vm.get_attribute(cls.as_object().to_owned(), "_argtypes_") { Ok(_) => Self::from_dll(cls, func_name, arg, vm), Err(_) => Err(vm.new_type_error( - "cannot construct instance of this class: no argtypes".to_string(), + "cannot construct instance of this class: no argtypes slot".to_string(), )), } } @@ -154,7 +148,7 @@ impl PyCFuncPtr { /// # Arguments /// /// * `func_name` - A string that names the function symbol - /// * `dll` - A Python object with _handle attribute of type SharedLibrary + /// * `arg` - A Python object with _handle attribute of type SharedLibrary /// fn from_dll( cls: PyTypeRef, @@ -170,21 +164,20 @@ impl PyCFuncPtr { PyCFuncPtr { _name_: func_name.to_string(), - _argtypes_: PyRwLock::new(Vec::new()), - _restype_: PyRwLock::new(Box::new(vm.ctx.none())), + _argtypes_: AtomicCell::default(), + _restype_: AtomicCell::new(vm.ctx.none()), _handle: handle_obj.clone(), - _f: PyRwLock::new(Box::new(Function::new( + _f: PyRwLock::new(Function::new( fn_ptr, Vec::new(), "P", // put a default here - ))), + )), } .into_ref_with_type(vm, cls) } else { - // @TODO: Needs to return the name of the type, not String(inner_obj) Err(vm.new_type_error(format!( "_handle must be SharedLibrary not {}", - arg.to_string() + arg.class().name ))) } } else { @@ -197,17 +190,18 @@ impl PyCFuncPtr { impl Callable for PyCFuncPtr { fn call(zelf: &PyRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - if args.args.len() != zelf._argtypes_.read().len() { + let inner_args = unsafe { &*zelf._argtypes_.as_ptr() }; + + if args.args.len() != inner_args.len() { return Err(vm.new_runtime_error(format!( "invalid number of arguments, required {}, but {} found", - zelf._argtypes_.read().len(), + inner_args.len(), args.args.len() ))); } - // Needs to check their types and convert to middle::Arg based on zelf._argtypes_ let arg_vec = map_types_to_res(&args.args, vm)?; - zelf._f.write().as_mut().call(arg_vec, vm) + (*zelf._f.write()).call(arg_vec, vm) } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 8fb4dec981..13b8672fed 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -8,7 +8,6 @@ mod dll; mod function; mod primitive; -use crate::stdlib::ctypes::basics::*; use crate::stdlib::ctypes::dll::*; use crate::stdlib::ctypes::function::*; use crate::stdlib::ctypes::primitive::*; @@ -22,7 +21,6 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { "dlclose" => ctx.new_function(dlclose), "CFuncPtr" => PyCFuncPtr::make_class(ctx), - "_CData" => PyCData::make_class(ctx), "_SimpleCData" => PySimpleType::make_class(ctx) }) } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index b9bd8084ee..b104a79743 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,41 +1,86 @@ -use crate::builtins::pystr::PyStrRef; +use crossbeam_utils::atomic::AtomicCell; +use std::fmt; + +use crate::builtins::pystr::PyStr; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyResult, PyValue, StaticType}; +use crate::pyobject::{PyObjectRc, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{CDataObject, SIMPLE_TYPE_CHARS}; +use crate::stdlib::ctypes::basics::PyCData; + +pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; -#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "CDataObject")] -#[derive(Debug)] +#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] pub struct PySimpleType { - _type_: PyStrRef, + _type_: String, + value: AtomicCell>, +} + +impl fmt::Debug for PySimpleType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = match unsafe { (*self.value.as_ptr()).as_ref() } { + Some(v) => v.to_string(), + _ => "None".to_string(), + }; + + write!( + f, + "PySimpleType {{ + _type_: {}, + value: {}, + }}", + self._type_.as_str(), + value + ) + } } impl PyValue for PySimpleType { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_type() + Self::init_bare_type() } } #[pyimpl] impl PySimpleType { - #[inline] - pub fn new(_type: PyStrRef, vm: &VirtualMachine) -> PyResult { - // Needs to force the existence of _type_ - // Does it need to be here? - // Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) - - let s_type = _type.to_string(); - - if s_type.len() != 1 { - Err(vm.new_attribute_error( - "class must define a '_type_' attribute which must be a string of length 1" - .to_string(), - )) - } else if SIMPLE_TYPE_CHARS.contains(s_type.as_str()) { - Ok(PySimpleType { _type_: _type }) - } else { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of '{}'.",SIMPLE_TYPE_CHARS))) + #[pyslot] + fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + match vm.get_attribute(cls.as_object().to_owned(), "_type_") { + Ok(_type_) => { + if vm.isinstance(&_type_, &vm.ctx.types.str_type)? { + if _type_.to_string().len() != 1 { + Err(vm.new_value_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) + } else if !SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) { + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of {}.",SIMPLE_TYPE_CHARS))) + } else { + PySimpleType { + _type_: _type_.downcast_exact::(vm).unwrap().to_string(), + value: AtomicCell::default(), + } + .into_ref_with_type(vm, cls) + } + } else { + Err(vm.new_type_error( + "class must define a '_type_' string attribute".to_string(), + )) + } + } + Err(_) => { + Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) + } } } + + #[pymethod(name = "__init__")] + fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { + let content = if let Some(ref v) = value { + // @TODO: Needs to check if value has a simple payload + Some(v.clone()) + } else { + Some(vm.ctx.none()) + }; + + self.value.store(content); + Ok(()) + } } From d7888f9202cf1cdb0177d12bc555a694ea9998c8 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 19 Nov 2020 17:03:10 -0300 Subject: [PATCH 21/56] Adding metaclass --- vm/src/stdlib/ctypes/basics.rs | 75 +++++++-- vm/src/stdlib/ctypes/common.rs | 265 +----------------------------- vm/src/stdlib/ctypes/function.rs | 263 ++++++++++++++++++++++++++++- vm/src/stdlib/ctypes/primitive.rs | 32 +++- 4 files changed, 363 insertions(+), 272 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index c278b89d98..138547d986 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,28 +1,83 @@ use crate::builtins::PyTypeRef; -use crate::pyobject::{PyRef, PyResult, PyValue, StaticType}; +use crate::pyobject::{PyObjectRc, PyRef, PyResult, PyValue, StaticType}; use crate::VirtualMachine; +use crate::stdlib::ctypes::common::PyCData_as_buffer; + // This class is the equivalent of PyCData_Type on tp_base for -// PyCStructType_Type, UnionType_Type, PyCPointerType_Type -// PyCArrayType_Type, PyCSimpleType_Type, PyCFuncPtrType_Type +// Struct_Type, Union_Type, PyCPointer_Type +// PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type + #[pyclass(module = false, name = "_CData")] #[derive(Debug)] -pub struct PyCData {} +pub struct PyCData { + _objects: Vec, +} impl PyValue for PyCData { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_baseclass() + Self::init_bare_type() } } -#[pyimpl(flags(BASETYPE))] +#[pyimpl] impl PyCData { + // Methods here represent PyCData_methods + + #[pymethod] + pub fn __ctypes_from_outparam__() {} + + #[pymethod] + pub fn __reduce__() {} + + #[pymethod] + pub fn __setstate__() {} +} + +// #[pyimpl] +// impl PyCData_as_buffer for PyCData { + +// } + +// This class has no attributes and we care about it's methods +#[pyclass(module = false, name = "_CDataMeta")] +#[derive(Debug)] +pub struct PyCDataMeta; + +impl PyValue for PyCDataMeta { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_baseclass() + } +} + +#[pyimpl] +impl PyCDataMeta { // A lot of the logic goes in this trait // There's also other traits that should have different implementations for some functions // present here - // #[pyslot] - // fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { - // PyCData {}.into_ref_with_type(vm, cls) - // } + // The default methods (representing CDataType_methods) here are for: + // StructType_Type + // UnionType_Type + // PyCArrayType_Type + // PyCFuncPtrType_Type + + #[pymethod] + pub fn from_param() {} + + #[pymethod] + pub fn from_address() {} + + #[pymethod] + pub fn from_buffer() {} + + #[pymethod] + pub fn from_buffer_copy() {} + + #[pymethod] + pub fn in_dll() {} } + +// CDataType_as_sequence methods are default for all types inherinting from PyCDataMeta +// Basically the sq_repeat slot is CDataType_repeat +// which transforms into a Array diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/common.rs index 9fd56d2342..f5829cc40f 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/common.rs @@ -2,273 +2,22 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, os::raw::*, ptr}; +use ::std::{collections::HashMap, os::raw::c_void}; -use libffi::low::{ - call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, - Error as FFIError, -}; -use libffi::middle; use libloading::Library; -use num_bigint::BigInt; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; +use crate::pyobject::{PyRef, PyValue}; +use crate::slots::BufferProtocol; use crate::VirtualMachine; -macro_rules! ffi_type { - ($name: ident) => { - middle::Type::$name().as_raw_ptr() - }; +// This trait will be used by all types +pub trait PyCData_as_buffer: BufferProtocol { + // @TODO: Translate PyCData_NewGetBuffer + // fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult>; } -macro_rules! match_ffi_type { - ( - $pointer: expr, - - $( - $($type: ident)|+ => $body: expr - )+ - ) => { - match $pointer { - $( - $( - t if t == ffi_type!($type) => { $body } - )+ - )+ - _ => unreachable!() - } - }; - ( - $kind: expr, - - $( - $($type: tt)|+ => $body: ident - )+ - ) => { - match $kind { - $( - $( - t if t == $type => { ffi_type!($body) } - )+ - )+ - _ => unreachable!() - } - } -} - -pub fn str_to_type(ty: &str) -> *mut ffi_type { - match_ffi_type!( - ty, - "c" => c_schar - "u" => c_int - "b" => i8 - "h" => c_short - "H" => c_ushort - "i" => c_int - "I" => c_uint - "l" => c_long - "q" => c_longlong - "L" => c_ulong - "Q" => c_ulonglong - "f" => f32 - "d" => f64 - "g" => longdouble - "?" | "B" => c_uchar - "z" | "Z" => pointer - "P" => void - ) -} - -fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> *mut c_void { - match_ffi_type!( - unsafe { *ty }, - c_schar => { - let mut r = i8::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_int => { - let mut r = i32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_short => { - let mut r = i16::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_ushort => { - let mut r = u16::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_uint => { - let mut r = u32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - //@ TODO: Convert c*longlong from BigInt? - c_long | c_longlong => { - let mut r = i64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_ulong | c_ulonglong => { - let mut r = u64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - f32 => { - let mut r = f32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - f64 | longdouble=> { - let mut r = f64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - c_uchar => { - let mut r = u8::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void - } - pointer => { - usize::try_from_object(vm, obj).unwrap() as *mut c_void - } - void => { - ptr::null_mut() - } - ) -} - -#[derive(Debug)] -pub struct Function { - pointer: *const c_void, - cif: ffi_cif, - arguments: Vec<*mut ffi_type>, - return_type: Box<*mut ffi_type>, -} - -impl Function { - pub fn new(fn_ptr: *const c_void, arguments: Vec, return_type: &str) -> Function { - Function { - pointer: fn_ptr, - cif: Default::default(), - arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), - - return_type: Box::new(str_to_type(return_type)), - } - } - pub fn set_args(&mut self, args: Vec) { - self.arguments.clear(); - self.arguments - .extend(args.iter().map(|s| str_to_type(s.as_str()))); - } - - pub fn set_ret(&mut self, ret: &str) { - (*self.return_type.as_mut()) = str_to_type(ret); - // mem::replace(self.return_type.as_mut(), str_to_type(ret)); - } - - pub fn call( - &mut self, - arg_ptrs: Vec, - vm: &VirtualMachine, - ) -> PyResult { - let return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; - - let result = unsafe { - prep_cif( - &mut self.cif, - ABI, - self.arguments.len(), - return_type, - self.arguments.as_mut_ptr(), - ) - }; - - if let Err(FFIError::Typedef) = result { - return Err(vm.new_runtime_error( - "The type representation is invalid or unsupported".to_string(), - )); - } else if let Err(FFIError::Abi) = result { - return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); - } - - let mut argument_pointers: Vec<*mut c_void> = arg_ptrs - .iter() - .zip(self.arguments.iter_mut()) - .map(|(o, t)| { - let tt: *mut *mut ffi_type = t; - py_to_ffi(tt, o.clone(), vm) - }) - .collect(); - - let cif_ptr = &self.cif as *const _ as *mut _; - let fun_ptr = CodePtr::from_ptr(self.pointer); - let args_ptr = argument_pointers.as_mut_ptr(); - - let ret_ptr = unsafe { - match_ffi_type!( - return_type, - c_schar => { - let r: c_schar = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as i8) - } - c_int => { - let r: c_int = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as i32) - } - c_short => { - let r: c_short = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as i16) - } - c_ushort => { - let r: c_ushort = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u16) - } - c_uint => { - let r: c_uint = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u32) - } - c_long => { - let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as i64) - } - c_longlong => { - let r: c_longlong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(BigInt::from(r as i128)) - } - c_ulong => { - let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u64) - } - c_ulonglong => { - let r: c_ulonglong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(BigInt::from(r as u128)) - } - f32 => { - let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as f32) - } - f64 | longdouble => { - let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as f64) - } - c_uchar => { - let r: c_uchar = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as u8) - } - pointer => { - let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(r as *const _ as usize) - } - void => { - vm.ctx.none() - } - ) - }; - - Ok(ret_ptr) - } -} - -unsafe impl Send for Function {} -unsafe impl Sync for Function {} - #[derive(Debug)] pub struct SharedLibrary { path_name: String, diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 7243aad84b..f3e7fa007b 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,9 +1,16 @@ extern crate libffi; -use std::{fmt, os::raw::c_void}; +use std::{fmt, os::raw::*, ptr}; use crossbeam_utils::atomic::AtomicCell; +use libffi::low::{ + call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, + Error as FFIError, +}; +use libffi::middle; +use num_bigint::BigInt; + use crate::builtins::pystr::PyStrRef; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; @@ -15,11 +22,263 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; -use crate::stdlib::ctypes::common::{Function, SharedLibrary}; +use crate::stdlib::ctypes::common::SharedLibrary; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; +macro_rules! ffi_type { + ($name: ident) => { + middle::Type::$name().as_raw_ptr() + }; +} + +macro_rules! match_ffi_type { + ( + $pointer: expr, + + $( + $($type: ident)|+ => $body: expr + )+ + ) => { + match $pointer { + $( + $( + t if t == ffi_type!($type) => { $body } + )+ + )+ + _ => unreachable!() + } + }; + ( + $kind: expr, + + $( + $($type: tt)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { ffi_type!($body) } + )+ + )+ + _ => unreachable!() + } + } +} + +pub fn str_to_type(ty: &str) -> *mut ffi_type { + match_ffi_type!( + ty, + "c" => c_schar + "u" => c_int + "b" => i8 + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => f32 + "d" => f64 + "g" => longdouble + "?" | "B" => c_uchar + "z" | "Z" => pointer + "P" => void + ) +} + +fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> *mut c_void { + match_ffi_type!( + unsafe { *ty }, + c_schar => { + let mut r = i8::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_int => { + let mut r = i32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_short => { + let mut r = i16::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_ushort => { + let mut r = u16::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_uint => { + let mut r = u32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + //@ TODO: Convert c*longlong from BigInt? + c_long | c_longlong => { + let mut r = i64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_ulong | c_ulonglong => { + let mut r = u64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + f32 => { + let mut r = f32::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + f64 | longdouble=> { + let mut r = f64::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + c_uchar => { + let mut r = u8::try_from_object(vm, obj).unwrap(); + &mut r as *mut _ as *mut c_void + } + pointer => { + usize::try_from_object(vm, obj).unwrap() as *mut c_void + } + void => { + ptr::null_mut() + } + ) +} + +#[derive(Debug)] +pub struct Function { + pointer: *const c_void, + cif: ffi_cif, + arguments: Vec<*mut ffi_type>, + return_type: Box<*mut ffi_type>, +} + +impl Function { + pub fn new(fn_ptr: *const c_void, arguments: Vec, return_type: &str) -> Function { + Function { + pointer: fn_ptr, + cif: Default::default(), + arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), + + return_type: Box::new(str_to_type(return_type)), + } + } + pub fn set_args(&mut self, args: Vec) { + self.arguments.clear(); + self.arguments + .extend(args.iter().map(|s| str_to_type(s.as_str()))); + } + + pub fn set_ret(&mut self, ret: &str) { + (*self.return_type.as_mut()) = str_to_type(ret); + // mem::replace(self.return_type.as_mut(), str_to_type(ret)); + } + + pub fn call( + &mut self, + arg_ptrs: Vec, + vm: &VirtualMachine, + ) -> PyResult { + let return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; + + let result = unsafe { + prep_cif( + &mut self.cif, + ABI, + self.arguments.len(), + return_type, + self.arguments.as_mut_ptr(), + ) + }; + + if let Err(FFIError::Typedef) = result { + return Err(vm.new_runtime_error( + "The type representation is invalid or unsupported".to_string(), + )); + } else if let Err(FFIError::Abi) = result { + return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); + } + + let mut argument_pointers: Vec<*mut c_void> = arg_ptrs + .iter() + .zip(self.arguments.iter_mut()) + .map(|(o, t)| { + let tt: *mut *mut ffi_type = t; + py_to_ffi(tt, o.clone(), vm) + }) + .collect(); + + let cif_ptr = &self.cif as *const _ as *mut _; + let fun_ptr = CodePtr::from_ptr(self.pointer); + let args_ptr = argument_pointers.as_mut_ptr(); + + let ret_ptr = unsafe { + match_ffi_type!( + return_type, + c_schar => { + let r: c_schar = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i8) + } + c_int => { + let r: c_int = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i32) + } + c_short => { + let r: c_short = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i16) + } + c_ushort => { + let r: c_ushort = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u16) + } + c_uint => { + let r: c_uint = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u32) + } + c_long => { + let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as i64) + } + c_longlong => { + let r: c_longlong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(BigInt::from(r as i128)) + } + c_ulong => { + let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u64) + } + c_ulonglong => { + let r: c_ulonglong = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(BigInt::from(r as u128)) + } + f32 => { + let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as f32) + } + f64 | longdouble => { + let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as f64) + } + c_uchar => { + let r: c_uchar = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as u8) + } + pointer => { + let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); + vm.new_pyobj(r as *const _ as usize) + } + void => { + vm.ctx.none() + } + ) + }; + + Ok(ret_ptr) + } +} + +unsafe impl Send for Function {} +unsafe impl Sync for Function {} + fn map_types_to_res(args: &[PyObjectRc], vm: &VirtualMachine) -> PyResult> { args.iter() .enumerate() diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index b104a79743..2aa1a0a38a 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -72,9 +72,9 @@ impl PySimpleType { } #[pymethod(name = "__init__")] - fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { + pub fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { let content = if let Some(ref v) = value { - // @TODO: Needs to check if value has a simple payload + // @TODO: Needs to check if value has a simple (rust native) payload Some(v.clone()) } else { Some(vm.ctx.none()) @@ -83,4 +83,32 @@ impl PySimpleType { self.value.store(content); Ok(()) } + + #[pymethod] + pub fn __ctypes_from_outparam__() {} + + #[pymethod] + pub fn from_param() {} + + #[pymethod] + pub fn from_address() {} + + #[pymethod] + pub fn from_buffer() {} + + #[pymethod] + pub fn from_buffer_copy() {} + + #[pymethod] + pub fn in_dll() {} + + // #[pymethod(name = "__repr__")] + // fn repr(&self) -> String { + // // "%s(%s)" % (self.__class__.__name__, self.value) + // } + + // #[pymethod(name = "__bool__")] + // fn bool(&self) -> bool { + // + // } } From 145afc7226bdf4ef4a4d385ea1fca91f7ac0d012 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 21 Nov 2020 15:05:09 -0300 Subject: [PATCH 22/56] Adding PyCDataMethods trait --- vm/src/stdlib/ctypes/basics.rs | 154 ++++++++++++------ vm/src/stdlib/ctypes/dll.rs | 4 +- vm/src/stdlib/ctypes/function.rs | 2 +- vm/src/stdlib/ctypes/mod.rs | 2 +- vm/src/stdlib/ctypes/primitive.rs | 24 +-- .../ctypes/{common.rs => shared_lib.rs} | 40 +++-- 6 files changed, 136 insertions(+), 90 deletions(-) rename vm/src/stdlib/ctypes/{common.rs => shared_lib.rs} (72%) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 138547d986..6ea33a526c 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,14 +1,72 @@ +use crate::builtins::int::PyInt; +use crate::builtins::memory::Buffer; +use crate::builtins::pystr::PyStrRef; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyObjectRc, PyRef, PyResult, PyValue, StaticType}; +use crate::function::OptionalArg; +use crate::pyobject::{ + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, +}; +use crate::slots::BufferProtocol; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::PyCData_as_buffer; +#[pyimpl] +pub trait PyCDataMethods: PyValue { + // A lot of the logic goes in this trait + // There's also other traits that should have different implementations for some functions + // present here -// This class is the equivalent of PyCData_Type on tp_base for + // The default methods (representing CDataType_methods) here are for: + // StructType_Type + // UnionType_Type + // PyCArrayType_Type + // PyCFuncPtrType_Type + + #[pyclassmethod] + fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) + -> PyResult>; + + #[pyclassmethod] + fn from_address( + cls: PyTypeRef, + address: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult>; + + #[pyclassmethod] + fn from_buffer( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult>; + + #[pyclassmethod] + fn from_buffer_copy( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult>; + + #[pyclassmethod] + fn in_dll( + cls: PyTypeRef, + dll: PyObjectRef, + name: PyStrRef, + vm: &VirtualMachine, + ) -> PyResult>; +} + +// This trait will be used by all types +pub trait PyCDataBuffer: BufferProtocol { + // @TODO: Translate PyCData_NewGetBuffer + fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult>; +} + +// This Trait is the equivalent of PyCData_Type on tp_base for // Struct_Type, Union_Type, PyCPointer_Type // PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type - -#[pyclass(module = false, name = "_CData")] +#[pyclass(module = "ctypes", name = "_CData")] #[derive(Debug)] pub struct PyCData { _objects: Vec, @@ -24,60 +82,50 @@ impl PyValue for PyCData { impl PyCData { // Methods here represent PyCData_methods - #[pymethod] - pub fn __ctypes_from_outparam__() {} - - #[pymethod] - pub fn __reduce__() {} + #[pymethod(name = "__ctypes_from_outparam__")] + pub fn ctypes_from_outparam(&self) {} - #[pymethod] - pub fn __setstate__() {} -} + #[pymethod(name = "__reduce__")] + pub fn reduce(&self) {} -// #[pyimpl] -// impl PyCData_as_buffer for PyCData { + #[pymethod(name = "__setstate__")] + pub fn setstate(&self) {} -// } + // CDataType_as_sequence methods are default for all types implementing PyCDataMethods + // Basically the sq_repeat slot is CDataType_repeat + // which transforms into a Array -// This class has no attributes and we care about it's methods -#[pyclass(module = false, name = "_CDataMeta")] -#[derive(Debug)] -pub struct PyCDataMeta; + // #[pymethod(name = "__mul__")] + // fn mul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + // } -impl PyValue for PyCDataMeta { - fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_baseclass() - } + // #[pymethod(name = "__rmul__")] + // fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + // self.mul(counter, &vm) + // } } -#[pyimpl] -impl PyCDataMeta { - // A lot of the logic goes in this trait - // There's also other traits that should have different implementations for some functions - // present here - - // The default methods (representing CDataType_methods) here are for: - // StructType_Type - // UnionType_Type - // PyCArrayType_Type - // PyCFuncPtrType_Type - - #[pymethod] - pub fn from_param() {} - - #[pymethod] - pub fn from_address() {} - - #[pymethod] - pub fn from_buffer() {} - - #[pymethod] - pub fn from_buffer_copy() {} +// #[pyimpl] +// impl PyCDataBuffer for PyCData { - #[pymethod] - pub fn in_dll() {} -} +// } -// CDataType_as_sequence methods are default for all types inherinting from PyCDataMeta -// Basically the sq_repeat slot is CDataType_repeat -// which transforms into a Array +// #[pyimpl] +// impl PyCDataMethods for PyCData { +// #[pyclassmethod] +// fn from_address( +// cls: PyTypeRef, +// address: PyObjectRef, +// vm: &VirtualMachine +// ) -> PyResult> { +// if let Ok(obj) = address.downcast_exact::(vm) { +// if let Ok(v) = usize::try_from_object(vm, obj.into_object()) { + +// } else { +// Err(vm.new_runtime_error("casting pointer failed".to_string())) +// } +// } else { +// Err(vm.new_type_error("integer expected".to_string())) +// } +// } +// } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 185986399e..3f3f2a5773 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -5,7 +5,7 @@ use crate::builtins::PyInt; use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult}; use crate::VirtualMachine; -use crate::stdlib::ctypes::common::{SharedLibrary, CDATACACHE}; +use crate::stdlib::ctypes::shared_lib::{SharedLibrary, LIBCACHE}; pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult { // Match this error first @@ -14,7 +14,7 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult Err(e), }?; - let mut data_cache = CDATACACHE.write(); + let mut data_cache = LIBCACHE.write(); let result = data_cache.get_or_insert_lib(lib_str_path.as_ref(), vm); diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index f3e7fa007b..3a034fa1cd 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -22,7 +22,7 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; -use crate::stdlib::ctypes::common::SharedLibrary; +use crate::stdlib::ctypes::shared_lib::SharedLibrary; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 13b8672fed..be398ff31a 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -3,10 +3,10 @@ use crate::pyobject::PyObjectRef; use crate::VirtualMachine; mod basics; -mod common; mod dll; mod function; mod primitive; +mod shared_lib; use crate::stdlib::ctypes::dll::*; use crate::stdlib::ctypes::function::*; diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 2aa1a0a38a..62efa10b2f 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -84,27 +84,17 @@ impl PySimpleType { Ok(()) } - #[pymethod] - pub fn __ctypes_from_outparam__() {} + // From Simple_Type Simple_methods + #[pymethod(name = "__ctypes_from_outparam__")] + pub fn ctypes_from_outparam(&self) {} - #[pymethod] - pub fn from_param() {} - - #[pymethod] - pub fn from_address() {} - - #[pymethod] - pub fn from_buffer() {} - - #[pymethod] - pub fn from_buffer_copy() {} - - #[pymethod] - pub fn in_dll() {} + // From PyCSimpleType_Type PyCSimpleType_methods + #[pyclassmethod] + pub fn from_param(cls: PyTypeRef, vm: &VirtualMachine) {} // #[pymethod(name = "__repr__")] // fn repr(&self) -> String { - // // "%s(%s)" % (self.__class__.__name__, self.value) + // format!("{}({})",self.class().name, self.value.to_string()) // } // #[pymethod(name = "__bool__")] diff --git a/vm/src/stdlib/ctypes/common.rs b/vm/src/stdlib/ctypes/shared_lib.rs similarity index 72% rename from vm/src/stdlib/ctypes/common.rs rename to vm/src/stdlib/ctypes/shared_lib.rs index f5829cc40f..66171b5684 100644 --- a/vm/src/stdlib/ctypes/common.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -2,26 +2,33 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, os::raw::c_void}; +use ::std::{collections::HashMap, fmt, os::raw::c_void}; +use crossbeam_utils::atomic::AtomicCell; use libloading::Library; use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::pyobject::{PyRef, PyValue}; -use crate::slots::BufferProtocol; use crate::VirtualMachine; -// This trait will be used by all types -pub trait PyCData_as_buffer: BufferProtocol { - // @TODO: Translate PyCData_NewGetBuffer - // fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult>; -} - -#[derive(Debug)] pub struct SharedLibrary { path_name: String, - lib: PyRwLock>, + lib: AtomicCell>, +} + +impl fmt::Debug for SharedLibrary { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "SharedLibrary {{ + path_name: {}, + lib: {}, + }}", + self.path_name.as_str(), + self.is_closed() + ) + } } impl PyValue for SharedLibrary { @@ -34,13 +41,12 @@ impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { path_name: name.to_string(), - lib: PyRwLock::new(Some(Library::new(name.to_string())?)), + lib: AtomicCell::new(Some(Library::new(name.to_string())?)), }) } pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> { - let guard = self.lib.read(); - let inner = if let Some(inner) = guard.as_ref() { + let inner = if let Some(ref inner) = unsafe { &*self.lib.as_ptr() } { inner } else { return Err("The library has been closed".to_string()); @@ -55,11 +61,13 @@ impl SharedLibrary { } pub fn is_closed(&self) -> bool { - self.lib.read().is_none() + unsafe { &*self.lib.as_ptr() }.is_none() } pub fn close(&self) { - drop(self.lib.write().take()); + let old = self.lib.take(); + self.lib.store(None); + drop(old); } } @@ -101,5 +109,5 @@ impl ExternalLibs { } lazy_static::lazy_static! { - pub static ref CDATACACHE: PyRwLock = PyRwLock::new(ExternalLibs::new()); + pub static ref LIBCACHE: PyRwLock = PyRwLock::new(ExternalLibs::new()); } From aff291f8e729f730bdbc54457b2ee17c68e7baff Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 22 Nov 2020 18:47:39 -0300 Subject: [PATCH 23/56] Adding default value for PySimpleType --- vm/src/stdlib/ctypes/primitive.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 62efa10b2f..15c1a50219 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,5 +1,5 @@ -use crossbeam_utils::atomic::AtomicCell; use std::fmt; +use crossbeam_utils::atomic::AtomicCell; use crate::builtins::pystr::PyStr; use crate::builtins::PyTypeRef; @@ -13,15 +13,12 @@ pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; #[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] pub struct PySimpleType { _type_: String, - value: AtomicCell>, + value: AtomicCell, } impl fmt::Debug for PySimpleType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = match unsafe { (*self.value.as_ptr()).as_ref() } { - Some(v) => v.to_string(), - _ => "None".to_string(), - }; + let value = unsafe { (*self.value.as_ptr()).to_string() }; write!( f, @@ -55,7 +52,7 @@ impl PySimpleType { } else { PySimpleType { _type_: _type_.downcast_exact::(vm).unwrap().to_string(), - value: AtomicCell::default(), + value: AtomicCell::new(vm.ctx.none()), } .into_ref_with_type(vm, cls) } @@ -75,9 +72,16 @@ impl PySimpleType { pub fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { let content = if let Some(ref v) = value { // @TODO: Needs to check if value has a simple (rust native) payload - Some(v.clone()) + // and convert into the type on _type_ + v.clone() } else { - Some(vm.ctx.none()) + match self._type_.as_str() { + "c" | "u" => vm.ctx.new_bytes(vec![0]), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), + "f" | "d" | "g" => vm.ctx.new_float(0.0), + "?" => vm.ctx.new_bool(false), + "z" | "Z" | "P" | _ => vm.ctx.none(), + } }; self.value.store(content); From d29c3bf25e3b593e7815603ab1eee6b1246452bd Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 23 Nov 2020 14:02:05 -0300 Subject: [PATCH 24/56] Adding some comments --- vm/src/stdlib/ctypes/basics.rs | 7 ++++++- vm/src/stdlib/ctypes/primitive.rs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 6ea33a526c..6c76f11307 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,14 +1,19 @@ use crate::builtins::int::PyInt; use crate::builtins::memory::Buffer; use crate::builtins::pystr::PyStrRef; -use crate::builtins::PyTypeRef; use crate::function::OptionalArg; +use crate::builtins::pytype::{PyType, PyTypeRef}; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, }; use crate::slots::BufferProtocol; use crate::VirtualMachine; +// GenericPyCData_new -> PyResult +pub fn generic_pycdata_new(type_: PyTypeRef, vm: &VirtualMachine) { + // @TODO: To be used on several places +} + #[pyimpl] pub trait PyCDataMethods: PyValue { // A lot of the logic goes in this trait diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 15c1a50219..6b5d6bc405 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -34,7 +34,7 @@ impl fmt::Debug for PySimpleType { impl PyValue for PySimpleType { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::init_bare_type() + Self::static_metaclass() } } @@ -80,7 +80,7 @@ impl PySimpleType { "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), "f" | "d" | "g" => vm.ctx.new_float(0.0), "?" => vm.ctx.new_bool(false), - "z" | "Z" | "P" | _ => vm.ctx.none(), + "z" | "Z" | "P" | _ => vm.ctx.none(), // @TODO: What should we do here? Throw an error? } }; From 6fdffab95679fd75444ee6a4ffe91fd08c787eaf Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 26 Nov 2020 20:41:42 -0300 Subject: [PATCH 25/56] Implement PySimpleType __init__ --- vm/src/stdlib/ctypes/basics.rs | 2 +- vm/src/stdlib/ctypes/primitive.rs | 155 ++++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 18 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 6c76f11307..6617194cf3 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,8 +1,8 @@ use crate::builtins::int::PyInt; use crate::builtins::memory::Buffer; use crate::builtins::pystr::PyStrRef; -use crate::function::OptionalArg; use crate::builtins::pytype::{PyType, PyTypeRef}; +use crate::function::OptionalArg; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, }; diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 6b5d6bc405..d28df4f3f2 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,9 +1,14 @@ -use std::fmt; use crossbeam_utils::atomic::AtomicCell; +use num_bigint::BigInt; +use num_traits::FromPrimitive; +use rustpython_common::borrow::BorrowValue; +use std::fmt; -use crate::builtins::pystr::PyStr; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyObjectRc, PyRef, PyResult, PyValue, StaticType}; +use crate::builtins::{PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; +use crate::pyobject::{ + PyObjectRc, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, +}; use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; @@ -14,6 +19,7 @@ pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; pub struct PySimpleType { _type_: String, value: AtomicCell, + __abstract__: bool, } impl fmt::Debug for PySimpleType { @@ -53,6 +59,9 @@ impl PySimpleType { PySimpleType { _type_: _type_.downcast_exact::(vm).unwrap().to_string(), value: AtomicCell::new(vm.ctx.none()), + __abstract__: vm + .isinstance(&cls.as_object(), PySimpleType::static_type()) + .is_ok(), } .into_ref_with_type(vm, cls) } @@ -70,22 +79,134 @@ impl PySimpleType { #[pymethod(name = "__init__")] pub fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { - let content = if let Some(ref v) = value { - // @TODO: Needs to check if value has a simple (rust native) payload - // and convert into the type on _type_ - v.clone() - } else { - match self._type_.as_str() { - "c" | "u" => vm.ctx.new_bytes(vec![0]), - "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), - "f" | "d" | "g" => vm.ctx.new_float(0.0), - "?" => vm.ctx.new_bool(false), - "z" | "Z" | "P" | _ => vm.ctx.none(), // @TODO: What should we do here? Throw an error? + match value.clone() { + Some(ref v) if !self.__abstract__ => { + let content = match self._type_.as_str() { + "c" => { + if v.clone() + .downcast_exact::(vm) + .map(|v| v.len() == 1) + .is_ok() + || v.clone() + .downcast_exact::(vm) + .map(|v| v.borrow_value().len() == 1) + .is_ok() + || v.clone() + .downcast_exact::(vm) + .map(|v| { + v.borrow_value().ge(&BigInt::from_i64(0).unwrap()) + || v.borrow_value().le(&BigInt::from_i64(255).unwrap()) + }) + .is_ok() + { + Ok(v.clone()) + } else { + Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )) + } + } + "u" => { + if let Ok(b) = v + .clone() + .downcast_exact::(vm) + .map(|v| v.as_ref().chars().count() == 1) + { + if b { + Ok(v.clone()) + } else { + Err(vm.new_type_error( + "one character unicode string expected".to_string(), + )) + } + } else { + Err(vm.new_type_error(format!( + "unicode string expected instead of {} instance", + v.class().name + ))) + } + } + "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { + if v.clone().downcast_exact::(vm).is_ok() { + Ok(v.clone()) + } else { + Err(vm.new_type_error(format!( + "an integer is required (got type {})", + v.class().name + ))) + } + } + "f" | "d" | "g" => { + if v.clone().downcast_exact::(vm).is_ok() { + Ok(v.clone()) + } else { + Err(vm.new_type_error(format!( + "must be real number, not {}", + v.class().name + ))) + } + } + "?" => Ok(vm.ctx.none()), + "B" => { + if let Ok(v_c) = v.clone().downcast_exact::(vm) { + Ok(vm.new_pyobj(u8::try_from_object(vm, v.clone()).unwrap())) + } else { + Err(vm.new_type_error(format!( + "int expected instead of {}", + v.class().name + ))) + } + } + "z" => { + if v.clone().downcast_exact::(vm).is_ok() + || v.clone().downcast_exact::(vm).is_ok() + { + Ok(v.clone()) + } else { + Err(vm.new_type_error(format!( + "bytes or integer address expected instead of {} instance", + v.class().name + ))) + } + } + "Z" => { + if v.clone().downcast_exact::(vm).is_ok() { + Ok(v.clone()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + v.class().name + ))) + } + } + _ => { + // "P" + if v.clone().downcast_exact::(vm).is_ok() + || v.clone().downcast_exact::(vm).is_ok() + { + Ok(v.clone()) + } else { + Err(vm.new_type_error("cannot be converted to pointer".to_string())) + } + } + }?; + + self.value.store(content); + Ok(()) } - }; + Some(_) => Err(vm.new_type_error("abstract class".to_string())), + _ => { + self.value.store(match self._type_.as_str() { + "c" | "u" => vm.ctx.new_bytes(vec![0]), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), + "f" | "d" | "g" => vm.ctx.new_float(0.0), + "?" => vm.ctx.new_bool(false), + _ => vm.ctx.none(), // "z" | "Z" | "P" + }); - self.value.store(content); - Ok(()) + Ok(()) + } + } } // From Simple_Type Simple_methods From 568c8386aa900c6de92553c539506a4656494d7e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 29 Nov 2020 17:25:02 -0300 Subject: [PATCH 26/56] Modifying Buffer for PyCData --- vm/src/stdlib/ctypes/basics.rs | 17 ++- vm/src/stdlib/ctypes/primitive.rs | 242 ++++++++++++++++-------------- 2 files changed, 137 insertions(+), 122 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 6617194cf3..ced61777ef 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,12 +1,12 @@ use crate::builtins::int::PyInt; -use crate::builtins::memory::Buffer; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::{PyType, PyTypeRef}; +use crate::builtins::memory::{Buffer, BufferOptions}; use crate::function::OptionalArg; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, }; -use crate::slots::BufferProtocol; +use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::VirtualMachine; // GenericPyCData_new -> PyResult @@ -63,9 +63,14 @@ pub trait PyCDataMethods: PyValue { } // This trait will be used by all types -pub trait PyCDataBuffer: BufferProtocol { - // @TODO: Translate PyCData_NewGetBuffer - fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult>; +pub trait PyCDataBuffer: Buffer { + fn obj_bytes(&self) -> BorrowedValue<[u8]>; + + fn obj_bytes_mut(&self) -> BorrowedValueMut<[u8]>; + + fn release(&self); + + fn get_options(&self) -> BorrowedValue; } // This Trait is the equivalent of PyCData_Type on tp_base for @@ -106,7 +111,7 @@ impl PyCData { // #[pymethod(name = "__rmul__")] // fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - // self.mul(counter, &vm) + // self.mul(counter, vm) // } } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index d28df4f3f2..7a54e87ca8 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -7,13 +7,13 @@ use std::fmt; use crate::builtins::PyTypeRef; use crate::builtins::{PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; use crate::pyobject::{ - PyObjectRc, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; -pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfuzZqQP?g"; +pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; #[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] pub struct PySimpleType { @@ -38,6 +38,113 @@ impl fmt::Debug for PySimpleType { } } +fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyResult { + match _type_ { + "c" => { + if value + .clone() + .downcast_exact::(vm) + .map(|v| v.len() == 1) + .is_ok() + || value + .clone() + .downcast_exact::(vm) + .map(|v| v.borrow_value().len() == 1) + .is_ok() + || value + .clone() + .downcast_exact::(vm) + .map(|v| { + v.borrow_value().ge(&BigInt::from_i64(0).unwrap()) + || v.borrow_value().le(&BigInt::from_i64(255).unwrap()) + }) + .is_ok() + { + Ok(value.clone()) + } else { + Err(vm.new_type_error( + "one character bytes, bytearray or integer expected".to_string(), + )) + } + } + "u" => { + if let Ok(b) = value + .clone() + .downcast_exact::(vm) + .map(|v| v.as_ref().chars().count() == 1) + { + if b { + Ok(value.clone()) + } else { + Err(vm.new_type_error("one character unicode string expected".to_string())) + } + } else { + Err(vm.new_type_error(format!( + "unicode string expected instead of {} instance", + value.class().name + ))) + } + } + "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "an integer is required (got type {})", + value.class().name + ))) + } + } + "f" | "d" | "g" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!("must be real number, not {}", value.class().name))) + } + } + "?" => Ok(vm.ctx.none()), + "B" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone()).unwrap())) + } else { + Err(vm.new_type_error(format!("int expected instead of {}", value.class().name))) + } + } + "z" => { + if value.clone().downcast_exact::(vm).is_ok() + || value.clone().downcast_exact::(vm).is_ok() + { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "bytes or integer address expected instead of {} instance", + value.class().name + ))) + } + } + "Z" => { + if value.clone().downcast_exact::(vm).is_ok() { + Ok(value.clone()) + } else { + Err(vm.new_type_error(format!( + "unicode string or integer address expected instead of {} instance", + value.class().name + ))) + } + } + _ => { + // "P" + if value.clone().downcast_exact::(vm).is_ok() + || value.clone().downcast_exact::(vm).is_ok() + { + Ok(value.clone()) + } else { + Err(vm.new_type_error("cannot be converted to pointer".to_string())) + } + } + } +} + impl PyValue for PySimpleType { fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_metaclass() @@ -81,116 +188,7 @@ impl PySimpleType { pub fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { match value.clone() { Some(ref v) if !self.__abstract__ => { - let content = match self._type_.as_str() { - "c" => { - if v.clone() - .downcast_exact::(vm) - .map(|v| v.len() == 1) - .is_ok() - || v.clone() - .downcast_exact::(vm) - .map(|v| v.borrow_value().len() == 1) - .is_ok() - || v.clone() - .downcast_exact::(vm) - .map(|v| { - v.borrow_value().ge(&BigInt::from_i64(0).unwrap()) - || v.borrow_value().le(&BigInt::from_i64(255).unwrap()) - }) - .is_ok() - { - Ok(v.clone()) - } else { - Err(vm.new_type_error( - "one character bytes, bytearray or integer expected".to_string(), - )) - } - } - "u" => { - if let Ok(b) = v - .clone() - .downcast_exact::(vm) - .map(|v| v.as_ref().chars().count() == 1) - { - if b { - Ok(v.clone()) - } else { - Err(vm.new_type_error( - "one character unicode string expected".to_string(), - )) - } - } else { - Err(vm.new_type_error(format!( - "unicode string expected instead of {} instance", - v.class().name - ))) - } - } - "b" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => { - if v.clone().downcast_exact::(vm).is_ok() { - Ok(v.clone()) - } else { - Err(vm.new_type_error(format!( - "an integer is required (got type {})", - v.class().name - ))) - } - } - "f" | "d" | "g" => { - if v.clone().downcast_exact::(vm).is_ok() { - Ok(v.clone()) - } else { - Err(vm.new_type_error(format!( - "must be real number, not {}", - v.class().name - ))) - } - } - "?" => Ok(vm.ctx.none()), - "B" => { - if let Ok(v_c) = v.clone().downcast_exact::(vm) { - Ok(vm.new_pyobj(u8::try_from_object(vm, v.clone()).unwrap())) - } else { - Err(vm.new_type_error(format!( - "int expected instead of {}", - v.class().name - ))) - } - } - "z" => { - if v.clone().downcast_exact::(vm).is_ok() - || v.clone().downcast_exact::(vm).is_ok() - { - Ok(v.clone()) - } else { - Err(vm.new_type_error(format!( - "bytes or integer address expected instead of {} instance", - v.class().name - ))) - } - } - "Z" => { - if v.clone().downcast_exact::(vm).is_ok() { - Ok(v.clone()) - } else { - Err(vm.new_type_error(format!( - "unicode string or integer address expected instead of {} instance", - v.class().name - ))) - } - } - _ => { - // "P" - if v.clone().downcast_exact::(vm).is_ok() - || v.clone().downcast_exact::(vm).is_ok() - { - Ok(v.clone()) - } else { - Err(vm.new_type_error("cannot be converted to pointer".to_string())) - } - } - }?; - + let content = set_primitive(self._type_.as_str(), v, vm)?; self.value.store(content); Ok(()) } @@ -209,6 +207,18 @@ impl PySimpleType { } } + #[pyproperty(name = "value")] + fn value(&self) -> PyObjectRef { + unsafe { (*self.value.as_ptr()).clone() } + } + + #[pyproperty(name = "value", setter)] + fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let content = set_primitive(self._type_.as_str(), &value, vm)?; + self.value.store(content); + Ok(()) + } + // From Simple_Type Simple_methods #[pymethod(name = "__ctypes_from_outparam__")] pub fn ctypes_from_outparam(&self) {} @@ -217,10 +227,10 @@ impl PySimpleType { #[pyclassmethod] pub fn from_param(cls: PyTypeRef, vm: &VirtualMachine) {} - // #[pymethod(name = "__repr__")] - // fn repr(&self) -> String { - // format!("{}({})",self.class().name, self.value.to_string()) - // } + #[pymethod(name = "__repr__")] + fn repr(zelf: PyRef) -> String { + format!("{}({})", zelf.class().name, zelf.value().to_string()) + } // #[pymethod(name = "__bool__")] // fn bool(&self) -> bool { From fb07e533e70713835b31d0a3eb58a05ba48ee8a7 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 30 Nov 2020 16:32:09 -0300 Subject: [PATCH 27/56] Adding methods for PyCDataMethods --- vm/src/stdlib/ctypes/basics.rs | 232 +++++++++++++++++++++++------- vm/src/stdlib/ctypes/primitive.rs | 7 +- 2 files changed, 186 insertions(+), 53 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index ced61777ef..10191a608d 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,19 +1,114 @@ +use std::{fmt, slice}; + use crate::builtins::int::PyInt; +use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; use crate::builtins::pystr::PyStrRef; -use crate::builtins::pytype::{PyType, PyTypeRef}; -use crate::builtins::memory::{Buffer, BufferOptions}; +use crate::builtins::pytype::PyTypeRef; +use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; -use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::VirtualMachine; +use crate::stdlib::ctypes::dll::dlsym; + +use crossbeam_utils::atomic::AtomicCell; + // GenericPyCData_new -> PyResult pub fn generic_pycdata_new(type_: PyTypeRef, vm: &VirtualMachine) { // @TODO: To be used on several places } +fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult> { + match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { + Ok(attr) => match bool::try_from_object(vm, attr) { + Ok(b) if b => { + let len = vm + .get_attribute(cls.as_object().to_owned(), "_length_") + .map_or(Ok(1), |o: PyObjectRc| { + match i64::try_from_object(vm, o.clone()) { + Ok(v_int) => { + if v_int < 0 { + Err(vm.new_type_error("'_length_' must positive".to_string())) + } else { + Ok(v_int as usize) + } + } + _ => { + Err(vm.new_type_error("'_length_' must be an integer".to_string())) + } + } + })?; + + let slice = unsafe { slice::from_raw_parts(buf as *const u8, len) }; + Ok(slice.to_vec()) + } + Ok(_) => Err(vm.new_type_error("abstract class".to_string())), + Err(_) => Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), + }, + Err(_) => Err(vm.new_attribute_error("abstract class".to_string())), + } +} + +fn buffer_copy( + cls: PyTypeRef, + obj: PyObjectRef, + offset: OptionalArg, + vm: &VirtualMachine, + copy: bool, +) -> PyResult { + match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { + Ok(attr) => { + match bool::try_from_object(vm, attr) { + Ok(b) if b => { + let buffer = try_buffer_from_object(vm, &obj)?; + let opts = buffer.get_options().clone(); + + // @TODO: Fix the way the size of stored + let cls_size = vm + .get_attribute(cls.as_object().to_owned(), "_size") + .map(|c_s| usize::try_from_object(vm, c_s.clone()))??; + + let offset_int = offset + .into_option() + .map_or(Ok(0), |off| i64::try_from_object(vm, off.clone()))?; + + if opts.readonly { + Err(vm.new_type_error("underlying buffer is not writable".to_string())) + } else if !opts.contiguous { + Err(vm.new_type_error("underlying buffer is not C contiguous".to_string())) + } else if offset_int < 0 { + Err(vm.new_value_error("offset cannot be negative".to_string())) + } else if cls_size > opts.len - (offset_int as usize) { + Err(vm.new_value_error(format!( + "Buffer size too small ({} instead of at least {} bytes)", + cls_size, + opts.len + (offset_int as usize) + ))) + } else if let Some(mut buffer) = buffer.as_contiguous_mut() { + let buffered = if copy { + buffer.to_vec() + } else { + let b_ptr: *mut u8 = buffer.as_mut_ptr(); + unsafe { Vec::from_raw_parts(b_ptr, buffer.len(), buffer.len()) } + }; + + Ok(PyCData::new(None, Some(buffered))) + } else { + Err(vm.new_buffer_error("empty buffer".to_string())) + } + } + Ok(_) => Err(vm.new_type_error("abstract class".to_string())), + Err(_) => { + Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())) + } + } + } + Err(_) => Err(vm.new_type_error("abstract class".to_string())), + } +} + #[pyimpl] pub trait PyCDataMethods: PyValue { // A lot of the logic goes in this trait @@ -27,15 +122,25 @@ pub trait PyCDataMethods: PyValue { // PyCFuncPtrType_Type #[pyclassmethod] - fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) - -> PyResult>; + fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; #[pyclassmethod] fn from_address( cls: PyTypeRef, address: PyObjectRef, vm: &VirtualMachine, - ) -> PyResult>; + ) -> PyResult { + if let Ok(obj) = address.downcast_exact::(vm) { + if let Ok(v) = usize::try_from_object(vm, obj.into_object()) { + let buffer = at_address(&cls, v, vm)?; + Ok(PyCData::new(None, Some(buffer))) + } else { + Err(vm.new_runtime_error("casting pointer failed".to_string())) + } + } else { + Err(vm.new_type_error("integer expected".to_string())) + } + } #[pyclassmethod] fn from_buffer( @@ -43,7 +148,9 @@ pub trait PyCDataMethods: PyValue { obj: PyObjectRef, offset: OptionalArg, vm: &VirtualMachine, - ) -> PyResult>; + ) -> PyResult { + buffer_copy(cls, obj, offset, vm, false) + } #[pyclassmethod] fn from_buffer_copy( @@ -51,7 +158,9 @@ pub trait PyCDataMethods: PyValue { obj: PyObjectRef, offset: OptionalArg, vm: &VirtualMachine, - ) -> PyResult>; + ) -> PyResult { + buffer_copy(cls, obj, offset, vm, true) + } #[pyclassmethod] fn in_dll( @@ -59,7 +168,47 @@ pub trait PyCDataMethods: PyValue { dll: PyObjectRef, name: PyStrRef, vm: &VirtualMachine, - ) -> PyResult>; + ) -> PyResult { + if let Ok(h) = vm.get_attribute(cls.as_object().to_owned(), "_handle") { + // This is something to be "CPython" like + let raw_ptr = dlsym(h.clone(), name.into_object(), vm).map_err(|e| { + if vm + .isinstance(&e.clone().into(), &vm.ctx.exceptions.value_error) + .is_ok() + { + vm.new_type_error(format!( + "_handle must be SharedLibrary not {}", + dll.clone().class().name + )) + } else { + e + } + })?; + + let sym_ptr = usize::try_from_object(vm, raw_ptr.into_object(vm))?; + + let buffer = at_address(&cls, sym_ptr, vm)?; + Ok(PyCData::new(None, Some(buffer))) + } else { + Err(vm.new_attribute_error("atribute '_handle' not found".to_string())) + } + } +} + +#[pyimpl] +pub trait PyCDataSequenceMethods: PyValue { + // CDataType_as_sequence methods are default for all *Type_Type + // Basically the sq_repeat slot is CDataType_repeat + // which transforms into a Array + + // #[pymethod(name = "__mul__")] + // fn mul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + // } + + // #[pymethod(name = "__rmul__")] + // fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + // self.mul(counter, vm) + // } } // This trait will be used by all types @@ -70,16 +219,22 @@ pub trait PyCDataBuffer: Buffer { fn release(&self); - fn get_options(&self) -> BorrowedValue; + fn get_options(&self) -> BorrowedValue; } // This Trait is the equivalent of PyCData_Type on tp_base for // Struct_Type, Union_Type, PyCPointer_Type // PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type #[pyclass(module = "ctypes", name = "_CData")] -#[derive(Debug)] pub struct PyCData { - _objects: Vec, + _objects: AtomicCell>, + _buffer: AtomicCell>, +} + +impl fmt::Debug for PyCData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCData {{ _objects: {{}}, _buffer: {{}}}}",) + } } impl PyValue for PyCData { @@ -88,54 +243,29 @@ impl PyValue for PyCData { } } -#[pyimpl] impl PyCData { - // Methods here represent PyCData_methods + fn new(objs: Option>, buffer: Option>) -> Self { + PyCData { + _objects: AtomicCell::new(objs.unwrap_or_default()), + _buffer: AtomicCell::new(buffer.unwrap_or_default()), + } + } +} +#[pyimpl] +impl PyCData { + // PyCData_methods #[pymethod(name = "__ctypes_from_outparam__")] - pub fn ctypes_from_outparam(&self) {} + pub fn ctypes_from_outparam(zelf: PyRef) {} #[pymethod(name = "__reduce__")] - pub fn reduce(&self) {} + pub fn reduce(zelf: PyRef) {} #[pymethod(name = "__setstate__")] - pub fn setstate(&self) {} - - // CDataType_as_sequence methods are default for all types implementing PyCDataMethods - // Basically the sq_repeat slot is CDataType_repeat - // which transforms into a Array - - // #[pymethod(name = "__mul__")] - // fn mul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - // } - - // #[pymethod(name = "__rmul__")] - // fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - // self.mul(counter, vm) - // } + pub fn setstate(zelf: PyRef) {} } // #[pyimpl] // impl PyCDataBuffer for PyCData { // } - -// #[pyimpl] -// impl PyCDataMethods for PyCData { -// #[pyclassmethod] -// fn from_address( -// cls: PyTypeRef, -// address: PyObjectRef, -// vm: &VirtualMachine -// ) -> PyResult> { -// if let Ok(obj) = address.downcast_exact::(vm) { -// if let Ok(v) = usize::try_from_object(vm, obj.into_object()) { - -// } else { -// Err(vm.new_runtime_error("casting pointer failed".to_string())) -// } -// } else { -// Err(vm.new_type_error("integer expected".to_string())) -// } -// } -// } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 7a54e87ca8..b233ac59f3 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -6,6 +6,7 @@ use std::fmt; use crate::builtins::PyTypeRef; use crate::builtins::{PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; +use crate::function::OptionalArg; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; @@ -185,8 +186,8 @@ impl PySimpleType { } #[pymethod(name = "__init__")] - pub fn init(&self, value: Option, vm: &VirtualMachine) -> PyResult<()> { - match value.clone() { + pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + match value.into_option() { Some(ref v) if !self.__abstract__ => { let content = set_primitive(self._type_.as_str(), v, vm)?; self.value.store(content); @@ -227,11 +228,13 @@ impl PySimpleType { #[pyclassmethod] pub fn from_param(cls: PyTypeRef, vm: &VirtualMachine) {} + // Simple_repr #[pymethod(name = "__repr__")] fn repr(zelf: PyRef) -> String { format!("{}({})", zelf.class().name, zelf.value().to_string()) } + // Simple_as_number // #[pymethod(name = "__bool__")] // fn bool(&self) -> bool { // From 6122e4046c454a32c12fb6e227a371abfe754534 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 4 Dec 2020 13:58:51 -0300 Subject: [PATCH 28/56] Adding PyCData_NewGetBuffer related code --- vm/src/stdlib/ctypes/basics.rs | 95 +++++++++++++++++++++++-------- vm/src/stdlib/ctypes/primitive.rs | 13 ++--- 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 10191a608d..9dbf6ba513 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -5,10 +5,15 @@ use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; +use crate::common::lock::{ + PyRwLock, PyRwLockReadGuard, PyRwLockUpgradableReadGuard, PyRwLockWriteGuard, +}; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + BorrowValue, PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + TypeProtocol, }; +use crate::slots::BufferProtocol; use crate::VirtualMachine; use crate::stdlib::ctypes::dll::dlsym; @@ -41,8 +46,8 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult Err(vm.new_type_error("abstract class".to_string())), Err(_) => Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), @@ -66,6 +71,8 @@ fn buffer_copy( let opts = buffer.get_options().clone(); // @TODO: Fix the way the size of stored + // Would this be the proper replacement? + // vm.call_method(cls.as_object(), "size", ())?. let cls_size = vm .get_attribute(cls.as_object().to_owned(), "_size") .map(|c_s| usize::try_from_object(vm, c_s.clone()))??; @@ -87,14 +94,17 @@ fn buffer_copy( opts.len + (offset_int as usize) ))) } else if let Some(mut buffer) = buffer.as_contiguous_mut() { - let buffered = if copy { - buffer.to_vec() - } else { - let b_ptr: *mut u8 = buffer.as_mut_ptr(); - unsafe { Vec::from_raw_parts(b_ptr, buffer.len(), buffer.len()) } - }; - - Ok(PyCData::new(None, Some(buffered))) + let buffered = + unsafe { slice::from_raw_parts(buffer.as_mut_ptr(), buffer.len()) }; + // @TODO: Is this ok? + Ok(PyCData::new( + None, + Some(if copy { + buffered.to_owned().to_vec() + } else { + buffered.to_vec() + }), + )) } else { Err(vm.new_buffer_error("empty buffer".to_string())) } @@ -211,15 +221,47 @@ pub trait PyCDataSequenceMethods: PyValue { // } } -// This trait will be used by all types -pub trait PyCDataBuffer: Buffer { - fn obj_bytes(&self) -> BorrowedValue<[u8]>; +impl<'a> BorrowValue<'a> for PyCData { + type Borrowed = PyRwLockReadGuard<'a, Vec>; + + fn borrow_value(&'a self) -> Self::Borrowed { + self._buffer.read() + } +} - fn obj_bytes_mut(&self) -> BorrowedValueMut<[u8]>; +impl BufferProtocol for PyCData { + fn get_buffer(zelf: &PyRef, _vm: &VirtualMachine) -> PyResult> { + Ok(Box::new(zelf.clone())) + } +} - fn release(&self); +// This trait will be used by all types +impl Buffer for PyCDataRef { + fn obj_bytes(&self) -> BorrowedValue<[u8]> { + PyRwLockReadGuard::map(self.borrow_value(), |x| x.as_slice()).into() + } - fn get_options(&self) -> BorrowedValue; + fn obj_bytes_mut(&self) -> BorrowedValueMut<[u8]> { + PyRwLockWriteGuard::map(self.borrow_value_mut(), |x| x.as_mut_slice()).into() + } + + fn release(&self) {} + + fn get_options(&self) -> BorrowedValue { + let guard = self.buffer_options.upgradable_read(); + let guard = if guard.is_none() { + let mut w = PyRwLockUpgradableReadGuard::upgrade(guard); + *w = Some(Box::new(BufferOptions { + readonly: false, + len: self._buffer.read().len(), + ..Default::default() + })); + PyRwLockWriteGuard::downgrade(w) + } else { + PyRwLockUpgradableReadGuard::downgrade(guard) + }; + PyRwLockReadGuard::map(guard, |x| x.as_ref().unwrap().as_ref()).into() + } } // This Trait is the equivalent of PyCData_Type on tp_base for @@ -228,9 +270,12 @@ pub trait PyCDataBuffer: Buffer { #[pyclass(module = "ctypes", name = "_CData")] pub struct PyCData { _objects: AtomicCell>, - _buffer: AtomicCell>, + _buffer: PyRwLock>, + buffer_options: PyRwLock>>, } +pub type PyCDataRef = PyRef; + impl fmt::Debug for PyCData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "PyCData {{ _objects: {{}}, _buffer: {{}}}}",) @@ -247,9 +292,14 @@ impl PyCData { fn new(objs: Option>, buffer: Option>) -> Self { PyCData { _objects: AtomicCell::new(objs.unwrap_or_default()), - _buffer: AtomicCell::new(buffer.unwrap_or_default()), + _buffer: PyRwLock::new(buffer.unwrap_or_default()), + buffer_options: PyRwLock::new(None), } } + + pub fn borrow_value_mut(&self) -> PyRwLockWriteGuard<'_, Vec> { + self._buffer.write() + } } #[pyimpl] @@ -263,9 +313,4 @@ impl PyCData { #[pymethod(name = "__setstate__")] pub fn setstate(zelf: PyRef) {} -} - -// #[pyimpl] -// impl PyCDataBuffer for PyCData { - -// } +} \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index b233ac59f3..ee19cb074c 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -5,7 +5,7 @@ use rustpython_common::borrow::BorrowValue; use std::fmt; use crate::builtins::PyTypeRef; -use crate::builtins::{PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; +use crate::builtins::{pybool::boolval, PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; use crate::function::OptionalArg; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, @@ -45,21 +45,18 @@ fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyRes if value .clone() .downcast_exact::(vm) - .map(|v| v.len() == 1) - .is_ok() + .map_or(false, |v| v.len() == 1) || value .clone() .downcast_exact::(vm) - .map(|v| v.borrow_value().len() == 1) - .is_ok() + .map_or(false, |v| v.borrow_value().len() == 1) || value .clone() .downcast_exact::(vm) - .map(|v| { + .map_or(false, |v| { v.borrow_value().ge(&BigInt::from_i64(0).unwrap()) || v.borrow_value().le(&BigInt::from_i64(255).unwrap()) }) - .is_ok() { Ok(value.clone()) } else { @@ -103,7 +100,7 @@ fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyRes Err(vm.new_type_error(format!("must be real number, not {}", value.class().name))) } } - "?" => Ok(vm.ctx.none()), + "?" => Ok(vm.ctx.new_bool(boolval(vm, value.clone())?)), "B" => { if value.clone().downcast_exact::(vm).is_ok() { Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone()).unwrap())) From 6a4f8810ac7661dd8925a56b6007d7434f36fc5e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 4 Dec 2020 18:17:31 -0300 Subject: [PATCH 29/56] Fixing small fixes --- vm/src/stdlib/ctypes/basics.rs | 21 ++++++++++----------- vm/src/stdlib/ctypes/mod.rs | 5 +++-- vm/src/stdlib/ctypes/primitive.rs | 19 ++++++++++++------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 9dbf6ba513..2a055139af 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -20,15 +20,10 @@ use crate::stdlib::ctypes::dll::dlsym; use crossbeam_utils::atomic::AtomicCell; -// GenericPyCData_new -> PyResult -pub fn generic_pycdata_new(type_: PyTypeRef, vm: &VirtualMachine) { - // @TODO: To be used on several places -} - fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult> { match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { Ok(attr) => match bool::try_from_object(vm, attr) { - Ok(b) if b => { + Ok(b) if !b => { let len = vm .get_attribute(cls.as_object().to_owned(), "_length_") .map_or(Ok(1), |o: PyObjectRc| { @@ -50,8 +45,10 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult Err(vm.new_type_error("abstract class".to_string())), + // @TODO: A sanity check Err(_) => Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), }, + // @TODO: I think it's unreacheble Err(_) => Err(vm.new_attribute_error("abstract class".to_string())), } } @@ -66,7 +63,7 @@ fn buffer_copy( match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { Ok(attr) => { match bool::try_from_object(vm, attr) { - Ok(b) if b => { + Ok(b) if !b => { let buffer = try_buffer_from_object(vm, &obj)?; let opts = buffer.get_options().clone(); @@ -111,10 +108,12 @@ fn buffer_copy( } Ok(_) => Err(vm.new_type_error("abstract class".to_string())), Err(_) => { + // @TODO: A sanity check Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())) } } } + // @TODO: I think this is unreacheble... Err(_) => Err(vm.new_type_error("abstract class".to_string())), } } @@ -267,7 +266,7 @@ impl Buffer for PyCDataRef { // This Trait is the equivalent of PyCData_Type on tp_base for // Struct_Type, Union_Type, PyCPointer_Type // PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type -#[pyclass(module = "ctypes", name = "_CData")] +#[pyclass(module = "_ctypes", name = "_CData")] pub struct PyCData { _objects: AtomicCell>, _buffer: PyRwLock>, @@ -284,7 +283,7 @@ impl fmt::Debug for PyCData { impl PyValue for PyCData { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::init_bare_type() + Self::static_type() } } @@ -302,7 +301,7 @@ impl PyCData { } } -#[pyimpl] +#[pyimpl(flags(BASETYPE), with(BufferProtocol))] impl PyCData { // PyCData_methods #[pymethod(name = "__ctypes_from_outparam__")] @@ -313,4 +312,4 @@ impl PyCData { #[pymethod(name = "__setstate__")] pub fn setstate(zelf: PyRef) {} -} \ No newline at end of file +} diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index be398ff31a..ce604dc76d 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -8,9 +8,10 @@ mod function; mod primitive; mod shared_lib; +use crate::stdlib::ctypes::basics::PyCData; use crate::stdlib::ctypes::dll::*; -use crate::stdlib::ctypes::function::*; -use crate::stdlib::ctypes::primitive::*; +use crate::stdlib::ctypes::function::PyCFuncPtr; +use crate::stdlib::ctypes::primitive::PySimpleType; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index ee19cb074c..62c3a68eba 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -145,14 +145,22 @@ fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyRes impl PyValue for PySimpleType { fn class(_vm: &VirtualMachine) -> &PyTypeRef { - Self::static_metaclass() + Self::static_type() } } #[pyimpl] impl PySimpleType { #[pyslot] - fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { + let is_abstract = vm + .isinstance(&cls.as_object(), PySimpleType::static_type()) + .is_ok(); + + if is_abstract { + return Err(vm.new_type_error("abstract class".to_string())); + } + match vm.get_attribute(cls.as_object().to_owned(), "_type_") { Ok(_type_) => { if vm.isinstance(&_type_, &vm.ctx.types.str_type)? { @@ -164,9 +172,7 @@ impl PySimpleType { PySimpleType { _type_: _type_.downcast_exact::(vm).unwrap().to_string(), value: AtomicCell::new(vm.ctx.none()), - __abstract__: vm - .isinstance(&cls.as_object(), PySimpleType::static_type()) - .is_ok(), + __abstract__: is_abstract, } .into_ref_with_type(vm, cls) } @@ -185,12 +191,11 @@ impl PySimpleType { #[pymethod(name = "__init__")] pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { match value.into_option() { - Some(ref v) if !self.__abstract__ => { + Some(ref v) => { let content = set_primitive(self._type_.as_str(), v, vm)?; self.value.store(content); Ok(()) } - Some(_) => Err(vm.new_type_error("abstract class".to_string())), _ => { self.value.store(match self._type_.as_str() { "c" | "u" => vm.ctx.new_bytes(vec![0]), From e2e0ac8470079578b2b378b883d3e3cba810587f Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 5 Dec 2020 09:10:46 -0300 Subject: [PATCH 30/56] Testing PySimpleType basic functionalities --- Lib/ctypes/__init__.py | 60 ++++++++++++++++++++++++++++++ vm/src/stdlib/ctypes/basics.rs | 6 +-- vm/src/stdlib/ctypes/dll.rs | 22 +++++------ vm/src/stdlib/ctypes/mod.rs | 1 + vm/src/stdlib/ctypes/primitive.rs | 26 +++++++------ vm/src/stdlib/ctypes/shared_lib.rs | 30 +++++++++++++-- 6 files changed, 116 insertions(+), 29 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index fa7f5df6e9..34b899a78a 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -2,6 +2,66 @@ from _ctypes import CFuncPtr as _CFuncPtr from _ctypes import dlopen as _dlopen +from _ctypes import _SimpleCData + +class c_short(_SimpleCData): + _type_ = "h" + +class c_ushort(_SimpleCData): + _type_ = "H" + +class c_long(_SimpleCData): + _type_ = "l" + +class c_ulong(_SimpleCData): + _type_ = "L" + +class c_int(_SimpleCData): + _type_ = "i" + +class c_uint(_SimpleCData): + _type_ = "I" + +class c_float(_SimpleCData): + _type_ = "f" + +class c_double(_SimpleCData): + _type_ = "d" + +class c_longdouble(_SimpleCData): + _type_ = "g" + +class c_longlong(_SimpleCData): + _type_ = "q" + +class c_ulonglong(_SimpleCData): + _type_ = "Q" + +class c_ubyte(_SimpleCData): + _type_ = "B" + +class c_byte(_SimpleCData): + _type_ = "b" + +class c_char(_SimpleCData): + _type_ = "c" + +class c_char_p(_SimpleCData): + _type_ = "z" + +class c_void_p(_SimpleCData): + _type_ = "P" + +c_voidp = c_void_p # backwards compatibility (to a bug) + +class c_bool(_SimpleCData): + _type_ = "?" + +class c_wchar_p(_SimpleCData): + _type_ = "Z" + +class c_wchar(_SimpleCData): + _type_ = "u" class CDLL(object): diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 2a055139af..2af863212c 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -304,12 +304,12 @@ impl PyCData { #[pyimpl(flags(BASETYPE), with(BufferProtocol))] impl PyCData { // PyCData_methods - #[pymethod(name = "__ctypes_from_outparam__")] + #[pymethod(magic)] pub fn ctypes_from_outparam(zelf: PyRef) {} - #[pymethod(name = "__reduce__")] + #[pymethod(magic)] pub fn reduce(zelf: PyRef) {} - #[pymethod(name = "__setstate__")] + #[pymethod(magic)] pub fn setstate(zelf: PyRef) {} } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 3f3f2a5773..da4a847d89 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -2,7 +2,7 @@ extern crate libloading; use crate::builtins::pystr::PyStr; use crate::builtins::PyInt; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, TypeProtocol}; use crate::VirtualMachine; use crate::stdlib::ctypes::shared_lib::{SharedLibrary, LIBCACHE}; @@ -34,7 +34,7 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul let str_ref = func.downcast::().unwrap(); let func_name = str_ref.as_ref(); - match slib.downcast::() { + match slib.clone().downcast::() { Ok(lib) => { match lib.get_sym(func_name) { Ok(ptr) => Ok(PyInt::from(ptr as *const _ as usize)), @@ -42,22 +42,22 @@ pub fn dlsym(slib: PyObjectRc, func: PyObjectRc, vm: &VirtualMachine) -> PyResul .new_runtime_error(format!("Error while opening symbol {}: {}", func_name, e))), } } - Err(_) => { - Err(vm - .new_value_error("first argument (slib) is not a valid SharedLibrary".to_string())) - } + Err(_) => Err(vm.new_type_error(format!( + "a SharedLibrary is required, found {}", + slib.class().name + ))), } } pub fn dlclose(slib: PyObjectRc, vm: &VirtualMachine) -> PyResult { - match slib.downcast::() { + match slib.clone().downcast::() { Ok(lib) => { lib.close(); Ok(vm.ctx.none()) } - Err(_) => { - Err(vm - .new_value_error("first argument (slib) is not a valid SharedLibrary".to_string())) - } + Err(_) => Err(vm.new_type_error(format!( + "a SharedLibrary is required, found {}", + slib.class().name + ))), } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index ce604dc76d..acb741d126 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -15,6 +15,7 @@ use crate::stdlib::ctypes::primitive::PySimpleType; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + PyCData::make_class(ctx); py_module!(vm, "_ctypes", { "dlopen" => ctx.new_function(dlopen), diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 62c3a68eba..762bf6f4f8 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -149,13 +149,11 @@ impl PyValue for PySimpleType { } } -#[pyimpl] +#[pyimpl(flags(BASETYPE))] impl PySimpleType { #[pyslot] fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { - let is_abstract = vm - .isinstance(&cls.as_object(), PySimpleType::static_type()) - .is_ok(); + let is_abstract = cls.name == PySimpleType::static_type().name; if is_abstract { return Err(vm.new_type_error("abstract class".to_string())); @@ -164,13 +162,15 @@ impl PySimpleType { match vm.get_attribute(cls.as_object().to_owned(), "_type_") { Ok(_type_) => { if vm.isinstance(&_type_, &vm.ctx.types.str_type)? { - if _type_.to_string().len() != 1 { + let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); + + if tp_str.len() != 1 { Err(vm.new_value_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) - } else if !SIMPLE_TYPE_CHARS.contains(_type_.to_string().as_str()) { + } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of {}.",SIMPLE_TYPE_CHARS))) } else { PySimpleType { - _type_: _type_.downcast_exact::(vm).unwrap().to_string(), + _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), __abstract__: is_abstract, } @@ -194,7 +194,6 @@ impl PySimpleType { Some(ref v) => { let content = set_primitive(self._type_.as_str(), v, vm)?; self.value.store(content); - Ok(()) } _ => { self.value.store(match self._type_.as_str() { @@ -204,10 +203,9 @@ impl PySimpleType { "?" => vm.ctx.new_bool(false), _ => vm.ctx.none(), // "z" | "Z" | "P" }); - - Ok(()) } } + Ok(()) } #[pyproperty(name = "value")] @@ -232,8 +230,12 @@ impl PySimpleType { // Simple_repr #[pymethod(name = "__repr__")] - fn repr(zelf: PyRef) -> String { - format!("{}({})", zelf.class().name, zelf.value().to_string()) + fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(format!( + "{}({})", + zelf.class().name, + vm.to_repr(&zelf.value())?.to_string() + )) } // Simple_as_number diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index 66171b5684..6da2f8641f 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -2,7 +2,7 @@ extern crate lazy_static; extern crate libffi; extern crate libloading; -use ::std::{collections::HashMap, fmt, os::raw::c_void}; +use ::std::{collections::HashMap, fmt, os::raw::c_void, ptr::null}; use crossbeam_utils::atomic::AtomicCell; use libloading::Library; @@ -11,7 +11,7 @@ use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::pyobject::{PyRef, PyValue}; use crate::VirtualMachine; - +#[pyclass(module = false, name = "SharedLibrary")] pub struct SharedLibrary { path_name: String, lib: AtomicCell>, @@ -26,7 +26,7 @@ impl fmt::Debug for SharedLibrary { lib: {}, }}", self.path_name.as_str(), - self.is_closed() + self.get_pointer() ) } } @@ -60,6 +60,14 @@ impl SharedLibrary { } } + pub fn get_pointer(&self) -> usize { + if let Some(l) = unsafe { &*self.lib.as_ptr() } { + l as *const Library as *const u32 as usize + } else { + null() as *const u32 as usize + } + } + pub fn is_closed(&self) -> bool { unsafe { &*self.lib.as_ptr() }.is_none() } @@ -71,6 +79,22 @@ impl SharedLibrary { } } +#[pyimpl] +impl SharedLibrary { + #[pymethod(magic)] + fn repr(&self) -> String { + let pointer = self.get_pointer(); + + let value = if pointer == 0usize { + "None".to_string() + } else { + pointer.to_string() + }; + + format!("SharedLibrary({})", value) + } +} + pub struct ExternalLibs { libraries: HashMap>, } From c1324ceb3ed20b4eca60b3c03e55aae456482c1d Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 5 Dec 2020 12:22:59 -0300 Subject: [PATCH 31/56] Refactoring SharedLibrary --- vm/src/stdlib/ctypes/basics.rs | 21 ++++------ vm/src/stdlib/ctypes/dll.rs | 64 +++++++++++------------------- vm/src/stdlib/ctypes/function.rs | 16 +++----- vm/src/stdlib/ctypes/mod.rs | 8 ++-- vm/src/stdlib/ctypes/primitive.rs | 2 +- vm/src/stdlib/ctypes/shared_lib.rs | 40 ++++++------------- 6 files changed, 55 insertions(+), 96 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 2af863212c..ef63c96c7d 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -180,19 +180,14 @@ pub trait PyCDataMethods: PyValue { ) -> PyResult { if let Ok(h) = vm.get_attribute(cls.as_object().to_owned(), "_handle") { // This is something to be "CPython" like - let raw_ptr = dlsym(h.clone(), name.into_object(), vm).map_err(|e| { - if vm - .isinstance(&e.clone().into(), &vm.ctx.exceptions.value_error) - .is_ok() - { - vm.new_type_error(format!( - "_handle must be SharedLibrary not {}", - dll.clone().class().name - )) - } else { - e - } - })?; + let raw_ptr = if let Ok(h_int) = h.downcast_exact::(vm) { + dlsym(h_int, name, vm) + } else { + Err(vm.new_type_error(format!( + "_handle must be an int not {}", + dll.clone().class().name + ))) + }?; let sym_ptr = usize::try_from_object(vm, raw_ptr.into_object(vm))?; diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index da4a847d89..2fe16f897f 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,25 +1,19 @@ extern crate libloading; -use crate::builtins::pystr::PyStr; -use crate::builtins::PyInt; -use crate::pyobject::{PyObjectRc, PyObjectRef, PyResult, TypeProtocol}; +use crate::builtins::pystr::PyStrRef; +use crate::builtins::{PyInt, PyIntRef}; +use crate::pyobject::{PyResult, TryFromObject}; use crate::VirtualMachine; -use crate::stdlib::ctypes::shared_lib::{SharedLibrary, LIBCACHE}; - -pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult { - // Match this error first - let lib_str_path = match vm.isinstance(&lib_path, &vm.ctx.types.str_type) { - Ok(_) => Ok(lib_path.to_string()), - Err(e) => Err(e), - }?; +use crate::stdlib::ctypes::shared_lib::LIBCACHE; +pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { let mut data_cache = LIBCACHE.write(); - let result = data_cache.get_or_insert_lib(lib_str_path.as_ref(), vm); + let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); match result { - Ok(lib) => Ok(lib.clone().into_object()), + Ok(lib) => Ok(PyInt::from(lib.get_pointer())), Err(_) => Err(vm.new_os_error(format!( "{} : cannot open shared object file: No such file or directory", lib_path.to_string() @@ -27,37 +21,27 @@ pub fn dlopen(lib_path: PyObjectRc, vm: &VirtualMachine) -> PyResult PyResult { - if !vm.isinstance(&func, &vm.ctx.types.str_type)? { - return Err(vm.new_value_error("second argument (func) must be str".to_string())); - } - let str_ref = func.downcast::().unwrap(); +pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { let func_name = str_ref.as_ref(); - - match slib.clone().downcast::() { - Ok(lib) => { - match lib.get_sym(func_name) { - Ok(ptr) => Ok(PyInt::from(ptr as *const _ as usize)), - Err(e) => Err(vm - .new_runtime_error(format!("Error while opening symbol {}: {}", func_name, e))), - } - } - Err(_) => Err(vm.new_type_error(format!( - "a SharedLibrary is required, found {}", - slib.class().name - ))), + let data_cache = LIBCACHE.read(); + + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => match l.get_sym(func_name) { + Ok(ptr) => Ok(PyInt::from(ptr as *const _ as usize)), + Err(e) => Err(vm.new_runtime_error(e)), + }, + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), } } -pub fn dlclose(slib: PyObjectRc, vm: &VirtualMachine) -> PyResult { - match slib.clone().downcast::() { - Ok(lib) => { - lib.close(); - Ok(vm.ctx.none()) +pub fn dlclose(slib: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let data_cache = LIBCACHE.read(); + + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => { + l.close(); + Ok(()) } - Err(_) => Err(vm.new_type_error(format!( - "a SharedLibrary is required, found {}", - slib.class().name - ))), + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 3a034fa1cd..f21dd97141 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -12,7 +12,7 @@ use libffi::middle; use num_bigint::BigInt; use crate::builtins::pystr::PyStrRef; -use crate::builtins::PyTypeRef; +use crate::builtins::{PyInt, PyTypeRef}; use crate::common::lock::PyRwLock; use crate::function::FuncArgs; @@ -22,7 +22,6 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; -use crate::stdlib::ctypes::shared_lib::SharedLibrary; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; @@ -416,16 +415,16 @@ impl PyCFuncPtr { vm: &VirtualMachine, ) -> PyResult> { if let Ok(h) = vm.get_attribute(arg.clone(), "_handle") { - if let Ok(handle) = h.downcast::() { - let handle_obj = handle.into_object(); - let ptr_fn = dlsym(handle_obj.clone(), func_name.clone().into_object(), vm)?; + if let Ok(handle) = h.downcast::() { + let handle_obj = handle.clone().into_object(); + let ptr_fn = dlsym(handle, func_name.clone(), vm)?; let fn_ptr = usize::try_from_object(vm, ptr_fn.into_object(vm))? as *mut c_void; PyCFuncPtr { _name_: func_name.to_string(), _argtypes_: AtomicCell::default(), _restype_: AtomicCell::new(vm.ctx.none()), - _handle: handle_obj.clone(), + _handle: handle_obj, _f: PyRwLock::new(Function::new( fn_ptr, Vec::new(), @@ -434,10 +433,7 @@ impl PyCFuncPtr { } .into_ref_with_type(vm, cls) } else { - Err(vm.new_type_error(format!( - "_handle must be SharedLibrary not {}", - arg.class().name - ))) + Err(vm.new_type_error(format!("_handle must be an int not {}", arg.class().name))) } } else { Err(vm.new_attribute_error( diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index acb741d126..62cca00615 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -8,10 +8,10 @@ mod function; mod primitive; mod shared_lib; -use crate::stdlib::ctypes::basics::PyCData; -use crate::stdlib::ctypes::dll::*; -use crate::stdlib::ctypes::function::PyCFuncPtr; -use crate::stdlib::ctypes::primitive::PySimpleType; +use basics::PyCData; +use dll::*; +use function::PyCFuncPtr; +use primitive::PySimpleType; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 762bf6f4f8..46b53b8f37 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -167,7 +167,7 @@ impl PySimpleType { if tp_str.len() != 1 { Err(vm.new_value_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\na single character string containing one of {}.",SIMPLE_TYPE_CHARS))) + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) } else { PySimpleType { _type_: tp_str, diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index 6da2f8641f..cc4c2bf5c3 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -11,7 +11,6 @@ use crate::builtins::PyTypeRef; use crate::common::lock::PyRwLock; use crate::pyobject::{PyRef, PyValue}; use crate::VirtualMachine; -#[pyclass(module = false, name = "SharedLibrary")] pub struct SharedLibrary { path_name: String, lib: AtomicCell>, @@ -79,24 +78,8 @@ impl SharedLibrary { } } -#[pyimpl] -impl SharedLibrary { - #[pymethod(magic)] - fn repr(&self) -> String { - let pointer = self.get_pointer(); - - let value = if pointer == 0usize { - "None".to_string() - } else { - pointer.to_string() - }; - - format!("SharedLibrary({})", value) - } -} - pub struct ExternalLibs { - libraries: HashMap>, + libraries: HashMap>, } impl ExternalLibs { @@ -106,29 +89,30 @@ impl ExternalLibs { } } + pub fn get_lib(&self, key: usize) -> Option<&PyRef> { + self.libraries.get(&key) + } + pub fn get_or_insert_lib( &mut self, library_path: &str, vm: &VirtualMachine, ) -> Result<&PyRef, libloading::Error> { - match self.libraries.get(&library_path.to_string()) { + let nlib = SharedLibrary::new(library_path)?.into_ref(vm); + let key = nlib.get_pointer(); + + match self.libraries.get(&key) { Some(l) => { if l.is_closed() { - self.libraries.insert( - library_path.to_string(), - SharedLibrary::new(library_path)?.into_ref(vm), - ); + self.libraries.insert(key, nlib); } } _ => { - self.libraries.insert( - library_path.to_string(), - SharedLibrary::new(library_path)?.into_ref(vm), - ); + self.libraries.insert(key, nlib); } }; - Ok(self.libraries.get(&library_path.to_string()).unwrap()) + Ok(self.libraries.get(&key).unwrap()) } } From 3f40466d370ba6ece0774e310adc35d54b711cb6 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 5 Dec 2020 16:38:23 -0300 Subject: [PATCH 32/56] Fixing several bugs --- Lib/ctypes/__init__.py | 43 +++++++--- vm/src/stdlib/ctypes/function.rs | 137 +++++++++++++++--------------- vm/src/stdlib/ctypes/primitive.rs | 2 +- 3 files changed, 99 insertions(+), 83 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 34b899a78a..bd33e5c7b8 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,9 +1,34 @@ -import os, sys +import os as _os, sys as _sys from _ctypes import CFuncPtr as _CFuncPtr from _ctypes import dlopen as _dlopen from _ctypes import _SimpleCData +_c_functype_cache = {} +def CFUNCTYPE(restype, *argtypes, **kw): + """CFUNCTYPE(restype, *argtypes, + use_errno=False, use_last_error=False) -> function prototype. + restype: the result type + argtypes: a sequence specifying the argument types + The function prototype can be called in different ways to create a + callable object: + prototype(integer address) -> foreign function + prototype(callable) -> create and return a C callable function from callable + prototype(integer index, method name[, paramflags]) -> foreign function calling a COM method + prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal + prototype((function name, dll object)[, paramflags]) -> foreign function exported by name + """ + if kw: + raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) + try: + return _c_functype_cache[(restype, argtypes)] + except KeyError: + class CFunctionType(_CFuncPtr): + _argtypes_ = argtypes + _restype_ = restype + _c_functype_cache[(restype, argtypes)] = CFunctionType + return CFunctionType + class c_short(_SimpleCData): _type_ = "h" @@ -64,7 +89,6 @@ class c_wchar(_SimpleCData): _type_ = "u" class CDLL(object): - """An instance of this class represents a loaded dll/shared library, exporting functions using the standard C calling convention (named 'cdecl' on Windows). @@ -75,17 +99,16 @@ class CDLL(object): Calling the functions releases the Python GIL during the call and reacquires it afterwards. """ + _func_restype_ = c_int # default values for repr _name = '' _handle = 0 _FuncPtr = None - def __init__(self, name, handle=None): + def __init__(self, name,handle=None): self._name = name - class _FuncPtr(_CFuncPtr): - pass - + _restype_ = self._func_restype_ self._FuncPtr = _FuncPtr if handle is None: @@ -96,24 +119,20 @@ class _FuncPtr(_CFuncPtr): def __repr__(self): return "<%s '%s', handle %x at %#x>" % \ (self.__class__.__name__, self._name, - (self._handle & (sys.maxsize*2 + 1)), - id(self) & (sys.maxsize*2 + 1)) + (self._handle & (_sys.maxsize*2 + 1)), + id(self) & (_sys.maxsize*2 + 1)) def __getattr__(self, name): if name.startswith('__') and name.endswith('__'): raise AttributeError(name) - func = self.__getitem__(name) setattr(self, name, func) - return func def __getitem__(self, name_or_ordinal): func = self._FuncPtr(name_or_ordinal, self) - if not isinstance(name_or_ordinal, int): func.__name__ = name_or_ordinal - return func class LibraryLoader(object): diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index f21dd97141..85cda26ef2 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -9,7 +9,6 @@ use libffi::low::{ Error as FFIError, }; use libffi::middle; -use num_bigint::BigInt; use crate::builtins::pystr::PyStrRef; use crate::builtins::{PyInt, PyTypeRef}; @@ -22,6 +21,7 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::primitive::PySimpleType; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; @@ -90,49 +90,43 @@ pub fn str_to_type(ty: &str) -> *mut ffi_type { ) } -fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> *mut c_void { - match_ffi_type!( +fn py_to_ffi( + ty: *mut *mut ffi_type, + obj: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<*mut c_void> { + let res = match_ffi_type!( unsafe { *ty }, c_schar => { - let mut r = i8::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut i8::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_int => { - let mut r = i32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut i32::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_short => { - let mut r = i16::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut i16::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_ushort => { - let mut r = u16::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut u16::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_uint => { - let mut r = u32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut u32::try_from_object(vm, obj)? as *mut _ as *mut c_void } //@ TODO: Convert c*longlong from BigInt? c_long | c_longlong => { - let mut r = i64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut i64::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_ulong | c_ulonglong => { - let mut r = u64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut u64::try_from_object(vm, obj)? as *mut _ as *mut c_void } f32 => { - let mut r = f32::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut f32::try_from_object(vm, obj)? as *mut _ as *mut c_void } f64 | longdouble=> { - let mut r = f64::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut f64::try_from_object(vm, obj)? as *mut _ as *mut c_void } c_uchar => { - let mut r = u8::try_from_object(vm, obj).unwrap(); - &mut r as *mut _ as *mut c_void + &mut u8::try_from_object(vm, obj)? as *mut _ as *mut c_void } pointer => { usize::try_from_object(vm, obj).unwrap() as *mut c_void @@ -140,7 +134,9 @@ fn py_to_ffi(ty: *mut *mut ffi_type, obj: PyObjectRef, vm: &VirtualMachine) -> * void => { ptr::null_mut() } - ) + ); + + Ok(res) } #[derive(Debug)] @@ -169,7 +165,6 @@ impl Function { pub fn set_ret(&mut self, ret: &str) { (*self.return_type.as_mut()) = str_to_type(ret); - // mem::replace(self.return_type.as_mut(), str_to_type(ret)); } pub fn call( @@ -197,7 +192,7 @@ impl Function { return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); } - let mut argument_pointers: Vec<*mut c_void> = arg_ptrs + let argument_pointers: Result, _> = arg_ptrs .iter() .zip(self.arguments.iter_mut()) .map(|(o, t)| { @@ -206,9 +201,9 @@ impl Function { }) .collect(); - let cif_ptr = &self.cif as *const _ as *mut _; + let cif_ptr = &self.cif as *const _ as *mut ffi_cif; let fun_ptr = CodePtr::from_ptr(self.pointer); - let args_ptr = argument_pointers.as_mut_ptr(); + let args_ptr = argument_pointers?.as_mut_ptr(); let ret_ptr = unsafe { match_ffi_type!( @@ -233,22 +228,14 @@ impl Function { let r: c_uint = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as u32) } - c_long => { + c_long | c_longlong => { let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as i64) } - c_longlong => { - let r: c_longlong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(BigInt::from(r as i128)) - } - c_ulong => { + c_ulong | c_ulonglong => { let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as u64) } - c_ulonglong => { - let r: c_ulonglong = ffi_call(cif_ptr, fun_ptr, args_ptr); - vm.new_pyobj(BigInt::from(r as u128)) - } f32 => { let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); vm.new_pyobj(r as f32) @@ -278,23 +265,6 @@ impl Function { unsafe impl Send for Function {} unsafe impl Sync for Function {} -fn map_types_to_res(args: &[PyObjectRc], vm: &VirtualMachine) -> PyResult> { - args.iter() - .enumerate() - .map(|(idx, inner_obj)| { - match vm.isinstance(inner_obj, PyCData::static_type()) { - // @TODO: checks related to _type_ are temporary - Ok(_) => Ok(vm.get_attribute(inner_obj.clone(), "_type_").unwrap()), - Err(_) => Err(vm.new_type_error(format!( - "object at {} is not an instance of _CData, type {} found", - idx, - inner_obj.class().name - ))), - } - }) - .collect() -} - #[pyclass(module = "_ctypes", name = "CFuncPtr", base = "PyCData")] pub struct PyCFuncPtr { pub _name_: String, @@ -334,25 +304,34 @@ impl PyCFuncPtr { if vm.isinstance(&argtypes, &vm.ctx.types.list_type).is_ok() || vm.isinstance(&argtypes, &vm.ctx.types.tuple_type).is_ok() { - let args: Vec = vm.extract_elements(&argtypes).unwrap(); + let args: Vec = vm.extract_elements(&argtypes)?; + let c_args_res: PyResult> = args + .iter() + .enumerate() + .map(|(idx, inner_obj)| { + match vm.isinstance(inner_obj, PySimpleType::static_type()) { + // @TODO: checks related to _type_ are temporary + Ok(_) => Ok(vm.get_attribute(inner_obj.clone(), "_type_").unwrap()), + _ => Err(vm.new_type_error(format!( + "positional argument {} must be subclass of _SimpleType, but type {} found", + idx, + inner_obj.class().name + ))), + } + }) + .collect(); - let c_args = map_types_to_res(&args, vm)?; + let c_args = c_args_res?; self._argtypes_.store(c_args.clone()); - let str_types: Result, _> = c_args + let str_types: Vec = c_args .iter() - .map(|obj| { - if let Ok(attr) = vm.get_attribute(obj.clone(), "_type_") { - Ok(attr.to_string()) - } else { - Err(()) - } - }) + .map(|obj| vm.to_str(&obj).unwrap().to_string()) .collect(); let mut fn_ptr = self._f.write(); - fn_ptr.set_args(str_types.unwrap()); + fn_ptr.set_args(str_types); Ok(()) } else { @@ -365,14 +344,14 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_", setter)] fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - match vm.isinstance(&restype, PyCData::static_type()) { + match vm.isinstance(&restype, PySimpleType::static_type()) { // @TODO: checks related to _type_ are temporary Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { Ok(_type_) => { self._restype_.store(restype.clone()); let mut fn_ptr = self._f.write(); - fn_ptr.set_ret(_type_.to_string().as_str()); + fn_ptr.set_ret(vm.to_str(&_type_)?.as_ref()); Ok(()) } @@ -455,8 +434,26 @@ impl Callable for PyCFuncPtr { ))); } - let arg_vec = map_types_to_res(&args.args, vm)?; + let arg_res: Result, _> = args + .args + .iter() + .enumerate() + .map(|(idx, obj)| { + if vm + .issubclass(&obj.clone_class(), PySimpleType::static_type()) + .is_ok() + { + Ok(vm.get_attribute(obj.clone(), "value")?) + } else { + Err(vm.new_type_error(format!( + "positional argument {} must be subclass of _SimpleType, but type {} found", + idx, + obj.class().name + ))) + } + }) + .collect(); - (*zelf._f.write()).call(arg_vec, vm) + (*zelf._f.write()).call(arg_res?, vm) } } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 46b53b8f37..87ec030f92 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -209,7 +209,7 @@ impl PySimpleType { } #[pyproperty(name = "value")] - fn value(&self) -> PyObjectRef { + pub fn value(&self) -> PyObjectRef { unsafe { (*self.value.as_ptr()).clone() } } From 3e526256ab455f26806c18ae8bf07855b091b86a Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 6 Dec 2020 13:07:23 -0300 Subject: [PATCH 33/56] Fixing function call --- Lib/ctypes/__init__.py | 9 +-- vm/src/stdlib/ctypes/basics.rs | 8 +-- vm/src/stdlib/ctypes/dll.rs | 12 ++-- vm/src/stdlib/ctypes/function.rs | 99 +++++++++++++++--------------- vm/src/stdlib/ctypes/primitive.rs | 22 +++---- vm/src/stdlib/ctypes/shared_lib.rs | 8 +-- 6 files changed, 74 insertions(+), 84 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index bd33e5c7b8..0aa799e312 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -96,19 +96,16 @@ class CDLL(object): indexing with the function name. Examples: .qsort -> callable object ['qsort'] -> callable object - Calling the functions releases the Python GIL during the call and - reacquires it afterwards. """ - _func_restype_ = c_int - # default values for repr _name = '' _handle = 0 - _FuncPtr = None def __init__(self, name,handle=None): self._name = name + class _FuncPtr(_CFuncPtr): - _restype_ = self._func_restype_ + pass + self._FuncPtr = _FuncPtr if handle is None: diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index ef63c96c7d..0b2f843cab 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -68,8 +68,8 @@ fn buffer_copy( let opts = buffer.get_options().clone(); // @TODO: Fix the way the size of stored - // Would this be the proper replacement? - // vm.call_method(cls.as_object(), "size", ())?. + // Would this be the a proper replacement? + // vm.call_method(cls.as_object().to_owned(), "size", ())?. let cls_size = vm .get_attribute(cls.as_object().to_owned(), "_size") .map(|c_s| usize::try_from_object(vm, c_s.clone()))??; @@ -93,7 +93,7 @@ fn buffer_copy( } else if let Some(mut buffer) = buffer.as_contiguous_mut() { let buffered = unsafe { slice::from_raw_parts(buffer.as_mut_ptr(), buffer.len()) }; - // @TODO: Is this ok? + // @TODO: Is this avoiding unecessary data copy? Ok(PyCData::new( None, Some(if copy { @@ -189,7 +189,7 @@ pub trait PyCDataMethods: PyValue { ))) }?; - let sym_ptr = usize::try_from_object(vm, raw_ptr.into_object(vm))?; + let sym_ptr = usize::try_from_object(vm, raw_ptr)?; let buffer = at_address(&cls, sym_ptr, vm)?; Ok(PyCData::new(None, Some(buffer))) diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 2fe16f897f..c14c41be11 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,19 +1,19 @@ extern crate libloading; use crate::builtins::pystr::PyStrRef; -use crate::builtins::{PyInt, PyIntRef}; -use crate::pyobject::{PyResult, TryFromObject}; +use crate::builtins::PyIntRef; +use crate::pyobject::{PyObjectRc, PyResult, TryFromObject}; use crate::VirtualMachine; use crate::stdlib::ctypes::shared_lib::LIBCACHE; -pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { +pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { let mut data_cache = LIBCACHE.write(); let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); match result { - Ok(lib) => Ok(PyInt::from(lib.get_pointer())), + Ok(lib) => Ok(vm.new_pyobj(lib.get_pointer())), Err(_) => Err(vm.new_os_error(format!( "{} : cannot open shared object file: No such file or directory", lib_path.to_string() @@ -21,13 +21,13 @@ pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { } } -pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { +pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { let func_name = str_ref.as_ref(); let data_cache = LIBCACHE.read(); match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { Some(l) => match l.get_sym(func_name) { - Ok(ptr) => Ok(PyInt::from(ptr as *const _ as usize)), + Ok(ptr) => Ok(vm.new_pyobj(ptr as *const _ as usize)), Err(e) => Err(vm.new_runtime_error(e)), }, _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 85cda26ef2..91d374ac97 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -141,20 +141,20 @@ fn py_to_ffi( #[derive(Debug)] pub struct Function { - pointer: *const c_void, + pointer: *mut c_void, cif: ffi_cif, arguments: Vec<*mut ffi_type>, - return_type: Box<*mut ffi_type>, + return_type: Box, } impl Function { - pub fn new(fn_ptr: *const c_void, arguments: Vec, return_type: &str) -> Function { + pub fn new(fn_ptr: usize, arguments: Vec, return_type: &str) -> Function { Function { - pointer: fn_ptr, + pointer: fn_ptr as *mut _, cif: Default::default(), arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), - return_type: Box::new(str_to_type(return_type)), + return_type: Box::new(return_type.to_string()), } } pub fn set_args(&mut self, args: Vec) { @@ -164,7 +164,7 @@ impl Function { } pub fn set_ret(&mut self, ret: &str) { - (*self.return_type.as_mut()) = str_to_type(ret); + (*self.return_type.as_mut()) = ret.to_string(); } pub fn call( @@ -172,14 +172,12 @@ impl Function { arg_ptrs: Vec, vm: &VirtualMachine, ) -> PyResult { - let return_type: *mut ffi_type = &mut unsafe { self.return_type.read() }; - let result = unsafe { prep_cif( &mut self.cif, ABI, self.arguments.len(), - return_type, + str_to_type(self.return_type.as_ref()), self.arguments.as_mut_ptr(), ) }; @@ -195,67 +193,64 @@ impl Function { let argument_pointers: Result, _> = arg_ptrs .iter() .zip(self.arguments.iter_mut()) - .map(|(o, t)| { - let tt: *mut *mut ffi_type = t; - py_to_ffi(tt, o.clone(), vm) - }) + .map(|(o, t)| py_to_ffi(t as *mut *mut _, o.clone(), vm)) .collect(); - let cif_ptr = &self.cif as *const _ as *mut ffi_cif; - let fun_ptr = CodePtr::from_ptr(self.pointer); - let args_ptr = argument_pointers?.as_mut_ptr(); + // @TODO: Something wrong with declaring argument_pointers?.as_mut_ptr() before + let fun_ptr = CodePtr(self.pointer); + + let cif_ptr = &mut self.cif; let ret_ptr = unsafe { - match_ffi_type!( - return_type, - c_schar => { - let r: c_schar = ffi_call(cif_ptr, fun_ptr, args_ptr); + match self.return_type.as_str() { + "b" | "c" => { + let r: c_schar = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as i8) } - c_int => { - let r: c_int = ffi_call(cif_ptr, fun_ptr, args_ptr); + "u" | "i" => { + let r: c_int = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as i32) } - c_short => { - let r: c_short = ffi_call(cif_ptr, fun_ptr, args_ptr); + "h" => { + let r: c_short = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as i16) } - c_ushort => { - let r: c_ushort = ffi_call(cif_ptr, fun_ptr, args_ptr); + "H" => { + let r: c_ushort = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as u16) } - c_uint => { - let r: c_uint = ffi_call(cif_ptr, fun_ptr, args_ptr); + "I" => { + let r: c_uint = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as u32) } - c_long | c_longlong => { - let r: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); + "l" | "q" => { + let r: c_long = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as i64) } - c_ulong | c_ulonglong => { - let r: c_ulong = ffi_call(cif_ptr, fun_ptr, args_ptr); + "L" | "Q" => { + let r: c_ulong = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as u64) } - f32 => { - let r: c_float = ffi_call(cif_ptr, fun_ptr, args_ptr); + "f" => { + let r: c_float = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as f32) } - f64 | longdouble => { - let r: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); + "d" | "g" => { + let r: c_double = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as f64) } - c_uchar => { - let r: c_uchar = ffi_call(cif_ptr, fun_ptr, args_ptr); + "?" | "B" => { + let r: c_uchar = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as u8) } - pointer => { - let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, args_ptr); + "z" | "Z" => { + let r: *mut c_void = + ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); vm.new_pyobj(r as *const _ as usize) } - void => { - vm.ctx.none() - } - ) + "P" => vm.ctx.none(), + _ => unreachable!(), + } }; Ok(ret_ptr) @@ -311,9 +306,10 @@ impl PyCFuncPtr { .map(|(idx, inner_obj)| { match vm.isinstance(inner_obj, PySimpleType::static_type()) { // @TODO: checks related to _type_ are temporary + // it needs to check for from_param method, instead Ok(_) => Ok(vm.get_attribute(inner_obj.clone(), "_type_").unwrap()), _ => Err(vm.new_type_error(format!( - "positional argument {} must be subclass of _SimpleType, but type {} found", + "item {} in _argtypes_ must be subclass of _SimpleType, but type {} found", idx, inner_obj.class().name ))), @@ -336,7 +332,7 @@ impl PyCFuncPtr { Ok(()) } else { Err(vm.new_type_error(format!( - "argtypes must be Tuple or List, {} found.", + "_argtypes_ must be a sequence of types, {} found.", argtypes.to_string() ))) } @@ -348,8 +344,8 @@ impl PyCFuncPtr { // @TODO: checks related to _type_ are temporary Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { Ok(_type_) => { + // @TODO: restype must be a type, a callable, or None self._restype_.store(restype.clone()); - let mut fn_ptr = self._f.write(); fn_ptr.set_ret(vm.to_str(&_type_)?.as_ref()); @@ -385,7 +381,7 @@ impl PyCFuncPtr { /// # Arguments /// /// * `func_name` - A string that names the function symbol - /// * `arg` - A Python object with _handle attribute of type SharedLibrary + /// * `arg` - A Python object with _handle attribute of type int /// fn from_dll( cls: PyTypeRef, @@ -397,7 +393,7 @@ impl PyCFuncPtr { if let Ok(handle) = h.downcast::() { let handle_obj = handle.clone().into_object(); let ptr_fn = dlsym(handle, func_name.clone(), vm)?; - let fn_ptr = usize::try_from_object(vm, ptr_fn.into_object(vm))? as *mut c_void; + let fn_ptr = usize::try_from_object(vm, ptr_fn)?; PyCFuncPtr { _name_: func_name.to_string(), @@ -407,7 +403,7 @@ impl PyCFuncPtr { _f: PyRwLock::new(Function::new( fn_ptr, Vec::new(), - "P", // put a default here + "i", // put a default here )), } .into_ref_with_type(vm, cls) @@ -428,8 +424,9 @@ impl Callable for PyCFuncPtr { if args.args.len() != inner_args.len() { return Err(vm.new_runtime_error(format!( - "invalid number of arguments, required {}, but {} found", + "this function takes at least {} argument{} ({} given)", inner_args.len(), + if !inner_args.is_empty() { "s" } else { "" }, args.args.len() ))); } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 87ec030f92..9030b1e07d 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,11 +1,11 @@ use crossbeam_utils::atomic::AtomicCell; -use num_bigint::BigInt; -use num_traits::FromPrimitive; use rustpython_common::borrow::BorrowValue; use std::fmt; use crate::builtins::PyTypeRef; -use crate::builtins::{pybool::boolval, PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr}; +use crate::builtins::{ + int::try_to_primitive, pybool::boolval, PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr, +}; use crate::function::OptionalArg; use crate::pyobject::{ PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, @@ -53,10 +53,10 @@ fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyRes || value .clone() .downcast_exact::(vm) - .map_or(false, |v| { - v.borrow_value().ge(&BigInt::from_i64(0).unwrap()) - || v.borrow_value().le(&BigInt::from_i64(255).unwrap()) - }) + .map_or(Ok(false), |v| { + let n: i64 = try_to_primitive(v.borrow_value(), vm)?; + Ok(0 <= n && n <= 255) + })? { Ok(value.clone()) } else { @@ -188,7 +188,7 @@ impl PySimpleType { } } - #[pymethod(name = "__init__")] + #[pymethod(magic)] pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { match value.into_option() { Some(ref v) => { @@ -221,7 +221,7 @@ impl PySimpleType { } // From Simple_Type Simple_methods - #[pymethod(name = "__ctypes_from_outparam__")] + #[pymethod(magic)] pub fn ctypes_from_outparam(&self) {} // From PyCSimpleType_Type PyCSimpleType_methods @@ -229,7 +229,7 @@ impl PySimpleType { pub fn from_param(cls: PyTypeRef, vm: &VirtualMachine) {} // Simple_repr - #[pymethod(name = "__repr__")] + #[pymethod(magic)] fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { Ok(format!( "{}({})", @@ -239,7 +239,7 @@ impl PySimpleType { } // Simple_as_number - // #[pymethod(name = "__bool__")] + // #[pymethod(magic)] // fn bool(&self) -> bool { // // } diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index cc4c2bf5c3..4b0d56bce3 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -12,7 +12,6 @@ use crate::common::lock::PyRwLock; use crate::pyobject::{PyRef, PyValue}; use crate::VirtualMachine; pub struct SharedLibrary { - path_name: String, lib: AtomicCell>, } @@ -21,10 +20,8 @@ impl fmt::Debug for SharedLibrary { write!( f, "SharedLibrary {{ - path_name: {}, lib: {}, }}", - self.path_name.as_str(), self.get_pointer() ) } @@ -39,7 +36,6 @@ impl PyValue for SharedLibrary { impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { - path_name: name.to_string(), lib: AtomicCell::new(Some(Library::new(name.to_string())?)), }) } @@ -61,9 +57,9 @@ impl SharedLibrary { pub fn get_pointer(&self) -> usize { if let Some(l) = unsafe { &*self.lib.as_ptr() } { - l as *const Library as *const u32 as usize + l as *const Library as usize } else { - null() as *const u32 as usize + null::() as usize } } From 3454751a44a444f04e90ccb32522abd7dab4d842 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 6 Dec 2020 14:53:20 -0300 Subject: [PATCH 34/56] Fixing more of function calls --- vm/src/stdlib/ctypes/function.rs | 71 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 91d374ac97..1867f22c35 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -144,7 +144,7 @@ pub struct Function { pointer: *mut c_void, cif: ffi_cif, arguments: Vec<*mut ffi_type>, - return_type: Box, + return_type: Box<*mut ffi_type>, } impl Function { @@ -154,7 +154,7 @@ impl Function { cif: Default::default(), arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), - return_type: Box::new(return_type.to_string()), + return_type: Box::new(str_to_type(return_type)), } } pub fn set_args(&mut self, args: Vec) { @@ -164,7 +164,7 @@ impl Function { } pub fn set_ret(&mut self, ret: &str) { - (*self.return_type.as_mut()) = ret.to_string(); + (*self.return_type.as_mut()) = str_to_type(ret); } pub fn call( @@ -177,7 +177,7 @@ impl Function { &mut self.cif, ABI, self.arguments.len(), - str_to_type(self.return_type.as_ref()), + *self.return_type.as_mut(), self.arguments.as_mut_ptr(), ) }; @@ -190,70 +190,71 @@ impl Function { return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); } - let argument_pointers: Result, _> = arg_ptrs + let arg_results: Result, _> = arg_ptrs .iter() .zip(self.arguments.iter_mut()) .map(|(o, t)| py_to_ffi(t as *mut *mut _, o.clone(), vm)) .collect(); - // @TODO: Something wrong with declaring argument_pointers?.as_mut_ptr() before let fun_ptr = CodePtr(self.pointer); let cif_ptr = &mut self.cif; + let mut arg_pointers = arg_results?; - let ret_ptr = unsafe { - match self.return_type.as_str() { - "b" | "c" => { - let r: c_schar = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + let res = unsafe { + match_ffi_type!( + *self.return_type.as_mut(), + c_schar => { + let r: c_schar = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as i8) } - "u" | "i" => { - let r: c_int = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_int => { + let r: c_int = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as i32) } - "h" => { - let r: c_short = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_short => { + let r: c_short = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as i16) } - "H" => { - let r: c_ushort = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_ushort => { + let r: c_ushort = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as u16) } - "I" => { - let r: c_uint = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_uint => { + let r: c_uint = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as u32) } - "l" | "q" => { - let r: c_long = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_long | c_longlong => { + let r: c_long = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as i64) } - "L" | "Q" => { - let r: c_ulong = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_ulong | c_ulonglong => { + let r: c_ulong = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as u64) } - "f" => { - let r: c_float = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + f32 => { + let r: c_float = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as f32) } - "d" | "g" => { - let r: c_double = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + f64 | longdouble=> { + let r: c_double = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as f64) } - "?" | "B" => { - let r: c_uchar = ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + c_uchar => { + let r: c_uchar = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as u8) } - "z" | "Z" => { - let r: *mut c_void = - ffi_call(cif_ptr, fun_ptr, argument_pointers?.as_mut_ptr()); + pointer => { + let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); vm.new_pyobj(r as *const _ as usize) } - "P" => vm.ctx.none(), - _ => unreachable!(), - } + void => { + vm.ctx.none() + } + ) }; - Ok(ret_ptr) + Ok(res) } } From 0f25f4d5b69021d39e2525886d356476f194f80a Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 7 Dec 2020 16:32:11 -0300 Subject: [PATCH 35/56] PySimpleType from_param initial commit --- vm/src/stdlib/ctypes/array.rs | 22 +++ vm/src/stdlib/ctypes/basics.rs | 4 +- vm/src/stdlib/ctypes/mod.rs | 4 + vm/src/stdlib/ctypes/pointer.rs | 22 +++ vm/src/stdlib/ctypes/primitive.rs | 279 ++++++++++++++++++++++++------ 5 files changed, 272 insertions(+), 59 deletions(-) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index e69de29bb2..c953673453 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -0,0 +1,22 @@ +use std::fmt; + +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType}; +use crate::VirtualMachine; + +use crate::stdlib::ctypes::basics::PyCData; + +#[pyclass(module = "_ctypes", name = "Array", base = "PyCData")] +pub struct PyCArray {} + +impl fmt::Debug for PyCArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCArray {{}}") + } +} + +impl PyValue for PyCArray { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 0b2f843cab..af4b06a0ad 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -48,7 +48,7 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), }, - // @TODO: I think it's unreacheble + // @TODO: I think it's unreachable Err(_) => Err(vm.new_attribute_error("abstract class".to_string())), } } @@ -113,7 +113,7 @@ fn buffer_copy( } } } - // @TODO: I think this is unreacheble... + // @TODO: I think this is unreachable... Err(_) => Err(vm.new_type_error("abstract class".to_string())), } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 62cca00615..478aac5305 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -2,15 +2,19 @@ use crate::pyobject::PyClassImpl; use crate::pyobject::PyObjectRef; use crate::VirtualMachine; +mod array; mod basics; mod dll; mod function; +mod pointer; mod primitive; mod shared_lib; +use array::PyCArray; use basics::PyCData; use dll::*; use function::PyCFuncPtr; +use pointer::PyCPointer; use primitive::PySimpleType; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index e69de29bb2..b783d40da9 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -0,0 +1,22 @@ +use std::fmt; + +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType}; +use crate::VirtualMachine; + +use crate::stdlib::ctypes::basics::PyCData; + +#[pyclass(module = "_ctypes", name = "_Pointer", base = "PyCData")] +pub struct PyCPointer {} + +impl fmt::Debug for PyCPointer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "_Pointer {{}}") + } +} + +impl PyValue for PyCPointer { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 9030b1e07d..7c4992105b 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -12,33 +12,13 @@ use crate::pyobject::{ }; use crate::VirtualMachine; +use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::function::PyCFuncPtr; +use crate::stdlib::ctypes::pointer::PyCPointer; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; -#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] -pub struct PySimpleType { - _type_: String, - value: AtomicCell, - __abstract__: bool, -} - -impl fmt::Debug for PySimpleType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = unsafe { (*self.value.as_ptr()).to_string() }; - - write!( - f, - "PySimpleType {{ - _type_: {}, - value: {}, - }}", - self._type_.as_str(), - value - ) - } -} - fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyResult { match _type_ { "c" => { @@ -143,6 +123,162 @@ fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyRes } } +fn generic_xxx_p_from_param( + cls: &PyTypeRef, + value: &PyObjectRef, + type_str: &str, + vm: &VirtualMachine, +) -> PyResult { + if vm.is_none(value) + || vm.isinstance(value, &vm.ctx.types.str_type)? + || vm.isinstance(value, &vm.ctx.types.bytes_type)? + { + Ok(PySimpleType { + _type_: type_str.to_string(), + value: AtomicCell::new(value.clone()), + __abstract__: true, + } + .into_object(vm)) + } else if vm.isinstance(value, PySimpleType::static_type())? + && (type_str == "z" || type_str == "Z" || type_str == "P") + { + Ok(value.clone()) + } else { + // @TODO: better message + Err(vm.new_type_error("wrong type".to_string())) + } +} + +fn from_param_char_p( + cls: &PyTypeRef, + value: &PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let _type_ = vm + .get_attribute(value.clone(), "_type_")? + .downcast_exact::(vm) + .unwrap(); + let type_str = _type_.as_ref(); + + let res = generic_xxx_p_from_param(cls, value, type_str, vm)?; + + if !vm.is_none(&res) { + Ok(res) + } else if (vm.isinstance(value, PyCArray::static_type())? + || vm.isinstance(value, PyCPointer::static_type())?) + && (type_str == "z" || type_str == "Z" || type_str == "P") + { + Ok(value.clone()) + } else { + // @TODO: Make sure of what goes here + Err(vm.new_type_error("some error".to_string())) + } +} + +fn from_param_void_p( + cls: &PyTypeRef, + value: &PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let _type_ = vm + .get_attribute(value.clone(), "_type_")? + .downcast_exact::(vm) + .unwrap(); + let type_str = _type_.as_ref(); + + let res = generic_xxx_p_from_param(cls, value, type_str, vm)?; + + if !vm.is_none(&res) { + Ok(res) + } else if vm.isinstance(value, PyCArray::static_type())? { + Ok(value.clone()) + } else if vm.isinstance(value, PyCFuncPtr::static_type())? + || vm.isinstance(value, PyCPointer::static_type())? + { + // @TODO: Is there a better way of doing this? + if let Some(from_address) = vm.get_method(cls.as_object().clone(), "from_address") { + if let Ok(cdata) = value.clone().downcast_exact::(vm) { + let mut buffer_guard = cdata.borrow_value_mut(); + let addr = buffer_guard.as_mut_ptr() as usize; + + Ok(vm.invoke(&from_address?, (cls.clone_class(), addr))?) + } else { + // @TODO: Make sure of what goes here + Err(vm.new_type_error("value should be an instance of _CData".to_string())) + } + } else { + // @TODO: Make sure of what goes here + Err(vm.new_attribute_error("class has no from_address method".to_string())) + } + } else if vm.isinstance(value, &vm.ctx.types.int_type)? { + Ok(PySimpleType { + _type_: type_str.to_string(), + value: AtomicCell::new(value.clone()), + __abstract__: true, + } + .into_object(vm)) + } else { + // @TODO: Make sure of what goes here + Err(vm.new_type_error("some error".to_string())) + } +} + +fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { + let is_abstract = cls.name == PySimpleType::static_type().name; + + if is_abstract { + return Err(vm.new_type_error("abstract class".to_string())); + } + + match vm.get_attribute(cls.as_object().to_owned(), "_type_") { + Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? => { + let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); + + if tp_str.len() != 1 { + Err(vm.new_value_error( + "class must define a '_type_' attribute which must be a string of length 1" + .to_string(), + )) + } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) + } else { + Ok(PySimpleType { + _type_: tp_str, + value: AtomicCell::new(vm.ctx.none()), + __abstract__: is_abstract, + }) + } + } + Ok(_) => { + Err(vm.new_type_error("class must define a '_type_' string attribute".to_string())) + } + Err(_) => Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())), + } +} + +#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] +pub struct PySimpleType { + _type_: String, + value: AtomicCell, + __abstract__: bool, +} + +impl fmt::Debug for PySimpleType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = unsafe { (*self.value.as_ptr()).to_string() }; + + write!( + f, + "PySimpleType {{ + _type_: {}, + value: {}, + }}", + self._type_.as_str(), + value + ) + } +} + impl PyValue for PySimpleType { fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() @@ -153,39 +289,7 @@ impl PyValue for PySimpleType { impl PySimpleType { #[pyslot] fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { - let is_abstract = cls.name == PySimpleType::static_type().name; - - if is_abstract { - return Err(vm.new_type_error("abstract class".to_string())); - } - - match vm.get_attribute(cls.as_object().to_owned(), "_type_") { - Ok(_type_) => { - if vm.isinstance(&_type_, &vm.ctx.types.str_type)? { - let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); - - if tp_str.len() != 1 { - Err(vm.new_value_error("class must define a '_type_' attribute which must be a string of length 1".to_string())) - } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) - } else { - PySimpleType { - _type_: tp_str, - value: AtomicCell::new(vm.ctx.none()), - __abstract__: is_abstract, - } - .into_ref_with_type(vm, cls) - } - } else { - Err(vm.new_type_error( - "class must define a '_type_' string attribute".to_string(), - )) - } - } - Err(_) => { - Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) - } - } + new_simple_type(&cls, vm)?.into_ref_with_type(vm, cls) } #[pymethod(magic)] @@ -226,7 +330,68 @@ impl PySimpleType { // From PyCSimpleType_Type PyCSimpleType_methods #[pyclassmethod] - pub fn from_param(cls: PyTypeRef, vm: &VirtualMachine) {} + pub fn from_param( + cls: PyTypeRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + if cls.name == PySimpleType::static_type().name { + Err(vm.new_type_error("abstract class".to_string())) + } else if vm.isinstance(&value, &cls)? { + Ok(value) + } else { + match vm.get_attribute(cls.as_object().to_owned(), "_type_") { + Ok(tp_obj) if vm.isinstance(&tp_obj, &vm.ctx.types.str_type)? => { + let _type_ = tp_obj.downcast_exact::(vm).unwrap(); + let tp_str = _type_.as_ref(); + + match tp_str { + "z" | "Z" => from_param_char_p(&cls, &value, vm), + "P" => from_param_void_p(&cls, &value, vm), + _ => { + match new_simple_type(&cls, vm) { + Ok(obj) => Ok(obj.into_object(vm)), + Err(e) + if vm.isinstance( + &e.clone().into_object(), + &vm.ctx.exceptions.type_error, + )? || vm.isinstance( + &e.clone().into_object(), + &vm.ctx.exceptions.value_error, + )? => + { + if let Some(my_base) = cls.base.clone() { + if let Some(from_param) = + vm.get_method(my_base.as_object().clone(), "from_param") + { + Ok(vm.invoke( + &from_param?, + (my_base.clone_class(), value.clone()), + )?) + } else { + // @TODO: Make sure of what goes here + Err(vm.new_attribute_error( + "class has no from_param method".to_string(), + )) + } + } else { + // @TODO: This should be unreachable + Err(vm.new_type_error("class has no base".to_string())) + } + } + Err(e) => Err(e), + } + } + } + } + // @TODO: Sanity check, this should be unreachable + _ => { + Err(vm + .new_attribute_error("class must define a '_type_' attribute".to_string())) + } + } + } + } // Simple_repr #[pymethod(magic)] From c4f27fb5c502527383a4e4de7290388ad348769e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 7 Dec 2020 17:57:16 -0300 Subject: [PATCH 36/56] Adding more methods to PySimpleType --- vm/src/stdlib/ctypes/basics.rs | 8 ++++++++ vm/src/stdlib/ctypes/primitive.rs | 32 +++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index af4b06a0ad..42cb301fcb 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -20,6 +20,14 @@ use crate::stdlib::ctypes::dll::dlsym; use crossbeam_utils::atomic::AtomicCell; +pub fn compare_classes( + cls_a: &PyTypeRef, + cls_b: &PyTypeRef, + vm: &VirtualMachine, +) -> PyResult { + Ok(vm.bool_eq(cls_a.as_object(), cls_b.as_object())?) +} + fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult> { match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { Ok(attr) => match bool::try_from_object(vm, attr) { diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 7c4992105b..079b32c76c 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -2,6 +2,7 @@ use crossbeam_utils::atomic::AtomicCell; use rustpython_common::borrow::BorrowValue; use std::fmt; +use crate::builtins::memory::try_buffer_from_object; use crate::builtins::PyTypeRef; use crate::builtins::{ int::try_to_primitive, pybool::boolval, PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr, @@ -13,7 +14,7 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::array::PyCArray; -use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::basics::{compare_classes, PyCData}; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; @@ -136,7 +137,7 @@ fn generic_xxx_p_from_param( Ok(PySimpleType { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: true, + __abstract__: compare_classes(cls, PySimpleType::static_type(), vm)?, } .into_object(vm)) } else if vm.isinstance(value, PySimpleType::static_type())? @@ -214,7 +215,7 @@ fn from_param_void_p( Ok(PySimpleType { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: true, + __abstract__: compare_classes(cls, PySimpleType::static_type(), vm)?, } .into_object(vm)) } else { @@ -224,7 +225,7 @@ fn from_param_void_p( } fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { - let is_abstract = cls.name == PySimpleType::static_type().name; + let is_abstract = compare_classes(cls, PySimpleType::static_type(), vm)?; if is_abstract { return Err(vm.new_type_error("abstract class".to_string())); @@ -326,7 +327,14 @@ impl PySimpleType { // From Simple_Type Simple_methods #[pymethod(magic)] - pub fn ctypes_from_outparam(&self) {} + pub fn ctypes_from_outparam(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + if let Some(base) = zelf.class().base.clone() { + if vm.bool_eq(&base.as_object(), PySimpleType::static_type().as_object())? { + return Ok(zelf.as_object().clone()); + } + } + return Ok(zelf.value()); + } // From PyCSimpleType_Type PyCSimpleType_methods #[pyclassmethod] @@ -335,7 +343,7 @@ impl PySimpleType { value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - if cls.name == PySimpleType::static_type().name { + if compare_classes(&cls, PySimpleType::static_type(), vm)? { Err(vm.new_type_error("abstract class".to_string())) } else if vm.isinstance(&value, &cls)? { Ok(value) @@ -404,8 +412,12 @@ impl PySimpleType { } // Simple_as_number - // #[pymethod(magic)] - // fn bool(&self) -> bool { - // - // } + #[pymethod(magic)] + fn bool(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let buffer = try_buffer_from_object(vm, zelf.as_object())? + .obj_bytes() + .to_vec(); + + Ok(vm.new_pyobj(buffer != vec![0])) + } } From b88e544d22626d7bbaab5b7199d9b2153362ab63 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Wed, 9 Dec 2020 09:37:00 -0600 Subject: [PATCH 37/56] Minor fixes to get compiling on master --- vm/src/stdlib/ctypes/basics.rs | 48 +++++++++++++++---------------- vm/src/stdlib/ctypes/dll.rs | 6 ++-- vm/src/stdlib/ctypes/function.rs | 10 ++----- vm/src/stdlib/ctypes/primitive.rs | 6 ++-- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 42cb301fcb..549bb0095d 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -10,8 +10,7 @@ use crate::common::lock::{ }; use crate::function::OptionalArg; use crate::pyobject::{ - BorrowValue, PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, - TypeProtocol, + BorrowValue, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; use crate::VirtualMachine; @@ -34,7 +33,7 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult { let len = vm .get_attribute(cls.as_object().to_owned(), "_length_") - .map_or(Ok(1), |o: PyObjectRc| { + .map_or(Ok(1), |o: PyObjectRef| { match i64::try_from_object(vm, o.clone()) { Ok(v_int) => { if v_int < 0 { @@ -233,36 +232,37 @@ impl<'a> BorrowValue<'a> for PyCData { impl BufferProtocol for PyCData { fn get_buffer(zelf: &PyRef, _vm: &VirtualMachine) -> PyResult> { - Ok(Box::new(zelf.clone())) + Ok(Box::new(PyCDataBuffer { + data: zelf.clone(), + options: BufferOptions { + readonly: false, + len: zelf._buffer.read().len(), + ..Default::default() + }, + })) } } +#[derive(Debug)] +struct PyCDataBuffer { + data: PyCDataRef, + options: BufferOptions, +} + // This trait will be used by all types -impl Buffer for PyCDataRef { +impl Buffer for PyCDataBuffer { fn obj_bytes(&self) -> BorrowedValue<[u8]> { - PyRwLockReadGuard::map(self.borrow_value(), |x| x.as_slice()).into() + PyRwLockReadGuard::map(self.data.borrow_value(), |x| x.as_slice()).into() } fn obj_bytes_mut(&self) -> BorrowedValueMut<[u8]> { - PyRwLockWriteGuard::map(self.borrow_value_mut(), |x| x.as_mut_slice()).into() + PyRwLockWriteGuard::map(self.data.borrow_value_mut(), |x| x.as_mut_slice()).into() } fn release(&self) {} - fn get_options(&self) -> BorrowedValue { - let guard = self.buffer_options.upgradable_read(); - let guard = if guard.is_none() { - let mut w = PyRwLockUpgradableReadGuard::upgrade(guard); - *w = Some(Box::new(BufferOptions { - readonly: false, - len: self._buffer.read().len(), - ..Default::default() - })); - PyRwLockWriteGuard::downgrade(w) - } else { - PyRwLockUpgradableReadGuard::downgrade(guard) - }; - PyRwLockReadGuard::map(guard, |x| x.as_ref().unwrap().as_ref()).into() + fn get_options(&self) -> &BufferOptions { + &self.options } } @@ -271,9 +271,8 @@ impl Buffer for PyCDataRef { // PyCArray_Type, PyCSimple_Type, PyCFuncPtr_Type #[pyclass(module = "_ctypes", name = "_CData")] pub struct PyCData { - _objects: AtomicCell>, + _objects: AtomicCell>, _buffer: PyRwLock>, - buffer_options: PyRwLock>>, } pub type PyCDataRef = PyRef; @@ -291,11 +290,10 @@ impl PyValue for PyCData { } impl PyCData { - fn new(objs: Option>, buffer: Option>) -> Self { + fn new(objs: Option>, buffer: Option>) -> Self { PyCData { _objects: AtomicCell::new(objs.unwrap_or_default()), _buffer: PyRwLock::new(buffer.unwrap_or_default()), - buffer_options: PyRwLock::new(None), } } diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index c14c41be11..2bf3fdf94d 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -2,12 +2,12 @@ extern crate libloading; use crate::builtins::pystr::PyStrRef; use crate::builtins::PyIntRef; -use crate::pyobject::{PyObjectRc, PyResult, TryFromObject}; +use crate::pyobject::{PyResult, TryFromObject}; use crate::VirtualMachine; use crate::stdlib::ctypes::shared_lib::LIBCACHE; -pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { +pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { let mut data_cache = LIBCACHE.write(); let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); @@ -21,7 +21,7 @@ pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { } } -pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { +pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { let func_name = str_ref.as_ref(); let data_cache = LIBCACHE.read(); diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 1867f22c35..8d6a6e046a 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -16,7 +16,7 @@ use crate::common::lock::PyRwLock; use crate::function::FuncArgs; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::VirtualMachine; @@ -167,11 +167,7 @@ impl Function { (*self.return_type.as_mut()) = str_to_type(ret); } - pub fn call( - &mut self, - arg_ptrs: Vec, - vm: &VirtualMachine, - ) -> PyResult { + pub fn call(&mut self, arg_ptrs: Vec, vm: &VirtualMachine) -> PyResult { let result = unsafe { prep_cif( &mut self.cif, @@ -266,7 +262,7 @@ pub struct PyCFuncPtr { pub _name_: String, pub _argtypes_: AtomicCell>, pub _restype_: AtomicCell, - _handle: PyObjectRc, + _handle: PyObjectRef, _f: PyRwLock, } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 079b32c76c..2e8ee9957b 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -9,7 +9,7 @@ use crate::builtins::{ }; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRc, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::VirtualMachine; @@ -20,7 +20,7 @@ use crate::stdlib::ctypes::pointer::PyCPointer; pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; -fn set_primitive(_type_: &str, value: &PyObjectRc, vm: &VirtualMachine) -> PyResult { +fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult { match _type_ { "c" => { if value @@ -260,7 +260,7 @@ fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult, + value: AtomicCell, __abstract__: bool, } From 4ce112334d3f2c89b27f3123319432a871ae8230 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Sun, 13 Dec 2020 14:59:39 -0600 Subject: [PATCH 38/56] Use static_cell for libcache --- vm/Cargo.toml | 7 +++---- vm/src/stdlib/ctypes/dll.rs | 10 ++++------ vm/src/stdlib/ctypes/function.rs | 2 -- vm/src/stdlib/ctypes/shared_lib.rs | 14 +++++++------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 7b1838bb48..4ad6e631f4 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -114,10 +114,6 @@ flamer = { version = "0.4", optional = true } rustpython-common = { path = "../common" } -lazy_static = "1.4.0" -libloading = "0.6.5" -libffi = "1.0.0" - [target.'cfg(unix)'.dependencies] exitcode = "1.1.2" uname = "0.1.1" @@ -134,6 +130,9 @@ openssl-sys = { version = "0.9", optional = true } openssl-probe = { version = "0.1", optional = true } which = "4.0" foreign-types-shared = { version = "0.1", optional = true } +# ctypes +libloading = "0.6.5" +libffi = "1.0.0" [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] num_cpus = "1" diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 2bf3fdf94d..144df60300 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,14 +1,12 @@ -extern crate libloading; - use crate::builtins::pystr::PyStrRef; use crate::builtins::PyIntRef; use crate::pyobject::{PyResult, TryFromObject}; use crate::VirtualMachine; -use crate::stdlib::ctypes::shared_lib::LIBCACHE; +use super::shared_lib::libcache; pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let mut data_cache = LIBCACHE.write(); + let mut data_cache = libcache().write(); let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); @@ -23,7 +21,7 @@ pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { let func_name = str_ref.as_ref(); - let data_cache = LIBCACHE.read(); + let data_cache = libcache().read(); match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { Some(l) => match l.get_sym(func_name) { @@ -35,7 +33,7 @@ pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult } pub fn dlclose(slib: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { - let data_cache = LIBCACHE.read(); + let data_cache = libcache().read(); match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { Some(l) => { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 8d6a6e046a..d07a2fc3d1 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,5 +1,3 @@ -extern crate libffi; - use std::{fmt, os::raw::*, ptr}; use crossbeam_utils::atomic::AtomicCell; diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index 4b0d56bce3..c705a3fd81 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -1,8 +1,4 @@ -extern crate lazy_static; -extern crate libffi; -extern crate libloading; - -use ::std::{collections::HashMap, fmt, os::raw::c_void, ptr::null}; +use std::{collections::HashMap, fmt, os::raw::c_void, ptr::null}; use crossbeam_utils::atomic::AtomicCell; use libloading::Library; @@ -112,6 +108,10 @@ impl ExternalLibs { } } -lazy_static::lazy_static! { - pub static ref LIBCACHE: PyRwLock = PyRwLock::new(ExternalLibs::new()); +rustpython_common::static_cell! { + static LIBCACHE: PyRwLock; +} + +pub fn libcache() -> &'static PyRwLock { + LIBCACHE.get_or_init(|| PyRwLock::new(ExternalLibs::new())) } From e528fe7bb6970ad43f9027214e519a24e0477204 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 19 Dec 2020 14:47:02 -0300 Subject: [PATCH 39/56] Adding RawBuffer & reworking low level function call --- vm/src/stdlib/ctypes/basics.rs | 102 ++++++++++++++------------- vm/src/stdlib/ctypes/function.rs | 111 ++++++++++++------------------ vm/src/stdlib/ctypes/primitive.rs | 20 +++--- 3 files changed, 107 insertions(+), 126 deletions(-) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 549bb0095d..f6695b2b28 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,13 +1,11 @@ -use std::{fmt, slice}; +use std::{fmt, ptr, slice}; use crate::builtins::int::PyInt; use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; -use crate::common::lock::{ - PyRwLock, PyRwLockReadGuard, PyRwLockUpgradableReadGuard, PyRwLockWriteGuard, -}; +use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ BorrowValue, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, @@ -19,37 +17,27 @@ use crate::stdlib::ctypes::dll::dlsym; use crossbeam_utils::atomic::AtomicCell; -pub fn compare_classes( - cls_a: &PyTypeRef, - cls_b: &PyTypeRef, - vm: &VirtualMachine, -) -> PyResult { - Ok(vm.bool_eq(cls_a.as_object(), cls_b.as_object())?) -} - -fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult> { +fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult { match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { Ok(attr) => match bool::try_from_object(vm, attr) { - Ok(b) if !b => { + Ok(false) => { let len = vm .get_attribute(cls.as_object().to_owned(), "_length_") - .map_or(Ok(1), |o: PyObjectRef| { - match i64::try_from_object(vm, o.clone()) { - Ok(v_int) => { - if v_int < 0 { - Err(vm.new_type_error("'_length_' must positive".to_string())) - } else { - Ok(v_int as usize) - } - } - _ => { - Err(vm.new_type_error("'_length_' must be an integer".to_string())) + .map_or(Ok(1), |o: PyObjectRef| match i64::try_from_object(vm, o) { + Ok(v_int) => { + if v_int < 0 { + Err(vm.new_type_error("'_length_' must positive".to_string())) + } else { + Ok(v_int as usize) } } + _ => Err(vm.new_type_error("'_length_' must be an integer".to_string())), })?; - let slice_ = unsafe { slice::from_raw_parts(buf as *const u8, len) }; - Ok(slice_.to_vec()) + Ok(RawBuffer { + inner: buf as *const u8 as *mut _, + size: len, + }) } Ok(_) => Err(vm.new_type_error("abstract class".to_string())), // @TODO: A sanity check @@ -79,11 +67,11 @@ fn buffer_copy( // vm.call_method(cls.as_object().to_owned(), "size", ())?. let cls_size = vm .get_attribute(cls.as_object().to_owned(), "_size") - .map(|c_s| usize::try_from_object(vm, c_s.clone()))??; + .map(|c_s| usize::try_from_object(vm, c_s))??; let offset_int = offset .into_option() - .map_or(Ok(0), |off| i64::try_from_object(vm, off.clone()))?; + .map_or(Ok(0), |off| i64::try_from_object(vm, off))?; if opts.readonly { Err(vm.new_type_error("underlying buffer is not writable".to_string())) @@ -98,15 +86,20 @@ fn buffer_copy( opts.len + (offset_int as usize) ))) } else if let Some(mut buffer) = buffer.as_contiguous_mut() { - let buffered = - unsafe { slice::from_raw_parts(buffer.as_mut_ptr(), buffer.len()) }; - // @TODO: Is this avoiding unecessary data copy? + // @TODO: Is this copying? + + let buffered = if copy { + unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr(), buffer.len()) } + .as_mut_ptr() + } else { + buffer.as_mut_ptr() + }; + Ok(PyCData::new( None, - Some(if copy { - buffered.to_owned().to_vec() - } else { - buffered.to_vec() + Some(RawBuffer { + inner: buffered, + size: buffer.len(), }), )) } else { @@ -190,10 +183,7 @@ pub trait PyCDataMethods: PyValue { let raw_ptr = if let Ok(h_int) = h.downcast_exact::(vm) { dlsym(h_int, name, vm) } else { - Err(vm.new_type_error(format!( - "_handle must be an int not {}", - dll.clone().class().name - ))) + Err(vm.new_type_error(format!("_handle must be an int not {}", dll.class().name))) }?; let sym_ptr = usize::try_from_object(vm, raw_ptr)?; @@ -223,7 +213,7 @@ pub trait PyCDataSequenceMethods: PyValue { } impl<'a> BorrowValue<'a> for PyCData { - type Borrowed = PyRwLockReadGuard<'a, Vec>; + type Borrowed = PyRwLockReadGuard<'a, RawBuffer>; fn borrow_value(&'a self) -> Self::Borrowed { self._buffer.read() @@ -236,7 +226,7 @@ impl BufferProtocol for PyCData { data: zelf.clone(), options: BufferOptions { readonly: false, - len: zelf._buffer.read().len(), + len: zelf._buffer.read().size, ..Default::default() }, })) @@ -252,11 +242,17 @@ struct PyCDataBuffer { // This trait will be used by all types impl Buffer for PyCDataBuffer { fn obj_bytes(&self) -> BorrowedValue<[u8]> { - PyRwLockReadGuard::map(self.data.borrow_value(), |x| x.as_slice()).into() + PyRwLockReadGuard::map(self.data.borrow_value(), |x| unsafe { + slice::from_raw_parts(x.inner, x.size) + }) + .into() } fn obj_bytes_mut(&self) -> BorrowedValueMut<[u8]> { - PyRwLockWriteGuard::map(self.data.borrow_value_mut(), |x| x.as_mut_slice()).into() + PyRwLockWriteGuard::map(self.data.borrow_value_mut(), |x| unsafe { + slice::from_raw_parts_mut(x.inner, x.size) + }) + .into() } fn release(&self) {} @@ -265,6 +261,13 @@ impl Buffer for PyCDataBuffer { &self.options } } +pub struct RawBuffer { + pub inner: *mut u8, + pub size: usize, +} + +unsafe impl Send for RawBuffer {} +unsafe impl Sync for RawBuffer {} // This Trait is the equivalent of PyCData_Type on tp_base for // Struct_Type, Union_Type, PyCPointer_Type @@ -272,7 +275,7 @@ impl Buffer for PyCDataBuffer { #[pyclass(module = "_ctypes", name = "_CData")] pub struct PyCData { _objects: AtomicCell>, - _buffer: PyRwLock>, + _buffer: PyRwLock, } pub type PyCDataRef = PyRef; @@ -290,14 +293,17 @@ impl PyValue for PyCData { } impl PyCData { - fn new(objs: Option>, buffer: Option>) -> Self { + fn new(objs: Option>, buffer: Option) -> Self { PyCData { _objects: AtomicCell::new(objs.unwrap_or_default()), - _buffer: PyRwLock::new(buffer.unwrap_or_default()), + _buffer: PyRwLock::new(buffer.unwrap_or(RawBuffer { + inner: ptr::null::() as *mut _, + size: 0, + })), } } - pub fn borrow_value_mut(&self) -> PyRwLockWriteGuard<'_, Vec> { + pub fn borrow_value_mut(&self) -> PyRwLockWriteGuard<'_, RawBuffer> { self._buffer.write() } } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index d07a2fc3d1..0cf9beb88b 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -2,11 +2,7 @@ use std::{fmt, os::raw::*, ptr}; use crossbeam_utils::atomic::AtomicCell; -use libffi::low::{ - call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, prep_cif, CodePtr, - Error as FFIError, -}; -use libffi::middle; +use libffi::middle::{arg, Arg, Cif, CodePtr, Type}; use crate::builtins::pystr::PyStrRef; use crate::builtins::{PyInt, PyTypeRef}; @@ -26,7 +22,7 @@ use crate::stdlib::ctypes::dll::dlsym; macro_rules! ffi_type { ($name: ident) => { - middle::Type::$name().as_raw_ptr() + Type::$name() }; } @@ -38,10 +34,10 @@ macro_rules! match_ffi_type { $($type: ident)|+ => $body: expr )+ ) => { - match $pointer { + match $pointer.as_raw_ptr() { $( $( - t if t == ffi_type!($type) => { $body } + t if t == ffi_type!($type).as_raw_ptr() => { $body } )+ )+ _ => unreachable!() @@ -65,7 +61,7 @@ macro_rules! match_ffi_type { } } -pub fn str_to_type(ty: &str) -> *mut ffi_type { +pub fn str_to_type(ty: &str) -> Type { match_ffi_type!( ty, "c" => c_schar @@ -88,49 +84,44 @@ pub fn str_to_type(ty: &str) -> *mut ffi_type { ) } -fn py_to_ffi( - ty: *mut *mut ffi_type, - obj: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult<*mut c_void> { +fn py_to_ffi(ty: &Type, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { let res = match_ffi_type!( - unsafe { *ty }, + ty , c_schar => { - &mut i8::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&i8::try_from_object(vm, obj)?) } c_int => { - &mut i32::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&i32::try_from_object(vm, obj)?) } c_short => { - &mut i16::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&i16::try_from_object(vm, obj)?) } c_ushort => { - &mut u16::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&u16::try_from_object(vm, obj)?) } c_uint => { - &mut u32::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&u32::try_from_object(vm, obj)?) } - //@ TODO: Convert c*longlong from BigInt? c_long | c_longlong => { - &mut i64::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&i64::try_from_object(vm, obj)?) } c_ulong | c_ulonglong => { - &mut u64::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&u64::try_from_object(vm, obj)?) } f32 => { - &mut f32::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&f32::try_from_object(vm, obj)?) } f64 | longdouble=> { - &mut f64::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&f64::try_from_object(vm, obj)?) } c_uchar => { - &mut u8::try_from_object(vm, obj)? as *mut _ as *mut c_void + arg(&u8::try_from_object(vm, obj)?) } pointer => { - usize::try_from_object(vm, obj).unwrap() as *mut c_void + arg(&usize::try_from_object(vm, obj).unwrap()) } void => { - ptr::null_mut() + arg(&ptr::null::()) } ); @@ -140,16 +131,14 @@ fn py_to_ffi( #[derive(Debug)] pub struct Function { pointer: *mut c_void, - cif: ffi_cif, - arguments: Vec<*mut ffi_type>, - return_type: Box<*mut ffi_type>, + arguments: Vec, + return_type: Box, } impl Function { pub fn new(fn_ptr: usize, arguments: Vec, return_type: &str) -> Function { Function { pointer: fn_ptr as *mut _, - cif: Default::default(), arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), return_type: Box::new(str_to_type(return_type)), @@ -166,80 +155,66 @@ impl Function { } pub fn call(&mut self, arg_ptrs: Vec, vm: &VirtualMachine) -> PyResult { - let result = unsafe { - prep_cif( - &mut self.cif, - ABI, - self.arguments.len(), - *self.return_type.as_mut(), - self.arguments.as_mut_ptr(), - ) - }; - - if let Err(FFIError::Typedef) = result { - return Err(vm.new_runtime_error( - "The type representation is invalid or unsupported".to_string(), - )); - } else if let Err(FFIError::Abi) = result { - return Err(vm.new_runtime_error("The ABI is invalid or unsupported".to_string())); - } - - let arg_results: Result, _> = arg_ptrs + let args_vec: Vec = arg_ptrs .iter() .zip(self.arguments.iter_mut()) - .map(|(o, t)| py_to_ffi(t as *mut *mut _, o.clone(), vm)) - .collect(); + .map(|(o, t)| py_to_ffi(t, o.clone(), vm)) + .collect::>>()?; - let fun_ptr = CodePtr(self.pointer); + let args = args_vec.as_slice(); - let cif_ptr = &mut self.cif; - let mut arg_pointers = arg_results?; + let cif = Cif::new( + self.arguments.clone().into_iter(), + self.return_type.as_ref().to_owned(), + ); + + let fun_ptr = CodePtr(self.pointer); let res = unsafe { match_ffi_type!( - *self.return_type.as_mut(), + self.return_type.as_ref(), c_schar => { - let r: c_schar = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_schar = cif.call(fun_ptr, args); vm.new_pyobj(r as i8) } c_int => { - let r: c_int = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_int = cif.call(fun_ptr, args); vm.new_pyobj(r as i32) } c_short => { - let r: c_short = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_short = cif.call(fun_ptr, args); vm.new_pyobj(r as i16) } c_ushort => { - let r: c_ushort = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_ushort = cif.call(fun_ptr, args); vm.new_pyobj(r as u16) } c_uint => { - let r: c_uint = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_uint = cif.call(fun_ptr, args); vm.new_pyobj(r as u32) } c_long | c_longlong => { - let r: c_long = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_long = cif.call(fun_ptr, args); vm.new_pyobj(r as i64) } c_ulong | c_ulonglong => { - let r: c_ulong = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_ulong = cif.call(fun_ptr, args); vm.new_pyobj(r as u64) } f32 => { - let r: c_float = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_float = cif.call(fun_ptr, args); vm.new_pyobj(r as f32) } f64 | longdouble=> { - let r: c_double = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_double = cif.call(fun_ptr, args); vm.new_pyobj(r as f64) } c_uchar => { - let r: c_uchar = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: c_uchar = cif.call(fun_ptr, args); vm.new_pyobj(r as u8) } pointer => { - let r: *mut c_void = ffi_call(cif_ptr, fun_ptr, arg_pointers.as_mut_ptr()); + let r: *mut c_void = cif.call(fun_ptr, args); vm.new_pyobj(r as *const _ as usize) } void => { diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 2e8ee9957b..808f8403da 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -9,12 +9,12 @@ use crate::builtins::{ }; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::VirtualMachine; use crate::stdlib::ctypes::array::PyCArray; -use crate::stdlib::ctypes::basics::{compare_classes, PyCData}; +use crate::stdlib::ctypes::basics::PyCData; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; @@ -137,7 +137,7 @@ fn generic_xxx_p_from_param( Ok(PySimpleType { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: compare_classes(cls, PySimpleType::static_type(), vm)?, + __abstract__: cls.is(PySimpleType::static_type()), } .into_object(vm)) } else if vm.isinstance(value, PySimpleType::static_type())? @@ -199,8 +199,8 @@ fn from_param_void_p( // @TODO: Is there a better way of doing this? if let Some(from_address) = vm.get_method(cls.as_object().clone(), "from_address") { if let Ok(cdata) = value.clone().downcast_exact::(vm) { - let mut buffer_guard = cdata.borrow_value_mut(); - let addr = buffer_guard.as_mut_ptr() as usize; + let buffer_guard = cdata.borrow_value_mut(); + let addr = buffer_guard.inner as usize; Ok(vm.invoke(&from_address?, (cls.clone_class(), addr))?) } else { @@ -215,7 +215,7 @@ fn from_param_void_p( Ok(PySimpleType { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: compare_classes(cls, PySimpleType::static_type(), vm)?, + __abstract__: cls.is(PySimpleType::static_type()), } .into_object(vm)) } else { @@ -225,7 +225,7 @@ fn from_param_void_p( } fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { - let is_abstract = compare_classes(cls, PySimpleType::static_type(), vm)?; + let is_abstract = cls.is(PySimpleType::static_type()); if is_abstract { return Err(vm.new_type_error("abstract class".to_string())); @@ -333,7 +333,7 @@ impl PySimpleType { return Ok(zelf.as_object().clone()); } } - return Ok(zelf.value()); + Ok(zelf.value()) } // From PyCSimpleType_Type PyCSimpleType_methods @@ -343,7 +343,7 @@ impl PySimpleType { value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - if compare_classes(&cls, PySimpleType::static_type(), vm)? { + if cls.is(PySimpleType::static_type()) { Err(vm.new_type_error("abstract class".to_string())) } else if vm.isinstance(&value, &cls)? { Ok(value) @@ -374,7 +374,7 @@ impl PySimpleType { { Ok(vm.invoke( &from_param?, - (my_base.clone_class(), value.clone()), + (my_base.clone_class(), value), )?) } else { // @TODO: Make sure of what goes here From 420a67a33d8e4503c3ead479409134cdc6391370 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 21 Dec 2020 09:11:09 -0300 Subject: [PATCH 40/56] Small fixes --- vm/src/stdlib/ctypes/function.rs | 4 ++-- vm/src/stdlib/ctypes/primitive.rs | 4 ++-- vm/src/stdlib/ctypes/shared_lib.rs | 16 +++++++--------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 0cf9beb88b..85ff7aac0b 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -118,10 +118,10 @@ fn py_to_ffi(ty: &Type, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult arg(&u8::try_from_object(vm, obj)?) } pointer => { - arg(&usize::try_from_object(vm, obj).unwrap()) + arg(&(usize::try_from_object(vm, obj)? as *mut usize as *mut c_void)) } void => { - arg(&ptr::null::()) + arg(&ptr::null::()) } ); diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 808f8403da..b0d0b2685b 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -18,7 +18,7 @@ use crate::stdlib::ctypes::basics::PyCData; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; -pub const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; +const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult { match _type_ { @@ -84,7 +84,7 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe "?" => Ok(vm.ctx.new_bool(boolval(vm, value.clone())?)), "B" => { if value.clone().downcast_exact::(vm).is_ok() { - Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone()).unwrap())) + Ok(vm.new_pyobj(u8::try_from_object(vm, value.clone())?)) } else { Err(vm.new_type_error(format!("int expected instead of {}", value.class().name))) } diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index c705a3fd81..68d800504c 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -37,17 +37,15 @@ impl SharedLibrary { } pub fn get_sym(&self, name: &str) -> Result<*mut c_void, String> { - let inner = if let Some(ref inner) = unsafe { &*self.lib.as_ptr() } { - inner + if let Some(inner) = unsafe { &*self.lib.as_ptr() } { + unsafe { + inner + .get(name.as_bytes()) + .map(|f: libloading::Symbol<*mut c_void>| *f) + .map_err(|err| err.to_string()) + } } else { return Err("The library has been closed".to_string()); - }; - - unsafe { - inner - .get(name.as_bytes()) - .map(|f: libloading::Symbol<*mut c_void>| *f) - .map_err(|err| err.to_string()) } } From d6566502c455ea37a766db8c15a9d3456622bff8 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Mon, 21 Dec 2020 16:43:09 -0300 Subject: [PATCH 41/56] Initial commit for PyCArray --- vm/Cargo.toml | 1 + vm/src/stdlib/ctypes/array.rs | 496 ++++++++++++++++++++++++++++- vm/src/stdlib/ctypes/basics.rs | 42 ++- vm/src/stdlib/ctypes/function.rs | 72 +++-- vm/src/stdlib/ctypes/shared_lib.rs | 2 +- 5 files changed, 564 insertions(+), 49 deletions(-) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 4ad6e631f4..1df2e49a1b 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -133,6 +133,7 @@ foreign-types-shared = { version = "0.1", optional = true } # ctypes libloading = "0.6.5" libffi = "1.0.0" +widestring = "0.4.3" [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] num_cpus = "1" diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index c953673453..c2c13c7499 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -1,17 +1,203 @@ -use std::fmt; +use std::convert::TryInto; +use std::{fmt, mem, os::raw::*}; -use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType}; +use num_bigint::Sign; +use widestring::{WideCString, WideChar}; + +use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; +use crate::builtins::slice::PySliceRef; +use crate::builtins::{PyBytes, PyInt, PyList, PyStr, PyTypeRef}; +use crate::common::lock::PyRwLockReadGuard; +use crate::function::FuncArgs; +use crate::pyobject::{ + BorrowValue, Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, + TryFromObject, TypeProtocol, +}; +use crate::slots::BufferProtocol; use crate::VirtualMachine; -use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::basics::{PyCData, PyCDataBuffer, RawBuffer}; +use crate::stdlib::ctypes::pointer::PyCPointer; +use crate::stdlib::ctypes::primitive::PySimpleType; + +macro_rules! os_match_type { + ( + $kind: expr, + + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { mem::size_of::<$body>() } + )+ + )+ + _ => unreachable!() + } + } +} + +macro_rules! byte_match_type { + ( + $kind: expr, + $byte: expr, + $vm: expr, + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { + let chunk: [u8; mem::size_of::<$body>()] = $byte.try_into().unwrap(); + $vm.new_pyobj($body::from_ne_bytes(chunk)) + } + )+ + )+ + _ => unreachable!() + } + } +} + +fn get_size(ty: &str) -> usize { + os_match_type!( + ty, + "u" => WideChar + "c" | "b" => c_schar + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => c_float + "d" | "g" => c_double + "?" | "B" => c_uchar + "P" | "z" | "Z" => c_void + ) +} + +fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { + if ty == "u" { + vm.new_pyobj(if cfg!(windows) { + let chunk: [u8; 2] = b.try_into().unwrap(); + u16::from_ne_bytes(chunk) as u32 + } else { + let chunk: [u8; 4] = b.try_into().unwrap(); + u32::from_ne_bytes(chunk) + }) + } else { + byte_match_type!( + ty, + b, + vm, + "c" | "b" => i8 + "h" => i16 + "H" => u16 + "i" => i32 + "I" => u32 + "l" | "q" => i64 + "L" | "Q" => u64 + "f" => f32 + "d" | "g" => f64 + "?" | "B" => u8 + "P" | "z" | "Z" => usize + ) + } +} + +fn slice_adjust_size(length: isize, start: &mut isize, stop: &mut isize, step: isize) -> isize { + if *start < 0 { + *start += length; + if *start < 0 { + *start = if step < 0 { -1 } else { 0 }; + } + } else if *start >= length { + *start = if step < 0 { length - 1 } else { length }; + } + + if *stop < 0 { + *stop += length; + if *stop < 0 { + *stop = if step < 0 { -1 } else { 0 }; + } + } else if *stop >= length { + *stop = if step < 0 { length - 1 } else { length }; + } + + if step < 0 { + if *stop < *start { + return (*start - *stop - 1) / (-step) + 1; + } + } else if *start < *stop { + return (*stop - *start - 1) / step + 1; + } + + 0 +} + +pub fn make_array_with_lenght( + cls: PyTypeRef, + length: usize, + vm: &VirtualMachine, +) -> PyResult> { + if let Ok(ref outer_type) = vm.get_attribute(cls.as_object().to_owned(), "_type_") { + match vm.get_attribute(outer_type.clone(), "_type_") { + Ok(inner_type) + if vm.issubclass(&inner_type.clone_class(), &PyCPointer::static_type())? + || vm + .issubclass(&inner_type.clone_class(), &PySimpleType::static_type())? => + { + let subletter = vm + .get_attribute(outer_type.clone(), "_type_")? + .downcast_exact::(vm) + .unwrap() + .to_string(); + + let itemsize = get_size(subletter.as_str()); + + let parent = PyCData::new( + None, + Some(RawBuffer { + inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), + size: length * itemsize, + }), + ); + + Ok(PyCArray { + _type_: subletter, + _length_: length, + _parent: parent, + } + .into_ref_with_type(vm, cls)?) + } + _ => Err(vm.new_type_error("_type_ must have storage info".to_string())), + } + } else { + Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) + } +} #[pyclass(module = "_ctypes", name = "Array", base = "PyCData")] -pub struct PyCArray {} +pub struct PyCArray { + _type_: String, + _length_: usize, + _parent: PyCData, //a hack +} impl fmt::Debug for PyCArray { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "PyCArray {{}}") + write!( + f, + "PyCArray {{ {} {} }}", + self._type_.as_str(), + self._length_ + ) } } @@ -20,3 +206,301 @@ impl PyValue for PyCArray { Self::static_type() } } + +impl<'a> BorrowValue<'a> for PyCArray { + type Borrowed = PyRwLockReadGuard<'a, RawBuffer>; + + fn borrow_value(&'a self) -> Self::Borrowed { + self._parent.borrow_value() + } +} + +impl BufferProtocol for PyCArray { + fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult> { + let raw_buffer_guard = zelf.borrow_value(); + let raw_buffer = RawBuffer { + inner: raw_buffer_guard.inner, + size: raw_buffer_guard.size, + }; + let parent_cloned_ref = PyCData::new(None, Some(raw_buffer)).into_ref(vm); + + Ok(Box::new(PyCDataBuffer { + data: parent_cloned_ref, + options: BufferOptions { + readonly: false, + len: raw_buffer_guard.size, + ..Default::default() + }, + })) + } +} + +#[pyimpl(flags(BASETYPE), with(BufferProtocol))] +impl PyCArray { + #[pyslot] + fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + let length = match vm.get_attribute(cls.as_object().to_owned(), "_length_") { + Ok(ref length_obj) => { + if let Ok(length_int) = length_obj.clone().downcast_exact::(vm) { + if length_int.borrow_value().sign() == Sign::Minus { + Err(vm.new_value_error( + "The '_length_' attribute must not be negative".to_string(), + )) + } else { + Ok(usize::try_from_object(vm, length_obj.clone()) + .or(Err(vm.new_overflow_error( + "The '_length_' attribute is too large".to_string(), + )))?) + } + } else { + Err(vm + .new_type_error("The '_length_' attribute must be an integer".to_string())) + } + } + Err(_) => { + Err(vm.new_attribute_error("class must define a '_type_' _length_".to_string())) + } + }?; + + make_array_with_lenght(cls, length, vm) + } + + #[pymethod(magic)] + pub fn init(&self, value: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + // @TODO + Ok(()) + } + + #[pyproperty(name = "value")] + pub fn value(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let obj = zelf.as_object(); + let buffer = try_buffer_from_object(vm, obj)?; + + let res = if zelf._type_ == "u" { + vm.new_pyobj( + unsafe { + if cfg!(windows) { + WideCString::from_vec_with_nul_unchecked( + buffer + .obj_bytes() + .chunks_exact(2) + .map(|c| { + let chunk: [u8; 2] = c.try_into().unwrap(); + u16::from_ne_bytes(chunk) as u32 + }) + .collect::>(), + ) + } else { + WideCString::from_vec_with_nul_unchecked( + buffer + .obj_bytes() + .chunks(4) + .map(|c| { + let chunk: [u8; 4] = c.try_into().unwrap(); + u32::from_ne_bytes(chunk) + }) + .collect::>(), + ) + } + } + .to_string() + .map_err(|e| vm.new_runtime_error(e.to_string()))?, + ) + } else { + // self._type_ == "c" + let bytes = buffer.obj_bytes(); + + let bytes_inner = if let Some((last, elements)) = bytes.split_last() { + if *last == 0 { + elements.to_vec() + } else { + bytes.to_vec() + } + } else { + vec![0; 0] + }; + + PyBytes::from(bytes_inner).into_object(vm) + }; + + Ok(res) + } + + #[pyproperty(name = "value", setter)] + fn set_value(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let obj = zelf.as_object(); + let buffer = try_buffer_from_object(vm, obj)?; + let my_size = buffer.get_options().len; + let mut bytes = buffer.obj_bytes_mut(); + + if zelf._type_ == "c" { + // bytes + if let Ok(value) = value.clone().downcast_exact::(vm) { + let wide_bytes = value.to_vec(); + + if wide_bytes.len() > my_size { + Err(vm.new_value_error("byte string too long".to_string())) + } else { + bytes[0..wide_bytes.len()].copy_from_slice(wide_bytes.as_slice()); + if wide_bytes.len() < my_size { + bytes[my_size] = 0; + } + Ok(()) + } + } else { + Err(vm.new_value_error(format!( + "bytes expected instead of {} instance", + value.class().name + ))) + } + } else { + // unicode string zelf._type_ == "u" + if let Ok(value) = value.clone().downcast_exact::(vm) { + let wide_str = + unsafe { WideCString::from_str_with_nul_unchecked(value.to_string()) }; + + let wide_str_len = wide_str.len(); + + if wide_str.len() > my_size { + Err(vm.new_value_error("string too long".to_string())) + } else { + let res = if cfg!(windows) { + wide_str + .into_vec() + .iter_mut() + .map(|i| u16::to_ne_bytes(*i as u16).to_vec()) + .flatten() + .collect::>() + } else { + wide_str + .into_vec() + .iter_mut() + .map(|i| u32::to_ne_bytes(*i).to_vec()) + .flatten() + .collect::>() + }; + + bytes[0..wide_str_len].copy_from_slice(res.as_slice()); + + Ok(()) + } + } else { + Err(vm.new_value_error(format!( + "unicode string expected instead of {} instance", + value.class().name + ))) + } + } + } + + #[pyproperty(name = "raw")] + pub fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // zelf._type_ == "c" + + let obj = zelf.as_object(); + let buffer = try_buffer_from_object(vm, obj)?; + let buffer_vec = buffer.obj_bytes().to_vec(); + + Ok(PyBytes::from(buffer_vec).into_object(vm)) + } + + #[pyproperty(name = "raw", setter)] + fn set_raw(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let obj = zelf.as_object(); + let my_buffer = try_buffer_from_object(vm, obj)?; + let my_size = my_buffer.get_options().len; + + let new_value = try_buffer_from_object(vm, &value)?; + let new_size = new_value.get_options().len; + + // byte string zelf._type_ == "c" + if new_size > my_size { + Err(vm.new_value_error("byte string too long".to_string())) + } else { + let mut borrowed_buffer = my_buffer.obj_bytes_mut(); + let src = new_value.obj_bytes(); + borrowed_buffer[0..new_size].copy_from_slice(&src); + Ok(()) + } + } + + #[pymethod(name = "__len__")] + fn len(&self) -> usize { + self._length_ + } + + #[pymethod(magic)] + fn getitem( + zelf: PyRef, + k_or_idx: Either, + vm: &VirtualMachine, + ) -> PyResult { + let buffer = try_buffer_from_object(vm, zelf.as_object())?; + let buffer_size = buffer.get_options().len; + let buffer_bytes = buffer.obj_bytes(); + let offset = buffer_size / zelf.len(); + + let res = match k_or_idx { + Either::A(idx) => { + if idx < 0 || idx as usize > zelf._length_ { + Err(vm.new_index_error("invalid index".to_string())) + } else { + let idx = idx as usize; + let buffer_slice = buffer_bytes[idx..idx + offset].as_ref(); + Ok(byte_to_pyobj(zelf._type_.as_str(), buffer_slice, vm)) + }? + } + Either::B(slice) => { + let step = slice + .step + .clone() + .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + + let mut start = slice + .start + .clone() + .map_or(Ok(0), |o| isize::try_from_object(vm, o))?; + + let mut stop = if slice.stop.is(&vm.ctx.none()) { + Err(vm.new_value_error("slice stop is required".to_string())) + } else { + Ok(isize::try_from_object(vm, slice.stop.clone())?) + }?; + + let slice_length = + slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; + + let mut obj_vec = Vec::with_capacity(slice_length); + + for i in (start as usize..stop as usize).step_by(step as usize) { + obj_vec.push(byte_to_pyobj( + zelf._type_.as_str(), + buffer_bytes[i..i + offset].as_ref(), + vm, + )); + } + + PyList::from(obj_vec).into_object(vm) + } + }; + + Ok(res) + } + + // #[pymethod(magic)] + // fn setitem( + // zelf: PyRef, + // k_or_idx: Either, + // obj: PyObjectRef, + // vm: &VirtualMachine, + // ) -> PyResult<()> { + // match k_or_idx { + // Either::A(idx) => { + + // }, + // Either::B(slice) => { + // let slice_length = slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; + // } + // } + // } +} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index f6695b2b28..168c1f4d02 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -13,6 +13,7 @@ use crate::pyobject::{ use crate::slots::BufferProtocol; use crate::VirtualMachine; +use crate::stdlib::ctypes::array::make_array_with_lenght; use crate::stdlib::ctypes::dll::dlsym; use crossbeam_utils::atomic::AtomicCell; @@ -202,14 +203,19 @@ pub trait PyCDataSequenceMethods: PyValue { // Basically the sq_repeat slot is CDataType_repeat // which transforms into a Array - // #[pymethod(name = "__mul__")] - // fn mul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - // } - - // #[pymethod(name = "__rmul__")] - // fn rmul(&self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - // self.mul(counter, vm) - // } + #[pymethod(name = "__mul__")] + #[pymethod(name = "__rmul__")] + fn mul(zelf: PyRef, length: isize, vm: &VirtualMachine) -> PyResult { + if length < 0 { + Err(vm.new_value_error(format!("Array length must be >= 0, not {} length", length))) + } else { + Ok( + make_array_with_lenght(zelf.clone_class(), length as usize, vm)? + .as_object() + .clone(), + ) + } + } } impl<'a> BorrowValue<'a> for PyCData { @@ -234,9 +240,9 @@ impl BufferProtocol for PyCData { } #[derive(Debug)] -struct PyCDataBuffer { - data: PyCDataRef, - options: BufferOptions, +pub struct PyCDataBuffer { + pub data: PyCDataRef, + pub options: BufferOptions, } // This trait will be used by all types @@ -266,6 +272,18 @@ pub struct RawBuffer { pub size: usize, } +impl fmt::Debug for RawBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RawBuffer {{ size: {} }}", self.size) + } +} + +impl PyValue for RawBuffer { + fn class(vm: &VirtualMachine) -> &PyTypeRef { + &vm.ctx.types.object_type + } +} + unsafe impl Send for RawBuffer {} unsafe impl Sync for RawBuffer {} @@ -293,7 +311,7 @@ impl PyValue for PyCData { } impl PyCData { - fn new(objs: Option>, buffer: Option) -> Self { + pub fn new(objs: Option>, buffer: Option) -> Self { PyCData { _objects: AtomicCell::new(objs.unwrap_or_default()), _buffer: PyRwLock::new(buffer.unwrap_or(RawBuffer { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 85ff7aac0b..ef37a747a2 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -1,4 +1,4 @@ -use std::{fmt, os::raw::*, ptr}; +use std::{fmt, os::raw::*}; use crossbeam_utils::atomic::AtomicCell; @@ -47,41 +47,47 @@ macro_rules! match_ffi_type { $kind: expr, $( - $($type: tt)|+ => $body: ident + $($type: literal)|+ => $body: ident )+ ) => { match $kind { $( $( t if t == $type => { ffi_type!($body) } - )+ + )? )+ _ => unreachable!() } } } -pub fn str_to_type(ty: &str) -> Type { - match_ffi_type!( - ty, - "c" => c_schar - "u" => c_int - "b" => i8 - "h" => c_short - "H" => c_ushort - "i" => c_int - "I" => c_uint - "l" => c_long - "q" => c_longlong - "L" => c_ulong - "Q" => c_ulonglong - "f" => f32 - "d" => f64 - "g" => longdouble - "?" | "B" => c_uchar - "z" | "Z" => pointer - "P" => void - ) +fn str_to_type(ty: &str) -> Type { + if ty == "u" { + if cfg!(windows) { + ffi_type!(c_ushort) + } else { + ffi_type!(c_uint) + } + } else { + match_ffi_type!( + ty, + "c" => c_schar + "b" => i8 + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => f32 + "d" => f64 + "g" => longdouble + "?" | "B" => c_uchar + "P" | "z" | "Z" => pointer + ) + } } fn py_to_ffi(ty: &Type, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -120,16 +126,14 @@ fn py_to_ffi(ty: &Type, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult pointer => { arg(&(usize::try_from_object(vm, obj)? as *mut usize as *mut c_void)) } - void => { - arg(&ptr::null::()) - } + // void should not be here, once an argument cannot be pure void ); Ok(res) } #[derive(Debug)] -pub struct Function { +struct Function { pointer: *mut c_void, arguments: Vec, return_type: Box, @@ -141,7 +145,11 @@ impl Function { pointer: fn_ptr as *mut _, arguments: arguments.iter().map(|s| str_to_type(s.as_str())).collect(), - return_type: Box::new(str_to_type(return_type)), + return_type: Box::new(if return_type == "P" { + Type::void() + } else { + str_to_type(return_type) + }), } } pub fn set_args(&mut self, args: Vec) { @@ -151,7 +159,11 @@ impl Function { } pub fn set_ret(&mut self, ret: &str) { - (*self.return_type.as_mut()) = str_to_type(ret); + (*self.return_type.as_mut()) = if ret == "P" { + Type::void() + } else { + str_to_type(ret) + }; } pub fn call(&mut self, arg_ptrs: Vec, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index 68d800504c..96e73bfc3d 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -45,7 +45,7 @@ impl SharedLibrary { .map_err(|err| err.to_string()) } } else { - return Err("The library has been closed".to_string()); + Err("The library has been closed".to_string()) } } From 2a1f6d78040522f02964ba80a3a4543660006223 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Wed, 30 Dec 2020 13:19:57 -0300 Subject: [PATCH 42/56] Reworking CData buffers --- vm/src/stdlib/ctypes/array.rs | 92 ++++++++++++++++------------ vm/src/stdlib/ctypes/basics.rs | 99 +++++++++++++++++++++++-------- vm/src/stdlib/ctypes/pointer.rs | 11 +++- vm/src/stdlib/ctypes/primitive.rs | 17 ++++-- 4 files changed, 151 insertions(+), 68 deletions(-) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index c2c13c7499..141a271a8f 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -4,10 +4,10 @@ use std::{fmt, mem, os::raw::*}; use num_bigint::Sign; use widestring::{WideCString, WideChar}; -use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; +use crate::builtins::memory::{try_buffer_from_object, Buffer}; use crate::builtins::slice::PySliceRef; use crate::builtins::{PyBytes, PyInt, PyList, PyStr, PyTypeRef}; -use crate::common::lock::PyRwLockReadGuard; +use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::FuncArgs; use crate::pyobject::{ BorrowValue, Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, @@ -16,7 +16,10 @@ use crate::pyobject::{ use crate::slots::BufferProtocol; use crate::VirtualMachine; -use crate::stdlib::ctypes::basics::{PyCData, PyCDataBuffer, RawBuffer}; +use crate::stdlib::ctypes::basics::{ + generic_get_buffer, BorrowValue as BorrowValueCData, BorrowValueMut, PyCData, PyCDataMethods, + RawBuffer, +}; use crate::stdlib::ctypes::pointer::PyCPointer; use crate::stdlib::ctypes::primitive::PySimpleType; @@ -161,18 +164,13 @@ pub fn make_array_with_lenght( let itemsize = get_size(subletter.as_str()); - let parent = PyCData::new( - None, - Some(RawBuffer { - inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), - size: length * itemsize, - }), - ); - Ok(PyCArray { _type_: subletter, _length_: length, - _parent: parent, + _buffer: PyRwLock::new(RawBuffer { + inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), + size: length * itemsize, + }), } .into_ref_with_type(vm, cls)?) } @@ -187,7 +185,7 @@ pub fn make_array_with_lenght( pub struct PyCArray { _type_: String, _length_: usize, - _parent: PyCData, //a hack + _buffer: PyRwLock, } impl fmt::Debug for PyCArray { @@ -207,34 +205,30 @@ impl PyValue for PyCArray { } } -impl<'a> BorrowValue<'a> for PyCArray { - type Borrowed = PyRwLockReadGuard<'a, RawBuffer>; +impl<'a> BorrowValueCData<'a> for PyCArray { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer> { + self._buffer.read() + } +} - fn borrow_value(&'a self) -> Self::Borrowed { - self._parent.borrow_value() +impl<'a> BorrowValueMut<'a> for PyCArray { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer> { + self._buffer.write() } } impl BufferProtocol for PyCArray { fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult> { - let raw_buffer_guard = zelf.borrow_value(); - let raw_buffer = RawBuffer { - inner: raw_buffer_guard.inner, - size: raw_buffer_guard.size, - }; - let parent_cloned_ref = PyCData::new(None, Some(raw_buffer)).into_ref(vm); - - Ok(Box::new(PyCDataBuffer { - data: parent_cloned_ref, - options: BufferOptions { - readonly: false, - len: raw_buffer_guard.size, - ..Default::default() - }, - })) + generic_get_buffer::(zelf, vm) } } +// impl PyCDataMethods for PyCArray { +// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + +// } +// } + #[pyimpl(flags(BASETYPE), with(BufferProtocol))] impl PyCArray { #[pyslot] @@ -247,10 +241,11 @@ impl PyCArray { "The '_length_' attribute must not be negative".to_string(), )) } else { - Ok(usize::try_from_object(vm, length_obj.clone()) - .or(Err(vm.new_overflow_error( + Ok(usize::try_from_object(vm, length_obj.clone()).map_err(|_| { + vm.new_overflow_error( "The '_length_' attribute is too large".to_string(), - )))?) + ) + })?) } } else { Err(vm @@ -394,7 +389,7 @@ impl PyCArray { } #[pyproperty(name = "raw")] - pub fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { // zelf._type_ == "c" let obj = zelf.as_object(); @@ -456,6 +451,9 @@ impl PyCArray { .clone() .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + assert!(step != 0); + assert!(step >= -isize::MAX); + let mut start = slice .start .clone() @@ -494,9 +492,29 @@ impl PyCArray { // obj: PyObjectRef, // vm: &VirtualMachine, // ) -> PyResult<()> { + // let buffer = try_buffer_from_object(vm, zelf.as_object())?; + // let buffer_size = buffer.get_options().len; + // let buffer_bytes = buffer.obj_bytes_mut(); + // let offset = buffer_size / zelf.len(); + // match k_or_idx { // Either::A(idx) => { - + // if idx < 0 || idx as usize > zelf._length_ { + // Err(vm.new_index_error("invalid index".to_string())) + // } else { + // let idx = idx as usize; + // let type_obj = get_obj(zelf._type_.as_str()) + // if let Some(from_param) = vm.get_method(type_obj.clone(), "from_param"){ + // let cobj = vm.invoke( + // &from_param?, + // (type_obj, obj), + // )?; + // } else { + + // } + // // buffer_bytes[idx..idx + offset]; + // Ok(()) + // }? // }, // Either::B(slice) => { // let slice_length = slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 168c1f4d02..cb0e661113 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -8,7 +8,7 @@ use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - BorrowValue, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; use crate::VirtualMachine; @@ -119,6 +119,24 @@ fn buffer_copy( } } +pub fn default_from_param( + cls: PyTypeRef, + value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + if vm.isinstance(&value, &cls)? { + Ok(value) + } else if let Ok(parameter) = vm.get_attribute(value.clone(), "_as_parameter_") { + default_from_param(cls, parameter, vm) + } else { + Err(vm.new_attribute_error(format!( + "expected {} instance instead of {}", + cls.name, + value.class().name + ))) + } +} + #[pyimpl] pub trait PyCDataMethods: PyValue { // A lot of the logic goes in this trait @@ -132,7 +150,8 @@ pub trait PyCDataMethods: PyValue { // PyCFuncPtrType_Type #[pyclassmethod] - fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; + fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) + -> PyResult; #[pyclassmethod] fn from_address( @@ -218,35 +237,59 @@ pub trait PyCDataSequenceMethods: PyValue { } } -impl<'a> BorrowValue<'a> for PyCData { - type Borrowed = PyRwLockReadGuard<'a, RawBuffer>; +pub fn generic_get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult> +where + for<'a> T: PyValue + fmt::Debug + BorrowValue<'a> + BorrowValueMut<'a>, +{ + if let Ok(buffer) = vm.get_attribute(zelf.as_object().clone(), "_buffer") { + if let Ok(_buffer) = buffer.downcast_exact::(vm) { + Ok(Box::new(PyCBuffer:: { + data: zelf.clone(), + options: BufferOptions { + readonly: false, + len: _buffer.size, + ..Default::default() + }, + })) + } else { + Err(vm.new_attribute_error("_buffer attribute should be RawBuffer".to_string())) + } + } else { + Err(vm.new_attribute_error("_buffer not found".to_string())) + } +} + +pub trait BorrowValueMut<'a> { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer>; +} - fn borrow_value(&'a self) -> Self::Borrowed { +pub trait BorrowValue<'a> { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer>; +} + +impl<'a> BorrowValue<'a> for PyCData { + fn borrow_value(&'a self) -> PyRwLockReadGuard<'a, RawBuffer> { self._buffer.read() } } -impl BufferProtocol for PyCData { - fn get_buffer(zelf: &PyRef, _vm: &VirtualMachine) -> PyResult> { - Ok(Box::new(PyCDataBuffer { - data: zelf.clone(), - options: BufferOptions { - readonly: false, - len: zelf._buffer.read().size, - ..Default::default() - }, - })) +impl<'a> BorrowValueMut<'a> for PyCData { + fn borrow_value_mut(&'a self) -> PyRwLockWriteGuard<'a, RawBuffer> { + self._buffer.write() } } -#[derive(Debug)] -pub struct PyCDataBuffer { - pub data: PyCDataRef, - pub options: BufferOptions, +impl BufferProtocol for PyCData { + fn get_buffer(zelf: &PyRef, vm: &VirtualMachine) -> PyResult> { + generic_get_buffer::(zelf, vm) + } } // This trait will be used by all types -impl Buffer for PyCDataBuffer { +impl Buffer for PyCBuffer +where + for<'a> T: PyValue + fmt::Debug + BorrowValue<'a> + BorrowValueMut<'a>, +{ fn obj_bytes(&self) -> BorrowedValue<[u8]> { PyRwLockReadGuard::map(self.data.borrow_value(), |x| unsafe { slice::from_raw_parts(x.inner, x.size) @@ -267,6 +310,16 @@ impl Buffer for PyCDataBuffer { &self.options } } + +#[derive(Debug)] +pub struct PyCBuffer +where + for<'a> T: PyValue + fmt::Debug + BorrowValue<'a> + BorrowValueMut<'a>, +{ + pub data: PyRef, + pub options: BufferOptions, +} + pub struct RawBuffer { pub inner: *mut u8, pub size: usize, @@ -296,8 +349,6 @@ pub struct PyCData { _buffer: PyRwLock, } -pub type PyCDataRef = PyRef; - impl fmt::Debug for PyCData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "PyCData {{ _objects: {{}}, _buffer: {{}}}}",) @@ -320,10 +371,6 @@ impl PyCData { })), } } - - pub fn borrow_value_mut(&self) -> PyRwLockWriteGuard<'_, RawBuffer> { - self._buffer.write() - } } #[pyimpl(flags(BASETYPE), with(BufferProtocol))] diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index b783d40da9..9b10ca55d0 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -4,7 +4,7 @@ use crate::builtins::PyTypeRef; use crate::pyobject::{PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::basics::{PyCData, PyCDataMethods}; #[pyclass(module = "_ctypes", name = "_Pointer", base = "PyCData")] pub struct PyCPointer {} @@ -20,3 +20,12 @@ impl PyValue for PyCPointer { Self::static_type() } } + +// impl PyCDataMethods for PyCPointer { +// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + +// } +// } + +#[pyimpl(flags(BASETYPE))] +impl PyCPointer {} diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index b0d0b2685b..933349e04e 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -14,7 +14,7 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::array::PyCArray; -use crate::stdlib::ctypes::basics::PyCData; +use crate::stdlib::ctypes::basics::{BorrowValueMut, PyCData, PyCDataMethods}; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; @@ -130,8 +130,11 @@ fn generic_xxx_p_from_param( type_str: &str, vm: &VirtualMachine, ) -> PyResult { - if vm.is_none(value) - || vm.isinstance(value, &vm.ctx.types.str_type)? + if vm.is_none(value) { + return Ok(vm.ctx.none()); + } + + if vm.isinstance(value, &vm.ctx.types.str_type)? || vm.isinstance(value, &vm.ctx.types.bytes_type)? { Ok(PySimpleType { @@ -198,7 +201,7 @@ fn from_param_void_p( { // @TODO: Is there a better way of doing this? if let Some(from_address) = vm.get_method(cls.as_object().clone(), "from_address") { - if let Ok(cdata) = value.clone().downcast_exact::(vm) { + if let Ok(cdata) = value.clone().downcast::() { let buffer_guard = cdata.borrow_value_mut(); let addr = buffer_guard.inner as usize; @@ -286,6 +289,12 @@ impl PyValue for PySimpleType { } } +// impl PyCDataMethods for PySimpleType { +// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + +// } +// } + #[pyimpl(flags(BASETYPE))] impl PySimpleType { #[pyslot] From ed4426946b50e4f69b1af7220b8d184418a72dc5 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 31 Dec 2020 11:32:21 -0300 Subject: [PATCH 43/56] Adding PyCArray setitem --- vm/src/stdlib/ctypes/array.rs | 212 ++++++++++++++++++------------ vm/src/stdlib/ctypes/basics.rs | 9 +- vm/src/stdlib/ctypes/pointer.rs | 2 +- vm/src/stdlib/ctypes/primitive.rs | 118 ++++++++--------- 4 files changed, 191 insertions(+), 150 deletions(-) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 141a271a8f..be5092a916 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -7,6 +7,7 @@ use widestring::{WideCString, WideChar}; use crate::builtins::memory::{try_buffer_from_object, Buffer}; use crate::builtins::slice::PySliceRef; use crate::builtins::{PyBytes, PyInt, PyList, PyStr, PyTypeRef}; +use crate::common::borrow::BorrowedValueMut; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::FuncArgs; use crate::pyobject::{ @@ -150,40 +151,91 @@ pub fn make_array_with_lenght( vm: &VirtualMachine, ) -> PyResult> { if let Ok(ref outer_type) = vm.get_attribute(cls.as_object().to_owned(), "_type_") { - match vm.get_attribute(outer_type.clone(), "_type_") { - Ok(inner_type) - if vm.issubclass(&inner_type.clone_class(), &PyCPointer::static_type())? - || vm - .issubclass(&inner_type.clone_class(), &PySimpleType::static_type())? => - { - let subletter = vm - .get_attribute(outer_type.clone(), "_type_")? - .downcast_exact::(vm) - .unwrap() - .to_string(); - - let itemsize = get_size(subletter.as_str()); - - Ok(PyCArray { - _type_: subletter, - _length_: length, - _buffer: PyRwLock::new(RawBuffer { - inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), - size: length * itemsize, - }), - } - .into_ref_with_type(vm, cls)?) + if let Ok(_type_) = outer_type.clone().downcast_exact::(vm) { + let itemsize = get_size(_type_._type_.as_str()); + + Ok(PyCArray { + _type_, + _length_: length, + _buffer: PyRwLock::new(RawBuffer { + inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), + size: length * itemsize, + }), } - _ => Err(vm.new_type_error("_type_ must have storage info".to_string())), + .into_ref_with_type(vm, cls)?) + } else { + Err(vm.new_type_error("_type_ must have storage info".to_string())) } } else { Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) } } +fn set_array_value( + zelf: &PyRef, + dst_buffer: &mut BorrowedValueMut<[u8]>, + idx: usize, + offset: usize, + obj: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult<()> { + if obj.clone().downcast::().is_err() { + let value = PyCDataMethods::from_param(zelf._type_.clone(), obj, vm)?; + + let v_buffer = try_buffer_from_object(vm, &value)?; + let v_buffer_bytes = v_buffer.obj_bytes_mut(); + + dst_buffer[idx..idx + offset].copy_from_slice(&v_buffer_bytes[0..]); + Ok(()) + } else if vm.isinstance(&obj, &zelf._type_.clone_class())? { + let o_buffer = try_buffer_from_object(vm, &obj)?; + let src_buffer = o_buffer.obj_bytes_mut(); + + dst_buffer[idx..idx + offset].copy_from_slice(&src_buffer[idx..idx + offset]); + Ok(()) + } else if vm.isinstance(zelf._type_.as_object(), PyCPointer::static_type())? + && vm.isinstance(&obj, PyCArray::static_type())? + { + //@TODO: Fill here once CPointer is done + Ok(()) + } else { + Err(vm.new_type_error(format!( + "incompatible types, {} instance instead of {} instance", + obj.class().name, + zelf.class().name + ))) + } +} + +fn array_get_slice_inner( + slice: PySliceRef, + vm: &VirtualMachine, +) -> PyResult<(isize, isize, isize)> { + let step = slice + .step + .clone() + .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + + assert!(step != 0); + assert!(step >= -isize::MAX); + + let start = slice + .start + .clone() + .map_or(Ok(0), |o| isize::try_from_object(vm, o))?; + + let stop = if slice.stop.is(&vm.ctx.none()) { + Err(vm.new_value_error("slice stop is required".to_string())) + } else { + Ok(isize::try_from_object(vm, slice.stop.clone())?) + }?; + + Ok((step, start, stop)) +} + #[pyclass(module = "_ctypes", name = "Array", base = "PyCData")] pub struct PyCArray { - _type_: String, + _type_: PyRef, _length_: usize, _buffer: PyRwLock, } @@ -193,7 +245,7 @@ impl fmt::Debug for PyCArray { write!( f, "PyCArray {{ {} {} }}", - self._type_.as_str(), + self._type_._type_.as_str(), self._length_ ) } @@ -224,7 +276,7 @@ impl BufferProtocol for PyCArray { } // impl PyCDataMethods for PyCArray { -// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { +// fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { // } // } @@ -271,7 +323,7 @@ impl PyCArray { let obj = zelf.as_object(); let buffer = try_buffer_from_object(vm, obj)?; - let res = if zelf._type_ == "u" { + let res = if zelf._type_._type_ == "u" { vm.new_pyobj( unsafe { if cfg!(windows) { @@ -328,7 +380,7 @@ impl PyCArray { let my_size = buffer.get_options().len; let mut bytes = buffer.obj_bytes_mut(); - if zelf._type_ == "c" { + if zelf._type_._type_ == "c" { // bytes if let Ok(value) = value.clone().downcast_exact::(vm) { let wide_bytes = value.to_vec(); @@ -442,28 +494,11 @@ impl PyCArray { } else { let idx = idx as usize; let buffer_slice = buffer_bytes[idx..idx + offset].as_ref(); - Ok(byte_to_pyobj(zelf._type_.as_str(), buffer_slice, vm)) + Ok(byte_to_pyobj(zelf._type_._type_.as_str(), buffer_slice, vm)) }? } Either::B(slice) => { - let step = slice - .step - .clone() - .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; - - assert!(step != 0); - assert!(step >= -isize::MAX); - - let mut start = slice - .start - .clone() - .map_or(Ok(0), |o| isize::try_from_object(vm, o))?; - - let mut stop = if slice.stop.is(&vm.ctx.none()) { - Err(vm.new_value_error("slice stop is required".to_string())) - } else { - Ok(isize::try_from_object(vm, slice.stop.clone())?) - }?; + let (step, mut start, mut stop) = array_get_slice_inner(slice, vm)?; let slice_length = slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; @@ -472,7 +507,7 @@ impl PyCArray { for i in (start as usize..stop as usize).step_by(step as usize) { obj_vec.push(byte_to_pyobj( - zelf._type_.as_str(), + zelf._type_._type_.as_str(), buffer_bytes[i..i + offset].as_ref(), vm, )); @@ -485,40 +520,47 @@ impl PyCArray { Ok(res) } - // #[pymethod(magic)] - // fn setitem( - // zelf: PyRef, - // k_or_idx: Either, - // obj: PyObjectRef, - // vm: &VirtualMachine, - // ) -> PyResult<()> { - // let buffer = try_buffer_from_object(vm, zelf.as_object())?; - // let buffer_size = buffer.get_options().len; - // let buffer_bytes = buffer.obj_bytes_mut(); - // let offset = buffer_size / zelf.len(); - - // match k_or_idx { - // Either::A(idx) => { - // if idx < 0 || idx as usize > zelf._length_ { - // Err(vm.new_index_error("invalid index".to_string())) - // } else { - // let idx = idx as usize; - // let type_obj = get_obj(zelf._type_.as_str()) - // if let Some(from_param) = vm.get_method(type_obj.clone(), "from_param"){ - // let cobj = vm.invoke( - // &from_param?, - // (type_obj, obj), - // )?; - // } else { - - // } - // // buffer_bytes[idx..idx + offset]; - // Ok(()) - // }? - // }, - // Either::B(slice) => { - // let slice_length = slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; - // } - // } - // } + #[pymethod(magic)] + fn setitem( + zelf: PyRef, + k_or_idx: Either, + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let buffer = try_buffer_from_object(vm, zelf.as_object())?; + let buffer_size = buffer.get_options().len; + let mut buffer_bytes = buffer.obj_bytes_mut(); + + let offset = buffer_size / zelf.len(); + + match k_or_idx { + Either::A(idx) => { + if idx < 0 || idx as usize > zelf._length_ { + Err(vm.new_index_error("invalid index".to_string())) + } else { + set_array_value(&zelf, &mut buffer_bytes, idx as usize, offset, obj, vm) + } + } + Either::B(slice) => { + let (step, mut start, mut stop) = array_get_slice_inner(slice, vm)?; + + let slice_length = + slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; + + let values: Vec = vm.extract_elements(&obj)?; + + if values.len() != slice_length { + Err(vm.new_value_error("Can only assign sequence of same size".to_string())) + } else { + let mut cur = start as usize; + + for v in values { + set_array_value(&zelf, &mut buffer_bytes, cur, offset, v, vm)?; + cur += step as usize; + } + Ok(()) + } + } + } + } } diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index cb0e661113..f65e92d471 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -149,9 +149,12 @@ pub trait PyCDataMethods: PyValue { // PyCArrayType_Type // PyCFuncPtrType_Type - #[pyclassmethod] - fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) - -> PyResult; + #[pymethod] + fn from_param( + zelf: PyRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult; #[pyclassmethod] fn from_address( diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index 9b10ca55d0..b798feb4ba 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -22,7 +22,7 @@ impl PyValue for PyCPointer { } // impl PyCDataMethods for PyCPointer { -// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { +// fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { // } // } diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 933349e04e..064b85b6db 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -262,7 +262,7 @@ fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult, __abstract__: bool, } @@ -289,75 +289,20 @@ impl PyValue for PySimpleType { } } -// impl PyCDataMethods for PySimpleType { -// fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - -// } -// } - -#[pyimpl(flags(BASETYPE))] -impl PySimpleType { - #[pyslot] - fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { - new_simple_type(&cls, vm)?.into_ref_with_type(vm, cls) - } - - #[pymethod(magic)] - pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { - match value.into_option() { - Some(ref v) => { - let content = set_primitive(self._type_.as_str(), v, vm)?; - self.value.store(content); - } - _ => { - self.value.store(match self._type_.as_str() { - "c" | "u" => vm.ctx.new_bytes(vec![0]), - "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), - "f" | "d" | "g" => vm.ctx.new_float(0.0), - "?" => vm.ctx.new_bool(false), - _ => vm.ctx.none(), // "z" | "Z" | "P" - }); - } - } - Ok(()) - } - - #[pyproperty(name = "value")] - pub fn value(&self) -> PyObjectRef { - unsafe { (*self.value.as_ptr()).clone() } - } - - #[pyproperty(name = "value", setter)] - fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let content = set_primitive(self._type_.as_str(), &value, vm)?; - self.value.store(content); - Ok(()) - } - - // From Simple_Type Simple_methods - #[pymethod(magic)] - pub fn ctypes_from_outparam(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - if let Some(base) = zelf.class().base.clone() { - if vm.bool_eq(&base.as_object(), PySimpleType::static_type().as_object())? { - return Ok(zelf.as_object().clone()); - } - } - Ok(zelf.value()) - } - +impl PyCDataMethods for PySimpleType { // From PyCSimpleType_Type PyCSimpleType_methods - #[pyclassmethod] - pub fn from_param( - cls: PyTypeRef, + fn from_param( + zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { + let cls = zelf.clone_class(); if cls.is(PySimpleType::static_type()) { Err(vm.new_type_error("abstract class".to_string())) } else if vm.isinstance(&value, &cls)? { Ok(value) } else { - match vm.get_attribute(cls.as_object().to_owned(), "_type_") { + match vm.get_attribute(zelf.as_object().clone(), "_type_") { Ok(tp_obj) if vm.isinstance(&tp_obj, &vm.ctx.types.str_type)? => { let _type_ = tp_obj.downcast_exact::(vm).unwrap(); let tp_str = _type_.as_ref(); @@ -409,6 +354,57 @@ impl PySimpleType { } } } +} + +#[pyimpl(flags(BASETYPE))] +impl PySimpleType { + #[pyslot] + fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { + new_simple_type(&cls, vm)?.into_ref_with_type(vm, cls) + } + + #[pymethod(magic)] + pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + match value.into_option() { + Some(ref v) => { + let content = set_primitive(self._type_.as_str(), v, vm)?; + self.value.store(content); + } + _ => { + self.value.store(match self._type_.as_str() { + "c" | "u" => vm.ctx.new_bytes(vec![0]), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), + "f" | "d" | "g" => vm.ctx.new_float(0.0), + "?" => vm.ctx.new_bool(false), + _ => vm.ctx.none(), // "z" | "Z" | "P" + }); + } + } + Ok(()) + } + + #[pyproperty(name = "value")] + pub fn value(&self) -> PyObjectRef { + unsafe { (*self.value.as_ptr()).clone() } + } + + #[pyproperty(name = "value", setter)] + fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let content = set_primitive(self._type_.as_str(), &value, vm)?; + self.value.store(content); + Ok(()) + } + + // From Simple_Type Simple_methods + #[pymethod(magic)] + pub fn ctypes_from_outparam(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + if let Some(base) = zelf.class().base.clone() { + if vm.bool_eq(&base.as_object(), PySimpleType::static_type().as_object())? { + return Ok(zelf.as_object().clone()); + } + } + Ok(zelf.value()) + } // Simple_repr #[pymethod(magic)] From 4d2c67895863161e9cb524679b207918ef1b73b0 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Thu, 31 Dec 2020 16:16:17 -0300 Subject: [PATCH 44/56] Adding some helper functions and initial tests --- Lib/ctypes/__init__.py | 181 +++++++- Lib/ctypes/_aix.py | 331 ++++++++++++++ Lib/ctypes/_endian.py | 0 Lib/ctypes/macholib/README.ctypes | 7 + Lib/ctypes/macholib/__init__.py | 9 + Lib/ctypes/macholib/dyld.py | 173 ++++++++ Lib/ctypes/macholib/dylib.py | 63 +++ Lib/ctypes/macholib/fetch_macholib | 2 + Lib/ctypes/macholib/fetch_macholib.bat | 1 + Lib/ctypes/macholib/framework.py | 65 +++ Lib/ctypes/tests/__init__.py | 16 + Lib/ctypes/tests/__main__.py | 5 + Lib/ctypes/tests/test_arrays.py | 239 ++++++++++ Lib/ctypes/tests/test_buffers.py | 73 ++++ Lib/ctypes/tests/test_bytes.py | 68 +++ Lib/ctypes/tests/test_funcptr.py | 133 ++++++ Lib/ctypes/tests/test_functions.py | 412 ++++++++++++++++++ Lib/ctypes/tests/test_loading.py | 182 ++++++++ Lib/ctypes/tests/test_strings.py | 232 ++++++++++ Lib/ctypes/tests/test_unicode.py | 56 +++ Lib/ctypes/util.py | 376 ++++++++++++++++ Lib/ctypes/wintypes.py | 202 +++++++++ .../snippets/ctypes_tests/test_array.py | 68 +++ .../snippets/ctypes_tests/test_buffers.py | 70 +++ .../snippets/ctypes_tests/test_loading.py | 8 + vm/src/stdlib/ctypes/array.rs | 89 ++-- vm/src/stdlib/ctypes/basics.rs | 105 ++++- vm/src/stdlib/ctypes/mod.rs | 21 +- vm/src/stdlib/ctypes/pointer.rs | 6 +- vm/src/stdlib/ctypes/primitive.rs | 26 +- vm/src/stdlib/ctypes/structure.rs | 25 ++ 31 files changed, 3182 insertions(+), 62 deletions(-) create mode 100644 Lib/ctypes/_aix.py create mode 100644 Lib/ctypes/_endian.py create mode 100644 Lib/ctypes/macholib/README.ctypes create mode 100644 Lib/ctypes/macholib/__init__.py create mode 100644 Lib/ctypes/macholib/dyld.py create mode 100644 Lib/ctypes/macholib/dylib.py create mode 100755 Lib/ctypes/macholib/fetch_macholib create mode 100644 Lib/ctypes/macholib/fetch_macholib.bat create mode 100644 Lib/ctypes/macholib/framework.py create mode 100644 Lib/ctypes/tests/__init__.py create mode 100644 Lib/ctypes/tests/__main__.py create mode 100644 Lib/ctypes/tests/test_arrays.py create mode 100644 Lib/ctypes/tests/test_buffers.py create mode 100644 Lib/ctypes/tests/test_bytes.py create mode 100644 Lib/ctypes/tests/test_funcptr.py create mode 100644 Lib/ctypes/tests/test_functions.py create mode 100644 Lib/ctypes/tests/test_loading.py create mode 100644 Lib/ctypes/tests/test_strings.py create mode 100644 Lib/ctypes/tests/test_unicode.py create mode 100644 Lib/ctypes/util.py create mode 100644 Lib/ctypes/wintypes.py create mode 100644 extra_tests/snippets/ctypes_tests/test_array.py create mode 100644 extra_tests/snippets/ctypes_tests/test_buffers.py create mode 100644 extra_tests/snippets/ctypes_tests/test_loading.py diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 0aa799e312..dc1dff704e 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -1,8 +1,40 @@ + +"""create and manipulate C data types in Python""" + import os as _os, sys as _sys +import types as _types + +__version__ = "1.1.0" +from _ctypes import Struct, Array +from _ctypes import _Pointer from _ctypes import CFuncPtr as _CFuncPtr from _ctypes import dlopen as _dlopen -from _ctypes import _SimpleCData + +from struct import calcsize as _calcsize + +def create_string_buffer(init, size=None): + """create_string_buffer(aBytes) -> character array + create_string_buffer(anInteger) -> character array + create_string_buffer(aBytes, anInteger) -> character array + """ + if isinstance(init, bytes): + if size is None: + size = len(init)+1 + _sys.audit("ctypes.create_string_buffer", init, 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 + buf = buftype() + return buf + raise TypeError(init) + +def c_buffer(init, size=None): + return create_string_buffer(init, size) _c_functype_cache = {} def CFUNCTYPE(restype, *argtypes, **kw): @@ -29,65 +61,190 @@ class CFunctionType(_CFuncPtr): _c_functype_cache[(restype, argtypes)] = CFunctionType return CFunctionType +if _os.name == "nt": + _win_functype_cache = {} + def WINFUNCTYPE(restype, *argtypes, **kw): + # docstring set later (very similar to CFUNCTYPE.__doc__) + if kw: + raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) + try: + return _win_functype_cache[(restype, argtypes)] + except KeyError: + class WinFunctionType(_CFuncPtr): + _argtypes_ = argtypes + _restype_ = restype + _win_functype_cache[(restype, argtypes)] = WinFunctionType + return WinFunctionType + if WINFUNCTYPE.__doc__: + WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE") + + +from _ctypes import sizeof, byref, addressof, alignment +from _ctypes import _SimpleCData + + +def _check_size(typ, typecode=None): + # Check if sizeof(ctypes_type) against struct.calcsize. This + # should protect somewhat against a misconfigured libffi. + from struct import calcsize + if typecode is None: + # Most _type_ codes are the same as used in struct + typecode = typ._type_ + actual, required = sizeof(typ), calcsize(typecode) + if actual != required: + raise SystemError("sizeof(%s) wrong: %d instead of %d" % \ + (typ, actual, required)) + class c_short(_SimpleCData): _type_ = "h" +_check_size(c_short) class c_ushort(_SimpleCData): _type_ = "H" class c_long(_SimpleCData): _type_ = "l" +_check_size(c_long) class c_ulong(_SimpleCData): _type_ = "L" +_check_size(c_ulong) -class c_int(_SimpleCData): - _type_ = "i" +if _calcsize("i") == _calcsize("l"): +# if int and long have the same size, make c_int an alias for c_long + c_int = c_long + c_uint = c_ulong +else: + class c_int(_SimpleCData): + _type_ = "i" + _check_size(c_int) -class c_uint(_SimpleCData): - _type_ = "I" + class c_uint(_SimpleCData): + _type_ = "I" + _check_size(c_uint) class c_float(_SimpleCData): _type_ = "f" +_check_size(c_float) class c_double(_SimpleCData): _type_ = "d" +_check_size(c_double) class c_longdouble(_SimpleCData): _type_ = "g" - -class c_longlong(_SimpleCData): - _type_ = "q" - -class c_ulonglong(_SimpleCData): - _type_ = "Q" +if sizeof(c_longdouble) == sizeof(c_double): + c_longdouble = c_double + +if _calcsize("l") == _calcsize("q"): + # if long and long long have the same size, make c_longlong an alias for c_long + c_longlong = c_long + c_ulonglong = c_ulong +else: + class c_longlong(_SimpleCData): + _type_ = "q" + _check_size(c_longlong) + + class c_ulonglong(_SimpleCData): + _type_ = "Q" + _check_size(c_ulonglong) class c_ubyte(_SimpleCData): _type_ = "B" +c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte +_check_size(c_ubyte) class c_byte(_SimpleCData): _type_ = "b" +c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte +_check_size(c_byte) class c_char(_SimpleCData): _type_ = "c" +c_char.__ctype_le__ = c_char.__ctype_be__ = c_char +_check_size(c_char) class c_char_p(_SimpleCData): _type_ = "z" + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) +_check_size(c_char_p, "P") + class c_void_p(_SimpleCData): _type_ = "P" - c_voidp = c_void_p # backwards compatibility (to a bug) +_check_size(c_void_p) class c_bool(_SimpleCData): _type_ = "?" +from _ctypes import POINTER, pointer, _pointer_type_cache + class c_wchar_p(_SimpleCData): _type_ = "Z" + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) class c_wchar(_SimpleCData): _type_ = "u" +def _reset_cache(): + _pointer_type_cache.clear() + _c_functype_cache.clear() + if _os.name == "nt": + _win_functype_cache.clear() + # _SimpleCData.c_wchar_p_from_param + POINTER(c_wchar).from_param = c_wchar_p.from_param + # _SimpleCData.c_char_p_from_param + POINTER(c_char).from_param = c_char_p.from_param + _pointer_type_cache[None] = c_void_p + +def create_unicode_buffer(init, size=None): + """create_unicode_buffer(aString) -> character array + create_unicode_buffer(anInteger) -> character array + create_unicode_buffer(aString, anInteger) -> character array + """ + if isinstance(init, str): + if size is None: + if sizeof(c_wchar) == 2: + # UTF-16 requires a surrogate pair (2 wchar_t) for non-BMP + # characters (outside [U+0000; U+FFFF] range). +1 for trailing + # NUL character. + size = sum(2 if ord(c) > 0xFFFF else 1 for c in init) + 1 + else: + # 32-bit wchar_t (1 wchar_t per Unicode character). +1 for + # trailing NUL character. + size = len(init) + 1 + _sys.audit("ctypes.create_unicode_buffer", init, size) + buftype = c_wchar * size + buf = buftype() + buf.value = init + return buf + elif isinstance(init, int): + _sys.audit("ctypes.create_unicode_buffer", None, init) + buftype = c_wchar * init + buf = buftype() + return buf + raise TypeError(init) + + +# XXX Deprecated +def SetPointerType(pointer, cls): + if _pointer_type_cache.get(cls, None) is not None: + raise RuntimeError("This type already exists in the cache") + if id(pointer) not in _pointer_type_cache: + raise RuntimeError("What's this???") + pointer.set_type(cls) + _pointer_type_cache[cls] = pointer + del _pointer_type_cache[id(pointer)] + +# XXX Deprecated +def ARRAY(typ, len): + return typ * len + +################################################################ + class CDLL(object): """An instance of this class represents a loaded dll/shared library, exporting functions using the standard C calling diff --git a/Lib/ctypes/_aix.py b/Lib/ctypes/_aix.py new file mode 100644 index 0000000000..190cac6507 --- /dev/null +++ b/Lib/ctypes/_aix.py @@ -0,0 +1,331 @@ +""" +Lib/ctypes.util.find_library() support for AIX +Similar approach as done for Darwin support by using separate files +but unlike Darwin - no extension such as ctypes.macholib.* + +dlopen() is an interface to AIX initAndLoad() - primary documentation at: +https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/dlopen.htm +https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/load.htm + +AIX supports two styles for dlopen(): svr4 (System V Release 4) which is common on posix +platforms, but also a BSD style - aka SVR3. + +From AIX 5.3 Difference Addendum (December 2004) +2.9 SVR4 linking affinity +Nowadays, there are two major object file formats used by the operating systems: +XCOFF: The COFF enhanced by IBM and others. The original COFF (Common +Object File Format) was the base of SVR3 and BSD 4.2 systems. +ELF: Executable and Linking Format that was developed by AT&T and is a +base for SVR4 UNIX. + +While the shared library content is identical on AIX - one is located as a filepath name +(svr4 style) and the other is located as a member of an archive (and the archive +is located as a filepath name). + +The key difference arises when supporting multiple abi formats (i.e., 32 and 64 bit). +For svr4 either only one ABI is supported, or there are two directories, or there +are different file names. The most common solution for multiple ABI is multiple +directories. + +For the XCOFF (aka AIX) style - one directory (one archive file) is sufficient +as multiple shared libraries can be in the archive - even sharing the same name. +In documentation the archive is also referred to as the "base" and the shared +library object is referred to as the "member". + +For dlopen() on AIX (read initAndLoad()) the calls are similar. +Default activity occurs when no path information is provided. When path +information is provided dlopen() does not search any other directories. + +For SVR4 - the shared library name is the name of the file expected: libFOO.so +For AIX - the shared library is expressed as base(member). The search is for the +base (e.g., libFOO.a) and once the base is found the shared library - identified by +member (e.g., libFOO.so, or shr.o) is located and loaded. + +The mode bit RTLD_MEMBER tells initAndLoad() that it needs to use the AIX (SVR3) +naming style. +""" +__author__ = "Michael Felt " + +import re +from os import environ, path +from sys import executable +from ctypes import c_void_p, sizeof +from subprocess import Popen, PIPE, DEVNULL + +# Executable bit size - 32 or 64 +# Used to filter the search in an archive by size, e.g., -X64 +AIX_ABI = sizeof(c_void_p) * 8 + + +from sys import maxsize +def _last_version(libnames, sep): + def _num_version(libname): + # "libxyz.so.MAJOR.MINOR" => [MAJOR, MINOR] + parts = libname.split(sep) + nums = [] + try: + while parts: + nums.insert(0, int(parts.pop())) + except ValueError: + pass + return nums or [maxsize] + return max(reversed(libnames), key=_num_version) + +def get_ld_header(p): + # "nested-function, but placed at module level + ld_header = None + for line in p.stdout: + if line.startswith(('/', './', '../')): + ld_header = line + elif "INDEX" in line: + return ld_header.rstrip('\n') + return None + +def get_ld_header_info(p): + # "nested-function, but placed at module level + # as an ld_header was found, return known paths, archives and members + # these lines start with a digit + info = [] + for line in p.stdout: + if re.match("[0-9]", line): + info.append(line) + else: + # blank line (separator), consume line and end for loop + break + return info + +def get_ld_headers(file): + """ + Parse the header of the loader section of executable and archives + This function calls /usr/bin/dump -H as a subprocess + and returns a list of (ld_header, ld_header_info) tuples. + """ + # get_ld_headers parsing: + # 1. Find a line that starts with /, ./, or ../ - set as ld_header + # 2. If "INDEX" in occurs in a following line - return ld_header + # 3. get info (lines starting with [0-9]) + ldr_headers = [] + p = Popen(["/usr/bin/dump", f"-X{AIX_ABI}", "-H", file], + universal_newlines=True, stdout=PIPE, stderr=DEVNULL) + # be sure to read to the end-of-file - getting all entries + while True: + ld_header = get_ld_header(p) + if ld_header: + ldr_headers.append((ld_header, get_ld_header_info(p))) + else: + break + p.stdout.close() + p.wait() + return ldr_headers + +def get_shared(ld_headers): + """ + extract the shareable objects from ld_headers + character "[" is used to strip off the path information. + Note: the "[" and "]" characters that are part of dump -H output + are not removed here. + """ + shared = [] + for (line, _) in ld_headers: + # potential member lines contain "[" + # otherwise, no processing needed + if "[" in line: + # Strip off trailing colon (:) + shared.append(line[line.index("["):-1]) + return shared + +def get_one_match(expr, lines): + """ + Must be only one match, otherwise result is None. + When there is a match, strip leading "[" and trailing "]" + """ + # member names in the ld_headers output are between square brackets + expr = rf'\[({expr})\]' + matches = list(filter(None, (re.search(expr, line) for line in lines))) + if len(matches) == 1: + return matches[0].group(1) + else: + return None + +# additional processing to deal with AIX legacy names for 64-bit members +def get_legacy(members): + """ + This routine provides historical aka legacy naming schemes started + in AIX4 shared library support for library members names. + e.g., in /usr/lib/libc.a the member name shr.o for 32-bit binary and + shr_64.o for 64-bit binary. + """ + if AIX_ABI == 64: + # AIX 64-bit member is one of shr64.o, shr_64.o, or shr4_64.o + expr = r'shr4?_?64\.o' + member = get_one_match(expr, members) + if member: + return member + else: + # 32-bit legacy names - both shr.o and shr4.o exist. + # shr.o is the preffered name so we look for shr.o first + # i.e., shr4.o is returned only when shr.o does not exist + for name in ['shr.o', 'shr4.o']: + member = get_one_match(re.escape(name), members) + if member: + return member + return None + +def get_version(name, members): + """ + Sort list of members and return highest numbered version - if it exists. + This function is called when an unversioned libFOO.a(libFOO.so) has + not been found. + + Versioning for the member name is expected to follow + GNU LIBTOOL conventions: the highest version (x, then X.y, then X.Y.z) + * find [libFoo.so.X] + * find [libFoo.so.X.Y] + * find [libFoo.so.X.Y.Z] + + Before the GNU convention became the standard scheme regardless of + binary size AIX packagers used GNU convention "as-is" for 32-bit + archive members but used an "distinguishing" name for 64-bit members. + This scheme inserted either 64 or _64 between libFOO and .so + - generally libFOO_64.so, but occasionally libFOO64.so + """ + # the expression ending for versions must start as + # '.so.[0-9]', i.e., *.so.[at least one digit] + # while multiple, more specific expressions could be specified + # to search for .so.X, .so.X.Y and .so.X.Y.Z + # after the first required 'dot' digit + # any combination of additional 'dot' digits pairs are accepted + # anything more than libFOO.so.digits.digits.digits + # should be seen as a member name outside normal expectations + exprs = [rf'lib{name}\.so\.[0-9]+[0-9.]*', + rf'lib{name}_?64\.so\.[0-9]+[0-9.]*'] + for expr in exprs: + versions = [] + for line in members: + m = re.search(expr, line) + if m: + versions.append(m.group(0)) + if versions: + return _last_version(versions, '.') + return None + +def get_member(name, members): + """ + Return an archive member matching the request in name. + Name is the library name without any prefix like lib, suffix like .so, + or version number. + Given a list of members find and return the most appropriate result + Priority is given to generic libXXX.so, then a versioned libXXX.so.a.b.c + and finally, legacy AIX naming scheme. + """ + # look first for a generic match - prepend lib and append .so + expr = rf'lib{name}\.so' + member = get_one_match(expr, members) + if member: + return member + elif AIX_ABI == 64: + expr = rf'lib{name}64\.so' + member = get_one_match(expr, members) + if member: + return member + # since an exact match with .so as suffix was not found + # look for a versioned name + # If a versioned name is not found, look for AIX legacy member name + member = get_version(name, members) + if member: + return member + else: + return get_legacy(members) + +def get_libpaths(): + """ + On AIX, the buildtime searchpath is stored in the executable. + as "loader header information". + The command /usr/bin/dump -H extracts this info. + Prefix searched libraries with LD_LIBRARY_PATH (preferred), + or LIBPATH if defined. These paths are appended to the paths + to libraries the python executable is linked with. + This mimics AIX dlopen() behavior. + """ + libpaths = environ.get("LD_LIBRARY_PATH") + if libpaths is None: + libpaths = environ.get("LIBPATH") + if libpaths is None: + libpaths = [] + else: + libpaths = libpaths.split(":") + objects = get_ld_headers(executable) + for (_, lines) in objects: + for line in lines: + # the second (optional) argument is PATH if it includes a / + path = line.split()[1] + if "/" in path: + libpaths.extend(path.split(":")) + return libpaths + +def find_shared(paths, name): + """ + paths is a list of directories to search for an archive. + name is the abbreviated name given to find_library(). + Process: search "paths" for archive, and if an archive is found + return the result of get_member(). + If an archive is not found then return None + """ + for dir in paths: + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + # "lib" is prefixed to emulate compiler name resolution, + # e.g., -lc to libc + base = f'lib{name}.a' + archive = path.join(dir, base) + if path.exists(archive): + members = get_shared(get_ld_headers(archive)) + member = get_member(re.escape(name), members) + if member != None: + return (base, member) + else: + return (None, None) + return (None, None) + +def find_library(name): + """AIX implementation of ctypes.util.find_library() + Find an archive member that will dlopen(). If not available, + also search for a file (or link) with a .so suffix. + + AIX supports two types of schemes that can be used with dlopen(). + The so-called SystemV Release4 (svr4) format is commonly suffixed + with .so while the (default) AIX scheme has the library (archive) + ending with the suffix .a + As an archive has multiple members (e.g., 32-bit and 64-bit) in one file + the argument passed to dlopen must include both the library and + the member names in a single string. + + find_library() looks first for an archive (.a) with a suitable member. + If no archive+member pair is found, look for a .so file. + """ + + libpaths = get_libpaths() + (base, member) = find_shared(libpaths, name) + if base != None: + return f"{base}({member})" + + # To get here, a member in an archive has not been found + # In other words, either: + # a) a .a file was not found + # b) a .a file did not have a suitable member + # So, look for a .so file + # Check libpaths for .so file + # Note, the installation must prepare a link from a .so + # to a versioned file + # This is common practice by GNU libtool on other platforms + soname = f"lib{name}.so" + for dir in libpaths: + # /lib is a symbolic link to /usr/lib, skip it + if dir == "/lib": + continue + shlib = path.join(dir, soname) + if path.exists(shlib): + return soname + # if we are here, we have not found anything plausible + return None diff --git a/Lib/ctypes/_endian.py b/Lib/ctypes/_endian.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Lib/ctypes/macholib/README.ctypes b/Lib/ctypes/macholib/README.ctypes new file mode 100644 index 0000000000..2866e9f349 --- /dev/null +++ b/Lib/ctypes/macholib/README.ctypes @@ -0,0 +1,7 @@ +Files in this directory come from Bob Ippolito's py2app. + +License: Any components of the py2app suite may be distributed under +the MIT or PSF open source licenses. + +This is version 1.0, SVN revision 789, from 2006/01/25. +The main repository is http://svn.red-bean.com/bob/macholib/trunk/macholib/ \ No newline at end of file diff --git a/Lib/ctypes/macholib/__init__.py b/Lib/ctypes/macholib/__init__.py new file mode 100644 index 0000000000..5621defccd --- /dev/null +++ b/Lib/ctypes/macholib/__init__.py @@ -0,0 +1,9 @@ +""" +Enough Mach-O to make your head spin. + +See the relevant header files in /usr/include/mach-o + +And also Apple's documentation. +""" + +__version__ = '1.0' diff --git a/Lib/ctypes/macholib/dyld.py b/Lib/ctypes/macholib/dyld.py new file mode 100644 index 0000000000..1c3f8fd38b --- /dev/null +++ b/Lib/ctypes/macholib/dyld.py @@ -0,0 +1,173 @@ +""" +dyld emulation +""" + +import os +from ctypes.macholib.framework import framework_info +from ctypes.macholib.dylib import dylib_info +from itertools import * +try: + from _ctypes import _dyld_shared_cache_contains_path +except ImportError: + def _dyld_shared_cache_contains_path(*args): + raise NotImplementedError + +__all__ = [ + 'dyld_find', 'framework_find', + 'framework_info', 'dylib_info', +] + +# These are the defaults as per man dyld(1) +# +DEFAULT_FRAMEWORK_FALLBACK = [ + os.path.expanduser("~/Library/Frameworks"), + "/Library/Frameworks", + "/Network/Library/Frameworks", + "/System/Library/Frameworks", +] + +DEFAULT_LIBRARY_FALLBACK = [ + os.path.expanduser("~/lib"), + "/usr/local/lib", + "/lib", + "/usr/lib", +] + +def dyld_env(env, var): + if env is None: + env = os.environ + rval = env.get(var) + if rval is None: + return [] + return rval.split(':') + +def dyld_image_suffix(env=None): + if env is None: + env = os.environ + return env.get('DYLD_IMAGE_SUFFIX') + +def dyld_framework_path(env=None): + return dyld_env(env, 'DYLD_FRAMEWORK_PATH') + +def dyld_library_path(env=None): + return dyld_env(env, 'DYLD_LIBRARY_PATH') + +def dyld_fallback_framework_path(env=None): + return dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH') + +def dyld_fallback_library_path(env=None): + return dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH') + +def dyld_image_suffix_search(iterator, env=None): + """For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics""" + suffix = dyld_image_suffix(env) + if suffix is None: + return iterator + def _inject(iterator=iterator, suffix=suffix): + for path in iterator: + if path.endswith('.dylib'): + yield path[:-len('.dylib')] + suffix + '.dylib' + else: + yield path + suffix + yield path + return _inject() + +def dyld_override_search(name, env=None): + # If DYLD_FRAMEWORK_PATH is set and this dylib_name is a + # framework name, use the first file that exists in the framework + # path if any. If there is none go on to search the DYLD_LIBRARY_PATH + # if any. + + framework = framework_info(name) + + if framework is not None: + for path in dyld_framework_path(env): + yield os.path.join(path, framework['name']) + + # If DYLD_LIBRARY_PATH is set then use the first file that exists + # in the path. If none use the original name. + for path in dyld_library_path(env): + yield os.path.join(path, os.path.basename(name)) + +def dyld_executable_path_search(name, executable_path=None): + # If we haven't done any searching and found a library and the + # dylib_name starts with "@executable_path/" then construct the + # library name. + if name.startswith('@executable_path/') and executable_path is not None: + yield os.path.join(executable_path, name[len('@executable_path/'):]) + +def dyld_default_search(name, env=None): + yield name + + framework = framework_info(name) + + if framework is not None: + fallback_framework_path = dyld_fallback_framework_path(env) + for path in fallback_framework_path: + yield os.path.join(path, framework['name']) + + fallback_library_path = dyld_fallback_library_path(env) + for path in fallback_library_path: + yield os.path.join(path, os.path.basename(name)) + + if framework is not None and not fallback_framework_path: + for path in DEFAULT_FRAMEWORK_FALLBACK: + yield os.path.join(path, framework['name']) + + if not fallback_library_path: + for path in DEFAULT_LIBRARY_FALLBACK: + yield os.path.join(path, os.path.basename(name)) + +def dyld_find(name, executable_path=None, env=None): + """ + Find a library or framework using dyld semantics + """ + for path in dyld_image_suffix_search(chain( + dyld_override_search(name, env), + dyld_executable_path_search(name, executable_path), + dyld_default_search(name, env), + ), env): + + if os.path.isfile(path): + return path + try: + if _dyld_shared_cache_contains_path(path): + return path + except NotImplementedError: + pass + + raise ValueError("dylib %s could not be found" % (name,)) + +def framework_find(fn, executable_path=None, env=None): + """ + Find a framework using dyld semantics in a very loose manner. + + Will take input such as: + Python + Python.framework + Python.framework/Versions/Current + """ + error = None + try: + return dyld_find(fn, executable_path=executable_path, env=env) + except ValueError as e: + error = e + fmwk_index = fn.rfind('.framework') + if fmwk_index == -1: + fmwk_index = len(fn) + fn += '.framework' + fn = os.path.join(fn, os.path.basename(fn[:fmwk_index])) + try: + return dyld_find(fn, executable_path=executable_path, env=env) + except ValueError: + raise error + finally: + error = None + +def test_dyld_find(): + env = {} + assert dyld_find('libSystem.dylib') == '/usr/lib/libSystem.dylib' + assert dyld_find('System.framework/System') == '/System/Library/Frameworks/System.framework/System' + +if __name__ == '__main__': + test_dyld_find() diff --git a/Lib/ctypes/macholib/dylib.py b/Lib/ctypes/macholib/dylib.py new file mode 100644 index 0000000000..aa107507bd --- /dev/null +++ b/Lib/ctypes/macholib/dylib.py @@ -0,0 +1,63 @@ +""" +Generic dylib path manipulation +""" + +import re + +__all__ = ['dylib_info'] + +DYLIB_RE = re.compile(r"""(?x) +(?P^.*)(?:^|/) +(?P + (?P\w+?) + (?:\.(?P[^._]+))? + (?:_(?P[^._]+))? + \.dylib$ +) +""") + +def dylib_info(filename): + """ + A dylib name can take one of the following four forms: + Location/Name.SomeVersion_Suffix.dylib + Location/Name.SomeVersion.dylib + Location/Name_Suffix.dylib + Location/Name.dylib + + returns None if not found or a mapping equivalent to: + dict( + location='Location', + name='Name.SomeVersion_Suffix.dylib', + shortname='Name', + version='SomeVersion', + suffix='Suffix', + ) + + Note that SomeVersion and Suffix are optional and may be None + if not present. + """ + is_dylib = DYLIB_RE.match(filename) + if not is_dylib: + return None + return is_dylib.groupdict() + + +def test_dylib_info(): + def d(location=None, name=None, shortname=None, version=None, suffix=None): + return dict( + location=location, + name=name, + shortname=shortname, + version=version, + suffix=suffix + ) + assert dylib_info('completely/invalid') is None + assert dylib_info('completely/invalide_debug') is None + assert dylib_info('P/Foo.dylib') == d('P', 'Foo.dylib', 'Foo') + assert dylib_info('P/Foo_debug.dylib') == d('P', 'Foo_debug.dylib', 'Foo', suffix='debug') + assert dylib_info('P/Foo.A.dylib') == d('P', 'Foo.A.dylib', 'Foo', 'A') + assert dylib_info('P/Foo_debug.A.dylib') == d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A') + assert dylib_info('P/Foo.A_debug.dylib') == d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug') + +if __name__ == '__main__': + test_dylib_info() diff --git a/Lib/ctypes/macholib/fetch_macholib b/Lib/ctypes/macholib/fetch_macholib new file mode 100755 index 0000000000..e6d6a22659 --- /dev/null +++ b/Lib/ctypes/macholib/fetch_macholib @@ -0,0 +1,2 @@ +#!/bin/sh +svn export --force http://svn.red-bean.com/bob/macholib/trunk/macholib/ . diff --git a/Lib/ctypes/macholib/fetch_macholib.bat b/Lib/ctypes/macholib/fetch_macholib.bat new file mode 100644 index 0000000000..f9e1c0dc96 --- /dev/null +++ b/Lib/ctypes/macholib/fetch_macholib.bat @@ -0,0 +1 @@ +svn export --force http://svn.red-bean.com/bob/macholib/trunk/macholib/ . diff --git a/Lib/ctypes/macholib/framework.py b/Lib/ctypes/macholib/framework.py new file mode 100644 index 0000000000..ad6ed554ba --- /dev/null +++ b/Lib/ctypes/macholib/framework.py @@ -0,0 +1,65 @@ +""" +Generic framework path manipulation +""" + +import re + +__all__ = ['framework_info'] + +STRICT_FRAMEWORK_RE = re.compile(r"""(?x) +(?P^.*)(?:^|/) +(?P + (?P\w+).framework/ + (?:Versions/(?P[^/]+)/)? + (?P=shortname) + (?:_(?P[^_]+))? +)$ +""") + +def framework_info(filename): + """ + A framework name can take one of the following four forms: + Location/Name.framework/Versions/SomeVersion/Name_Suffix + Location/Name.framework/Versions/SomeVersion/Name + Location/Name.framework/Name_Suffix + Location/Name.framework/Name + + returns None if not found, or a mapping equivalent to: + dict( + location='Location', + name='Name.framework/Versions/SomeVersion/Name_Suffix', + shortname='Name', + version='SomeVersion', + suffix='Suffix', + ) + + Note that SomeVersion and Suffix are optional and may be None + if not present + """ + is_framework = STRICT_FRAMEWORK_RE.match(filename) + if not is_framework: + return None + return is_framework.groupdict() + +def test_framework_info(): + def d(location=None, name=None, shortname=None, version=None, suffix=None): + return dict( + location=location, + name=name, + shortname=shortname, + version=version, + suffix=suffix + ) + assert framework_info('completely/invalid') is None + assert framework_info('completely/invalid/_debug') is None + assert framework_info('P/F.framework') is None + assert framework_info('P/F.framework/_debug') is None + assert framework_info('P/F.framework/F') == d('P', 'F.framework/F', 'F') + assert framework_info('P/F.framework/F_debug') == d('P', 'F.framework/F_debug', 'F', suffix='debug') + assert framework_info('P/F.framework/Versions') is None + assert framework_info('P/F.framework/Versions/A') is None + assert framework_info('P/F.framework/Versions/A/F') == d('P', 'F.framework/Versions/A/F', 'F', 'A') + assert framework_info('P/F.framework/Versions/A/F_debug') == d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug') + +if __name__ == '__main__': + test_framework_info() diff --git a/Lib/ctypes/tests/__init__.py b/Lib/ctypes/tests/__init__.py new file mode 100644 index 0000000000..659a392b7e --- /dev/null +++ b/Lib/ctypes/tests/__init__.py @@ -0,0 +1,16 @@ +import os +import unittest +from test import support +from test.support import import_module + + +# skip tests if _ctypes was not built +ctypes = import_module('ctypes') +ctypes_symbols = dir(ctypes) + +def need_symbol(name): + return unittest.skipUnless(name in ctypes_symbols, + '{!r} is required'.format(name)) + +def load_tests(*args): + return support.load_package_tests(os.path.dirname(__file__), *args) \ No newline at end of file diff --git a/Lib/ctypes/tests/__main__.py b/Lib/ctypes/tests/__main__.py new file mode 100644 index 0000000000..b59e271e2a --- /dev/null +++ b/Lib/ctypes/tests/__main__.py @@ -0,0 +1,5 @@ + +from ctypes.test import load_tests +import unittest + +unittest.main() \ No newline at end of file diff --git a/Lib/ctypes/tests/test_arrays.py b/Lib/ctypes/tests/test_arrays.py new file mode 100644 index 0000000000..d45ebcb3c6 --- /dev/null +++ b/Lib/ctypes/tests/test_arrays.py @@ -0,0 +1,239 @@ +import unittest +from test.support import bigmemtest, _2G +import sys +from ctypes import * + +from ctypes.test import need_symbol + +formats = "bBhHiIlLqQfd" + +formats = c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, \ + c_long, c_ulonglong, c_float, c_double, c_longdouble + +class ArrayTestCase(unittest.TestCase): + def test_simple(self): + # create classes holding simple numeric types, and check + # various properties. + + init = list(range(15, 25)) + + for fmt in formats: + alen = len(init) + int_array = ARRAY(fmt, alen) + + ia = int_array(*init) + # length of instance ok? + self.assertEqual(len(ia), alen) + + # slot values ok? + values = [ia[i] for i in range(alen)] + self.assertEqual(values, init) + + # out-of-bounds accesses should be caught + with self.assertRaises(IndexError): ia[alen] + with self.assertRaises(IndexError): ia[-alen-1] + + # change the items + from operator import setitem + new_values = list(range(42, 42+alen)) + [setitem(ia, n, new_values[n]) for n in range(alen)] + values = [ia[i] for i in range(alen)] + self.assertEqual(values, new_values) + + # are the items initialized to 0? + ia = int_array() + values = [ia[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + # Too many initializers should be caught + self.assertRaises(IndexError, int_array, *range(alen*2)) + + CharArray = ARRAY(c_char, 3) + + ca = CharArray(b"a", b"b", b"c") + + # Should this work? It doesn't: + # CharArray("abc") + self.assertRaises(TypeError, CharArray, "abc") + + self.assertEqual(ca[0], b"a") + self.assertEqual(ca[1], b"b") + self.assertEqual(ca[2], b"c") + self.assertEqual(ca[-3], b"a") + self.assertEqual(ca[-2], b"b") + self.assertEqual(ca[-1], b"c") + + self.assertEqual(len(ca), 3) + + # cannot delete items + from operator import delitem + self.assertRaises(TypeError, delitem, ca, 0) + + def test_step_overflow(self): + a = (c_int * 5)() + a[3::sys.maxsize] = (1,) + self.assertListEqual(a[3::sys.maxsize], [1]) + a = (c_char * 5)() + a[3::sys.maxsize] = b"A" + self.assertEqual(a[3::sys.maxsize], b"A") + a = (c_wchar * 5)() + a[3::sys.maxsize] = u"X" + self.assertEqual(a[3::sys.maxsize], u"X") + + def test_numeric_arrays(self): + + alen = 5 + + numarray = ARRAY(c_int, alen) + + na = numarray() + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) + + na = numarray(*[c_int()] * alen) + values = [na[i] for i in range(alen)] + self.assertEqual(values, [0]*alen) + + na = numarray(1, 2, 3, 4, 5) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + na = numarray(*map(c_int, (1, 2, 3, 4, 5))) + values = [i for i in na] + self.assertEqual(values, [1, 2, 3, 4, 5]) + + def test_classcache(self): + self.assertIsNot(ARRAY(c_int, 3), ARRAY(c_int, 4)) + self.assertIs(ARRAY(c_int, 3), ARRAY(c_int, 3)) + + def test_from_address(self): + # Failed with 0.9.8, reported by JUrner + p = create_string_buffer(b"foo") + sz = (c_char * 3).from_address(addressof(p)) + self.assertEqual(sz[:], b"foo") + self.assertEqual(sz[::], b"foo") + self.assertEqual(sz[::-1], b"oof") + self.assertEqual(sz[::3], b"f") + self.assertEqual(sz[1:4:2], b"o") + self.assertEqual(sz.value, b"foo") + + @need_symbol('create_unicode_buffer') + def test_from_addressW(self): + p = create_unicode_buffer("foo") + sz = (c_wchar * 3).from_address(addressof(p)) + self.assertEqual(sz[:], "foo") + self.assertEqual(sz[::], "foo") + self.assertEqual(sz[::-1], "oof") + self.assertEqual(sz[::3], "f") + self.assertEqual(sz[1:4:2], "o") + self.assertEqual(sz.value, "foo") + + def test_cache(self): + # Array types are cached internally in the _ctypes extension, + # in a WeakValueDictionary. Make sure the array type is + # removed from the cache when the itemtype goes away. This + # test will not fail, but will show a leak in the testsuite. + + # Create a new type: + class my_int(c_int): + pass + # Create a new array type based on it: + t1 = my_int * 1 + t2 = my_int * 1 + self.assertIs(t1, t2) + + def test_subclass(self): + class T(Array): + _type_ = c_int + _length_ = 13 + class U(T): + pass + class V(U): + pass + class W(V): + pass + class X(T): + _type_ = c_short + class Y(T): + _length_ = 187 + + for c in [T, U, V, W]: + self.assertEqual(c._type_, c_int) + self.assertEqual(c._length_, 13) + self.assertEqual(c()._type_, c_int) + self.assertEqual(c()._length_, 13) + + self.assertEqual(X._type_, c_short) + self.assertEqual(X._length_, 13) + self.assertEqual(X()._type_, c_short) + self.assertEqual(X()._length_, 13) + + self.assertEqual(Y._type_, c_int) + self.assertEqual(Y._length_, 187) + self.assertEqual(Y()._type_, c_int) + self.assertEqual(Y()._length_, 187) + + def test_bad_subclass(self): + with self.assertRaises(AttributeError): + class T(Array): + pass + with self.assertRaises(AttributeError): + class T(Array): + _type_ = c_int + with self.assertRaises(AttributeError): + class T(Array): + _length_ = 13 + + def test_bad_length(self): + with self.assertRaises(ValueError): + class T(Array): + _type_ = c_int + _length_ = - sys.maxsize * 2 + with self.assertRaises(ValueError): + class T(Array): + _type_ = c_int + _length_ = -1 + with self.assertRaises(TypeError): + class T(Array): + _type_ = c_int + _length_ = 1.87 + with self.assertRaises(OverflowError): + class T(Array): + _type_ = c_int + _length_ = sys.maxsize * 2 + + def test_zero_length(self): + # _length_ can be zero. + class T(Array): + _type_ = c_int + _length_ = 0 + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_empty_element_struct(self): + class EmptyStruct(Structure): + _fields_ = [] + + obj = (EmptyStruct * 2)() # bpo37188: Floating point exception + self.assertEqual(sizeof(obj), 0) + + def test_empty_element_array(self): + class EmptyArray(Array): + _type_ = c_int + _length_ = 0 + + obj = (EmptyArray * 2)() # bpo37188: Floating point exception + self.assertEqual(sizeof(obj), 0) + + def test_bpo36504_signed_int_overflow(self): + # The overflow check in PyCArrayType_new() could cause signed integer + # overflow. + with self.assertRaises(OverflowError): + c_char * sys.maxsize * 2 + + @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') + @bigmemtest(size=_2G, memuse=1, dry_run=False) + def test_large_array(self, size): + c_char * size + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/ctypes/tests/test_buffers.py b/Lib/ctypes/tests/test_buffers.py new file mode 100644 index 0000000000..15782be757 --- /dev/null +++ b/Lib/ctypes/tests/test_buffers.py @@ -0,0 +1,73 @@ +from ctypes import * +from ctypes.test import need_symbol +import unittest + +class StringBufferTestCase(unittest.TestCase): + + def test_buffer(self): + b = create_string_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + + b = create_string_buffer(b"abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + self.assertEqual(b[0], b"a") + self.assertEqual(b[:], b"abc\0") + self.assertEqual(b[::], b"abc\0") + self.assertEqual(b[::-1], b"\0cba") + self.assertEqual(b[::2], b"ac") + self.assertEqual(b[::5], b"a") + + self.assertRaises(TypeError, create_string_buffer, "abc") + + def test_buffer_interface(self): + self.assertEqual(len(bytearray(create_string_buffer(0))), 0) + self.assertEqual(len(bytearray(create_string_buffer(1))), 1) + + @need_symbol('c_wchar') + def test_unicode_buffer(self): + b = create_unicode_buffer(32) + self.assertEqual(len(b), 32) + self.assertEqual(sizeof(b), 32 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + self.assertRaises(TypeError, create_unicode_buffer, b"abc") + + @need_symbol('c_wchar') + def test_unicode_conversion(self): + b = create_unicode_buffer("abc") + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_wchar)) + self.assertIs(type(b[0]), str) + self.assertEqual(b[0], "a") + self.assertEqual(b[:], "abc\0") + self.assertEqual(b[::], "abc\0") + self.assertEqual(b[::-1], "\0cba") + self.assertEqual(b[::2], "ac") + self.assertEqual(b[::5], "a") + + @need_symbol('c_wchar') + def test_create_unicode_buffer_non_bmp(self): + expected = 5 if sizeof(c_wchar) == 2 else 3 + for s in '\U00010000\U00100000', '\U00010000\U0010ffff': + b = create_unicode_buffer(s) + self.assertEqual(len(b), expected) + self.assertEqual(b[-1], '\0') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/ctypes/tests/test_bytes.py b/Lib/ctypes/tests/test_bytes.py new file mode 100644 index 0000000000..c3e5f99fb7 --- /dev/null +++ b/Lib/ctypes/tests/test_bytes.py @@ -0,0 +1,68 @@ +"""Test where byte objects are accepted""" +import unittest +import sys +from ctypes import * + +class BytesTest(unittest.TestCase): + def test_c_char(self): + x = c_char(b"x") + self.assertRaises(TypeError, c_char, "x") + x.value = b"y" + with self.assertRaises(TypeError): + x.value = "y" + c_char.from_param(b"x") + self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) + (c_char * 3)(b"a", b"b", b"c") + self.assertRaises(TypeError, c_char * 3, "a", "b", "c") + + def test_c_wchar(self): + x = c_wchar("x") + self.assertRaises(TypeError, c_wchar, b"x") + x.value = "y" + with self.assertRaises(TypeError): + x.value = b"y" + c_wchar.from_param("x") + self.assertRaises(TypeError, c_wchar.from_param, b"x") + (c_wchar * 3)("a", "b", "c") + self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") + + def test_c_char_p(self): + c_char_p(b"foo bar") + self.assertRaises(TypeError, c_char_p, "foo bar") + + def test_c_wchar_p(self): + c_wchar_p("foo bar") + self.assertRaises(TypeError, c_wchar_p, b"foo bar") + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_struct(self): + class X(Structure): + _fields_ = [("a", c_char * 3)] + + x = X(b"abc") + self.assertRaises(TypeError, X, "abc") + self.assertEqual(x.a, b"abc") + self.assertEqual(type(x.a), bytes) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_struct_W(self): + class X(Structure): + _fields_ = [("a", c_wchar * 3)] + + x = X("abc") + self.assertRaises(TypeError, X, b"abc") + self.assertEqual(x.a, "abc") + self.assertEqual(type(x.a), str) + + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_BSTR(self): + from _ctypes import _SimpleCData + class BSTR(_SimpleCData): + _type_ = "X" + + BSTR("abc") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/ctypes/tests/test_funcptr.py b/Lib/ctypes/tests/test_funcptr.py new file mode 100644 index 0000000000..1d94e51e00 --- /dev/null +++ b/Lib/ctypes/tests/test_funcptr.py @@ -0,0 +1,133 @@ +import unittest +from ctypes import * + +try: + WINFUNCTYPE +except NameError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +import _ctypes_test +lib = CDLL(_ctypes_test.__file__) + +class CFuncPtrTestCase(unittest.TestCase): + def test_basic(self): + X = WINFUNCTYPE(c_int, c_int, c_int) + + def func(*args): + return len(args) + + x = X(func) + self.assertEqual(x.restype, c_int) + self.assertEqual(x.argtypes, (c_int, c_int)) + self.assertEqual(sizeof(x), sizeof(c_voidp)) + self.assertEqual(sizeof(X), sizeof(c_voidp)) + + def test_first(self): + StdCallback = WINFUNCTYPE(c_int, c_int, c_int) + CdeclCallback = CFUNCTYPE(c_int, c_int, c_int) + + def func(a, b): + return a + b + + s = StdCallback(func) + c = CdeclCallback(func) + + self.assertEqual(s(1, 2), 3) + self.assertEqual(c(1, 2), 3) + # The following no longer raises a TypeError - it is now + # possible, as in C, to call cdecl functions with more parameters. + #self.assertRaises(TypeError, c, 1, 2, 3) + self.assertEqual(c(1, 2, 3, 4, 5, 6), 3) + if not WINFUNCTYPE is CFUNCTYPE: + self.assertRaises(TypeError, s, 1, 2, 3) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_structures(self): + WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + def wndproc(hwnd, msg, wParam, lParam): + return hwnd + msg + wParam + lParam + + HINSTANCE = c_int + HICON = c_int + HCURSOR = c_int + LPCTSTR = c_char_p + + class WNDCLASS(Structure): + _fields_ = [("style", c_uint), + ("lpfnWndProc", WNDPROC), + ("cbClsExtra", c_int), + ("cbWndExtra", c_int), + ("hInstance", HINSTANCE), + ("hIcon", HICON), + ("hCursor", HCURSOR), + ("lpszMenuName", LPCTSTR), + ("lpszClassName", LPCTSTR)] + + wndclass = WNDCLASS() + wndclass.lpfnWndProc = WNDPROC(wndproc) + + WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) + + # This is no longer true, now that WINFUNCTYPE caches created types internally. + ## # CFuncPtr subclasses are compared by identity, so this raises a TypeError: + ## self.assertRaises(TypeError, setattr, wndclass, + ## "lpfnWndProc", WNDPROC_2(wndproc)) + # instead: + + self.assertIs(WNDPROC, WNDPROC_2) + # 'wndclass.lpfnWndProc' leaks 94 references. Why? + self.assertEqual(wndclass.lpfnWndProc(1, 2, 3, 4), 10) + + + f = wndclass.lpfnWndProc + + del wndclass + del wndproc + + self.assertEqual(f(10, 11, 12, 13), 46) + + def test_dllfunctions(self): + + def NoNullHandle(value): + if not value: + raise WinError() + return value + + strchr = lib.my_strchr + strchr.restype = c_char_p + strchr.argtypes = (c_char_p, c_char) + self.assertEqual(strchr(b"abcdefghi", b"b"), b"bcdefghi") + self.assertEqual(strchr(b"abcdefghi", b"x"), None) + + + strtok = lib.my_strtok + strtok.restype = c_char_p + # Neither of this does work: strtok changes the buffer it is passed +## strtok.argtypes = (c_char_p, c_char_p) +## strtok.argtypes = (c_string, c_char_p) + + def c_string(init): + size = len(init) + 1 + return (c_char*size)(*init) + + s = b"a\nb\nc" + b = c_string(s) + +## b = (c_char * (len(s)+1))() +## b.value = s + +## b = c_string(s) + self.assertEqual(strtok(b, b"\n"), b"a") + self.assertEqual(strtok(None, b"\n"), b"b") + self.assertEqual(strtok(None, b"\n"), b"c") + self.assertEqual(strtok(None, b"\n"), None) + + def test_abstract(self): + from ctypes import _CFuncPtr + + self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid") + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/ctypes/tests/test_functions.py b/Lib/ctypes/tests/test_functions.py new file mode 100644 index 0000000000..2ed5a95d5b --- /dev/null +++ b/Lib/ctypes/tests/test_functions.py @@ -0,0 +1,412 @@ +""" +Here is probably the place to write the docs, since the test-cases +show how the type behave. + +Later... +""" + +from ctypes import * +from ctypes.test import need_symbol +import sys, unittest + +try: + WINFUNCTYPE +except NameError: + # fake to enable this test on Linux + WINFUNCTYPE = CFUNCTYPE + +import _ctypes_test +dll = CDLL(_ctypes_test.__file__) +if sys.platform == "win32": + windll = WinDLL(_ctypes_test.__file__) + +class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] +class RECT(Structure): + _fields_ = [("left", c_int), ("top", c_int), + ("right", c_int), ("bottom", c_int)] + +@unittest.skip("TODO: RUSTPYTHON, implrment Structure") +class FunctionTestCase(unittest.TestCase): + + def test_mro(self): + # in Python 2.3, this raises TypeError: MRO conflict among bases classes, + # in Python 2.2 it works. + # + # But in early versions of _ctypes.c, the result of tp_new + # wasn't checked, and it even crashed Python. + # Found by Greg Chapman. + + try: + class X(object, Array): + _length_ = 5 + _type_ = "i" + except TypeError: + pass + + + from _ctypes import _Pointer + try: + class X(object, _Pointer): + pass + except TypeError: + pass + + from _ctypes import _SimpleCData + try: + class X(object, _SimpleCData): + _type_ = "i" + except TypeError: + pass + + try: + class X(object, Structure): + _fields_ = [] + except TypeError: + pass + + + @need_symbol('c_wchar') + def test_wchar_parm(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_wchar, c_int, c_long, c_float, c_double] + result = f(1, "x", 3, 4, 5.0, 6.0) + self.assertEqual(result, 139) + self.assertEqual(type(result), int) + + @need_symbol('c_wchar') + def test_wchar_result(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_wchar + result = f(0, 0, 0, 0, 0, 0) + self.assertEqual(result, '\x00') + + @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") + def test_voidresult(self): + f = dll._testfunc_v + f.restype = None + f.argtypes = [c_int, c_int, POINTER(c_int)] + result = c_int() + self.assertEqual(None, f(1, 2, byref(result))) + self.assertEqual(result.value, 3) + + def test_intresult(self): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_int + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), int) + + # If we declare the function to return a short, + # is the high part split off? + f.restype = c_short + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + result = f(1, 2, 3, 0x10004, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), int) + + # You cannot assign character format codes as restype any longer + self.assertRaises(TypeError, setattr, f, "restype", "i") + + def test_floatresult(self): + f = dll._testfunc_f_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_float + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_doubleresult(self): + f = dll._testfunc_d_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_double + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + def test_longdoubleresult(self): + f = dll._testfunc_D_bhilfD + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_longdouble] + f.restype = c_longdouble + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + self.assertEqual(type(result), float) + + result = f(-1, -2, -3, -4, -5.0, -6.0) + self.assertEqual(result, -21) + self.assertEqual(type(result), float) + + @need_symbol('c_longlong') + def test_longlongresult(self): + f = dll._testfunc_q_bhilfd + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + result = f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(result, 21) + + f = dll._testfunc_q_bhilfdq + f.restype = c_longlong + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double, c_longlong] + result = f(1, 2, 3, 4, 5.0, 6.0, 21) + self.assertEqual(result, 42) + + def test_stringresult(self): + f = dll._testfunc_p_p + f.argtypes = None + f.restype = c_char_p + result = f(b"123") + self.assertEqual(result, b"123") + + result = f(None) + self.assertEqual(result, None) + + @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") + def test_pointers(self): + f = dll._testfunc_p_p + f.restype = POINTER(c_int) + f.argtypes = [POINTER(c_int)] + + # This only works if the value c_int(42) passed to the + # function is still alive while the pointer (the result) is + # used. + + v = c_int(42) + + self.assertEqual(pointer(v).contents.value, 42) + result = f(pointer(v)) + self.assertEqual(type(result), POINTER(c_int)) + self.assertEqual(result.contents.value, 42) + + # This on works... + result = f(pointer(v)) + self.assertEqual(result.contents.value, v.value) + + p = pointer(c_int(99)) + result = f(p) + self.assertEqual(result.contents.value, 99) + + arg = byref(v) + result = f(arg) + self.assertNotEqual(result.contents, v.value) + + self.assertRaises(ArgumentError, f, byref(c_short(22))) + + # It is dangerous, however, because you don't control the lifetime + # of the pointer: + result = f(byref(c_int(99))) + self.assertNotEqual(result.contents, 99) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_errors(self): + f = dll._testfunc_p_p + f.restype = c_int + + class X(Structure): + _fields_ = [("y", c_int)] + + self.assertRaises(TypeError, f, X()) #cannot convert parameter + + ################################################################ + def test_shorts(self): + f = dll._testfunc_callback_i_if + + args = [] + expected = [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, + 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + + def callback(v): + args.append(v) + return v + + CallBack = CFUNCTYPE(c_int, c_int) + + cb = CallBack(callback) + f(2**18, cb) + self.assertEqual(args, expected) + + ################################################################ + + + def test_callbacks(self): + f = dll._testfunc_callback_i_if + f.restype = c_int + f.argtypes = None + + MyCallback = CFUNCTYPE(c_int, c_int) + + def callback(value): + #print "called back with", value + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + # test with prototype + f.argtypes = [c_int, MyCallback] + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + AnotherCallback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + + # check that the prototype works: we call f with wrong + # argument types + cb = AnotherCallback(callback) + self.assertRaises(ArgumentError, f, -10, cb) + + + def test_callbacks_2(self): + # Can also use simple datatypes as argument type specifiers + # for the callback function. + # In this case the call receives an instance of that type + f = dll._testfunc_callback_i_if + f.restype = c_int + + MyCallback = CFUNCTYPE(c_int, c_int) + + f.argtypes = [c_int, MyCallback] + + def callback(value): + #print "called back with", value + self.assertEqual(type(value), int) + return value + + cb = MyCallback(callback) + result = f(-10, cb) + self.assertEqual(result, -18) + + @need_symbol('c_longlong') + def test_longlong_callbacks(self): + + f = dll._testfunc_callback_q_qf + f.restype = c_longlong + + MyCallback = CFUNCTYPE(c_longlong, c_longlong) + + f.argtypes = [c_longlong, MyCallback] + + def callback(value): + self.assertIsInstance(value, int) + return value & 0x7FFFFFFF + + cb = MyCallback(callback) + + self.assertEqual(13577625587, f(1000000000000, cb)) + + def test_errors(self): + self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") + self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") + + @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") + def test_byval(self): + + # without prototype + ptin = POINT(1, 2) + ptout = POINT() + # EXPORT int _testfunc_byval(point in, point *pout) + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 3, 1, 2 + self.assertEqual(got, expected) + + # with prototype + ptin = POINT(101, 102) + ptout = POINT() + dll._testfunc_byval.argtypes = (POINT, POINTER(POINT)) + dll._testfunc_byval.restype = c_int + result = dll._testfunc_byval(ptin, byref(ptout)) + got = result, ptout.x, ptout.y + expected = 203, 101, 102 + self.assertEqual(got, expected) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_struct_return_2H(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + dll.ret_2h_func.restype = S2H + dll.ret_2h_func.argtypes = [S2H] + inp = S2H(99, 88) + s2h = dll.ret_2h_func(inp) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_2H_stdcall(self): + class S2H(Structure): + _fields_ = [("x", c_short), + ("y", c_short)] + + windll.s_ret_2h_func.restype = S2H + windll.s_ret_2h_func.argtypes = [S2H] + s2h = windll.s_ret_2h_func(S2H(99, 88)) + self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_struct_return_8H(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + dll.ret_8i_func.restype = S8I + dll.ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = dll.ret_8i_func(inp) + self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') + def test_struct_return_8H_stdcall(self): + class S8I(Structure): + _fields_ = [("a", c_int), + ("b", c_int), + ("c", c_int), + ("d", c_int), + ("e", c_int), + ("f", c_int), + ("g", c_int), + ("h", c_int)] + windll.s_ret_8i_func.restype = S8I + windll.s_ret_8i_func.argtypes = [S8I] + inp = S8I(9, 8, 7, 6, 5, 4, 3, 2) + s8i = windll.s_ret_8i_func(inp) + self.assertEqual( + (s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), + (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) + + def test_sf1651235(self): + # see http://www.python.org/sf/1651235 + + proto = CFUNCTYPE(c_int, RECT, POINT) + def callback(*args): + return 0 + + callback = proto(callback) + self.assertRaises(ArgumentError, lambda: callback((1, 2, 3, 4), POINT())) + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/ctypes/tests/test_loading.py b/Lib/ctypes/tests/test_loading.py new file mode 100644 index 0000000000..7b930f92c7 --- /dev/null +++ b/Lib/ctypes/tests/test_loading.py @@ -0,0 +1,182 @@ +from ctypes import * +import os +import shutil +import subprocess +import sys +import unittest +import test.support +from test.support import import_helper +from test.support import os_helper +from ctypes.util import find_library + +libc_name = None + +def setUpModule(): + global libc_name + if os.name == "nt": + libc_name = find_library("c") + elif sys.platform == "cygwin": + libc_name = "cygwin1.dll" + else: + libc_name = find_library("c") + + if test.support.verbose: + print("libc_name is", libc_name) + +class LoaderTest(unittest.TestCase): + + unknowndll = "xxrandomnamexx" + + def test_load(self): + if libc_name is None: + self.skipTest('could not find libc') + CDLL(libc_name) + CDLL(os.path.basename(libc_name)) + self.assertRaises(OSError, CDLL, self.unknowndll) + + def test_load_version(self): + if libc_name is None: + self.skipTest('could not find libc') + if os.path.basename(libc_name) != 'libc.so.6': + self.skipTest('wrong libc path for test') + cdll.LoadLibrary("libc.so.6") + # linux uses version, libc 9 should not exist + self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9") + self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) + + def test_find(self): + for name in ("c", "m"): + lib = find_library(name) + if lib: + cdll.LoadLibrary(lib) + CDLL(lib) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_library(self): + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + #self.assertIsNotNone(libc_name) + if test.support.verbose: + print(find_library("kernel32")) + print(find_library("user32")) + + if os.name == "nt": + windll.kernel32.GetModuleHandleW + windll["kernel32"].GetModuleHandleW + windll.LoadLibrary("kernel32").GetModuleHandleW + WinDLL("kernel32").GetModuleHandleW + # embedded null character + self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0") + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_ordinal_functions(self): + import _ctypes_test + dll = WinDLL(_ctypes_test.__file__) + # We load the same function both via ordinal and name + func_ord = dll[2] + func_name = dll.GetString + # addressof gets the address where the function pointer is stored + a_ord = addressof(func_ord) + a_name = addressof(func_name) + f_ord_addr = c_void_p.from_address(a_ord).value + f_name_addr = c_void_p.from_address(a_name).value + self.assertEqual(hex(f_ord_addr), hex(f_name_addr)) + + self.assertRaises(AttributeError, dll.__getitem__, 1234) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_A(self): + from _ctypes import LoadLibrary, FreeLibrary + # On winXP 64-bit, advapi32 loads at an address that does + # NOT fit into a 32-bit integer. FreeLibrary must be able + # to accept this address. + + # These are tests for http://www.python.org/sf/1703286 + handle = LoadLibrary("advapi32") + FreeLibrary(handle) + + @unittest.skipUnless(os.name == "nt", 'Windows-specific test') + def test_1703286_B(self): + # Since on winXP 64-bit advapi32 loads like described + # above, the (arbitrarily selected) CloseEventLog function + # also has a high address. 'call_function' should accept + # addresses so large. + from _ctypes import call_function + advapi32 = windll.advapi32 + # Calling CloseEventLog with a NULL argument should fail, + # but the call should not segfault or so. + self.assertEqual(0, advapi32.CloseEventLog(None)) + windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p + windll.kernel32.GetProcAddress.restype = c_void_p + proc = windll.kernel32.GetProcAddress(advapi32._handle, + b"CloseEventLog") + self.assertTrue(proc) + # This is the real test: call the function via 'call_function' + self.assertEqual(0, call_function(proc, (None,))) + + @unittest.skipUnless(os.name == "nt", + 'test specific to Windows') + def test_load_dll_with_flags(self): + _sqlite3 = import_helper.import_module("_sqlite3") + src = _sqlite3.__file__ + if src.lower().endswith("_d.pyd"): + ext = "_d.dll" + else: + ext = ".dll" + + with os_helper.temp_dir() as tmp: + # We copy two files and load _sqlite3.dll (formerly .pyd), + # which has a dependency on sqlite3.dll. Then we test + # loading it in subprocesses to avoid it starting in memory + # for each test. + target = os.path.join(tmp, "_sqlite3.dll") + shutil.copy(src, target) + shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext), + os.path.join(tmp, "sqlite3" + ext)) + + def should_pass(command): + with self.subTest(command): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp + ) + + def should_fail(command): + with self.subTest(command): + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_output( + [sys.executable, "-c", + "from ctypes import *; import nt;" + command], + cwd=tmp, stderr=subprocess.STDOUT, + ) + + # Default load should not find this in CWD + should_fail("WinDLL('_sqlite3.dll')") + + # Relative path (but not just filename) should succeed + should_pass("WinDLL('./_sqlite3.dll')") + + # Insecure load flags should succeed + # Clear the DLL directory to avoid safe search settings propagating + should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)") + + # Full path load without DLL_LOAD_DIR shouldn't find dependency + should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)") + + # Full path load with DLL_LOAD_DIR should succeed + should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " + + "winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" + + "nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)") + + # User-specified directory should succeed + should_pass("import os; p = os.add_dll_directory(os.getcwd());" + + "WinDLL('_sqlite3.dll'); p.close()") + + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/ctypes/tests/test_strings.py b/Lib/ctypes/tests/test_strings.py new file mode 100644 index 0000000000..5434efda10 --- /dev/null +++ b/Lib/ctypes/tests/test_strings.py @@ -0,0 +1,232 @@ +import unittest +from ctypes import * +from ctypes.test import need_symbol + +class StringArrayTestCase(unittest.TestCase): + def test(self): + BUF = c_char * 4 + + buf = BUF(b"a", b"b", b"c") + self.assertEqual(buf.value, b"abc") + self.assertEqual(buf.raw, b"abc\000") + + buf.value = b"ABCD" + self.assertEqual(buf.value, b"ABCD") + self.assertEqual(buf.raw, b"ABCD") + + buf.value = b"x" + self.assertEqual(buf.value, b"x") + self.assertEqual(buf.raw, b"x\000CD") + + buf[1] = b"Z" + self.assertEqual(buf.value, b"xZCD") + self.assertEqual(buf.raw, b"xZCD") + + self.assertRaises(ValueError, setattr, buf, "value", b"aaaaaaaa") + self.assertRaises(TypeError, setattr, buf, "value", 42) + + def test_c_buffer_value(self): + buf = c_buffer(32) + + buf.value = b"Hello, World" + self.assertEqual(buf.value, b"Hello, World") + + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"Hello, World")) + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) + self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) + + def test_c_buffer_raw(self): + buf = c_buffer(32) + + buf.raw = memoryview(b"Hello, World") + self.assertEqual(buf.value, b"Hello, World") + self.assertRaises(TypeError, setattr, buf, "value", memoryview(b"abc")) + self.assertRaises(ValueError, setattr, buf, "raw", memoryview(b"x" * 100)) + + def test_param_1(self): + BUF = c_char * 4 + buf = BUF() +## print c_char_p.from_param(buf) + + def test_param_2(self): + BUF = c_char * 4 + buf = BUF() +## print BUF.from_param(c_char_p("python")) +## print BUF.from_param(BUF(*"pyth")) + + def test_del_segfault(self): + BUF = c_char * 4 + buf = BUF() + with self.assertRaises(AttributeError): + del buf.raw + + +@need_symbol('c_wchar') +class WStringArrayTestCase(unittest.TestCase): + def test(self): + BUF = c_wchar * 4 + + buf = BUF("a", "b", "c") + self.assertEqual(buf.value, "abc") + + buf.value = "ABCD" + self.assertEqual(buf.value, "ABCD") + + buf.value = "x" + self.assertEqual(buf.value, "x") + + buf[1] = "Z" + self.assertEqual(buf.value, "xZCD") + + @unittest.skipIf(sizeof(c_wchar) < 4, + "sizeof(wchar_t) is smaller than 4 bytes") + def test_nonbmp(self): + u = chr(0x10ffff) + w = c_wchar(u) + self.assertEqual(w.value, u) + +class StringTestCase(unittest.TestCase): + @unittest.skip('test disabled') + def test_basic_strings(self): + cs = c_string("abcdef") + + # Cannot call len on a c_string any longer + self.assertRaises(TypeError, len, cs) + self.assertEqual(sizeof(cs), 7) + + # The value property is the string up to the first terminating NUL. + self.assertEqual(cs.value, "abcdef") + self.assertEqual(c_string("abc\000def").value, "abc") + + # The raw property is the total buffer contents: + self.assertEqual(cs.raw, "abcdef\000") + self.assertEqual(c_string("abc\000def").raw, "abc\000def\000") + + # We can change the value: + cs.value = "ab" + self.assertEqual(cs.value, "ab") + self.assertEqual(cs.raw, "ab\000\000\000\000\000") + + cs.raw = "XY" + self.assertEqual(cs.value, "XY") + self.assertEqual(cs.raw, "XY\000\000\000\000\000") + + self.assertRaises(TypeError, c_string, "123") + + @unittest.skip('test disabled') + def test_sized_strings(self): + + # New in releases later than 0.4.0: + self.assertRaises(TypeError, c_string, None) + + # New in releases later than 0.4.0: + # c_string(number) returns an empty string of size number + self.assertEqual(len(c_string(32).raw), 32) + self.assertRaises(ValueError, c_string, -1) + self.assertRaises(ValueError, c_string, 0) + + # These tests fail, because it is no longer initialized +## self.assertEqual(c_string(2).value, "") +## self.assertEqual(c_string(2).raw, "\000\000") + self.assertEqual(c_string(2).raw[-1], "\000") + self.assertEqual(len(c_string(2).raw), 2) + + @unittest.skip('test disabled') + def test_initialized_strings(self): + + self.assertEqual(c_string("ab", 4).raw[:2], "ab") + self.assertEqual(c_string("ab", 4).raw[:2:], "ab") + self.assertEqual(c_string("ab", 4).raw[:2:-1], "ba") + self.assertEqual(c_string("ab", 4).raw[:2:2], "a") + self.assertEqual(c_string("ab", 4).raw[-1], "\000") + self.assertEqual(c_string("ab", 2).raw, "a\000") + + @unittest.skip('test disabled') + def test_toolong(self): + cs = c_string("abcdef") + # Much too long string: + self.assertRaises(ValueError, setattr, cs, "value", "123456789012345") + + # One char too long values: + self.assertRaises(ValueError, setattr, cs, "value", "1234567") + + @unittest.skip('test disabled') + def test_perf(self): + check_perf() + +@need_symbol('c_wchar') +class WStringTestCase(unittest.TestCase): + def test_wchar(self): + c_wchar("x") + repr(byref(c_wchar("x"))) + c_wchar("x") + + + @unittest.skip('test disabled') + def test_basic_wstrings(self): + cs = c_wstring("abcdef") + + # XXX This behaviour is about to change: + # len returns the size of the internal buffer in bytes. + # This includes the terminating NUL character. + self.assertEqual(sizeof(cs), 14) + + # The value property is the string up to the first terminating NUL. + self.assertEqual(cs.value, "abcdef") + self.assertEqual(c_wstring("abc\000def").value, "abc") + + self.assertEqual(c_wstring("abc\000def").value, "abc") + + # The raw property is the total buffer contents: + self.assertEqual(cs.raw, "abcdef\000") + self.assertEqual(c_wstring("abc\000def").raw, "abc\000def\000") + + # We can change the value: + cs.value = "ab" + self.assertEqual(cs.value, "ab") + self.assertEqual(cs.raw, "ab\000\000\000\000\000") + + self.assertRaises(TypeError, c_wstring, "123") + self.assertRaises(ValueError, c_wstring, 0) + + @unittest.skip('test disabled') + def test_toolong(self): + cs = c_wstring("abcdef") + # Much too long string: + self.assertRaises(ValueError, setattr, cs, "value", "123456789012345") + + # One char too long values: + self.assertRaises(ValueError, setattr, cs, "value", "1234567") + + +def run_test(rep, msg, func, arg): + items = range(rep) + from time import perf_counter as clock + start = clock() + for i in items: + func(arg); func(arg); func(arg); func(arg); func(arg) + stop = clock() + print("%20s: %.2f us" % (msg, ((stop-start)*1e6/5/rep))) + +def check_perf(): + # Construct 5 objects + + REP = 200000 + + run_test(REP, "c_string(None)", c_string, None) + run_test(REP, "c_string('abc')", c_string, 'abc') + +# Python 2.3 -OO, win2k, P4 700 MHz: +# +# c_string(None): 1.75 us +# c_string('abc'): 2.74 us + +# Python 2.2 -OO, win2k, P4 700 MHz: +# +# c_string(None): 2.95 us +# c_string('abc'): 3.67 us + + +if __name__ == '__main__': +## check_perf() + unittest.main() diff --git a/Lib/ctypes/tests/test_unicode.py b/Lib/ctypes/tests/test_unicode.py new file mode 100644 index 0000000000..c200af7b65 --- /dev/null +++ b/Lib/ctypes/tests/test_unicode.py @@ -0,0 +1,56 @@ +import unittest +import ctypes +from ctypes.test import need_symbol + +import _ctypes_test + +@need_symbol('c_wchar') +class UnicodeTestCase(unittest.TestCase): + def test_wcslen(self): + dll = ctypes.CDLL(_ctypes_test.__file__) + wcslen = dll.my_wcslen + wcslen.argtypes = [ctypes.c_wchar_p] + + self.assertEqual(wcslen("abc"), 3) + self.assertEqual(wcslen("ab\u2070"), 3) + self.assertRaises(ctypes.ArgumentError, wcslen, b"ab\xe4") + + def test_buffers(self): + buf = ctypes.create_unicode_buffer("abc") + self.assertEqual(len(buf), 3+1) + + buf = ctypes.create_unicode_buffer("ab\xe4\xf6\xfc") + self.assertEqual(buf[:], "ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::], "ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::-1], '\x00\xfc\xf6\xe4ba') + self.assertEqual(buf[::2], 'a\xe4\xfc') + self.assertEqual(buf[6:5:-1], "") + +func = ctypes.CDLL(_ctypes_test.__file__)._testfunc_p_p + +class StringTestCase(UnicodeTestCase): + def setUp(self): + func.argtypes = [ctypes.c_char_p] + func.restype = ctypes.c_char_p + + def tearDown(self): + func.argtypes = None + func.restype = ctypes.c_int + + def test_func(self): + self.assertEqual(func(b"abc\xe4"), b"abc\xe4") + + def test_buffers(self): + buf = ctypes.create_string_buffer(b"abc") + self.assertEqual(len(buf), 3+1) + + buf = ctypes.create_string_buffer(b"ab\xe4\xf6\xfc") + self.assertEqual(buf[:], b"ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::], b"ab\xe4\xf6\xfc\0") + self.assertEqual(buf[::-1], b'\x00\xfc\xf6\xe4ba') + self.assertEqual(buf[::2], b'a\xe4\xfc') + self.assertEqual(buf[6:5:-1], b"") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py new file mode 100644 index 0000000000..0c2510e161 --- /dev/null +++ b/Lib/ctypes/util.py @@ -0,0 +1,376 @@ +import os +import shutil +import subprocess +import sys + +# find_library(name) returns the pathname of a library, or None. +if os.name == "nt": + + def _get_build_version(): + """Return the version of MSVC that was used to build Python. + + For Python 2.3 and up, the version number is included in + sys.version. For earlier versions, assume the compiler is MSVC 6. + """ + # This function was copied from Lib/distutils/msvccompiler.py + prefix = "MSC v." + i = sys.version.find(prefix) + if i == -1: + return 6 + i = i + len(prefix) + s, rest = sys.version[i:].split(" ", 1) + majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + majorVersion += 1 + minorVersion = int(s[2:3]) / 10.0 + # I don't think paths are affected by minor version in version 6 + if majorVersion == 6: + minorVersion = 0 + if majorVersion >= 6: + return majorVersion + minorVersion + # else we don't know what version of the compiler this is + return None + + def find_msvcrt(): + """Return the name of the VC runtime dll""" + version = _get_build_version() + if version is None: + # better be safe than sorry + return None + if version <= 6: + clibname = 'msvcrt' + elif version <= 13: + clibname = 'msvcr%d' % (version * 10) + else: + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + return None + + # If python was built with in debug mode + import importlib.machinery + if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES: + clibname += 'd' + return clibname+'.dll' + + def find_library(name): + if name in ('c', 'm'): + return find_msvcrt() + # See MSDN for the REAL search order. + for directory in os.environ['PATH'].split(os.pathsep): + fname = os.path.join(directory, name) + if os.path.isfile(fname): + return fname + if fname.lower().endswith(".dll"): + continue + fname = fname + ".dll" + if os.path.isfile(fname): + return fname + return None + +elif os.name == "posix" and sys.platform == "darwin": + from ctypes.macholib.dyld import dyld_find as _dyld_find + def find_library(name): + possible = ['lib%s.dylib' % name, + '%s.dylib' % name, + '%s.framework/%s' % (name, name)] + for name in possible: + try: + return _dyld_find(name) + except ValueError: + continue + return None + +elif sys.platform.startswith("aix"): + # AIX has two styles of storing shared libraries + # GNU auto_tools refer to these as svr4 and aix + # svr4 (System V Release 4) is a regular file, often with .so as suffix + # AIX style uses an archive (suffix .a) with members (e.g., shr.o, libssl.so) + # see issue#26439 and _aix.py for more details + + from ctypes._aix import find_library + +elif os.name == "posix": + # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump + import re, tempfile + + def _is_elf(filename): + "Return True if the given file is an ELF file" + elf_header = b'\x7fELF' + with open(filename, 'br') as thefile: + return thefile.read(4) == elf_header + + def _findLib_gcc(name): + # Run GCC's linker with the -t (aka --trace) option and examine the + # library name it prints out. The GCC command will fail because we + # haven't supplied a proper program with main(), but that does not + # matter. + expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)) + + c_compiler = shutil.which('gcc') + if not c_compiler: + c_compiler = shutil.which('cc') + if not c_compiler: + # No C compiler available, give up + return None + + temp = tempfile.NamedTemporaryFile() + try: + args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name] + + env = dict(os.environ) + env['LC_ALL'] = 'C' + env['LANG'] = 'C' + try: + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env) + except OSError: # E.g. bad executable + return None + with proc: + trace = proc.stdout.read() + finally: + try: + temp.close() + except FileNotFoundError: + # Raised if the file was already removed, which is the normal + # behaviour of GCC if linking fails + pass + res = re.findall(expr, trace) + if not res: + return None + + for file in res: + # Check if the given file is an elf file: gcc can report + # some files that are linker scripts and not actual + # shared objects. See bpo-41976 for more details + if not _is_elf(file): + continue + return os.fsdecode(file) + + + if sys.platform == "sunos5": + # use /usr/ccs/bin/dump on solaris + def _get_soname(f): + if not f: + return None + + try: + proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. command not found + return None + with proc: + data = proc.stdout.read() + res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data) + if not res: + return None + return os.fsdecode(res.group(1)) + else: + def _get_soname(f): + # assuming GNU binutils / ELF + if not f: + return None + objdump = shutil.which('objdump') + if not objdump: + # objdump is not available, give up + return None + + try: + proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. bad executable + return None + with proc: + dump = proc.stdout.read() + res = re.search(br'\sSONAME\s+([^\s]+)', dump) + if not res: + return None + return os.fsdecode(res.group(1)) + + if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): + + def _num_version(libname): + # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] + parts = libname.split(b".") + nums = [] + try: + while parts: + nums.insert(0, int(parts.pop())) + except ValueError: + pass + return nums or [sys.maxsize] + + def find_library(name): + ename = re.escape(name) + expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename) + expr = os.fsencode(expr) + + try: + proc = subprocess.Popen(('/sbin/ldconfig', '-r'), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. command not found + data = b'' + else: + with proc: + data = proc.stdout.read() + + res = re.findall(expr, data) + if not res: + return _get_soname(_findLib_gcc(name)) + res.sort(key=_num_version) + return os.fsdecode(res[-1]) + + elif sys.platform == "sunos5": + + def _findLib_crle(name, is64): + if not os.path.exists('/usr/bin/crle'): + return None + + env = dict(os.environ) + env['LC_ALL'] = 'C' + + if is64: + args = ('/usr/bin/crle', '-64') + else: + args = ('/usr/bin/crle',) + + paths = None + try: + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + env=env) + except OSError: # E.g. bad executable + return None + with proc: + for line in proc.stdout: + line = line.strip() + if line.startswith(b'Default Library Path (ELF):'): + paths = os.fsdecode(line).split()[4] + + if not paths: + return None + + for dir in paths.split(":"): + libfile = os.path.join(dir, "lib%s.so" % name) + if os.path.exists(libfile): + return libfile + + return None + + def find_library(name, is64 = False): + return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) + + else: + + def _findSoname_ldconfig(name): + import struct + if struct.calcsize('l') == 4: + machine = os.uname().machine + '-32' + else: + machine = os.uname().machine + '-64' + mach_map = { + 'x86_64-64': 'libc6,x86-64', + 'ppc64-64': 'libc6,64bit', + 'sparc64-64': 'libc6,64bit', + 's390x-64': 'libc6,64bit', + 'ia64-64': 'libc6,IA-64', + } + abi_type = mach_map.get(machine, 'libc6') + + # XXX assuming GLIBC's ldconfig (with option -p) + regex = r'\s+(lib%s\.[^\s]+)\s+\(%s' + regex = os.fsencode(regex % (re.escape(name), abi_type)) + try: + with subprocess.Popen(['/sbin/ldconfig', '-p'], + stdin=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + stdout=subprocess.PIPE, + env={'LC_ALL': 'C', 'LANG': 'C'}) as p: + res = re.search(regex, p.stdout.read()) + if res: + return os.fsdecode(res.group(1)) + except OSError: + pass + + def _findLib_ld(name): + # See issue #9998 for why this is needed + expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name) + cmd = ['ld', '-t'] + libpath = os.environ.get('LD_LIBRARY_PATH') + if libpath: + for d in libpath.split(':'): + cmd.extend(['-L', d]) + cmd.extend(['-o', os.devnull, '-l%s' % name]) + result = None + try: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + out, _ = p.communicate() + res = re.findall(expr, os.fsdecode(out)) + for file in res: + # Check if the given file is an elf file: gcc can report + # some files that are linker scripts and not actual + # shared objects. See bpo-41976 for more details + if not _is_elf(file): + continue + return os.fsdecode(file) + except Exception: + pass # result will be None + return result + + def find_library(name): + # See issue #9998 + return _findSoname_ldconfig(name) or \ + _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name)) + +################################################################ +# test code + +def test(): + from ctypes import cdll + if os.name == "nt": + print(cdll.msvcrt) + print(cdll.load("msvcrt")) + print(find_library("msvcrt")) + + if os.name == "posix": + # find and load_version + print(find_library("m")) + print(find_library("c")) + print(find_library("bz2")) + + # load + if sys.platform == "darwin": + print(cdll.LoadLibrary("libm.dylib")) + print(cdll.LoadLibrary("libcrypto.dylib")) + print(cdll.LoadLibrary("libSystem.dylib")) + print(cdll.LoadLibrary("System.framework/System")) + # issue-26439 - fix broken test call for AIX + elif sys.platform.startswith("aix"): + from ctypes import CDLL + if sys.maxsize < 2**32: + print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}") + print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}") + # librpm.so is only available as 32-bit shared library + print(find_library("rpm")) + print(cdll.LoadLibrary("librpm.so")) + else: + print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}") + print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}") + print(f"crypt\t:: {find_library('crypt')}") + print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}") + print(f"crypto\t:: {find_library('crypto')}") + print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}") + else: + print(cdll.LoadLibrary("libm.so")) + print(cdll.LoadLibrary("libcrypt.so")) + print(find_library("crypt")) + +if __name__ == "__main__": + test() diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py new file mode 100644 index 0000000000..c619d27596 --- /dev/null +++ b/Lib/ctypes/wintypes.py @@ -0,0 +1,202 @@ +# The most useful windows datatypes +import ctypes + +BYTE = ctypes.c_byte +WORD = ctypes.c_ushort +DWORD = ctypes.c_ulong + +#UCHAR = ctypes.c_uchar +CHAR = ctypes.c_char +WCHAR = ctypes.c_wchar +UINT = ctypes.c_uint +INT = ctypes.c_int + +DOUBLE = ctypes.c_double +FLOAT = ctypes.c_float + +BOOLEAN = BYTE +BOOL = ctypes.c_long + +class VARIANT_BOOL(ctypes._SimpleCData): + _type_ = "v" + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.value) + +ULONG = ctypes.c_ulong +LONG = ctypes.c_long + +USHORT = ctypes.c_ushort +SHORT = ctypes.c_short + +# in the windows header files, these are structures. +_LARGE_INTEGER = LARGE_INTEGER = ctypes.c_longlong +_ULARGE_INTEGER = ULARGE_INTEGER = ctypes.c_ulonglong + +LPCOLESTR = LPOLESTR = OLESTR = ctypes.c_wchar_p +LPCWSTR = LPWSTR = ctypes.c_wchar_p +LPCSTR = LPSTR = ctypes.c_char_p +LPCVOID = LPVOID = ctypes.c_void_p + +# WPARAM is defined as UINT_PTR (unsigned type) +# LPARAM is defined as LONG_PTR (signed type) +if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): + WPARAM = ctypes.c_ulong + LPARAM = ctypes.c_long +elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): + WPARAM = ctypes.c_ulonglong + LPARAM = ctypes.c_longlong + +ATOM = WORD +LANGID = WORD + +COLORREF = DWORD +LGRPID = DWORD +LCTYPE = DWORD + +LCID = DWORD + +################################################################ +# HANDLE types +HANDLE = ctypes.c_void_p # in the header files: void * + +HACCEL = HANDLE +HBITMAP = HANDLE +HBRUSH = HANDLE +HCOLORSPACE = HANDLE +HDC = HANDLE +HDESK = HANDLE +HDWP = HANDLE +HENHMETAFILE = HANDLE +HFONT = HANDLE +HGDIOBJ = HANDLE +HGLOBAL = HANDLE +HHOOK = HANDLE +HICON = HANDLE +HINSTANCE = HANDLE +HKEY = HANDLE +HKL = HANDLE +HLOCAL = HANDLE +HMENU = HANDLE +HMETAFILE = HANDLE +HMODULE = HANDLE +HMONITOR = HANDLE +HPALETTE = HANDLE +HPEN = HANDLE +HRGN = HANDLE +HRSRC = HANDLE +HSTR = HANDLE +HTASK = HANDLE +HWINSTA = HANDLE +HWND = HANDLE +SC_HANDLE = HANDLE +SERVICE_STATUS_HANDLE = HANDLE + +################################################################ +# Some important structure definitions + +class RECT(ctypes.Structure): + _fields_ = [("left", LONG), + ("top", LONG), + ("right", LONG), + ("bottom", LONG)] +tagRECT = _RECTL = RECTL = RECT + +class _SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', SHORT), + ('Top', SHORT), + ('Right', SHORT), + ('Bottom', SHORT)] +SMALL_RECT = _SMALL_RECT + +class _COORD(ctypes.Structure): + _fields_ = [('X', SHORT), + ('Y', SHORT)] + +class POINT(ctypes.Structure): + _fields_ = [("x", LONG), + ("y", LONG)] +tagPOINT = _POINTL = POINTL = POINT + +class SIZE(ctypes.Structure): + _fields_ = [("cx", LONG), + ("cy", LONG)] +tagSIZE = SIZEL = SIZE + +def RGB(red, green, blue): + return red + (green << 8) + (blue << 16) + +class FILETIME(ctypes.Structure): + _fields_ = [("dwLowDateTime", DWORD), + ("dwHighDateTime", DWORD)] +_FILETIME = FILETIME + +class MSG(ctypes.Structure): + _fields_ = [("hWnd", HWND), + ("message", UINT), + ("wParam", WPARAM), + ("lParam", LPARAM), + ("time", DWORD), + ("pt", POINT)] +tagMSG = MSG +MAX_PATH = 260 + +class WIN32_FIND_DATAA(ctypes.Structure): + _fields_ = [("dwFileAttributes", DWORD), + ("ftCreationTime", FILETIME), + ("ftLastAccessTime", FILETIME), + ("ftLastWriteTime", FILETIME), + ("nFileSizeHigh", DWORD), + ("nFileSizeLow", DWORD), + ("dwReserved0", DWORD), + ("dwReserved1", DWORD), + ("cFileName", CHAR * MAX_PATH), + ("cAlternateFileName", CHAR * 14)] + +class WIN32_FIND_DATAW(ctypes.Structure): + _fields_ = [("dwFileAttributes", DWORD), + ("ftCreationTime", FILETIME), + ("ftLastAccessTime", FILETIME), + ("ftLastWriteTime", FILETIME), + ("nFileSizeHigh", DWORD), + ("nFileSizeLow", DWORD), + ("dwReserved0", DWORD), + ("dwReserved1", DWORD), + ("cFileName", WCHAR * MAX_PATH), + ("cAlternateFileName", WCHAR * 14)] + +################################################################ +# Pointer types + +LPBOOL = PBOOL = ctypes.POINTER(BOOL) +PBOOLEAN = ctypes.POINTER(BOOLEAN) +LPBYTE = PBYTE = ctypes.POINTER(BYTE) +PCHAR = ctypes.POINTER(CHAR) +LPCOLORREF = ctypes.POINTER(COLORREF) +LPDWORD = PDWORD = ctypes.POINTER(DWORD) +LPFILETIME = PFILETIME = ctypes.POINTER(FILETIME) +PFLOAT = ctypes.POINTER(FLOAT) +LPHANDLE = PHANDLE = ctypes.POINTER(HANDLE) +PHKEY = ctypes.POINTER(HKEY) +LPHKL = ctypes.POINTER(HKL) +LPINT = PINT = ctypes.POINTER(INT) +PLARGE_INTEGER = ctypes.POINTER(LARGE_INTEGER) +PLCID = ctypes.POINTER(LCID) +LPLONG = PLONG = ctypes.POINTER(LONG) +LPMSG = PMSG = ctypes.POINTER(MSG) +LPPOINT = PPOINT = ctypes.POINTER(POINT) +PPOINTL = ctypes.POINTER(POINTL) +LPRECT = PRECT = ctypes.POINTER(RECT) +LPRECTL = PRECTL = ctypes.POINTER(RECTL) +LPSC_HANDLE = ctypes.POINTER(SC_HANDLE) +PSHORT = ctypes.POINTER(SHORT) +LPSIZE = PSIZE = ctypes.POINTER(SIZE) +LPSIZEL = PSIZEL = ctypes.POINTER(SIZEL) +PSMALL_RECT = ctypes.POINTER(SMALL_RECT) +LPUINT = PUINT = ctypes.POINTER(UINT) +PULARGE_INTEGER = ctypes.POINTER(ULARGE_INTEGER) +PULONG = ctypes.POINTER(ULONG) +PUSHORT = ctypes.POINTER(USHORT) +PWCHAR = ctypes.POINTER(WCHAR) +LPWIN32_FIND_DATAA = PWIN32_FIND_DATAA = ctypes.POINTER(WIN32_FIND_DATAA) +LPWIN32_FIND_DATAW = PWIN32_FIND_DATAW = ctypes.POINTER(WIN32_FIND_DATAW) +LPWORD = PWORD = ctypes.POINTER(WORD) diff --git a/extra_tests/snippets/ctypes_tests/test_array.py b/extra_tests/snippets/ctypes_tests/test_array.py new file mode 100644 index 0000000000..052508c5e6 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_array.py @@ -0,0 +1,68 @@ +import pytest +import unittest +from ctypes import * + +def test_slice(): + values = list(range(5)) + numarray = c_int * 5 + + na = numarray(*(c_int(x) for x in values)) + + assert list(na[0:0]) == [] + assert list(na[:]) == values + assert list(na[:10]) == values + +def test_init_again(): + sz = (c_char * 3)() + addr1 = addressof(sz) + sz.__init__(*b"foo") + addr2 = addressof(sz) + assert addr1 == addr2 + +@unittest.skip("TODO: RUSTPYTHON, implement Structure") +def test_array_of_structures(): + class X(Structure): + _fields_ = [('x', c_int), ('y', c_int)] + + Y = X * 2 + y = Y() + x = X() + x.y = 3 + y[1] = x + assert y[1].y == 3 + +@unittest.skip("TODO: RUSTPYTHON, implement POINTER") +def test_output_simple(): + A = c_char * 10 + TP = POINTER(A) + x = TP(A()) + assert x[0] != b'' + + A = c_wchar * 10 + TP = POINTER(A) + x = TP(A()) + assert x[0] != b'' + +def test_output_simple_array(): + A = c_char * 10 + AA = A * 10 + aa = AA() + assert aa[0] != b'' + +@unittest.skip("TODO: RUSTPYTHON, implement Structure") +def test_output_complex_test(): + class Car(Structure): + _fields_ = [("brand", c_char * 10), + ("speed", c_float), + ("owner", c_char * 10)] + + assert isinstance(Car(b"abcdefghi", 42.0, b"12345").brand, bytes) + assert Car(b"abcdefghi", 42.0, b"12345").brand == b"abcdefghi" + assert Car(b"abcdefghio", 42.0, b"12345").brand == b"abcdefghio" + with pytest.raises(ValueError): + Car(b"abcdefghiop", 42.0, b"12345") + + A = Car._fields_[2][1] + TP = POINTER(A) + x = TP(A()) + assert x[0] != b'' diff --git a/extra_tests/snippets/ctypes_tests/test_buffers.py b/extra_tests/snippets/ctypes_tests/test_buffers.py new file mode 100644 index 0000000000..61ada37595 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_buffers.py @@ -0,0 +1,70 @@ +import pytest +import sys +from ctypes import * + +def test_buffer(): + b = create_string_buffer(32) + assert len(b) == 32 + assert sizeof(b) == 32 * sizeof(c_char) + assert type(b[0]) is bytes + + b = create_string_buffer(b"abc") + assert len(b) == 4 # trailing nul char + assert sizeof(b) == 4 * sizeof(c_char) + assert type(b[0]) is bytes + assert b[0] == b"a" + assert b[:] == b"abc\0" + +def test_from_buffer(): + b1 = bytearray(b"abcde") + b = (c_char * 5).from_buffer(b1) + assert b[2] == b"c" + # + b1 = bytearray(b"abcd") + b = c_int.from_buffer(b1) + assert b.value in (1684234849, # little endian + 1633837924) # big endian + +def test_from_buffer_keepalive(): + # Issue #2878 + b1 = bytearray(b"ab") + array = (c_uint16 * 32)() + array[6] = c_uint16.from_buffer(b1) + # this is also what we get on CPython. I don't think it makes + # sense because the array contains just a copy of the number. + assert array._objects == {'6': b1} + +def normalize(fmt): + if sys.byteorder == "big": + return fmt.replace('<', '>') + else: + return fmt + +s_long = {4: 'l', 8: 'q'}[sizeof(c_long)] +s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_long)] + +@pytest.mark.parametrize("tp, fmt", [ + ## simple types + (c_char, " $body: ident - )+ - ) => { - match $kind { - $( - $( - t if t == $type => { mem::size_of::<$body>() } - )+ - )+ - _ => unreachable!() - } - } -} - macro_rules! byte_match_type { ( $kind: expr, @@ -66,26 +47,6 @@ macro_rules! byte_match_type { } } -fn get_size(ty: &str) -> usize { - os_match_type!( - ty, - "u" => WideChar - "c" | "b" => c_schar - "h" => c_short - "H" => c_ushort - "i" => c_int - "I" => c_uint - "l" => c_long - "q" => c_longlong - "L" => c_ulong - "Q" => c_ulonglong - "f" => c_float - "d" | "g" => c_double - "?" | "B" => c_uchar - "P" | "z" | "Z" => c_void - ) -} - fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { if ty == "u" { vm.new_pyobj(if cfg!(windows) { @@ -564,3 +525,43 @@ impl PyCArray { } } } + +impl PyCDataFunctions for PyCArray { + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_pyobj( + zelf._length_ + * usize::try_from_object( + vm, + PyCDataFunctions::size_of_instances(zelf._type_.clone(), vm)?, + )?, + )) + } + + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + PyCDataFunctions::alignment_of_instances(zelf._type_.clone(), vm) + } + + fn ref_to( + zelf: PyRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let off_set = offset + .into_option() + .map_or(Ok(0), |o| usize::try_from_object(vm, o))?; + + if off_set > zelf._length_ { + Err(vm.new_index_error("offset out of bounds".to_string())) + } else { + let guard = zelf.borrow_value(); + let ref_at: *mut u8 = unsafe { guard.inner.add(off_set) }; + + Ok(vm.new_pyobj(ref_at as *mut _ as *mut usize as usize)) + } + } + + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm + .new_pyobj(unsafe { &*zelf.borrow_value().inner } as *const _ as *const usize as usize)) + } +} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index f65e92d471..5a55b3e219 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -1,4 +1,6 @@ -use std::{fmt, ptr, slice}; +use std::{fmt, mem, os::raw::*, ptr, slice}; + +use widestring::WideChar; use crate::builtins::int::PyInt; use crate::builtins::memory::{try_buffer_from_object, Buffer, BufferOptions}; @@ -18,6 +20,45 @@ use crate::stdlib::ctypes::dll::dlsym; use crossbeam_utils::atomic::AtomicCell; +macro_rules! os_match_type { + ( + $kind: expr, + + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match $kind { + $( + $( + t if t == $type => { mem::size_of::<$body>() } + )+ + )+ + _ => unreachable!() + } + } +} + +pub fn get_size(ty: &str) -> usize { + os_match_type!( + ty, + "u" => WideChar + "c" | "b" => c_schar + "h" => c_short + "H" => c_ushort + "i" => c_int + "I" => c_uint + "l" => c_long + "q" => c_longlong + "L" => c_ulong + "Q" => c_ulonglong + "f" => c_float + "d" | "g" => c_double + "?" | "B" => c_uchar + "P" | "z" | "Z" => c_void + ) +} + fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult { match vm.get_attribute(cls.as_object().to_owned(), "__abstract__") { Ok(attr) => match bool::try_from_object(vm, attr) { @@ -136,7 +177,21 @@ pub fn default_from_param( ))) } } +#[pyimpl] +pub trait PyCDataFunctions: PyValue { + #[pymethod] + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + #[pymethod] + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + + #[pymethod] + fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) + -> PyResult; + + #[pymethod] + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult; +} #[pyimpl] pub trait PyCDataMethods: PyValue { // A lot of the logic goes in this trait @@ -388,3 +443,51 @@ impl PyCData { #[pymethod(magic)] pub fn setstate(zelf: PyRef) {} } + +pub fn sizeof_func(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if tp.clone().downcast::().is_err() { + Err(vm.new_type_error(format!( + "sizeof() argument must be a ctypes instance, not {}", + tp.class().name + ))) + } else { + let size_of_instances = vm.get_method(tp, "size_of_instances").unwrap(); + vm.invoke(&size_of_instances?, ()) + } +} + +pub fn alignment(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if tp.clone().downcast::().is_err() { + Err(vm.new_type_error(format!( + "alignment() argument must be a ctypes instance, not {}", + tp.class().name + ))) + } else { + let alignment_of_instances = vm.get_method(tp, "alignment_of_instances").unwrap(); + vm.invoke(&alignment_of_instances?, ()) + } +} + +pub fn byref(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if tp.clone().downcast::().is_err() { + Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not {}", + tp.class().name + ))) + } else { + let ref_to = vm.get_method(tp, "ref_to").unwrap(); + vm.invoke(&ref_to?, ()) + } +} + +pub fn addressof(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if tp.clone().downcast::().is_err() { + Err(vm.new_type_error(format!( + "addressof() argument must be a ctypes instance, not {}", + tp.class().name + ))) + } else { + let address_of = vm.get_method(tp, "address_of").unwrap(); + vm.invoke(&address_of?, ()) + } +} diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 478aac5305..f922606e8d 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -9,24 +9,39 @@ mod function; mod pointer; mod primitive; mod shared_lib; +mod structure; use array::PyCArray; -use basics::PyCData; +use basics::{addressof, alignment, byref, sizeof_func, PyCData}; use dll::*; use function::PyCFuncPtr; -use pointer::PyCPointer; +use pointer::{pointer_fn, PyCPointer, POINTER}; use primitive::PySimpleType; +use structure::PyCStructure; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; PyCData::make_class(ctx); py_module!(vm, "_ctypes", { + "__version__" => ctx.new_str("1.1.0"), + "dlopen" => ctx.new_function(dlopen), "dlsym" => ctx.new_function(dlsym), "dlclose" => ctx.new_function(dlclose), + "alignment" => ctx.new_function(alignment), + "sizeof" => ctx.new_function(sizeof_func), + "byref" => ctx.new_function(byref), + "addressof" => ctx.new_function(addressof), + + "POINTER" => ctx.new_function(POINTER), + "pointer" => ctx.new_function(pointer_fn), + "CFuncPtr" => PyCFuncPtr::make_class(ctx), - "_SimpleCData" => PySimpleType::make_class(ctx) + "_SimpleCData" => PySimpleType::make_class(ctx), + "_Pointer" => PyCPointer::make_class(ctx), + "Array" => PyCArray::make_class(ctx), + "Struct" => PyCStructure::make_class(ctx) }) } diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index b798feb4ba..6a25784a2d 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,7 +1,7 @@ use std::fmt; use crate::builtins::PyTypeRef; -use crate::pyobject::{PyValue, StaticType}; +use crate::pyobject::{PyObjectRef, PyValue, StaticType}; use crate::VirtualMachine; use crate::stdlib::ctypes::basics::{PyCData, PyCDataMethods}; @@ -29,3 +29,7 @@ impl PyValue for PyCPointer { #[pyimpl(flags(BASETYPE))] impl PyCPointer {} + +pub fn POINTER(cls: PyTypeRef) {} + +pub fn pointer_fn(inst: PyObjectRef) {} diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 064b85b6db..df6adcf588 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -14,7 +14,9 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::array::PyCArray; -use crate::stdlib::ctypes::basics::{BorrowValueMut, PyCData, PyCDataMethods}; +use crate::stdlib::ctypes::basics::{ + get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, +}; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; @@ -426,3 +428,25 @@ impl PySimpleType { Ok(vm.new_pyobj(buffer != vec![0])) } } + +impl PyCDataFunctions for PySimpleType { + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_pyobj(get_size(zelf._type_.as_str()))) + } + + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Self::size_of_instances(zelf, vm) + } + + fn ref_to( + zelf: PyRef, + offset: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.new_pyobj(zelf.value.as_ptr() as *mut _ as *mut usize as usize)) + } + + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_pyobj(unsafe { &*zelf.value.as_ptr() } as *const _ as *const usize as usize)) + } +} diff --git a/vm/src/stdlib/ctypes/structure.rs b/vm/src/stdlib/ctypes/structure.rs index e69de29bb2..c2c5ac7640 100644 --- a/vm/src/stdlib/ctypes/structure.rs +++ b/vm/src/stdlib/ctypes/structure.rs @@ -0,0 +1,25 @@ +use std::fmt; + +use crate::builtins::PyTypeRef; +use crate::pyobject::{PyValue, StaticType}; +use crate::VirtualMachine; + +use crate::stdlib::ctypes::basics::PyCData; + +#[pyclass(module = "_ctypes", name = "Structure", base = "PyCData")] +pub struct PyCStructure {} + +impl fmt::Debug for PyCStructure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PyCStructure {{}}") + } +} + +impl PyValue for PyCStructure { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +#[pyimpl(flags(BASETYPE))] +impl PyCStructure {} From 2c440b37af3d2d6b11e097a9295014a5cc2b073e Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Fri, 1 Jan 2021 10:32:36 -0300 Subject: [PATCH 45/56] Adding PyCArray's from_param --- Lib/ctypes/__init__.py | 42 +++++++++++++++- vm/src/stdlib/ctypes/array.rs | 84 ++++++++++++++++++++++++++------ vm/src/stdlib/ctypes/basics.rs | 1 + vm/src/stdlib/ctypes/function.rs | 2 +- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index dc1dff704e..037eae132c 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -2,7 +2,6 @@ """create and manipulate C data types in Python""" import os as _os, sys as _sys -import types as _types __version__ = "1.1.0" @@ -307,3 +306,44 @@ def __getitem__(self, name): def LoadLibrary(self, name): return self._dlltype(name) + +cdll = LibraryLoader(CDLL) + +if _os.name == "nt": + windll = LibraryLoader(CDLL) + oledll = LibraryLoader(CDLL) + + GetLastError = windll.kernel32.GetLastError + + def WinError(code=None, descr=None): + if code is None: + code = GetLastError() + if descr is None: + # descr = FormatError(code).strip() + descr = "Windows Error " + str(code) + return OSError(None, descr, None, code) + +if sizeof(c_uint) == sizeof(c_void_p): + c_size_t = c_uint + c_ssize_t = c_int +elif sizeof(c_ulong) == sizeof(c_void_p): + c_size_t = c_ulong + c_ssize_t = c_long +elif sizeof(c_ulonglong) == sizeof(c_void_p): + c_size_t = c_ulonglong + c_ssize_t = c_longlong + +# Fill in specifically-sized types +c_int8 = c_byte +c_uint8 = c_ubyte +for kind in [c_short, c_int, c_long, c_longlong]: + if sizeof(kind) == 2: c_int16 = kind + elif sizeof(kind) == 4: c_int32 = kind + elif sizeof(kind) == 8: c_int64 = kind +for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]: + if sizeof(kind) == 2: c_uint16 = kind + elif sizeof(kind) == 4: c_uint32 = kind + elif sizeof(kind) == 8: c_uint64 = kind +del(kind) + +_reset_cache() \ No newline at end of file diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 8c11209d8c..82cf22ac19 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -5,11 +5,11 @@ use num_bigint::Sign; use widestring::WideCString; use crate::builtins::memory::{try_buffer_from_object, Buffer}; -use crate::builtins::slice::PySliceRef; +use crate::builtins::slice::PySlice; use crate::builtins::{PyBytes, PyInt, PyList, PyStr, PyTypeRef}; use crate::common::borrow::BorrowedValueMut; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; -use crate::function::{FuncArgs, OptionalArg}; +use crate::function::OptionalArg; use crate::pyobject::{ BorrowValue, Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, @@ -18,8 +18,8 @@ use crate::slots::BufferProtocol; use crate::VirtualMachine; use crate::stdlib::ctypes::basics::{ - generic_get_buffer, get_size, BorrowValue as BorrowValueCData, BorrowValueMut, PyCData, - PyCDataFunctions, PyCDataMethods, RawBuffer, + default_from_param, generic_get_buffer, get_size, BorrowValue as BorrowValueCData, + BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, RawBuffer, }; use crate::stdlib::ctypes::pointer::PyCPointer; use crate::stdlib::ctypes::primitive::PySimpleType; @@ -169,7 +169,7 @@ fn set_array_value( } fn array_get_slice_inner( - slice: PySliceRef, + slice: PyRef, vm: &VirtualMachine, ) -> PyResult<(isize, isize, isize)> { let step = slice @@ -236,11 +236,41 @@ impl BufferProtocol for PyCArray { } } -// impl PyCDataMethods for PyCArray { -// fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { +impl PyCDataMethods for PyCArray { + fn from_param( + zelf: PyRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + if vm.isinstance(&value, PyCArray::static_type())? { + return Ok(value); + }; + + if vm.obj_len(&value)? > zelf._length_ { + Err(vm.new_value_error("Invalid length".to_string())) + } else if zelf._type_._type_.as_str() == "c" + && value.clone().downcast_exact::(vm).is_err() + { + Err(vm.new_value_error(format!("expected bytes, {} found", value.class().name))) + } else if zelf._type_._type_.as_str() == "u" + && value.clone().downcast_exact::(vm).is_err() + { + Err(vm.new_value_error(format!( + "expected unicode string, {} found", + value.class().name + ))) + } else if vm.isinstance(&value, &vm.ctx.types.tuple_type)? { + Ok(()) + } else { + //@TODO: make sure what goes here + Err(vm.new_type_error("Invalid type".to_string())) + }?; -// } -// } + PyCArray::init(zelf.clone(), value, vm)?; + + default_from_param(zelf.clone_class(), zelf.as_object().clone(), vm) + } +} #[pyimpl(flags(BASETYPE), with(BufferProtocol))] impl PyCArray { @@ -274,9 +304,31 @@ impl PyCArray { } #[pymethod(magic)] - pub fn init(&self, value: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - // @TODO - Ok(()) + pub fn init(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + let value_lenght = vm.obj_len(&value)?; + + if value_lenght < zelf._length_ { + let value_vec: Vec = vm.extract_elements(&value)?; + for (i, v) in value_vec.iter().enumerate() { + Self::setitem(zelf.clone(), Either::A(i as isize), v.clone(), vm)? + } + Ok(()) + } else if value_lenght == zelf._length_ { + let py_slice = Either::B( + PySlice { + start: Some(vm.new_pyobj(0)), + stop: vm.new_pyobj(zelf._length_), + step: None, + } + .into_ref(vm), + ); + + Self::setitem(zelf, py_slice, value, vm) + } else { + Err(vm.new_value_error( + "number of values is greater than the size of the array".to_string(), + )) + } } #[pyproperty(name = "value")] @@ -440,7 +492,7 @@ impl PyCArray { #[pymethod(magic)] fn getitem( zelf: PyRef, - k_or_idx: Either, + k_or_idx: Either>, vm: &VirtualMachine, ) -> PyResult { let buffer = try_buffer_from_object(vm, zelf.as_object())?; @@ -484,7 +536,7 @@ impl PyCArray { #[pymethod(magic)] fn setitem( zelf: PyRef, - k_or_idx: Either, + k_or_idx: Either>, obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { @@ -511,7 +563,7 @@ impl PyCArray { let values: Vec = vm.extract_elements(&obj)?; if values.len() != slice_length { - Err(vm.new_value_error("Can only assign sequence of same size".to_string())) + Err(vm.new_value_error("can only assign sequence of same size".to_string())) } else { let mut cur = start as usize; @@ -550,7 +602,7 @@ impl PyCDataFunctions for PyCArray { .into_option() .map_or(Ok(0), |o| usize::try_from_object(vm, o))?; - if off_set > zelf._length_ { + if off_set > zelf._length_ * get_size(zelf._type_._type_.as_str()) { Err(vm.new_index_error("offset out of bounds".to_string())) } else { let guard = zelf.borrow_value(); diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 5a55b3e219..a5c341f9fd 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -165,6 +165,7 @@ pub fn default_from_param( value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { + //@TODO: check if this behaves like it should if vm.isinstance(&value, &cls)? { Ok(value) } else if let Ok(parameter) = vm.get_attribute(value.clone(), "_as_parameter_") { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index ef37a747a2..eea765aa63 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -281,7 +281,7 @@ impl PyCFuncPtr { if vm.isinstance(&argtypes, &vm.ctx.types.list_type).is_ok() || vm.isinstance(&argtypes, &vm.ctx.types.tuple_type).is_ok() { - let args: Vec = vm.extract_elements(&argtypes)?; + let args = vm.extract_elements(&argtypes)?; let c_args_res: PyResult> = args .iter() .enumerate() From 0b65e0d935e97413ed53978cab94c03b4f8c99e9 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 2 Jan 2021 17:53:57 -0300 Subject: [PATCH 46/56] Adding several changes to make some tests pass --- Lib/ctypes/__init__.py | 2 +- Lib/ctypes/{tests => test}/__init__.py | 0 Lib/ctypes/{tests => test}/__main__.py | 0 Lib/ctypes/{tests => test}/test_arrays.py | 11 +- Lib/ctypes/{tests => test}/test_buffers.py | 0 Lib/ctypes/{tests => test}/test_bytes.py | 0 Lib/ctypes/{tests => test}/test_funcptr.py | 0 Lib/ctypes/{tests => test}/test_functions.py | 0 Lib/ctypes/{tests => test}/test_loading.py | 0 Lib/ctypes/{tests => test}/test_strings.py | 0 Lib/ctypes/{tests => test}/test_unicode.py | 0 vm/src/stdlib/ctypes/array.rs | 100 +++++++++++-------- vm/src/stdlib/ctypes/basics.rs | 88 +++++++++------- vm/src/stdlib/ctypes/mod.rs | 4 +- vm/src/stdlib/ctypes/pointer.rs | 2 +- vm/src/stdlib/ctypes/primitive.rs | 30 ++++-- 16 files changed, 139 insertions(+), 98 deletions(-) rename Lib/ctypes/{tests => test}/__init__.py (100%) rename Lib/ctypes/{tests => test}/__main__.py (100%) rename Lib/ctypes/{tests => test}/test_arrays.py (98%) rename Lib/ctypes/{tests => test}/test_buffers.py (100%) rename Lib/ctypes/{tests => test}/test_bytes.py (100%) rename Lib/ctypes/{tests => test}/test_funcptr.py (100%) rename Lib/ctypes/{tests => test}/test_functions.py (100%) rename Lib/ctypes/{tests => test}/test_loading.py (100%) rename Lib/ctypes/{tests => test}/test_strings.py (100%) rename Lib/ctypes/{tests => test}/test_unicode.py (100%) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 037eae132c..c13121bf48 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -346,4 +346,4 @@ def WinError(code=None, descr=None): elif sizeof(kind) == 8: c_uint64 = kind del(kind) -_reset_cache() \ No newline at end of file +# _reset_cache() \ No newline at end of file diff --git a/Lib/ctypes/tests/__init__.py b/Lib/ctypes/test/__init__.py similarity index 100% rename from Lib/ctypes/tests/__init__.py rename to Lib/ctypes/test/__init__.py diff --git a/Lib/ctypes/tests/__main__.py b/Lib/ctypes/test/__main__.py similarity index 100% rename from Lib/ctypes/tests/__main__.py rename to Lib/ctypes/test/__main__.py diff --git a/Lib/ctypes/tests/test_arrays.py b/Lib/ctypes/test/test_arrays.py similarity index 98% rename from Lib/ctypes/tests/test_arrays.py rename to Lib/ctypes/test/test_arrays.py index d45ebcb3c6..3c5ff1d511 100644 --- a/Lib/ctypes/tests/test_arrays.py +++ b/Lib/ctypes/test/test_arrays.py @@ -177,37 +177,42 @@ def test_bad_subclass(self): with self.assertRaises(AttributeError): class T(Array): pass + T() with self.assertRaises(AttributeError): class T(Array): _type_ = c_int + T() with self.assertRaises(AttributeError): class T(Array): _length_ = 13 - + T() def test_bad_length(self): with self.assertRaises(ValueError): class T(Array): _type_ = c_int _length_ = - sys.maxsize * 2 + T() with self.assertRaises(ValueError): class T(Array): _type_ = c_int _length_ = -1 + T() with self.assertRaises(TypeError): class T(Array): _type_ = c_int _length_ = 1.87 + T() with self.assertRaises(OverflowError): class T(Array): _type_ = c_int _length_ = sys.maxsize * 2 - + T() def test_zero_length(self): # _length_ can be zero. class T(Array): _type_ = c_int _length_ = 0 - + T() @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_empty_element_struct(self): class EmptyStruct(Structure): diff --git a/Lib/ctypes/tests/test_buffers.py b/Lib/ctypes/test/test_buffers.py similarity index 100% rename from Lib/ctypes/tests/test_buffers.py rename to Lib/ctypes/test/test_buffers.py diff --git a/Lib/ctypes/tests/test_bytes.py b/Lib/ctypes/test/test_bytes.py similarity index 100% rename from Lib/ctypes/tests/test_bytes.py rename to Lib/ctypes/test/test_bytes.py diff --git a/Lib/ctypes/tests/test_funcptr.py b/Lib/ctypes/test/test_funcptr.py similarity index 100% rename from Lib/ctypes/tests/test_funcptr.py rename to Lib/ctypes/test/test_funcptr.py diff --git a/Lib/ctypes/tests/test_functions.py b/Lib/ctypes/test/test_functions.py similarity index 100% rename from Lib/ctypes/tests/test_functions.py rename to Lib/ctypes/test/test_functions.py diff --git a/Lib/ctypes/tests/test_loading.py b/Lib/ctypes/test/test_loading.py similarity index 100% rename from Lib/ctypes/tests/test_loading.py rename to Lib/ctypes/test/test_loading.py diff --git a/Lib/ctypes/tests/test_strings.py b/Lib/ctypes/test/test_strings.py similarity index 100% rename from Lib/ctypes/tests/test_strings.py rename to Lib/ctypes/test/test_strings.py diff --git a/Lib/ctypes/tests/test_unicode.py b/Lib/ctypes/test/test_unicode.py similarity index 100% rename from Lib/ctypes/tests/test_unicode.py rename to Lib/ctypes/test/test_unicode.py diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 82cf22ac19..d5a6f1b3ee 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -19,10 +19,10 @@ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::{ default_from_param, generic_get_buffer, get_size, BorrowValue as BorrowValueCData, - BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, RawBuffer, + BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, RawBuffer, }; use crate::stdlib::ctypes::pointer::PyCPointer; -use crate::stdlib::ctypes::primitive::PySimpleType; +use crate::stdlib::ctypes::primitive::{new_simple_type, PySimpleType}; macro_rules! byte_match_type { ( @@ -111,19 +111,24 @@ pub fn make_array_with_lenght( length: usize, vm: &VirtualMachine, ) -> PyResult> { - if let Ok(ref outer_type) = vm.get_attribute(cls.as_object().to_owned(), "_type_") { - if let Ok(_type_) = outer_type.clone().downcast_exact::(vm) { - let itemsize = get_size(_type_._type_.as_str()); - - Ok(PyCArray { - _type_, - _length_: length, - _buffer: PyRwLock::new(RawBuffer { - inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), - size: length * itemsize, - }), + if let Some(outer_type) = cls.get_attr("_type_") { + let length = length as usize; + if let Ok(_type_) = vm.get_attribute(outer_type.clone(), "_type_") { + let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); + + if length.checked_mul(itemsize).is_none() { + Err(vm.new_overflow_error("Array size too big".to_string())) + } else { + Ok(PyCArray { + _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(vm), + _length_: length, + _buffer: PyRwLock::new(RawBuffer { + inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), + size: length * itemsize, + }), + } + .into_ref_with_type(vm, cls)?) } - .into_ref_with_type(vm, cls)?) } else { Err(vm.new_type_error("_type_ must have storage info".to_string())) } @@ -140,7 +145,7 @@ fn set_array_value( obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - if obj.clone().downcast::().is_err() { + if !obj.clone_class().issubclass(PyCData::static_type()) { let value = PyCDataMethods::from_param(zelf._type_.clone(), obj, vm)?; let v_buffer = try_buffer_from_object(vm, &value)?; @@ -247,7 +252,7 @@ impl PyCDataMethods for PyCArray { }; if vm.obj_len(&value)? > zelf._length_ { - Err(vm.new_value_error("Invalid length".to_string())) + Err(vm.new_value_error("value has size greater than the array".to_string())) } else if zelf._type_._type_.as_str() == "c" && value.clone().downcast_exact::(vm).is_err() { @@ -266,13 +271,16 @@ impl PyCDataMethods for PyCArray { Err(vm.new_type_error("Invalid type".to_string())) }?; - PyCArray::init(zelf.clone(), value, vm)?; + PyCArray::init(zelf.clone(), OptionalArg::Present(value), vm)?; default_from_param(zelf.clone_class(), zelf.as_object().clone(), vm) } } -#[pyimpl(flags(BASETYPE), with(BufferProtocol))] +#[pyimpl( + flags(BASETYPE), + with(BufferProtocol, PyCDataFunctions, PyCDataMethods) +)] impl PyCArray { #[pyslot] fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { @@ -304,31 +312,31 @@ impl PyCArray { } #[pymethod(magic)] - pub fn init(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - let value_lenght = vm.obj_len(&value)?; - - if value_lenght < zelf._length_ { - let value_vec: Vec = vm.extract_elements(&value)?; - for (i, v) in value_vec.iter().enumerate() { - Self::setitem(zelf.clone(), Either::A(i as isize), v.clone(), vm)? - } - Ok(()) - } else if value_lenght == zelf._length_ { - let py_slice = Either::B( - PySlice { - start: Some(vm.new_pyobj(0)), - stop: vm.new_pyobj(zelf._length_), - step: None, + pub fn init(zelf: PyRef, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + value.map_or(Ok(()), |value| { + let value_lenght = vm.obj_len(&value)?; + + if value_lenght < zelf._length_ { + let value_vec: Vec = vm.extract_elements(&value)?; + for (i, v) in value_vec.iter().enumerate() { + Self::setitem(zelf.clone(), Either::A(i as isize), v.clone(), vm)? } - .into_ref(vm), - ); + Ok(()) + } else if value_lenght == zelf._length_ { + let py_slice = Either::B( + PySlice { + start: Some(vm.new_pyobj(0)), + stop: vm.new_pyobj(zelf._length_), + step: None, + } + .into_ref(vm), + ); - Self::setitem(zelf, py_slice, value, vm) - } else { - Err(vm.new_value_error( - "number of values is greater than the size of the array".to_string(), - )) - } + Self::setitem(zelf, py_slice, value, vm) + } else { + Err(vm.new_value_error("value has size greater than the array".to_string())) + } + }) } #[pyproperty(name = "value")] @@ -502,8 +510,10 @@ impl PyCArray { let res = match k_or_idx { Either::A(idx) => { - if idx < 0 || idx as usize > zelf._length_ { + if idx < 0 { Err(vm.new_index_error("invalid index".to_string())) + } else if idx as usize > zelf._length_ { + Err(vm.new_index_error("index out of bounds".to_string())) } else { let idx = idx as usize; let buffer_slice = buffer_bytes[idx..idx + offset].as_ref(); @@ -548,8 +558,10 @@ impl PyCArray { match k_or_idx { Either::A(idx) => { - if idx < 0 || idx as usize > zelf._length_ { + if idx < 0 { Err(vm.new_index_error("invalid index".to_string())) + } else if idx as usize > zelf._length_ { + Err(vm.new_index_error("index out of bounds".to_string())) } else { set_array_value(&zelf, &mut buffer_bytes, idx as usize, offset, obj, vm) } @@ -617,3 +629,5 @@ impl PyCDataFunctions for PyCArray { .new_pyobj(unsafe { &*zelf.borrow_value().inner } as *const _ as *const usize as usize)) } } + +impl PyCDataSequenceMethods for PyCArray {} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index a5c341f9fd..be7a053824 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -10,13 +10,14 @@ use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + Either, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; use crate::VirtualMachine; use crate::stdlib::ctypes::array::make_array_with_lenght; use crate::stdlib::ctypes::dll::dlsym; +use crate::stdlib::ctypes::primitive::{new_simple_type, PySimpleType}; use crossbeam_utils::atomic::AtomicCell; @@ -55,7 +56,7 @@ pub fn get_size(ty: &str) -> usize { "f" => c_float "d" | "g" => c_double "?" | "B" => c_uchar - "P" | "z" | "Z" => c_void + "P" | "z" | "Z" => usize ) } @@ -445,50 +446,61 @@ impl PyCData { pub fn setstate(zelf: PyRef) {} } -pub fn sizeof_func(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if tp.clone().downcast::().is_err() { - Err(vm.new_type_error(format!( - "sizeof() argument must be a ctypes instance, not {}", - tp.class().name - ))) - } else { - let size_of_instances = vm.get_method(tp, "size_of_instances").unwrap(); - vm.invoke(&size_of_instances?, ()) +pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> PyResult { + match tp { + Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { + let zelf = new_simple_type(Either::B(&type_), vm)?; + PyCDataFunctions::size_of_instances(zelf.into_ref(vm), vm) + } + Either::B(obj) if obj.has_class_attr("size_of_instances") => { + let size_of = vm.get_attribute(obj, "size_of_instances").unwrap(); + vm.invoke(&size_of, ()) + } + _ => Err(vm.new_type_error("this type has no size".to_string())), } } -pub fn alignment(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if tp.clone().downcast::().is_err() { - Err(vm.new_type_error(format!( - "alignment() argument must be a ctypes instance, not {}", - tp.class().name - ))) - } else { - let alignment_of_instances = vm.get_method(tp, "alignment_of_instances").unwrap(); - vm.invoke(&alignment_of_instances?, ()) +pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { + match tp { + Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { + let zelf = new_simple_type(Either::B(&type_), vm)?; + PyCDataFunctions::alignment_of_instances(zelf.into_ref(vm), vm) + } + Either::B(obj) if obj.has_class_attr("alignment_of_instances") => { + let size_of = vm.get_attribute(obj, "alignment_of_instances").unwrap(); + vm.invoke(&size_of, ()) + } + _ => Err(vm.new_type_error("no alignment info".to_string())), } } pub fn byref(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if tp.clone().downcast::().is_err() { - Err(vm.new_type_error(format!( - "byref() argument must be a ctypes instance, not {}", - tp.class().name - ))) - } else { - let ref_to = vm.get_method(tp, "ref_to").unwrap(); - vm.invoke(&ref_to?, ()) - } + //@TODO: Return a Pointer when Pointer implementation is ready + let class = tp.clone_class(); + + if class.issubclass(PyCData::static_type()) { + if let Some(ref_to) = vm.get_method(tp, "ref_to") { + return vm.invoke(&ref_to?, ()); + } + }; + + Err(vm.new_type_error(format!( + "byref() argument must be a ctypes instance, not '{}'", + class.name + ))) } pub fn addressof(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if tp.clone().downcast::().is_err() { - Err(vm.new_type_error(format!( - "addressof() argument must be a ctypes instance, not {}", - tp.class().name - ))) - } else { - let address_of = vm.get_method(tp, "address_of").unwrap(); - vm.invoke(&address_of?, ()) - } + let class = tp.clone_class(); + + if class.issubclass(PyCData::static_type()) { + if let Some(address_of) = vm.get_method(tp, "address_of") { + return vm.invoke(&address_of?, ()); + } + }; + + Err(vm.new_type_error(format!( + "addressof() argument must be a ctypes instance, not '{}'", + class.name + ))) } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index f922606e8d..c175513d55 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -1,5 +1,4 @@ -use crate::pyobject::PyClassImpl; -use crate::pyobject::PyObjectRef; +use crate::pyobject::{PyClassImpl, PyObjectRef, PyValue}; use crate::VirtualMachine; mod array; @@ -37,6 +36,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { "POINTER" => ctx.new_function(POINTER), "pointer" => ctx.new_function(pointer_fn), + "_pointer_type_cache" => ctx.new_dict(), "CFuncPtr" => PyCFuncPtr::make_class(ctx), "_SimpleCData" => PySimpleType::make_class(ctx), diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index 6a25784a2d..90cf9d5d6c 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::builtins::PyTypeRef; +use crate::builtins::{PyDict, PyTypeRef}; use crate::pyobject::{PyObjectRef, PyValue, StaticType}; use crate::VirtualMachine; diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index df6adcf588..cab6849a85 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -9,18 +9,19 @@ use crate::builtins::{ }; use crate::function::OptionalArg; use crate::pyobject::{ - IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + TypeProtocol, }; use crate::VirtualMachine; use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::basics::{ - get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, + get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, }; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; -const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZqQ?"; +const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?"; fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult { match _type_ { @@ -229,14 +230,21 @@ fn from_param_void_p( } } -fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { +pub fn new_simple_type( + cls: Either<&PyObjectRef, &PyTypeRef>, + vm: &VirtualMachine, +) -> PyResult { + let cls = match cls { + Either::A(obj) => obj, + Either::B(typ) => typ.as_object(), + }; + let is_abstract = cls.is(PySimpleType::static_type()); if is_abstract { return Err(vm.new_type_error("abstract class".to_string())); } - - match vm.get_attribute(cls.as_object().to_owned(), "_type_") { + match vm.get_attribute(cls.clone(), "_type_") { Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? => { let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); @@ -258,7 +266,7 @@ fn new_simple_type(cls: &PyTypeRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("class must define a '_type_' string attribute".to_string())) } - Err(_) => Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())), + _ => Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())), } } @@ -313,7 +321,7 @@ impl PyCDataMethods for PySimpleType { "z" | "Z" => from_param_char_p(&cls, &value, vm), "P" => from_param_void_p(&cls, &value, vm), _ => { - match new_simple_type(&cls, vm) { + match new_simple_type(Either::B(&cls), vm) { Ok(obj) => Ok(obj.into_object(vm)), Err(e) if vm.isinstance( @@ -358,11 +366,11 @@ impl PyCDataMethods for PySimpleType { } } -#[pyimpl(flags(BASETYPE))] +#[pyimpl(with(PyCDataFunctions, PyCDataMethods), flags(BASETYPE))] impl PySimpleType { #[pyslot] fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { - new_simple_type(&cls, vm)?.into_ref_with_type(vm, cls) + new_simple_type(Either::B(&cls), vm)?.into_ref_with_type(vm, cls) } #[pymethod(magic)] @@ -450,3 +458,5 @@ impl PyCDataFunctions for PySimpleType { Ok(vm.new_pyobj(unsafe { &*zelf.value.as_ptr() } as *const _ as *const usize as usize)) } } + +impl PyCDataSequenceMethods for PySimpleType {} From 7c529af943f594a22cde87393b4d70a8daf0c6c0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 7 Aug 2021 01:18:15 +0900 Subject: [PATCH 47/56] Fix build on master --- Cargo.lock | 1 + vm/src/stdlib/ctypes/array.rs | 6 +++--- vm/src/stdlib/ctypes/basics.rs | 3 ++- vm/src/stdlib/ctypes/mod.rs | 20 ++++++++++---------- vm/src/stdlib/ctypes/pointer.rs | 4 ++-- vm/src/stdlib/ctypes/primitive.rs | 12 +++++------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6415e2e2fc..ca45c92d9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1914,6 +1914,7 @@ dependencies = [ "itertools", "lexical-core", "libc", + "libffi", "libloading", "libz-sys", "log", diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index d5a6f1b3ee..71b1d6ba2b 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -11,10 +11,10 @@ use crate::common::borrow::BorrowedValueMut; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - BorrowValue, Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, - TryFromObject, TypeProtocol, + IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; +use crate::utils::Either; use crate::VirtualMachine; use crate::stdlib::ctypes::basics::{ @@ -287,7 +287,7 @@ impl PyCArray { let length = match vm.get_attribute(cls.as_object().to_owned(), "_length_") { Ok(ref length_obj) => { if let Ok(length_int) = length_obj.clone().downcast_exact::(vm) { - if length_int.borrow_value().sign() == Sign::Minus { + if length_int.as_bigint().sign() == Sign::Minus { Err(vm.new_value_error( "The '_length_' attribute must not be negative".to_string(), )) diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index be7a053824..feb009b854 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -10,9 +10,10 @@ use crate::common::borrow::{BorrowedValue, BorrowedValueMut}; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - Either, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; +use crate::utils::Either; use crate::VirtualMachine; use crate::stdlib::ctypes::array::make_array_with_lenght; diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index c175513d55..d011724229 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -1,4 +1,4 @@ -use crate::pyobject::{PyClassImpl, PyObjectRef, PyValue}; +use crate::pyobject::{PyClassImpl, PyObjectRef}; use crate::VirtualMachine; mod array; @@ -25,17 +25,17 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "_ctypes", { "__version__" => ctx.new_str("1.1.0"), - "dlopen" => ctx.new_function(dlopen), - "dlsym" => ctx.new_function(dlsym), - "dlclose" => ctx.new_function(dlclose), + "dlopen" => ctx.new_function("dlopen", dlopen), + "dlsym" => ctx.new_function("dlsym", dlsym), + "dlclose" => ctx.new_function("dlclose", dlclose), - "alignment" => ctx.new_function(alignment), - "sizeof" => ctx.new_function(sizeof_func), - "byref" => ctx.new_function(byref), - "addressof" => ctx.new_function(addressof), + "alignment" => ctx.new_function("alignment", alignment), + "sizeof" => ctx.new_function("sizeof", sizeof_func), + "byref" => ctx.new_function("byref", byref), + "addressof" => ctx.new_function("addressof", addressof), - "POINTER" => ctx.new_function(POINTER), - "pointer" => ctx.new_function(pointer_fn), + "POINTER" => ctx.new_function("POINTER", POINTER), + "pointer" => ctx.new_function("pointer", pointer_fn), "_pointer_type_cache" => ctx.new_dict(), "CFuncPtr" => PyCFuncPtr::make_class(ctx), diff --git a/vm/src/stdlib/ctypes/pointer.rs b/vm/src/stdlib/ctypes/pointer.rs index 90cf9d5d6c..a5fc734a32 100644 --- a/vm/src/stdlib/ctypes/pointer.rs +++ b/vm/src/stdlib/ctypes/pointer.rs @@ -1,10 +1,10 @@ use std::fmt; -use crate::builtins::{PyDict, PyTypeRef}; +use crate::builtins::PyTypeRef; use crate::pyobject::{PyObjectRef, PyValue, StaticType}; use crate::VirtualMachine; -use crate::stdlib::ctypes::basics::{PyCData, PyCDataMethods}; +use crate::stdlib::ctypes::basics::PyCData; #[pyclass(module = "_ctypes", name = "_Pointer", base = "PyCData")] pub struct PyCPointer {} diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index cab6849a85..c384c4da92 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -1,5 +1,4 @@ use crossbeam_utils::atomic::AtomicCell; -use rustpython_common::borrow::BorrowValue; use std::fmt; use crate::builtins::memory::try_buffer_from_object; @@ -9,17 +8,16 @@ use crate::builtins::{ }; use crate::function::OptionalArg; use crate::pyobject::{ - Either, IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, - TypeProtocol, + IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; -use crate::VirtualMachine; - use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::basics::{ get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, }; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; +use crate::utils::Either; +use crate::VirtualMachine; const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?"; @@ -33,12 +31,12 @@ fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyRe || value .clone() .downcast_exact::(vm) - .map_or(false, |v| v.borrow_value().len() == 1) + .map_or(false, |v| v.borrow_buf().len() == 1) || value .clone() .downcast_exact::(vm) .map_or(Ok(false), |v| { - let n: i64 = try_to_primitive(v.borrow_value(), vm)?; + let n: i64 = try_to_primitive(v.as_bigint(), vm)?; Ok(0 <= n && n <= 255) })? { From fe01b1ae6ae61fd3f81a009dbb78e380983986bc Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 7 Aug 2021 04:38:08 +0900 Subject: [PATCH 48/56] hide ctypes test --- Lib/test/libregrtest/refleak.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 822c905238..1866c816b1 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -264,7 +264,8 @@ def clear_caches(): except KeyError: pass else: - ctypes._reset_cache() + pass # FIXME: RustPython + # ctypes._reset_cache() try: typing = sys.modules['typing'] From 25e04036b3488bf72f17e951257c441837bfa874 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 8 Aug 2021 03:03:19 +0900 Subject: [PATCH 49/56] clean up ctypes::array --- vm/src/stdlib/ctypes/array.rs | 347 ++++++++++++++++----------------- vm/src/stdlib/ctypes/basics.rs | 4 +- 2 files changed, 165 insertions(+), 186 deletions(-) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 71b1d6ba2b..40fbbefccf 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -1,51 +1,30 @@ -use std::convert::TryInto; -use std::{fmt, mem}; - -use num_bigint::Sign; -use widestring::WideCString; - -use crate::builtins::memory::{try_buffer_from_object, Buffer}; -use crate::builtins::slice::PySlice; -use crate::builtins::{PyBytes, PyInt, PyList, PyStr, PyTypeRef}; +use super::{ + pointer::PyCPointer, + primitive::{new_simple_type, PySimpleType}, +}; +use crate::builtins::{ + self, + memory::{try_buffer_from_object, Buffer}, + slice::PySlice, + PyBytes, PyInt, PyList, PyStr, PyTypeRef, +}; use crate::common::borrow::BorrowedValueMut; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - IdProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::slots::BufferProtocol; -use crate::utils::Either; -use crate::VirtualMachine; - use crate::stdlib::ctypes::basics::{ default_from_param, generic_get_buffer, get_size, BorrowValue as BorrowValueCData, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, RawBuffer, }; -use crate::stdlib::ctypes::pointer::PyCPointer; -use crate::stdlib::ctypes::primitive::{new_simple_type, PySimpleType}; - -macro_rules! byte_match_type { - ( - $kind: expr, - $byte: expr, - $vm: expr, - $( - $($type: literal)|+ => $body: ident - )+ - ) => { - match $kind { - $( - $( - t if t == $type => { - let chunk: [u8; mem::size_of::<$body>()] = $byte.try_into().unwrap(); - $vm.new_pyobj($body::from_ne_bytes(chunk)) - } - )+ - )+ - _ => unreachable!() - } - } -} +use crate::utils::Either; +use crate::VirtualMachine; +use num_traits::Signed; +use std::convert::TryInto; +use std::fmt; +use widestring::WideCString; fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { if ty == "u" { @@ -57,10 +36,26 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { u32::from_ne_bytes(chunk) }) } else { + macro_rules! byte_match_type { + ( + $( + $($type: literal)|+ => $body: ident + )+ + ) => { + match ty { + $( + $( + t if t == $type => { + let chunk: [u8; std::mem::size_of::<$body>()] = b.try_into().unwrap(); + vm.new_pyobj($body::from_ne_bytes(chunk)) + } + )+ + )+ + _ => unreachable!() + } + } + } byte_match_type!( - ty, - b, - vm, "c" | "b" => i8 "h" => i16 "H" => u16 @@ -106,35 +101,31 @@ fn slice_adjust_size(length: isize, start: &mut isize, stop: &mut isize, step: i 0 } -pub fn make_array_with_lenght( +pub fn make_array_with_length( cls: PyTypeRef, length: usize, vm: &VirtualMachine, ) -> PyResult> { - if let Some(outer_type) = cls.get_attr("_type_") { - let length = length as usize; - if let Ok(_type_) = vm.get_attribute(outer_type.clone(), "_type_") { - let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); - - if length.checked_mul(itemsize).is_none() { - Err(vm.new_overflow_error("Array size too big".to_string())) - } else { - Ok(PyCArray { - _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(vm), - _length_: length, - _buffer: PyRwLock::new(RawBuffer { - inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), - size: length * itemsize, - }), - } - .into_ref_with_type(vm, cls)?) - } - } else { - Err(vm.new_type_error("_type_ must have storage info".to_string())) - } - } else { - Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) + let outer_type = cls.get_attr("_type_").ok_or_else(|| { + vm.new_attribute_error("class must define a '_type_' attribute".to_string()) + })?; + let length = length as usize; + let _type_ = vm + .get_attribute(outer_type.clone(), "_type_") + .map_err(|_| vm.new_type_error("_type_ must have storage info".to_string()))?; + let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); + let _ = length + .checked_mul(itemsize) + .ok_or_else(|| vm.new_overflow_error("Array size too big".to_string()))?; + Ok(PyCArray { + _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(vm), + _length_: length, + _buffer: PyRwLock::new(RawBuffer { + inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), + size: length * itemsize, + }), } + .into_ref_with_type(vm, cls)?) } fn set_array_value( @@ -145,32 +136,30 @@ fn set_array_value( obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - if !obj.clone_class().issubclass(PyCData::static_type()) { + if !obj.class().issubclass(PyCData::static_type()) { let value = PyCDataMethods::from_param(zelf._type_.clone(), obj, vm)?; let v_buffer = try_buffer_from_object(vm, &value)?; let v_buffer_bytes = v_buffer.obj_bytes_mut(); - dst_buffer[idx..idx + offset].copy_from_slice(&v_buffer_bytes[0..]); - Ok(()) + dst_buffer[idx..idx + offset].copy_from_slice(&v_buffer_bytes[..]); } else if vm.isinstance(&obj, &zelf._type_.clone_class())? { let o_buffer = try_buffer_from_object(vm, &obj)?; let src_buffer = o_buffer.obj_bytes_mut(); dst_buffer[idx..idx + offset].copy_from_slice(&src_buffer[idx..idx + offset]); - Ok(()) } else if vm.isinstance(zelf._type_.as_object(), PyCPointer::static_type())? && vm.isinstance(&obj, PyCArray::static_type())? { //@TODO: Fill here once CPointer is done - Ok(()) } else { - Err(vm.new_type_error(format!( + return Err(vm.new_type_error(format!( "incompatible types, {} instance instead of {} instance", obj.class().name, zelf.class().name - ))) + ))); } + Ok(()) } fn array_get_slice_inner( @@ -179,22 +168,24 @@ fn array_get_slice_inner( ) -> PyResult<(isize, isize, isize)> { let step = slice .step - .clone() - .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + .as_ref() + .map(|o| isize::try_from_object(vm, o.clone())) + .transpose()? // FIXME: unnessessary clone + .unwrap_or(1); assert!(step != 0); assert!(step >= -isize::MAX); let start = slice .start - .clone() + .clone() // FIXME: unnessessary clone .map_or(Ok(0), |o| isize::try_from_object(vm, o))?; - let stop = if slice.stop.is(&vm.ctx.none()) { - Err(vm.new_value_error("slice stop is required".to_string())) - } else { - Ok(isize::try_from_object(vm, slice.stop.clone())?) - }?; + if vm.is_none(&slice.stop) { + return Err(vm.new_value_error("slice stop is required".to_string())); + } + // FIXME: unnessessary clone + let stop = isize::try_from_object(vm, slice.stop.clone())?; Ok((step, start, stop)) } @@ -249,27 +240,30 @@ impl PyCDataMethods for PyCArray { ) -> PyResult { if vm.isinstance(&value, PyCArray::static_type())? { return Ok(value); - }; + } if vm.obj_len(&value)? > zelf._length_ { - Err(vm.new_value_error("value has size greater than the array".to_string())) - } else if zelf._type_._type_.as_str() == "c" + return Err(vm.new_value_error("value has size greater than the array".to_string())); + } + + if zelf._type_._type_.as_str() == "c" && value.clone().downcast_exact::(vm).is_err() { - Err(vm.new_value_error(format!("expected bytes, {} found", value.class().name))) - } else if zelf._type_._type_.as_str() == "u" - && value.clone().downcast_exact::(vm).is_err() + return Err(vm.new_value_error(format!("expected bytes, {} found", value.class().name))); + } + + if zelf._type_._type_.as_str() == "u" && value.clone().downcast_exact::(vm).is_err() { - Err(vm.new_value_error(format!( + return Err(vm.new_value_error(format!( "expected unicode string, {} found", value.class().name - ))) - } else if vm.isinstance(&value, &vm.ctx.types.tuple_type)? { - Ok(()) - } else { + ))); + } + + if !vm.isinstance(&value, &vm.ctx.types.tuple_type)? { //@TODO: make sure what goes here - Err(vm.new_type_error("Invalid type".to_string())) - }?; + return Err(vm.new_type_error("Invalid type".to_string())); + } PyCArray::init(zelf.clone(), OptionalArg::Present(value), vm)?; @@ -284,45 +278,39 @@ impl PyCDataMethods for PyCArray { impl PyCArray { #[pyslot] fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { - let length = match vm.get_attribute(cls.as_object().to_owned(), "_length_") { - Ok(ref length_obj) => { - if let Ok(length_int) = length_obj.clone().downcast_exact::(vm) { - if length_int.as_bigint().sign() == Sign::Minus { - Err(vm.new_value_error( - "The '_length_' attribute must not be negative".to_string(), - )) - } else { - Ok(usize::try_from_object(vm, length_obj.clone()).map_err(|_| { - vm.new_overflow_error( - "The '_length_' attribute is too large".to_string(), - ) - })?) - } - } else { - Err(vm - .new_type_error("The '_length_' attribute must be an integer".to_string())) - } - } - Err(_) => { - Err(vm.new_attribute_error("class must define a '_type_' _length_".to_string())) - } + let length_obj = vm + .get_attribute(cls.as_object().to_owned(), "_length_") + .map_err(|_| { + vm.new_attribute_error("class must define a '_type_' _length_".to_string()) + })?; + let length_int = length_obj.downcast_exact::(vm).map_err(|_| { + vm.new_type_error("The '_length_' attribute must be an integer".to_string()) + })?; + let length: usize = if length_int.as_bigint().is_negative() { + Err(vm.new_value_error("The '_length_' attribute must not be negative".to_string())) + } else { + Ok( + builtins::int::try_to_primitive(length_int.as_bigint(), vm).map_err(|_| { + vm.new_overflow_error("The '_length_' attribute is too large".to_owned()) + })?, + ) }?; - make_array_with_lenght(cls, length, vm) + make_array_with_length(cls, length, vm) } #[pymethod(magic)] pub fn init(zelf: PyRef, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { value.map_or(Ok(()), |value| { - let value_lenght = vm.obj_len(&value)?; + let value_length = vm.obj_len(&value)?; - if value_lenght < zelf._length_ { + if value_length < zelf._length_ { let value_vec: Vec = vm.extract_elements(&value)?; for (i, v) in value_vec.iter().enumerate() { Self::setitem(zelf.clone(), Either::A(i as isize), v.clone(), vm)? } Ok(()) - } else if value_lenght == zelf._length_ { + } else if value_length == zelf._length_ { let py_slice = Either::B( PySlice { start: Some(vm.new_pyobj(0)), @@ -339,8 +327,8 @@ impl PyCArray { }) } - #[pyproperty(name = "value")] - pub fn value(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + #[pyproperty] + pub fn value(zelf: PyRef, vm: &VirtualMachine) -> PyResult { let obj = zelf.as_object(); let buffer = try_buffer_from_object(vm, obj)?; @@ -385,7 +373,7 @@ impl PyCArray { bytes.to_vec() } } else { - vec![0; 0] + Vec::new() }; PyBytes::from(bytes_inner).into_object(vm) @@ -394,7 +382,7 @@ impl PyCArray { Ok(res) } - #[pyproperty(name = "value", setter)] + #[pyproperty(setter)] fn set_value(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let obj = zelf.as_object(); let buffer = try_buffer_from_object(vm, obj)?; @@ -403,76 +391,71 @@ impl PyCArray { if zelf._type_._type_ == "c" { // bytes - if let Ok(value) = value.clone().downcast_exact::(vm) { - let wide_bytes = value.to_vec(); - - if wide_bytes.len() > my_size { - Err(vm.new_value_error("byte string too long".to_string())) - } else { - bytes[0..wide_bytes.len()].copy_from_slice(wide_bytes.as_slice()); - if wide_bytes.len() < my_size { - bytes[my_size] = 0; - } - Ok(()) - } - } else { - Err(vm.new_value_error(format!( + let value = value.downcast_exact::(vm).map_err(|value| { + vm.new_value_error(format!( "bytes expected instead of {} instance", value.class().name - ))) + )) + })?; + let wide_bytes = value.to_vec(); + + if wide_bytes.len() > my_size { + return Err(vm.new_value_error("byte string too long".to_string())); + } + + bytes[..wide_bytes.len()].copy_from_slice(wide_bytes.as_slice()); + if wide_bytes.len() < my_size { + bytes[my_size] = 0; } } else { // unicode string zelf._type_ == "u" - if let Ok(value) = value.clone().downcast_exact::(vm) { - let wide_str = - unsafe { WideCString::from_str_with_nul_unchecked(value.to_string()) }; - - let wide_str_len = wide_str.len(); - - if wide_str.len() > my_size { - Err(vm.new_value_error("string too long".to_string())) - } else { - let res = if cfg!(windows) { - wide_str - .into_vec() - .iter_mut() - .map(|i| u16::to_ne_bytes(*i as u16).to_vec()) - .flatten() - .collect::>() - } else { - wide_str - .into_vec() - .iter_mut() - .map(|i| u32::to_ne_bytes(*i).to_vec()) - .flatten() - .collect::>() - }; - - bytes[0..wide_str_len].copy_from_slice(res.as_slice()); - - Ok(()) - } - } else { - Err(vm.new_value_error(format!( + let value = value.downcast_exact::(vm).map_err(|value| { + vm.new_value_error(format!( "unicode string expected instead of {} instance", value.class().name - ))) + )) + })?; + let wide_str = unsafe { WideCString::from_str_with_nul_unchecked(value.to_string()) }; + + let wide_str_len = wide_str.len(); + + if wide_str.len() > my_size { + return Err(vm.new_value_error("string too long".to_string())); } + let res = if cfg!(windows) { + wide_str + .into_vec() + .iter_mut() + .map(|i| u16::to_ne_bytes(*i as u16).to_vec()) + .flatten() + .collect::>() + } else { + wide_str + .into_vec() + .iter_mut() + .map(|i| u32::to_ne_bytes(*i).to_vec()) + .flatten() + .collect::>() + }; + + bytes[..wide_str_len].copy_from_slice(res.as_slice()); } + + Ok(()) } - #[pyproperty(name = "raw")] - fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + #[pyproperty] + fn raw(zelf: PyRef, vm: &VirtualMachine) -> PyResult { // zelf._type_ == "c" let obj = zelf.as_object(); let buffer = try_buffer_from_object(vm, obj)?; let buffer_vec = buffer.obj_bytes().to_vec(); - Ok(PyBytes::from(buffer_vec).into_object(vm)) + Ok(PyBytes::from(buffer_vec)) } - #[pyproperty(name = "raw", setter)] + #[pyproperty(setter)] fn set_raw(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let obj = zelf.as_object(); let my_buffer = try_buffer_from_object(vm, obj)?; @@ -487,12 +470,12 @@ impl PyCArray { } else { let mut borrowed_buffer = my_buffer.obj_bytes_mut(); let src = new_value.obj_bytes(); - borrowed_buffer[0..new_size].copy_from_slice(&src); + borrowed_buffer[..new_size].copy_from_slice(&src); Ok(()) } } - #[pymethod(name = "__len__")] + #[pymethod(magic)] fn len(&self) -> usize { self._length_ } @@ -591,7 +574,7 @@ impl PyCArray { } impl PyCDataFunctions for PyCArray { - fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { Ok(vm.new_pyobj( zelf._length_ * usize::try_from_object( @@ -601,15 +584,11 @@ impl PyCDataFunctions for PyCArray { )) } - fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { PyCDataFunctions::alignment_of_instances(zelf._type_.clone(), vm) } - fn ref_to( - zelf: PyRef, - offset: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { + fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult { let off_set = offset .into_option() .map_or(Ok(0), |o| usize::try_from_object(vm, o))?; @@ -624,7 +603,7 @@ impl PyCDataFunctions for PyCArray { } } - fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult { Ok(vm .new_pyobj(unsafe { &*zelf.borrow_value().inner } as *const _ as *const usize as usize)) } diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index feb009b854..107dc83002 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -16,7 +16,7 @@ use crate::slots::BufferProtocol; use crate::utils::Either; use crate::VirtualMachine; -use crate::stdlib::ctypes::array::make_array_with_lenght; +use crate::stdlib::ctypes::array::make_array_with_length; use crate::stdlib::ctypes::dll::dlsym; use crate::stdlib::ctypes::primitive::{new_simple_type, PySimpleType}; @@ -290,7 +290,7 @@ pub trait PyCDataSequenceMethods: PyValue { Err(vm.new_value_error(format!("Array length must be >= 0, not {} length", length))) } else { Ok( - make_array_with_lenght(zelf.clone_class(), length as usize, vm)? + make_array_with_length(zelf.clone_class(), length as usize, vm)? .as_object() .clone(), ) From efabe9b1b460ff8e2055419a6f5c51856f7c42b7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 8 Aug 2021 04:22:34 +0900 Subject: [PATCH 50/56] skeleton PyCSimpleType --- vm/src/stdlib/ctypes/mod.rs | 3 ++- vm/src/stdlib/ctypes/primitive.rs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index d011724229..e48543b68b 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -15,12 +15,13 @@ use basics::{addressof, alignment, byref, sizeof_func, PyCData}; use dll::*; use function::PyCFuncPtr; use pointer::{pointer_fn, PyCPointer, POINTER}; -use primitive::PySimpleType; +use primitive::{PyCSimpleType, PySimpleType}; use structure::PyCStructure; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; PyCData::make_class(ctx); + PyCSimpleType::make_class(ctx); py_module!(vm, "_ctypes", { "__version__" => ctx.new_str("1.1.0"), diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index c384c4da92..5dfdde23fd 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -5,6 +5,7 @@ use crate::builtins::memory::try_buffer_from_object; use crate::builtins::PyTypeRef; use crate::builtins::{ int::try_to_primitive, pybool::boolval, PyByteArray, PyBytes, PyFloat, PyInt, PyNone, PyStr, + PyType, }; use crate::function::OptionalArg; use crate::pyobject::{ @@ -268,7 +269,18 @@ pub fn new_simple_type( } } -#[pyclass(module = "_ctypes", name = "_SimpleCData", base = "PyCData")] +#[pyclass(module = "_ctypes", name = "CSimpleType", base = "PyType")] +pub struct PyCSimpleType {} + +#[pyimpl(flags(BASETYPE))] +impl PyCSimpleType {} + +#[pyclass( + module = "_ctypes", + name = "_SimpleCData", + base = "PyCData", + metaclass = "PyCSimpleType" +)] pub struct PySimpleType { pub _type_: String, value: AtomicCell, From b79477a50db7b7fbe484cf4087bd4343ea05ad88 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 8 Aug 2021 04:48:20 +0900 Subject: [PATCH 51/56] submodule extension for ctypes::dll --- vm/src/stdlib/ctypes/dll.rs | 74 ++++++++++++++++++++----------------- vm/src/stdlib/ctypes/mod.rs | 13 +++---- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/vm/src/stdlib/ctypes/dll.rs b/vm/src/stdlib/ctypes/dll.rs index 144df60300..bd32d2fa6a 100644 --- a/vm/src/stdlib/ctypes/dll.rs +++ b/vm/src/stdlib/ctypes/dll.rs @@ -1,45 +1,53 @@ -use crate::builtins::pystr::PyStrRef; -use crate::builtins::PyIntRef; -use crate::pyobject::{PyResult, TryFromObject}; -use crate::VirtualMachine; +pub(crate) use _ctypes::*; -use super::shared_lib::libcache; +#[pymodule] +pub(crate) mod _ctypes { + use crate::builtins::pystr::PyStrRef; + use crate::builtins::PyIntRef; + use crate::pyobject::{PyResult, TryFromObject}; + use crate::VirtualMachine; -pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { - let mut data_cache = libcache().write(); + use super::super::shared_lib::libcache; - let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); + #[pyfunction] + pub fn dlopen(lib_path: PyStrRef, vm: &VirtualMachine) -> PyResult { + let mut data_cache = libcache().write(); - match result { - Ok(lib) => Ok(vm.new_pyobj(lib.get_pointer())), - Err(_) => Err(vm.new_os_error(format!( - "{} : cannot open shared object file: No such file or directory", - lib_path.to_string() - ))), - } -} + let result = data_cache.get_or_insert_lib(lib_path.as_ref(), vm); -pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { - let func_name = str_ref.as_ref(); - let data_cache = libcache().read(); + match result { + Ok(lib) => Ok(vm.new_pyobj(lib.get_pointer())), + Err(_) => Err(vm.new_os_error(format!( + "{} : cannot open shared object file: No such file or directory", + lib_path.to_string() + ))), + } + } - match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { - Some(l) => match l.get_sym(func_name) { - Ok(ptr) => Ok(vm.new_pyobj(ptr as *const _ as usize)), - Err(e) => Err(vm.new_runtime_error(e)), - }, - _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), + #[pyfunction] + pub fn dlsym(slib: PyIntRef, str_ref: PyStrRef, vm: &VirtualMachine) -> PyResult { + let func_name = str_ref.as_ref(); + let data_cache = libcache().read(); + + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => match l.get_sym(func_name) { + Ok(ptr) => Ok(vm.new_pyobj(ptr as *const _ as usize)), + Err(e) => Err(vm.new_runtime_error(e)), + }, + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), + } } -} -pub fn dlclose(slib: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { - let data_cache = libcache().read(); + #[pyfunction] + pub fn dlclose(slib: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let data_cache = libcache().read(); - match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { - Some(l) => { - l.close(); - Ok(()) + match data_cache.get_lib(usize::try_from_object(vm, slib.as_object().clone())?) { + Some(l) => { + l.close(); + Ok(()) + } + _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), } - _ => Err(vm.new_runtime_error("not a valid pointer to a shared library".to_string())), } } diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index e48543b68b..3f99c74963 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -12,7 +12,6 @@ mod structure; use array::PyCArray; use basics::{addressof, alignment, byref, sizeof_func, PyCData}; -use dll::*; use function::PyCFuncPtr; use pointer::{pointer_fn, PyCPointer, POINTER}; use primitive::{PyCSimpleType, PySimpleType}; @@ -23,13 +22,9 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { PyCData::make_class(ctx); PyCSimpleType::make_class(ctx); - py_module!(vm, "_ctypes", { + let module = py_module!(vm, "_ctypes", { "__version__" => ctx.new_str("1.1.0"), - "dlopen" => ctx.new_function("dlopen", dlopen), - "dlsym" => ctx.new_function("dlsym", dlsym), - "dlclose" => ctx.new_function("dlclose", dlclose), - "alignment" => ctx.new_function("alignment", alignment), "sizeof" => ctx.new_function("sizeof", sizeof_func), "byref" => ctx.new_function("byref", byref), @@ -44,5 +39,9 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { "_Pointer" => PyCPointer::make_class(ctx), "Array" => PyCArray::make_class(ctx), "Struct" => PyCStructure::make_class(ctx) - }) + }); + + dll::extend_module(vm, &module); + + module } From 66a91cb56844896c1994e95ffc917b3dabdf0e51 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 8 Aug 2021 08:04:56 -0300 Subject: [PATCH 52/56] Add all tests from CPython and PyPy --- Lib/ctypes/test/test_arrays.py | 12 +- Lib/ctypes/test/test_bytes.py | 4 +- Lib/ctypes/test/test_funcptr.py | 1 - Lib/ctypes/test/test_functions.py | 12 +- Lib/ctypes/test/test_loading.py | 2 +- Lib/ctypes/test/test_unicode.py | 8 + extra_tests/snippets/ctypes_tests/__init__.py | 0 extra_tests/snippets/ctypes_tests/conftest.py | 102 +++ extra_tests/snippets/ctypes_tests/support.py | 57 ++ .../snippets/ctypes_tests/test_anon.py | 55 ++ .../snippets/ctypes_tests/test_array.py | 4 - .../snippets/ctypes_tests/test_base.py | 24 + .../snippets/ctypes_tests/test_bitfields.py | 19 + .../snippets/ctypes_tests/test_buffers.py | 12 +- .../ctypes_tests/test_callback_traceback.py | 35 + .../snippets/ctypes_tests/test_callbacks.py | 199 ++++ .../snippets/ctypes_tests/test_cast.py | 40 + .../snippets/ctypes_tests/test_commethods.py | 86 ++ .../snippets/ctypes_tests/test_errno.py | 19 + .../snippets/ctypes_tests/test_extra.py | 248 +++++ .../snippets/ctypes_tests/test_funcptr.py | 5 + .../snippets/ctypes_tests/test_functions.py | 243 +++++ .../ctypes_tests/test_guess_argtypes.py | 38 + .../snippets/ctypes_tests/test_keepalive.py | 263 ++++++ .../snippets/ctypes_tests/test_numbers.py | 56 ++ .../snippets/ctypes_tests/test_parameters.py | 24 + .../snippets/ctypes_tests/test_pointers.py | 92 ++ .../snippets/ctypes_tests/test_prototypes.py | 65 ++ .../snippets/ctypes_tests/test_structures.py | 223 +++++ .../snippets/ctypes_tests/test_unions.py | 28 + .../snippets/ctypes_tests/test_values.py | 10 + .../snippets/ctypes_tests/test_win32.py | 13 + vm/src/stdlib/ctypes/_ctypes_test.c | 860 ++++++++++++++++++ vm/src/stdlib/ctypes/_ctypes_test.h | 1 + 34 files changed, 2825 insertions(+), 35 deletions(-) create mode 100644 extra_tests/snippets/ctypes_tests/__init__.py create mode 100644 extra_tests/snippets/ctypes_tests/conftest.py create mode 100644 extra_tests/snippets/ctypes_tests/support.py create mode 100644 extra_tests/snippets/ctypes_tests/test_anon.py create mode 100644 extra_tests/snippets/ctypes_tests/test_base.py create mode 100644 extra_tests/snippets/ctypes_tests/test_bitfields.py create mode 100644 extra_tests/snippets/ctypes_tests/test_callback_traceback.py create mode 100644 extra_tests/snippets/ctypes_tests/test_callbacks.py create mode 100644 extra_tests/snippets/ctypes_tests/test_cast.py create mode 100644 extra_tests/snippets/ctypes_tests/test_commethods.py create mode 100644 extra_tests/snippets/ctypes_tests/test_errno.py create mode 100644 extra_tests/snippets/ctypes_tests/test_extra.py create mode 100644 extra_tests/snippets/ctypes_tests/test_funcptr.py create mode 100644 extra_tests/snippets/ctypes_tests/test_functions.py create mode 100644 extra_tests/snippets/ctypes_tests/test_guess_argtypes.py create mode 100644 extra_tests/snippets/ctypes_tests/test_keepalive.py create mode 100644 extra_tests/snippets/ctypes_tests/test_numbers.py create mode 100644 extra_tests/snippets/ctypes_tests/test_parameters.py create mode 100644 extra_tests/snippets/ctypes_tests/test_pointers.py create mode 100644 extra_tests/snippets/ctypes_tests/test_prototypes.py create mode 100644 extra_tests/snippets/ctypes_tests/test_structures.py create mode 100644 extra_tests/snippets/ctypes_tests/test_unions.py create mode 100644 extra_tests/snippets/ctypes_tests/test_values.py create mode 100644 extra_tests/snippets/ctypes_tests/test_win32.py create mode 100644 vm/src/stdlib/ctypes/_ctypes_test.c create mode 100644 vm/src/stdlib/ctypes/_ctypes_test.h diff --git a/Lib/ctypes/test/test_arrays.py b/Lib/ctypes/test/test_arrays.py index 3c5ff1d511..14603b7049 100644 --- a/Lib/ctypes/test/test_arrays.py +++ b/Lib/ctypes/test/test_arrays.py @@ -177,43 +177,37 @@ def test_bad_subclass(self): with self.assertRaises(AttributeError): class T(Array): pass - T() with self.assertRaises(AttributeError): class T(Array): _type_ = c_int - T() with self.assertRaises(AttributeError): class T(Array): _length_ = 13 - T() + def test_bad_length(self): with self.assertRaises(ValueError): class T(Array): _type_ = c_int _length_ = - sys.maxsize * 2 - T() with self.assertRaises(ValueError): class T(Array): _type_ = c_int _length_ = -1 - T() with self.assertRaises(TypeError): class T(Array): _type_ = c_int _length_ = 1.87 - T() with self.assertRaises(OverflowError): class T(Array): _type_ = c_int _length_ = sys.maxsize * 2 - T() + def test_zero_length(self): # _length_ can be zero. class T(Array): _type_ = c_int _length_ = 0 - T() - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_empty_element_struct(self): class EmptyStruct(Structure): _fields_ = [] diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py index c3e5f99fb7..092ec5af05 100644 --- a/Lib/ctypes/test/test_bytes.py +++ b/Lib/ctypes/test/test_bytes.py @@ -34,8 +34,7 @@ def test_c_char_p(self): def test_c_wchar_p(self): c_wchar_p("foo bar") self.assertRaises(TypeError, c_wchar_p, b"foo bar") - - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") + def test_struct(self): class X(Structure): _fields_ = [("a", c_char * 3)] @@ -45,7 +44,6 @@ class X(Structure): self.assertEqual(x.a, b"abc") self.assertEqual(type(x.a), bytes) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_struct_W(self): class X(Structure): _fields_ = [("a", c_wchar * 3)] diff --git a/Lib/ctypes/test/test_funcptr.py b/Lib/ctypes/test/test_funcptr.py index 1d94e51e00..e0b9b54e97 100644 --- a/Lib/ctypes/test/test_funcptr.py +++ b/Lib/ctypes/test/test_funcptr.py @@ -42,7 +42,6 @@ def func(a, b): if not WINFUNCTYPE is CFUNCTYPE: self.assertRaises(TypeError, s, 1, 2, 3) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_structures(self): WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int) diff --git a/Lib/ctypes/test/test_functions.py b/Lib/ctypes/test/test_functions.py index 2ed5a95d5b..d3c6536f27 100644 --- a/Lib/ctypes/test/test_functions.py +++ b/Lib/ctypes/test/test_functions.py @@ -25,8 +25,6 @@ class POINT(Structure): class RECT(Structure): _fields_ = [("left", c_int), ("top", c_int), ("right", c_int), ("bottom", c_int)] - -@unittest.skip("TODO: RUSTPYTHON, implrment Structure") class FunctionTestCase(unittest.TestCase): def test_mro(self): @@ -82,7 +80,6 @@ def test_wchar_result(self): result = f(0, 0, 0, 0, 0, 0) self.assertEqual(result, '\x00') - @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") def test_voidresult(self): f = dll._testfunc_v f.restype = None @@ -177,7 +174,6 @@ def test_stringresult(self): result = f(None) self.assertEqual(result, None) - @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") def test_pointers(self): f = dll._testfunc_p_p f.restype = POINTER(c_int) @@ -213,7 +209,6 @@ def test_pointers(self): result = f(byref(c_int(99))) self.assertNotEqual(result.contents, 99) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_errors(self): f = dll._testfunc_p_p f.restype = c_int @@ -315,7 +310,6 @@ def test_errors(self): self.assertRaises(AttributeError, getattr, dll, "_xxx_yyy") self.assertRaises(ValueError, c_int.in_dll, dll, "_xxx_yyy") - @unittest.skip("TODO: RUSTPYTHON, implrment POINTER") def test_byval(self): # without prototype @@ -337,7 +331,6 @@ def test_byval(self): expected = 203, 101, 102 self.assertEqual(got, expected) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_struct_return_2H(self): class S2H(Structure): _fields_ = [("x", c_short), @@ -348,7 +341,6 @@ class S2H(Structure): s2h = dll.ret_2h_func(inp) self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') def test_struct_return_2H_stdcall(self): class S2H(Structure): @@ -360,7 +352,6 @@ class S2H(Structure): s2h = windll.s_ret_2h_func(S2H(99, 88)) self.assertEqual((s2h.x, s2h.y), (99*2, 88*3)) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") def test_struct_return_8H(self): class S8I(Structure): _fields_ = [("a", c_int), @@ -378,7 +369,6 @@ class S8I(Structure): self.assertEqual((s8i.a, s8i.b, s8i.c, s8i.d, s8i.e, s8i.f, s8i.g, s8i.h), (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) - @unittest.skip("TODO: RUSTPYTHON, implrment Structure") @unittest.skipUnless(sys.platform == "win32", 'Windows-specific test') def test_struct_return_8H_stdcall(self): class S8I(Structure): @@ -399,7 +389,7 @@ class S8I(Structure): (9*2, 8*3, 7*4, 6*5, 5*6, 4*7, 3*8, 2*9)) def test_sf1651235(self): - # see http://www.python.org/sf/1651235 + # see https://www.python.org/sf/1651235 proto = CFUNCTYPE(c_int, RECT, POINT) def callback(*args): diff --git a/Lib/ctypes/test/test_loading.py b/Lib/ctypes/test/test_loading.py index 7b930f92c7..ea892277c4 100644 --- a/Lib/ctypes/test/test_loading.py +++ b/Lib/ctypes/test/test_loading.py @@ -93,7 +93,7 @@ def test_1703286_A(self): # NOT fit into a 32-bit integer. FreeLibrary must be able # to accept this address. - # These are tests for http://www.python.org/sf/1703286 + # These are tests for https://www.python.org/sf/1703286 handle = LoadLibrary("advapi32") FreeLibrary(handle) diff --git a/Lib/ctypes/test/test_unicode.py b/Lib/ctypes/test/test_unicode.py index c200af7b65..60c75424b7 100644 --- a/Lib/ctypes/test/test_unicode.py +++ b/Lib/ctypes/test/test_unicode.py @@ -26,6 +26,14 @@ def test_buffers(self): self.assertEqual(buf[::2], 'a\xe4\xfc') self.assertEqual(buf[6:5:-1], "") + def test_embedded_null(self): + class TestStruct(ctypes.Structure): + _fields_ = [("unicode", ctypes.c_wchar_p)] + t = TestStruct() + # This would raise a ValueError: + t.unicode = "foo\0bar\0\0" + + func = ctypes.CDLL(_ctypes_test.__file__)._testfunc_p_p class StringTestCase(UnicodeTestCase): diff --git a/extra_tests/snippets/ctypes_tests/__init__.py b/extra_tests/snippets/ctypes_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/extra_tests/snippets/ctypes_tests/conftest.py b/extra_tests/snippets/ctypes_tests/conftest.py new file mode 100644 index 0000000000..05283895a9 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/conftest.py @@ -0,0 +1,102 @@ +import py +import pytest +import sys +import os + +# XXX: copied from pypy/tool/cpyext/extbuild.py +if os.name != 'nt': + so_ext = 'so' +else: + so_ext = 'dll' + +def _build(cfilenames, outputfilename, compile_extra, link_extra, + include_dirs, libraries, library_dirs): + try: + # monkeypatch distutils for some versions of msvc compiler + import setuptools + except ImportError: + # XXX if this fails and is required, + # we must call pypy -mensurepip after translation + pass + from distutils.ccompiler import new_compiler + from distutils import sysconfig + + # XXX for Darwin running old versions of CPython 2.7.x + sysconfig.get_config_vars() + + compiler = new_compiler(force=1) + sysconfig.customize_compiler(compiler) # XXX + objects = [] + for cfile in cfilenames: + cfile = py.path.local(cfile) + old = cfile.dirpath().chdir() + try: + res = compiler.compile([cfile.basename], + include_dirs=include_dirs, extra_preargs=compile_extra) + assert len(res) == 1 + cobjfile = py.path.local(res[0]) + assert cobjfile.check() + objects.append(str(cobjfile)) + finally: + old.chdir() + + compiler.link_shared_object( + objects, str(outputfilename), + libraries=libraries, + extra_preargs=link_extra, + library_dirs=library_dirs) + +def c_compile(cfilenames, outputfilename, + compile_extra=None, link_extra=None, + include_dirs=None, libraries=None, library_dirs=None): + compile_extra = compile_extra or [] + link_extra = link_extra or [] + include_dirs = include_dirs or [] + libraries = libraries or [] + library_dirs = library_dirs or [] + if sys.platform == 'win32': + link_extra = link_extra + ['/DEBUG'] # generate .pdb file + if sys.platform == 'darwin': + # support Fink & Darwinports + for s in ('/sw/', '/opt/local/'): + if (s + 'include' not in include_dirs + and os.path.exists(s + 'include')): + include_dirs.append(s + 'include') + if s + 'lib' not in library_dirs and os.path.exists(s + 'lib'): + library_dirs.append(s + 'lib') + + outputfilename = py.path.local(outputfilename).new(ext=so_ext) + saved_environ = os.environ.copy() + try: + _build( + cfilenames, outputfilename, + compile_extra, link_extra, + include_dirs, libraries, library_dirs) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename +# end copy + +def compile_so_file(udir): + cfile = py.path.local(__file__).dirpath().join("_ctypes_test.c") + + if sys.platform == 'win32': + libraries = ['oleaut32'] + else: + libraries = [] + + return c_compile([cfile], str(udir / '_ctypes_test'), libraries=libraries) + +@pytest.fixture(scope='session') +def sofile(tmpdir_factory): + udir = tmpdir_factory.mktemp('_ctypes_test') + return str(compile_so_file(udir)) + +@pytest.fixture +def dll(sofile): + from ctypes import CDLL + return CDLL(str(sofile)) diff --git a/extra_tests/snippets/ctypes_tests/support.py b/extra_tests/snippets/ctypes_tests/support.py new file mode 100644 index 0000000000..7092507d77 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/support.py @@ -0,0 +1,57 @@ +import py +import sys +import ctypes + +try: + import _rawffi +except ImportError: + _rawffi = None + +class WhiteBoxTests: + + def setup_class(cls): + if _rawffi: + py.test.skip("white-box tests for pypy _rawffi based ctypes impl") + +def del_funcptr_refs_maybe(obj, attrname): + dll = getattr(obj, attrname, None) + if not dll: + return + _FuncPtr = dll._FuncPtr + for name in dir(dll): + obj = getattr(dll, name, None) + if isinstance(obj, _FuncPtr): + delattr(dll, name) + +class BaseCTypesTestChecker: + def setup_class(cls): + if _rawffi: + import gc + for _ in range(4): + gc.collect() + try: + cls.old_num = _rawffi._num_of_allocated_objects() + except RuntimeError: + pass + + def teardown_class(cls): + if not hasattr(sys, 'pypy_translation_info'): + return + if sys.pypy_translation_info['translation.gc'] == 'boehm': + return # it seems that boehm has problems with __del__, so not + # everything is freed + # + mod = sys.modules[cls.__module__] + del_funcptr_refs_maybe(mod, 'dll') + del_funcptr_refs_maybe(mod, 'dll2') + del_funcptr_refs_maybe(mod, 'lib') + del_funcptr_refs_maybe(mod, 'testdll') + del_funcptr_refs_maybe(mod, 'ctdll') + del_funcptr_refs_maybe(cls, '_dll') + # + if hasattr(cls, 'old_num'): + import gc + for _ in range(4): + gc.collect() + # there is one reference coming from the byref() above + assert _rawffi._num_of_allocated_objects() == cls.old_num diff --git a/extra_tests/snippets/ctypes_tests/test_anon.py b/extra_tests/snippets/ctypes_tests/test_anon.py new file mode 100644 index 0000000000..0c42b696f8 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_anon.py @@ -0,0 +1,55 @@ +import pytest +from ctypes import * + +@pytest.mark.pypy_only +def test_nested(): + class ANON_S(Structure): + _fields_ = [("a", c_int)] + + class ANON_U(Union): + _fields_ = [("_", ANON_S), + ("b", c_int)] + _anonymous_ = ["_"] + + class Y(Structure): + _fields_ = [("x", c_int), + ("_", ANON_U), + ("y", c_int)] + _anonymous_ = ["_"] + + assert Y.x.offset == 0 + assert Y.a.offset == sizeof(c_int) + assert Y.b.offset == sizeof(c_int) + assert Y._.offset == sizeof(c_int) + assert Y.y.offset == sizeof(c_int) * 2 + + assert Y._names_ == ['x', 'a', 'b', 'y'] + +def test_anonymous_fields_on_instance(): + # this is about the *instance-level* access of anonymous fields, + # which you'd guess is the most common, but used not to work + # (issue #2230) + + class B(Structure): + _fields_ = [("x", c_int), ("y", c_int), ("z", c_int)] + class A(Structure): + _anonymous_ = ["b"] + _fields_ = [("b", B)] + + a = A() + a.x = 5 + assert a.x == 5 + assert a.b.x == 5 + a.b.x += 1 + assert a.x == 6 + + class C(Structure): + _anonymous_ = ["a"] + _fields_ = [("v", c_int), ("a", A)] + + c = C() + c.v = 3 + c.y = -8 + assert c.v == 3 + assert c.y == c.a.y == c.a.b.y == -8 + assert not hasattr(c, 'b') diff --git a/extra_tests/snippets/ctypes_tests/test_array.py b/extra_tests/snippets/ctypes_tests/test_array.py index 052508c5e6..bf4de49fd5 100644 --- a/extra_tests/snippets/ctypes_tests/test_array.py +++ b/extra_tests/snippets/ctypes_tests/test_array.py @@ -1,5 +1,4 @@ import pytest -import unittest from ctypes import * def test_slice(): @@ -19,7 +18,6 @@ def test_init_again(): addr2 = addressof(sz) assert addr1 == addr2 -@unittest.skip("TODO: RUSTPYTHON, implement Structure") def test_array_of_structures(): class X(Structure): _fields_ = [('x', c_int), ('y', c_int)] @@ -31,7 +29,6 @@ class X(Structure): y[1] = x assert y[1].y == 3 -@unittest.skip("TODO: RUSTPYTHON, implement POINTER") def test_output_simple(): A = c_char * 10 TP = POINTER(A) @@ -49,7 +46,6 @@ def test_output_simple_array(): aa = AA() assert aa[0] != b'' -@unittest.skip("TODO: RUSTPYTHON, implement Structure") def test_output_complex_test(): class Car(Structure): _fields_ = [("brand", c_char * 10), diff --git a/extra_tests/snippets/ctypes_tests/test_base.py b/extra_tests/snippets/ctypes_tests/test_base.py new file mode 100644 index 0000000000..e3f9f21f16 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_base.py @@ -0,0 +1,24 @@ +import pytest +from ctypes import * + +pytestmark = pytest.mark.pypy_only + +def test_pointer(): + p = pointer(pointer(c_int(2))) + x = p[0] + assert x._base is p + +def test_structure(): + class X(Structure): + _fields_ = [('x', POINTER(c_int)), + ('y', POINTER(c_int))] + + x = X() + assert x.y._base is x + assert x.y._index == 1 + +def test_array(): + X = POINTER(c_int) * 24 + x = X() + assert x[16]._base is x + assert x[16]._index == 16 diff --git a/extra_tests/snippets/ctypes_tests/test_bitfields.py b/extra_tests/snippets/ctypes_tests/test_bitfields.py new file mode 100644 index 0000000000..2d9a139052 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_bitfields.py @@ -0,0 +1,19 @@ +import pytest +from ctypes import * + + +def test_set_fields_attr(): + class A(Structure): + pass + A._fields_ = [("a", c_byte), ("b", c_ubyte)] + +def test_set_fields_attr_bitfields(): + class A(Structure): + pass + A._fields_ = [("a", POINTER(A)), ("b", c_ubyte, 4)] + +def test_set_fields_cycle_fails(): + class A(Structure): + pass + with pytest.raises(AttributeError): + A._fields_ = [("a", A)] diff --git a/extra_tests/snippets/ctypes_tests/test_buffers.py b/extra_tests/snippets/ctypes_tests/test_buffers.py index 61ada37595..adbf664645 100644 --- a/extra_tests/snippets/ctypes_tests/test_buffers.py +++ b/extra_tests/snippets/ctypes_tests/test_buffers.py @@ -56,15 +56,15 @@ def normalize(fmt): (c_double, ">>" % (value,)) + monkeypatch.setattr(_rawffi, 'exit', custom_exit) + cb = CFUNCTYPE(c_int, c_int)(callback_func) + cb2 = cast(cast(cb, c_void_p), CFUNCTYPE(c_int, c_int)) + out, err = capsys.readouterr() + assert not err + cb2(0) + out, err = capsys.readouterr() + assert err.splitlines()[-1] == "Exception: <<>>" + # + cb = CFUNCTYPE(c_int, c_int)(callback_func) + cb(0) + out, err = capsys.readouterr() + assert err.splitlines()[-1] == "Exception: <<>>" diff --git a/extra_tests/snippets/ctypes_tests/test_callbacks.py b/extra_tests/snippets/ctypes_tests/test_callbacks.py new file mode 100644 index 0000000000..98b553cd30 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_callbacks.py @@ -0,0 +1,199 @@ +import pytest + +import math +from ctypes import * +from .support import BaseCTypesTestChecker + +functypes = [CFUNCTYPE] +try: + functypes.append(WINFUNCTYPE) +except NameError: + pass + + +def callback(*args): + callback.got_args = args + return args[-1] + +unwrapped_types = { + c_float: (float,), + c_double: (float,), + c_char: (bytes,), + c_char_p: (bytes,), + c_uint: (int,), + c_ulong: (int,), + } + +@pytest.mark.parametrize("typ, arg", [ + (c_byte, 42), + (c_byte, -42), + (c_ubyte, 42), + (c_short, 42), + (c_short, -42), + (c_ushort, 42), + (c_int, 42), + (c_int, -42), + (c_uint, 42), + (c_long, 42), + (c_long, -42), + (c_ulong, 42), + (c_longlong, 42), + (c_longlong, -42), + (c_ulonglong, 42), + (c_float, math.e), # only almost equal: double -> float -> double + (c_float, -math.e), + (c_double, 3.14), + (c_double, -3.14), + (c_char, b"x"), + (c_char, b"a"), +]) +@pytest.mark.parametrize('functype', functypes) +def test_types(typ, arg, functype): + PROTO = functype(typ, typ) + cfunc = PROTO(callback) + result = cfunc(arg) + if typ == c_float: + assert abs(result - arg) < 0.000001 + else: + assert callback.got_args == (arg,) + assert result == arg + + result2 = cfunc(typ(arg)) + assert type(result2) in unwrapped_types.get(typ, (int,)) + + PROTO = functype(typ, c_byte, typ) + result = PROTO(callback)(-3, arg) + if typ == c_float: + assert abs(result - arg) < 0.000001 + else: + assert callback.got_args == (-3, arg) + assert result == arg + +@pytest.mark.parametrize('functype', functypes) +def test_unsupported_restype_1(functype): + # Only "fundamental" result types are supported for callback + # functions, the type must have a non-NULL stgdict->setfunc. + # POINTER(c_double), for example, is not supported. + + prototype = functype(POINTER(c_double)) + # The type is checked when the prototype is called + with pytest.raises(TypeError): + prototype(lambda: None) + + +def test_callback_with_struct_argument(): + class RECT(Structure): + _fields_ = [("left", c_int), ("top", c_int), + ("right", c_int), ("bottom", c_int)] + + proto = CFUNCTYPE(c_int, RECT) + + def callback(point): + point.left *= -1 + return point.left + point.top + point.right + point.bottom + + cbp = proto(callback) + rect = RECT(-1000, 100, 10, 1) + res = cbp(rect) + assert res == 1111 + assert rect.left == -1000 # must not have been changed! + +def test_callback_from_c_with_struct_argument(dll): + class RECT(Structure): + _fields_ = [("left", c_long), ("top", c_long), + ("right", c_long), ("bottom", c_long)] + + proto = CFUNCTYPE(c_int, RECT) + + def callback(point): + return point.left + point.top + point.right + point.bottom + + cbp = proto(callback) + rect = RECT(1000, 100, 10, 1) + + call_callback_with_rect = dll.call_callback_with_rect + call_callback_with_rect.restype = c_int + call_callback_with_rect.argtypes = [proto, RECT] + res = call_callback_with_rect(cbp, rect) + assert res == 1111 + +def test_callback_unsupported_return_struct(): + class RECT(Structure): + _fields_ = [("left", c_int), ("top", c_int), + ("right", c_int), ("bottom", c_int)] + + proto = CFUNCTYPE(RECT, c_int) + with pytest.raises(TypeError): + proto(lambda r: 0) + + +def test_qsort(dll): + PI = POINTER(c_int) + A = c_int*5 + a = A() + for i in range(5): + a[i] = 5-i + + assert a[0] == 5 # sanity + + def comp(a, b): + a = a.contents.value + b = b.contents.value + if a < b: + return -1 + elif a > b: + return 1 + else: + return 0 + qs = dll.my_qsort + qs.restype = None + CMP = CFUNCTYPE(c_int, PI, PI) + qs.argtypes = (PI, c_size_t, c_size_t, CMP) + + qs(cast(a, PI), 5, sizeof(c_int), CMP(comp)) + + res = list(a) + + assert res == [1,2,3,4,5] + +def test_pyobject_as_opaque(dll): + def callback(arg): + return arg() + + CTP = CFUNCTYPE(c_int, py_object) + cfunc = dll._testfunc_callback_opaque + cfunc.argtypes = [CTP, py_object] + cfunc.restype = c_int + res = cfunc(CTP(callback), lambda : 3) + assert res == 3 + +def test_callback_void(capsys, dll): + def callback(): + pass + + CTP = CFUNCTYPE(None) + cfunc = dll._testfunc_callback_void + cfunc.argtypes = [CTP] + cfunc.restype = int + cfunc(CTP(callback)) + out, err = capsys.readouterr() + assert (out, err) == ("", "") + + +def test_callback_pyobject(): + def callback(obj): + return obj + + FUNC = CFUNCTYPE(py_object, py_object) + cfunc = FUNC(callback) + param = c_int(42) + assert cfunc(param) is param + +def test_raise_argumenterror(): + def callback(x): + pass + FUNC = CFUNCTYPE(None, c_void_p) + cfunc = FUNC(callback) + param = c_uint(42) + with pytest.raises(ArgumentError): + cfunc(param) diff --git a/extra_tests/snippets/ctypes_tests/test_cast.py b/extra_tests/snippets/ctypes_tests/test_cast.py new file mode 100644 index 0000000000..024978d497 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_cast.py @@ -0,0 +1,40 @@ +import pytest + +from ctypes import * + +def test_cast_functype(dll): + # make sure we can cast function type + my_sqrt = dll.my_sqrt + saved_objects = my_sqrt._objects.copy() + sqrt = cast(cast(my_sqrt, c_void_p), CFUNCTYPE(c_double, c_double)) + assert sqrt(4.0) == 2.0 + assert not cast(0, CFUNCTYPE(c_int)) + # + assert sqrt._objects is my_sqrt._objects # on CPython too + my_sqrt._objects.clear() + my_sqrt._objects.update(saved_objects) + +def test_cast_argumenterror(): + param = c_uint(42) + with pytest.raises(ArgumentError): + cast(param, c_void_p) + +def test_c_bool(): + x = c_bool(42) + assert x.value is True + x = c_bool(0.0) + assert x.value is False + x = c_bool("") + assert x.value is False + x = c_bool(['yadda']) + assert x.value is True + +def test_cast_array(): + import sys + data = b'data' + ubyte = c_ubyte * len(data) + byteslike = ubyte.from_buffer_copy(data) + m = memoryview(byteslike) + if sys.version_info > (3, 3): + b = m.cast('B') + assert bytes(b) == data diff --git a/extra_tests/snippets/ctypes_tests/test_commethods.py b/extra_tests/snippets/ctypes_tests/test_commethods.py new file mode 100644 index 0000000000..1912aaa80b --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_commethods.py @@ -0,0 +1,86 @@ +# unittest for SOME ctypes com function calls. +# Can't resist from implementing some kind of mini-comtypes +# theller ;-) + +import pytest +import sys +if sys.platform != "win32": + # this doesn't work, it still tries to do module-level imports + # pytestmark = pytest.mark.skip("skip_the_whole_module") + pytest.importorskip('skip_the_whole_module') # hack! + + +import ctypes, types, unittest +from ctypes import HRESULT +from _ctypes import COMError + +oleaut32 = ctypes.OleDLL("oleaut32") + +class UnboundMethod(object): + def __init__(self, func, index, name): + self.func = func + self.index = index + self.name = name + self.__doc__ = func.__doc__ + + def __repr__(self): + return "" % (self.index, self.name, id(self)) + + def __get__(self, instance, owner): + if instance is None: + return self + return types.MethodType(self.func, instance, owner) + +def commethod(index, restype, *argtypes): + """A decorator that generates COM methods. The decorated function + itself is not used except for it's name.""" + def make_commethod(func): + comfunc = ctypes.WINFUNCTYPE(restype, *argtypes)(index, func.__name__) + comfunc.__name__ = func.__name__ + comfunc.__doc__ = func.__doc__ + return UnboundMethod(comfunc, index, func.__name__) + return make_commethod + +class ICreateTypeLib2(ctypes.c_void_p): + + @commethod(1, ctypes.c_long) + def AddRef(self): + pass + + @commethod(2, ctypes.c_long) + def Release(self): + pass + + @commethod(4, HRESULT, ctypes.c_wchar_p) + def SetName(self): + """Set the name of the library.""" + + @commethod(12, HRESULT) + def SaveAllChanges(self): + pass + + +CreateTypeLib2 = oleaut32.CreateTypeLib2 +CreateTypeLib2.argtypes = (ctypes.c_int, ctypes.c_wchar_p, ctypes.POINTER(ICreateTypeLib2)) + +################################################################ + +def test_basic_comtypes(): + punk = ICreateTypeLib2() + hr = CreateTypeLib2(0, "foobar.tlb", punk) + assert hr == 0 + + assert 2 == punk.AddRef() + assert 3 == punk.AddRef() + assert 4 == punk.AddRef() + + punk.SetName("TypeLib_ByPYPY") + with pytest.raises(COMError): + punk.SetName(None) + + # This would save the typelib to disk. + ## punk.SaveAllChanges() + + assert 3 == punk.Release() + assert 2 == punk.Release() + assert 1 == punk.Release() diff --git a/extra_tests/snippets/ctypes_tests/test_errno.py b/extra_tests/snippets/ctypes_tests/test_errno.py new file mode 100644 index 0000000000..3b4613c3fb --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_errno.py @@ -0,0 +1,19 @@ +import pytest + +import ctypes +_rawffi = pytest.importorskip('_rawffi') # PyPy-only + +def test_errno_saved_and_restored(): + def check(): + assert _rawffi.get_errno() == 42 + assert ctypes.get_errno() == old + check.free_temp_buffers = lambda *args: None + f = ctypes._CFuncPtr() + old = _rawffi.get_errno() + f._flags_ = _rawffi.FUNCFLAG_USE_ERRNO + ctypes.set_errno(42) + f._call_funcptr(check) + assert _rawffi.get_errno() == old + ctypes.set_errno(0) + +# see also test_functions.test_errno diff --git a/extra_tests/snippets/ctypes_tests/test_extra.py b/extra_tests/snippets/ctypes_tests/test_extra.py new file mode 100644 index 0000000000..dea9dbb2da --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_extra.py @@ -0,0 +1,248 @@ +""" +The purpose of this test file is to check how ctypes really work, +down to what aliases what and what exact types operations return. +""" + +import pytest +from ctypes import * + +def test_primitive_pointer(): + x = c_int(5) + assert x.value == 5 + x.value = 6 + assert x.value == 6 + + p = pointer(x) # p ---> x = 6 + assert isinstance(p.contents, c_int) + p.contents.value += 1 + assert x.value == 7 # p ---> x = 7 + + y = c_int(12) + p.contents = y # p ---> y = 12 + p.contents.value += 2 # p ---> y = 14 + assert y.value == 14 + assert x.value == 7 + + pp = pointer(p) # pp ---> p ---> y = 14 + pp.contents.contents = x # pp ---> p ---> x = 7 + p.contents.value += 2 # pp ---> p ---> x = 9 + assert x.value == 9 + + assert isinstance(p[0], int) + p[0] += 1 # pp ---> p ---> x = 10 + assert x.value == 10 + z = c_int(86) + p[0] = z # pp ---> p ---> x = 86 (not z!) + assert x.value == 86 + z.value = 84 + assert x.value == 86 + + assert isinstance(pp[0], POINTER(c_int)) + assert pp[0].contents.value == x.value == 86 + pp[0].contents = z # pp ---> p ---> z = 84 + assert p.contents.value == z.value == 84 + +## *** the rest is commented out because it should work but occasionally +## *** trigger a ctypes bug (SourceForge bug #1467852). *** +## q = pointer(y) +## pp[0] = q # pp ---> p ---> y = 14 +## assert y.value == 14 # (^^^ not q! ) +## assert p.contents.value == 14 +## assert pp.contents.contents.value == 14 +## q.contents = x +## assert pp.contents.contents.value == 14 + + +def test_char_p(): + x = c_char_p(b"hello\x00world") + assert x.value == b"hello" + x.value = b"world" + assert x.value == b"world" + + p = pointer(x) + assert p[0] == x.value == b"world" + p[0] = b"other" + assert x.value == p.contents.value == p[0] == b"other" + + myarray = (c_char_p * 10)() + myarray[7] = b"hello" + assert isinstance(myarray[7], bytes) + assert myarray[7] == b"hello" + +def test_struct(): + class tagpoint(Structure): + _fields_ = [('x', c_int), + ('p', POINTER(c_short))] + + y = c_short(123) + z = c_short(-33) + s = tagpoint() + s.p.contents = z + assert s.p.contents.value == -33 + s.p = pointer(y) + assert s.p.contents.value == 123 + s.p.contents.value = 124 + assert y.value == 124 + + s = tagpoint(x=12) + assert s.x == 12 + s = tagpoint(17, p=pointer(z)) + assert s.x == 17 + assert s.p.contents.value == -33 + +def test_ptr_array(): + a = (POINTER(c_ushort) * 5)() + x = c_ushort(52) + y = c_ushort(1000) + + a[2] = pointer(x) + assert a[2].contents.value == 52 + a[2].contents.value += 1 + assert x.value == 53 + + a[3].contents = y + assert a[3].contents.value == 1000 + a[3].contents.value += 1 + assert y.value == 1001 + +def test_void_p(): + x = c_int(12) + p1 = cast(pointer(x), c_void_p) + p2 = cast(p1, POINTER(c_int)) + assert p2.contents.value == 12 + +def test_char_array(): + a = (c_char * 3)() + a[0] = b'x' + a[1] = b'y' + assert a.value == b'xy' + a[2] = b'z' + assert a.value == b'xyz' + + b = create_string_buffer(3) + assert type(b) is type(a) + assert len(b) == 3 + + b.value = b"nxw" + assert b[0] == b'n' + assert b[1] == b'x' + assert b[2] == b'w' + + b.value = b"?" + assert b[0] == b'?' + assert b[1] == b'\x00' + assert b[2] == b'w' + + class S(Structure): + _fields_ = [('p', POINTER(c_char))] + + s = S() + s.p = b + s.p.contents.value = b'!' + assert b.value == b'!' + + assert len(create_string_buffer(0)) == 0 + +def test_array(): + a = (c_int * 10)() + + class S(Structure): + _fields_ = [('p', POINTER(c_int))] + + s = S() + s.p = a + s.p.contents.value = 42 + assert a[0] == 42 + + a = (c_int * 5)(5, 6, 7) + assert list(a) == [5, 6, 7, 0, 0] + +def test_truth_value(): + p = POINTER(c_int)() + assert not p + p.contents = c_int(12) + assert p + # I can't figure out how to reset p to NULL... + + assert c_int(12) + assert not c_int(0) # a bit strange, if you ask me + assert c_int(-1) + assert not c_byte(0) + assert not c_char(b'\x00') # hum + assert not c_float(0.0) + assert not c_double(0.0) + assert not c_ulonglong(0) + assert c_ulonglong(2**42) + + assert c_char_p(b"hello") + assert c_char_p(b"") + assert not c_char_p(None) + + assert not c_void_p() + +def test_sizeof(): + x = create_string_buffer(117) + assert sizeof(x) == 117 # assumes that chars are one byte each + x = (c_int * 42)() + assert sizeof(x) == 42 * sizeof(c_int) + +def test_convert_pointers(dll): + func = dll._testfunc_p_p + func.restype = c_char_p + + # automatic conversions to c_char_p + func.argtypes = [c_char_p] + assert func(b"hello") == b"hello" + assert func(c_char_p(b"hello")) == b"hello" + assert func((c_char * 6)(*b"hello")) == b"hello" + assert func(create_string_buffer(b"hello")) == b"hello" + + # automatic conversions to c_void_p + func.argtypes = [c_void_p] + assert func(b"hello") == b"hello" + assert func(c_char_p(b"hello")) == b"hello" + assert func((c_char * 6)(*b"hello")) == b"hello" + assert func((c_byte * 6)(104,101,108,108,111)) ==b"hello" + assert func(create_string_buffer(b"hello")) == b"hello" + +def test_varsize_cast(): + import struct + N = struct.calcsize("l") + x = c_long() + p = cast(pointer(x), POINTER(c_ubyte*N)) + for i, c in enumerate(struct.pack("l", 12345678)): + p.contents[i] = c + assert x.value == 12345678 + +def test_cfunctype_inspection(): + T = CFUNCTYPE(c_int, c_ubyte) + # T.argtypes and T.restype don't work, must use a dummy instance + assert list(T().argtypes) == [c_ubyte] + assert T().restype == c_int + +def test_from_param(): + # other working cases of from_param + assert isinstance(c_void_p.from_param((c_int * 4)()), c_int * 4) + +def test_array_mul(): + assert c_int * 10 == 10 * c_int + with pytest.raises(TypeError): + c_int * c_int + with pytest.raises(TypeError): + c_int * (-1.5) + with pytest.raises(TypeError): + c_int * "foo" + with pytest.raises(TypeError): + (-1.5) * c_int + with pytest.raises(TypeError): + "foo" * c_int + +def test_cast_none(): + def check(P): + x = cast(None, P) + assert isinstance(x, P) + assert not x + check(c_void_p) + check(c_char_p) + check(POINTER(c_int)) + check(POINTER(c_int * 10)) diff --git a/extra_tests/snippets/ctypes_tests/test_funcptr.py b/extra_tests/snippets/ctypes_tests/test_funcptr.py new file mode 100644 index 0000000000..2e3193abc2 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_funcptr.py @@ -0,0 +1,5 @@ +from ctypes import * + +def test_restype(dll): + foo = dll.my_unused_function + assert foo.restype is c_int # by default diff --git a/extra_tests/snippets/ctypes_tests/test_functions.py b/extra_tests/snippets/ctypes_tests/test_functions.py new file mode 100644 index 0000000000..56b50420a9 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_functions.py @@ -0,0 +1,243 @@ +from ctypes import * +import sys +import pytest + +@pytest.fixture +def dll(sofile): + return CDLL(str(sofile), use_errno=True) + + +def test_char_result(dll): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_char + result = f(0, 0, 0, 0, 0, 0) + assert result == b'\x00' + +def test_boolresult(dll): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_bool + false_result = f(0, 0, 0, 0, 0, 0) + assert false_result is False + true_result = f(1, 0, 0, 0, 0, 0) + assert true_result is True + +def test_unicode_function_name(dll): + f = dll[u'_testfunc_i_bhilfd'] + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_int + result = f(1, 2, 3, 4, 5.0, 6.0) + assert result == 21 + +def test_truncate_python_longs(dll): + f = dll._testfunc_i_bhilfd + f.argtypes = [c_byte, c_short, c_int, c_long, c_float, c_double] + f.restype = c_int + x = sys.maxsize * 2 + result = f(x, x, x, x, 0, 0) + assert result == -8 + +def test_convert_pointers(dll): + f = dll.deref_LP_c_char_p + f.restype = c_char + f.argtypes = [POINTER(c_char_p)] + # + s = c_char_p(b'hello world') + ps = pointer(s) + assert f(ps) == b'h' + assert f(s) == b'h' # automatic conversion from char** to char* + +################################################################ + +def test_call_some_args(dll): + f = dll.my_strchr + f.argtypes = [c_char_p] + f.restype = c_char_p + result = f(b"abcd", ord("b")) + assert result == b"bcd" + +@pytest.mark.pypy_only +def test_keepalive_buffers(monkeypatch, dll): + import gc + f = dll.my_strchr + f.argtypes = [c_char_p] + f.restype = c_char_p + # + orig__call_funcptr = f._call_funcptr + def _call_funcptr(funcptr, *newargs): + gc.collect() + gc.collect() + gc.collect() + return orig__call_funcptr(funcptr, *newargs) + monkeypatch.setattr(f, '_call_funcptr', _call_funcptr) + # + result = f(b"abcd", ord("b")) + assert result == b"bcd" + +def test_caching_bug_1(dll): + # the same test as test_call_some_args, with two extra lines + # in the middle that trigger caching in f._ptr, which then + # makes the last two lines fail + f = dll.my_strchr + f.argtypes = [c_char_p, c_int] + f.restype = c_char_p + result = f(b"abcd", ord("b")) + assert result == b"bcd" + result = f(b"abcd", ord("b"), 42) + assert result == b"bcd" + +def test_argument_conversion_and_checks(dll): + #This test is designed to check for segfaults if the wrong type of argument is passed as parameter + strlen = dll.my_strchr + strlen.argtypes = [c_char_p, c_int] + strlen.restype = c_char_p + assert strlen(b"eggs", ord("g")) == b"ggs" + + # Should raise ArgumentError, not segfault + with pytest.raises(ArgumentError): + strlen(0, 0) + with pytest.raises(ArgumentError): + strlen(False, 0) + +def test_union_as_passed_value(dll): + class UN(Union): + _fields_ = [("x", c_short), + ("y", c_long)] + dll.ret_un_func.restype = UN + dll.ret_un_func.argtypes = [UN] + A = UN * 2 + a = A() + a[1].x = 33 + u = dll.ret_un_func(a[1]) + assert u.y == 33 * 10000 + +@pytest.mark.pypy_only +def test_cache_funcptr(dll): + tf_b = dll.tf_b + tf_b.restype = c_byte + tf_b.argtypes = (c_byte,) + assert tf_b(-126) == -42 + ptr = tf_b._ptr + assert ptr is not None + assert tf_b(-126) == -42 + assert tf_b._ptr is ptr + +def test_custom_from_param(dll): + class A(c_byte): + @classmethod + def from_param(cls, obj): + seen.append(obj) + return -126 + tf_b = dll.tf_b + tf_b.restype = c_byte + tf_b.argtypes = (c_byte,) + tf_b.argtypes = [A] + seen = [] + assert tf_b("yadda") == -42 + assert seen == ["yadda"] + +@pytest.mark.xfail(reason="warnings are disabled") +def test_warnings(dll): + import warnings + warnings.simplefilter("always") + with warnings.catch_warnings(record=True) as w: + dll.get_an_integer() + assert len(w) == 1 + assert issubclass(w[0].category, RuntimeWarning) + assert "C function without declared arguments called" in str(w[0].message) + +@pytest.mark.xfail +def test_errcheck(dll): + import warnings + def errcheck(result, func, args): + assert result == -42 + assert type(result) is int + arg, = args + assert arg == -126 + assert type(arg) is int + return result + # + tf_b = dll.tf_b + tf_b.restype = c_byte + tf_b.argtypes = (c_byte,) + tf_b.errcheck = errcheck + assert tf_b(-126) == -42 + del tf_b.errcheck + with warnings.catch_warnings(record=True) as w: + dll.get_an_integer.argtypes = [] + dll.get_an_integer() + assert len(w) == 1 + assert issubclass(w[0].category, RuntimeWarning) + assert "C function without declared return type called" in str(w[0].message) + + with warnings.catch_warnings(record=True) as w: + dll.get_an_integer.restype = None + dll.get_an_integer() + assert len(w) == 0 + + warnings.resetwarnings() + +def test_errno(dll): + test_errno = dll.test_errno + test_errno.restype = c_int + set_errno(42) + res = test_errno() + n = get_errno() + assert (res, n) == (42, 43) + set_errno(0) + assert get_errno() == 0 + +def test_issue1655(dll): + def ret_list_p(icount): + def sz_array_p(obj, func, args): + assert ('.LP_c_int object' in repr(obj) or + '.LP_c_long object' in repr(obj)) + assert repr(args) =="(b'testing!', c_int(4))" + assert args[icount].value == 4 + return [obj[i] for i in range(args[icount].value)] + return sz_array_p + + get_data_prototype = CFUNCTYPE(POINTER(c_int), + c_char_p, POINTER(c_int)) + get_data_paramflag = ((1,), (2,)) + get_data_signature = ('test_issue1655', dll) + + get_data = get_data_prototype(get_data_signature, get_data_paramflag) + assert get_data(b'testing!') == 4 + + get_data.errcheck = ret_list_p(1) + assert get_data(b'testing!') == [-1, -2, -3, -4] + +def test_issue2533(tmpdir): + import cffi + ffi = cffi.FFI() + ffi.cdef("int **fetchme(void);") + ffi.set_source("_x_cffi", """ + int **fetchme(void) + { + static int a = 42; + static int *pa = &a; + return &pa; + } + """) + ffi.compile(verbose=True, tmpdir=str(tmpdir)) + + import sys + sys.path.insert(0, str(tmpdir)) + try: + from _x_cffi import ffi, lib + finally: + sys.path.pop(0) + fetchme = ffi.addressof(lib, 'fetchme') + fetchme = int(ffi.cast("intptr_t", fetchme)) + + FN = CFUNCTYPE(POINTER(POINTER(c_int))) + ff = cast(fetchme, FN) + + g = ff() + assert g.contents.contents.value == 42 + + h = c_int(43) + g[0] = pointer(h) # used to crash here + assert g.contents.contents.value == 43 diff --git a/extra_tests/snippets/ctypes_tests/test_guess_argtypes.py b/extra_tests/snippets/ctypes_tests/test_guess_argtypes.py new file mode 100644 index 0000000000..e3e79f48b5 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_guess_argtypes.py @@ -0,0 +1,38 @@ + +""" This test checks whether args wrapping behavior is correct +""" +import pytest +import sys + +from ctypes import * + +@pytest.mark.pypy_only +def test_wrap_args(): + from _ctypes import CFuncPtr + + def guess(value): + _, cobj, ctype = CFuncPtr._conv_param(None, value) + return ctype + ## cobj = CFuncPtr._conv_param(None, value) + ## return type(cobj) + + assert guess(13) == c_int + assert guess(0) == c_int + assert guess(b'xca') == c_char_p + assert guess(None) == c_void_p + assert guess(c_int(3)) == c_int + assert guess(u'xca') == c_wchar_p + + class Stuff: + pass + s = Stuff() + s._as_parameter_ = None + + assert guess(s) == c_void_p + +def test_guess_unicode(dll): + if not hasattr(sys, 'pypy_translation_info') and sys.platform != 'win32': + pytest.skip("CPython segfaults: see http://bugs.python.org/issue5203") + wcslen = dll.my_wcslen + text = u"Some long unicode string" + assert wcslen(text) == len(text) diff --git a/extra_tests/snippets/ctypes_tests/test_keepalive.py b/extra_tests/snippets/ctypes_tests/test_keepalive.py new file mode 100644 index 0000000000..ae390aef4b --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_keepalive.py @@ -0,0 +1,263 @@ +""" Tests whether various objects land in _objects +or not +""" +import pytest + +from ctypes import * +import sys + +def test_array_of_pointers(): + # tests array item assignements & pointer.contents = ... + A = POINTER(c_long) * 24 + a = A() + l = c_long(2) + p = pointer(l) + a[3] = p + assert l._objects is None + assert p._objects == {'1':l} + assert a._objects == {'3':{'1':l}} + +def test_simple_structure_and_pointer(): + class X(Structure): + _fields_ = [('x', POINTER(c_int))] + + x = X() + p = POINTER(c_int)() + assert x._objects is None + assert p._objects is None + x.x = p + assert p._objects == {} + assert len(x._objects) == 1 + assert x._objects['0'] is p._objects + +def test_simple_structure_and_pointer_with_array(): + class X(Structure): + _fields_ = [('array', POINTER(c_int))] + + x = X() + a = (c_int * 3)(1, 2, 3) + assert x._objects is None + x.array = a + assert x._objects['0'] is a + +def test_structure_with_pointers(): + class X(Structure): + _fields_ = [('x', POINTER(c_int)), + ('y', POINTER(c_int))] + + x = X() + u = c_int(3) + p = pointer(u) + x.x = p + assert x.x._objects is None + assert p._objects == {'1': u} + assert x._objects == {'0': p._objects} + + w = c_int(4) + q = pointer(w) + x.y = q + assert p._objects == {'1': u} + assert q._objects == {'1': w} + assert x._objects == {'0': p._objects, '1': q._objects} + + n = POINTER(c_int)() + x.x = n + x.y = n + assert x._objects == {'0': n._objects, '1': n._objects} + assert x._objects['0'] is n._objects + assert n._objects is not None + +def test_union_with_pointers(): + class X(Union): + _fields_ = [('x', POINTER(c_int)), + ('y', POINTER(c_int))] + + x = X() + u = c_int(3) + p = pointer(u) + x.x = p + assert x.x._objects is None + assert p._objects == {'1': u} + assert x._objects == {'0': p._objects} + + # unions works just like structures it seems + w = c_int(4) + q = pointer(w) + x.y = q + assert p._objects == {'1': u} + assert q._objects == {'1': w} + assert x._objects == {'0': p._objects, '1': q._objects} + + n = POINTER(c_int)() + x.x = n + x.y = n + assert x._objects == {'0': n._objects, '1': n._objects} + assert x._objects['0'] is n._objects + assert n._objects is not None + +def test_pointer_setitem(): + x = c_int(2) + y = c_int(3) + p = pointer(x) + assert p._objects == {'1':x} + p[0] = y + assert list(p._objects.keys()) == ['1'] + assert p._objects['1'].value == 3 + +@pytest.mark.pypy_only +def test_primitive(): + assert c_char_p(b"abc")._objects._buffer[0] == b"a" + assert c_int(3)._objects is None + +def test_pointer_to_pointer(): + l = c_long(2) + assert l._objects is None + + p1 = pointer(l) + assert p1._objects == {'1':l} + + p2 = pointer(p1) + assert p2._objects == {'1':p1, '0':{'1':l}} + +def test_cfunc(): + def f(): + pass + cf = CFUNCTYPE(c_int, c_int)(f) + assert cf._objects == {'0':cf} + +def test_cfunc_cast(): + def f(): + pass + cf = CFUNCTYPE(c_int, c_int)(f) + p1 = cast(cf, c_void_p) + assert p1._objects == {id(cf): cf, '0': cf} + +def test_array_of_struct_with_pointer(): + class S(Structure): + _fields_ = [('x', c_int)] + PS = POINTER(S) + + class Q(Structure): + _fields_ = [('p', PS)] + + A = Q*10 + a=A() + s=S() + s.x=3 + a[3].p = pointer(s) + + assert a._objects['0:3']['1'] is s + +def test_array_of_union_with_pointer(): + class S(Structure): + _fields_ = [('x', c_int)] + PS = POINTER(S) + + class Q(Union): + _fields_ = [('p', PS), ('x', c_int)] + + A = Q*10 + a=A() + s=S() + s.x=3 + a[3].p = pointer(s) + + assert a._objects['0:3']['1'] is s + +def test_struct_with_inlined_array(): + class S(Structure): + _fields_ = [('b', c_int), + ('a', POINTER(c_int) * 2)] + + s = S() + stuff = c_int(2) + s.a[1] = pointer(stuff) + assert s._objects == {'1:1': {'1': stuff}} + +def test_union_with_inlined_array(): + class S(Union): + _fields_ = [('b', c_int), + ('a', POINTER(c_int) * 2)] + + s = S() + stuff = c_int(2) + s.a[1] = pointer(stuff) + assert s._objects == {'1:1': {'1': stuff}} + +def test_struct_within_struct(): + class R(Structure): + _fields_ = [('p', POINTER(c_int))] + + class S(Structure): + _fields_ = [('b', c_int), + ('r', R)] + + s = S() + stuff = c_int(2) + s.r.p = pointer(stuff) + assert s._objects == {'0:1': {'1': stuff}} + + r = R() + s.r = r + # obscure + assert s._objects == {'1': {}, '0:1': {'1': stuff}} + +def test_union_within_union(): + class R(Union): + _fields_ = [('p', POINTER(c_int))] + + class S(Union): + _fields_ = [('b', c_int), + ('r', R)] + + s = S() + stuff = c_int(2) + s.r.p = pointer(stuff) + assert s._objects == {'0:1': {'1': stuff}} + + r = R() + s.r = r + # obscure + assert s._objects == {'1': {}, '0:1': {'1': stuff}} + +def test_c_char_p(): + n = 2 + xs = b"hello" * n + x = c_char_p(xs) + del xs + import gc; gc.collect() + assert x.value == b'hellohello' + assert x._objects == b'hellohello' + # + class datum(Structure): + _fields_ = [ + ('dptr', c_char_p), + ('dsize', c_int), + ] + class union(Union): + _fields_ = [ + ('dptr', c_char_p), + ('dsize', c_int), + ] + for wrap in [False, True]: + n = 2 + xs = b"hello" * n + if wrap: + xs = c_char_p(xs) + dat = datum() + dat.dptr = xs + dat.dsize = 15 + del xs + import gc; gc.collect() + assert dat.dptr == b"hellohello" + assert list(dat._objects.keys()) == ['0'] + + xs = b"hello" * n + if wrap: + xs = c_char_p(xs) + dat = union() + dat.dptr = xs + del xs + import gc; gc.collect() + assert dat.dptr == b"hellohello" + assert list(dat._objects.keys()) == ['0'] diff --git a/extra_tests/snippets/ctypes_tests/test_numbers.py b/extra_tests/snippets/ctypes_tests/test_numbers.py new file mode 100644 index 0000000000..f73bf4f126 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_numbers.py @@ -0,0 +1,56 @@ +import pytest +from ctypes import * + +unsigned_types = [c_ubyte, c_ushort, c_uint, c_ulong] +signed_types = [c_byte, c_short, c_int, c_long, c_longlong] + +float_types = [c_double, c_float, c_longdouble] + +try: + c_ulonglong + c_longlong +except NameError: + pass +else: + unsigned_types.append(c_ulonglong) + signed_types.append(c_longlong) + +################################################################ + +@pytest.mark.parametrize('t', signed_types + unsigned_types + float_types) +def test_init_again(t): + parm = t() + addr1 = addressof(parm) + parm.__init__(0) + addr2 = addressof(parm) + assert addr1 == addr2 + +def test_subclass(): + class enum(c_int): + def __new__(cls, value): + dont_call_me + class S(Structure): + _fields_ = [('t', enum)] + assert isinstance(S().t, enum) + +#@pytest.mark.xfail("'__pypy__' not in sys.builtin_module_names") +@pytest.mark.xfail +def test_no_missing_shape_to_ffi_type(): + # whitebox test + "re-enable after adding 'g' to _shape_to_ffi_type.typemap, " + "which I think needs fighting all the way up from " + "rpython.rlib.libffi" + from _ctypes.basics import _shape_to_ffi_type + from _rawffi import Array + for i in range(1, 256): + try: + Array(chr(i)) + except ValueError: + pass + else: + assert chr(i) in _shape_to_ffi_type.typemap + +@pytest.mark.xfail +def test_pointer_to_long_double(): + import ctypes + ctypes.POINTER(ctypes.c_longdouble) diff --git a/extra_tests/snippets/ctypes_tests/test_parameters.py b/extra_tests/snippets/ctypes_tests/test_parameters.py new file mode 100644 index 0000000000..2f39e046b0 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_parameters.py @@ -0,0 +1,24 @@ +from ctypes import POINTER, c_void_p + +def test_pointer_subclasses(): + Void_pp = POINTER(c_void_p) + class My_void_p(c_void_p): + pass + + My_void_pp = POINTER(My_void_p) + o = My_void_pp() + + assert Void_pp.from_param(o) is o + + +def test_multiple_signature(dll): + # when .argtypes is not set, calling a function with a certain + # set of parameters should not prevent another call with + # another set. + func = dll._testfunc_p_p + + # This is call has too many arguments + assert func(None, 1) == 0 + + # This one is normal + assert func(None) == 0 diff --git a/extra_tests/snippets/ctypes_tests/test_pointers.py b/extra_tests/snippets/ctypes_tests/test_pointers.py new file mode 100644 index 0000000000..2dcf18dd2c --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_pointers.py @@ -0,0 +1,92 @@ +import pytest +from ctypes import * +import struct + +@pytest.mark.pypy_only +def test_get_ffi_argtype(): + P = POINTER(c_int) + ffitype = P.get_ffi_argtype() + assert P.get_ffi_argtype() is ffitype + assert ffitype.deref_pointer() is c_int.get_ffi_argtype() + +@pytest.mark.parametrize("c_type, py_type", [ + (c_byte, int), + (c_ubyte, int), + (c_short, int), + (c_ushort, int), + (c_int, int), + (c_uint, int), + (c_long, int), + (c_ulong, int), + (c_longlong, int), + (c_ulonglong, int), + (c_double, float), + (c_float, float), +]) +def test_byref(c_type, py_type): + i = c_type(42) + p = byref(i) + assert type(p._obj) is c_type + assert p._obj.value == 42 + +def test_pointer_to_pointer(): + x = c_int(32) + y = c_int(42) + p1 = pointer(x) + p2 = pointer(p1) + assert p2.contents.contents.value == 32 + p2.contents.contents = y + assert p2.contents.contents.value == 42 + assert p1.contents.value == 42 + +def test_c_char_p_byref(dll): + TwoOutArgs = dll.TwoOutArgs + TwoOutArgs.restype = None + TwoOutArgs.argtypes = [c_int, c_void_p, c_int, c_void_p] + a = c_int(3) + b = c_int(4) + c = c_int(5) + d = c_int(6) + TwoOutArgs(a, byref(b), c, byref(d)) + assert b.value == 7 + assert d.value == 11 + +def test_byref_cannot_be_bound(): + class A(object): + _byref = byref + A._byref(c_int(5)) + +def test_byref_with_offset(): + c = c_int() + d = byref(c) + base = cast(d, c_void_p).value + for i in [0, 1, 4, 1444, -10293]: + assert cast(byref(c, i), c_void_p).value == base + i + +@pytest.mark.pypy_only +def test_issue2813_fix(): + class C(Structure): + pass + POINTER(C) + C._fields_ = [('x', c_int)] + ffitype = C.get_ffi_argtype() + assert C.get_ffi_argtype() is ffitype + assert ffitype.sizeof() == sizeof(c_int) + +@pytest.mark.pypy_only +def test_issue2813_cant_change_fields_after_get_ffi_argtype(): + class C(Structure): + pass + ffitype = C.get_ffi_argtype() + with pytest.raises(NotImplementedError): + C._fields_ = [('x', c_int)] + +def test_memoryview(): + x = c_int(32) + p1 = pointer(x) + p2 = pointer(p1) + + m1 = memoryview(p1) + assert struct.unpack('P', m1)[0] == addressof(x) + m2 = memoryview(p2) + assert struct.unpack('P', m2)[0] == addressof(p1) diff --git a/extra_tests/snippets/ctypes_tests/test_prototypes.py b/extra_tests/snippets/ctypes_tests/test_prototypes.py new file mode 100644 index 0000000000..236ce74499 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_prototypes.py @@ -0,0 +1,65 @@ +import pytest +from ctypes import * + + +def test_restype_setattr(dll): + func = dll._testfunc_p_p + with pytest.raises(TypeError): + setattr(func, 'restype', 20) + +def test_argtypes_setattr(dll): + func = dll._testfunc_p_p + with pytest.raises(TypeError): + setattr(func, 'argtypes', 20) + with pytest.raises(TypeError): + setattr(func, 'argtypes', [20]) + + func = CFUNCTYPE(c_long, c_void_p, c_long)(lambda: None) + assert func.argtypes == (c_void_p, c_long) + +def test_paramflags_setattr(): + func = CFUNCTYPE(c_long, c_void_p, c_long)(lambda: None) + with pytest.raises(TypeError): + setattr(func, 'paramflags', 'spam') + with pytest.raises(ValueError): + setattr(func, 'paramflags', (1, 2, 3, 4)) + with pytest.raises(TypeError): + setattr(func, 'paramflags', ((1,), ('a',))) + func.paramflags = (1,), (1|4,) + +def test_kwargs(dll): + proto = CFUNCTYPE(c_char_p, c_char_p, c_int) + paramflags = (1, 'text', b"tavino"), (1, 'letter', ord('v')) + func = proto(('my_strchr', dll), paramflags) + assert func.argtypes == (c_char_p, c_int) + assert func.restype == c_char_p + + result = func(b"abcd", ord('b')) + assert result == b"bcd" + + result = func() + assert result == b"vino" + + result = func(b"grapevine") + assert result == b"vine" + + result = func(text=b"grapevine") + assert result == b"vine" + + result = func(letter=ord('i')) + assert result == b"ino" + + result = func(letter=ord('p'), text=b"impossible") + assert result == b"possible" + + result = func(text=b"impossible", letter=ord('p')) + assert result == b"possible" + +def test_array_to_ptr_wrongtype(dll): + ARRAY = c_byte * 8 + func = dll._testfunc_ai8 + func.restype = POINTER(c_int) + func.argtypes = [c_int * 8] + array = ARRAY(1, 2, 3, 4, 5, 6, 7, 8) + with pytest.raises(ArgumentError): + func(array) diff --git a/extra_tests/snippets/ctypes_tests/test_structures.py b/extra_tests/snippets/ctypes_tests/test_structures.py new file mode 100644 index 0000000000..96531ddf3c --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_structures.py @@ -0,0 +1,223 @@ +from ctypes import * + +import pytest +import sys + + +def test_subclass_initializer(): + class POINT(Structure): + _fields_ = [("x", c_int), ("y", c_int)] + + class POSITION(POINT): + # A subclass without _fields_ + pass + pos = POSITION(1, 2) + assert (pos.x, pos.y) == (1, 2) + # Try a second time, result may be different (cf. issue1498) + pos = POSITION(1, 2) + assert (pos.x, pos.y) == (1, 2) + +def test_fields_is_a_tuple(): + class Person(Structure): + _fields_ = (("name", c_char*6), + ("age", c_int)) + + # short enough + p = Person(b"123456", 6) + assert p.name == b"123456" + assert p.age == 6 + +def test___init__(): + class Person(Structure): + _fields_ = (("name", c_char*10), + ("age", c_int)) + + def __init__(self, name, surname, age): + self.name = name + b' ' + surname + self.age = age + + p = Person(b"John", b"Doe", 25) + assert p.name == b"John Doe" + assert p.age == 25 + +def test_setattr(): + class X(Structure): + _fields_ = [("a", c_int)] + + x = X() + x.other = 42 + assert x.other == 42 + +def test_withslots(): + class X(Structure): + _fields_ = [("a", c_int * 2)] + __slots__ = ['a'] + + x = X() + x.a = (42, 43) + assert tuple(x.a) == (42, 43) + +def test_getattr_recursion(): + # Structure.__getattr__ used to call itself recursively + # and hit the recursion limit. + import sys + events = [] + + def tracefunc(frame, event, arg): + funcname = frame.f_code.co_name + if 'getattr' in funcname: + events.append(funcname) + + oldtrace = sys.settrace(tracefunc) + try: + class X(Structure): + _fields_ = [("a", c_int)] + + assert len(events) < 20 + finally: + sys.settrace(oldtrace) + events = None + +def test_large_fields(): + # make sure that large fields are not "confused" with bitfields + # (because the bitfields use the higher bits of the "size" attribute) + Array = c_long * 8192 + class X(Structure): + _fields_ = [('items', Array)] + obj = X() + assert isinstance(obj.items, Array) + +def test_b_base(): + # _b_base_ used to be None here in PyPy + class X(Structure): + _fields_ = [('x', c_int)] + obj = X() + p = pointer(obj) + assert p.contents._b_base_ is p + +def test_swapped_bytes(): + import sys + + for i in [c_short, c_int, c_long, c_longlong, + c_float, c_double, c_ushort, c_uint, + c_ulong, c_ulonglong]: + FIELDS = [ + ('n', i) + ] + + class Native(Structure): + _fields_ = FIELDS + + class Big(BigEndianStructure): + _fields_ = FIELDS + + class Little(LittleEndianStructure): + _fields_ = FIELDS + + def dostruct(c): + ba = create_string_buffer(sizeof(c)) + ms = c.from_buffer(ba) + ms.n = 0xff00 + return repr(ba[:]) + + nstruct = dostruct(Native) + if sys.byteorder == 'little': + assert nstruct == dostruct(Little) + assert nstruct != dostruct(Big) + assert Big._fields_[0][1] is not i + else: + assert nstruct == dostruct(Big) + assert nstruct != dostruct(Little) + assert Little._fields_[0][1] is not i + +def test_from_buffer_copy(): + from array import array + + class S(Structure): + _fields_ = [('i', c_int)] + def __init__(self, some, unused, arguments): + pass + a = array('i', [1234567]) + s1 = S.from_buffer(a) + s2 = S.from_buffer_copy(a) + assert s1.i == 1234567 + assert s2.i == 1234567 + a[0] = -7654321 + assert s1.i == -7654321 + assert s2.i == 1234567 + + +def test_nonfinal_struct(): + class X(Structure): + pass + assert sizeof(X) == 0 + X._fields_ = [("a", c_int),] + with pytest.raises(AttributeError): + X._fields_ = [] + + class X(Structure): + pass + X() + with pytest.raises(AttributeError): + X._fields_ = [] + + class X(Structure): + pass + class Y(X): + pass + with pytest.raises(AttributeError): + X._fields_ = [] + Y.__fields__ = [] + + +def test_structure_overloading_getattr(): + class X(Structure): + _fields_ = [('x', c_int)] + + def __getattr__(self, name): + raise AttributeError(name) + + x = X() + assert x.x == 0 + +def test_duplicate_names(): + class S(Structure): + _fields_ = [('a', c_int), + ('b', c_int), + ('a', c_byte)] + s = S(260, -123) + assert sizeof(s) == 3 * sizeof(c_int) + assert s.a == 4 # 256 + 4 + assert s.b == -123 + +def test_memoryview(): + class S(Structure): + _fields_ = [('a', c_int16), + ('b', c_int16), + ] + + S3 = S * 3 + c_array = (2 * S3)( + S3(S(a=0, b=1), S(a=2, b=3), S(a=4, b=5)), + S3(S(a=6, b=7), S(a=8, b=9), S(a=10, b=11)), + ) + + mv = memoryview(c_array) + if sys.byteorder == 'little': + assert mv.format == 'T{h:a:>h:b:}' + assert mv.shape == (2, 3) + assert mv.itemsize == 4 + +def test_memoryview_endian(): + class LES(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('a', c_ubyte * 16), + ('i', c_uint64) + ] + c_les = LES() + mv = memoryview(c_les) + assert mv.format == 'B' + diff --git a/extra_tests/snippets/ctypes_tests/test_unions.py b/extra_tests/snippets/ctypes_tests/test_unions.py new file mode 100644 index 0000000000..f033f0846a --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_unions.py @@ -0,0 +1,28 @@ +import sys +from ctypes import * + +def test_getattr(): + class Stuff(Union): + _fields_ = [('x', c_char), ('y', c_int)] + + stuff = Stuff() + stuff.y = ord('x') | (ord('z') << 24) + if sys.byteorder == 'little': + assert stuff.x == b'x' + else: + assert stuff.x == b'z' + +def test_union_of_structures(): + class Stuff(Structure): + _fields_ = [('x', c_int)] + + class Stuff2(Structure): + _fields_ = [('x', c_int)] + + class UnionofStuff(Union): + _fields_ = [('one', Stuff), + ('two', Stuff2)] + + u = UnionofStuff() + u.one.x = 3 + assert u.two.x == 3 diff --git a/extra_tests/snippets/ctypes_tests/test_values.py b/extra_tests/snippets/ctypes_tests/test_values.py new file mode 100644 index 0000000000..2c3aa8d8f7 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_values.py @@ -0,0 +1,10 @@ +from ctypes import * + +def test_a_string(dll): + """ + A testcase which accesses *values* in a dll. + """ + a_string = (c_char * 16).in_dll(dll, "a_string") + assert a_string.raw == b"0123456789abcdef" + a_string[15:16] = b'$' + assert dll.get_a_string_char(15) == ord('$') diff --git a/extra_tests/snippets/ctypes_tests/test_win32.py b/extra_tests/snippets/ctypes_tests/test_win32.py new file mode 100644 index 0000000000..8ee68e6580 --- /dev/null +++ b/extra_tests/snippets/ctypes_tests/test_win32.py @@ -0,0 +1,13 @@ +# Windows specific tests + +from ctypes import * + +import pytest + +@pytest.mark.skipif("sys.platform != 'win32'") +def test_VARIANT(): + from ctypes import wintypes + a = wintypes.VARIANT_BOOL() + assert a.value is False + b = wintypes.VARIANT_BOOL(3) + assert b.value is True diff --git a/vm/src/stdlib/ctypes/_ctypes_test.c b/vm/src/stdlib/ctypes/_ctypes_test.c new file mode 100644 index 0000000000..17e10c64f4 --- /dev/null +++ b/vm/src/stdlib/ctypes/_ctypes_test.c @@ -0,0 +1,860 @@ +#if defined(_MSC_VER) || defined(__CYGWIN__) +#include +#define MS_WIN32 +#endif + +#ifdef _WIN32 +#define EXPORT(x) __declspec(dllexport) x +#else +#define EXPORT(x) extern x +#endif + +#include +#include +#include +#include +#include +#include + +#define HAVE_LONG_LONG +#define LONG_LONG long long +#define HAVE_WCHAR_H + +/* some functions handy for testing */ + +EXPORT(int) +_testfunc_cbk_reg_int(int a, int b, int c, int d, int e, + int (*func)(int, int, int, int, int)) +{ + return func(a*a, b*b, c*c, d*d, e*e); +} + +EXPORT(double) +_testfunc_cbk_reg_double(double a, double b, double c, double d, double e, + double (*func)(double, double, double, double, double)) +{ + return func(a*a, b*b, c*c, d*d, e*e); +} + +/* + * This structure should be the same as in test_callbacks.py and the + * method test_callback_large_struct. See issues 17310 and 20160: the + * structure must be larger than 8 bytes long. + */ + +typedef struct { + unsigned long first; + unsigned long second; + unsigned long third; +} Test; + +EXPORT(void) +_testfunc_cbk_large_struct(Test in, void (*func)(Test)) +{ + func(in); +} + +/* + * See issue 29565. Update a structure passed by value; + * the caller should not see any change. + */ + +EXPORT(void) +_testfunc_large_struct_update_value(Test in) +{ + ((volatile Test *)&in)->first = 0x0badf00d; + ((volatile Test *)&in)->second = 0x0badf00d; + ((volatile Test *)&in)->third = 0x0badf00d; +} + +typedef struct { + unsigned int first; + unsigned int second; +} TestReg; + + +EXPORT(TestReg) last_tfrsuv_arg = {0}; + + +EXPORT(void) +_testfunc_reg_struct_update_value(TestReg in) +{ + last_tfrsuv_arg = in; + ((volatile TestReg *)&in)->first = 0x0badf00d; + ((volatile TestReg *)&in)->second = 0x0badf00d; +} + +/* + * See bpo-22273. Structs containing arrays should work on Linux 64-bit. + */ + +typedef struct { + unsigned char data[16]; +} Test2; + +EXPORT(int) +_testfunc_array_in_struct1(Test2 in) +{ + int result = 0; + + for (unsigned i = 0; i < 16; i++) + result += in.data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +typedef struct { + double data[2]; +} Test3; + +typedef struct { + float data[2]; + float more_data[2]; +} Test3B; + +EXPORT(double) +_testfunc_array_in_struct2(Test3 in) +{ + double result = 0; + + for (unsigned i = 0; i < 2; i++) + result += in.data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +EXPORT(double) +_testfunc_array_in_struct2a(Test3B in) +{ + double result = 0; + + for (unsigned i = 0; i < 2; i++) + result += in.data[i]; + for (unsigned i = 0; i < 2; i++) + result += in.more_data[i]; + /* As the structure is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(in.data, 0, sizeof(in.data)); + return result; +} + +typedef union { + long a_long; + struct { + int an_int; + int another_int; + } a_struct; +} Test4; + +typedef struct { + int an_int; + struct { + int an_int; + Test4 a_union; + } nested; + int another_int; +} Test5; + +EXPORT(long) +_testfunc_union_by_value1(Test4 in) { + long result = in.a_long + in.a_struct.an_int + in.a_struct.another_int; + + /* As the union/struct are passed by value, changes to them shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_union_by_value2(Test5 in) { + long result = in.an_int + in.nested.an_int; + + /* As the union/struct are passed by value, changes to them shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference1(Test4 *in) { + long result = in->a_long; + + memset(in, 0, sizeof(Test4)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference2(Test4 *in) { + long result = in->a_struct.an_int + in->a_struct.another_int; + + memset(in, 0, sizeof(Test4)); + return result; +} + +EXPORT(long) +_testfunc_union_by_reference3(Test5 *in) { + long result = in->an_int + in->nested.an_int + in->another_int; + + memset(in, 0, sizeof(Test5)); + return result; +} + +typedef struct { + signed int A: 1, B:2, C:3, D:2; +} Test6; + +EXPORT(long) +_testfunc_bitfield_by_value1(Test6 in) { + long result = in.A + in.B + in.C + in.D; + + /* As the struct is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(long) +_testfunc_bitfield_by_reference1(Test6 *in) { + long result = in->A + in->B + in->C + in->D; + + memset(in, 0, sizeof(Test6)); + return result; +} + +typedef struct { + unsigned int A: 1, B:2, C:3, D:2; +} Test7; + +EXPORT(long) +_testfunc_bitfield_by_reference2(Test7 *in) { + long result = in->A + in->B + in->C + in->D; + + memset(in, 0, sizeof(Test7)); + return result; +} + +typedef union { + signed int A: 1, B:2, C:3, D:2; +} Test8; + +EXPORT(long) +_testfunc_bitfield_by_value2(Test8 in) { + long result = in.A + in.B + in.C + in.D; + + /* As the struct is passed by value, changes to it shouldn't be + * reflected in the caller. + */ + memset(&in, 0, sizeof(in)); + return result; +} + +EXPORT(void)testfunc_array(int values[4]) +{ + printf("testfunc_array %d %d %d %d\n", + values[0], + values[1], + values[2], + values[3]); +} + +EXPORT(long double)testfunc_Ddd(double a, double b) +{ + long double result = (long double)(a * b); + printf("testfunc_Ddd(%p, %p)\n", (void *)&a, (void *)&b); + printf("testfunc_Ddd(%g, %g)\n", a, b); + return result; +} + +EXPORT(long double)testfunc_DDD(long double a, long double b) +{ + long double result = a * b; + printf("testfunc_DDD(%p, %p)\n", (void *)&a, (void *)&b); + printf("testfunc_DDD(%Lg, %Lg)\n", a, b); + return result; +} + +EXPORT(int)testfunc_iii(int a, int b) +{ + int result = a * b; + printf("testfunc_iii(%p, %p)\n", (void *)&a, (void *)&b); + return result; +} + +EXPORT(int)myprintf(char *fmt, ...) +{ + int result; + va_list argptr; + va_start(argptr, fmt); + result = vprintf(fmt, argptr); + va_end(argptr); + return result; +} + +EXPORT(char *)my_strtok(char *token, const char *delim) +{ + return strtok(token, delim); +} + +EXPORT(char *)my_strchr(const char *s, int c) +{ + return strchr(s, c); +} + + +EXPORT(double) my_sqrt(double a) +{ + return sqrt(a); +} + +EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) +{ + qsort(base, num, width, compare); +} + +EXPORT(char) deref_LP_c_char_p(char** argv) +{ + char* s = *argv; + return s[0]; +} + +EXPORT(int *) _testfunc_ai8(int a[8]) +{ + return a; +} + +EXPORT(void) _testfunc_v(int a, int b, int *presult) +{ + *presult = a + b; +} + +EXPORT(int) _testfunc_i_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_i_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (int)(b + h + i + l + f + d); +} + +EXPORT(float) _testfunc_f_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_f_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (float)(b + h + i + l + f + d); +} + +EXPORT(double) _testfunc_d_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ +/* printf("_testfunc_d_bhilfd got %d %d %d %ld %f %f\n", + b, h, i, l, f, d); +*/ + return (double)(b + h + i + l + f + d); +} + +EXPORT(char *) _testfunc_p_p(void *s) +{ + return (char *)s; +} + +EXPORT(void *) _testfunc_c_p_p(int *argcp, char **argv) +{ + return argv[(*argcp)-1]; +} + +EXPORT(void *) get_strchr(void) +{ + return (void *)strchr; +} + +EXPORT(char *) my_strdup(char *src) +{ + char *dst = (char *)malloc(strlen(src)+1); + if (!dst) + return NULL; + strcpy(dst, src); + return dst; +} + +EXPORT(void)my_free(void *ptr) +{ + free(ptr); +} + +#ifdef HAVE_WCHAR_H +EXPORT(wchar_t *) my_wcsdup(wchar_t *src) +{ + size_t len = wcslen(src); + wchar_t *ptr = (wchar_t *)malloc((len + 1) * sizeof(wchar_t)); + if (ptr == NULL) + return NULL; + memcpy(ptr, src, (len+1) * sizeof(wchar_t)); + return ptr; +} + +EXPORT(size_t) my_wcslen(wchar_t *src) +{ + return wcslen(src); +} +#endif + +#ifndef MS_WIN32 +# ifndef __stdcall +# define __stdcall /* */ +# endif +#endif + +typedef struct { + int (*c)(int, int); + int (__stdcall *s)(int, int); +} FUNCS; + +EXPORT(int) _testfunc_callfuncp(FUNCS *fp) +{ + fp->c(1, 2); + fp->s(3, 4); + return 0; +} + +EXPORT(int) _testfunc_deref_pointer(int *pi) +{ + return *pi; +} + +#ifdef MS_WIN32 +EXPORT(int) _testfunc_piunk(IUnknown FAR *piunk) +{ + piunk->lpVtbl->AddRef(piunk); + return piunk->lpVtbl->Release(piunk); +} +#endif + +EXPORT(int) _testfunc_callback_with_pointer(int (*func)(int *)) +{ + int table[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + return (*func)(table); +} + +EXPORT(int) _testfunc_callback_opaque(int (*func)(void*), void* arg) +{ + return (*func)(arg); +} + +EXPORT(int) _testfunc_callback_void(void (*func)(void)) +{ + func(); + return 0; +} + +#ifdef HAVE_LONG_LONG +EXPORT(LONG_LONG) _testfunc_q_bhilfdq(signed char b, short h, int i, long l, float f, + double d, LONG_LONG q) +{ + return (LONG_LONG)(b + h + i + l + f + d + q); +} + +EXPORT(LONG_LONG) _testfunc_q_bhilfd(signed char b, short h, int i, long l, float f, double d) +{ + return (LONG_LONG)(b + h + i + l + f + d); +} + +EXPORT(int) _testfunc_callback_i_if(int value, int (*func)(int)) +{ + int sum = 0; + while (value != 0) { + sum += func(value); + value /= 2; + } + return sum; +} + +EXPORT(LONG_LONG) _testfunc_callback_q_qf(LONG_LONG value, + LONG_LONG (*func)(LONG_LONG)) +{ + LONG_LONG sum = 0; + + while (value != 0) { + sum += func(value); + value /= 2; + } + return sum; +} + +#endif + +typedef struct { + char *name; + char *value; +} SPAM; + +typedef struct { + char *name; + int num_spams; + SPAM *spams; +} EGG; + +SPAM my_spams[2] = { + { "name1", "value1" }, + { "name2", "value2" }, +}; + +EGG my_eggs[1] = { + { "first egg", 1, my_spams } +}; + +EXPORT(int) getSPAMANDEGGS(EGG **eggs) +{ + *eggs = my_eggs; + return 1; +} + +typedef struct tagpoint { + int x; + int y; +} point; + +EXPORT(int) _testfunc_byval(point in, point *pout) +{ + if (pout) { + pout->x = in.x; + pout->y = in.y; + } + return in.x + in.y; +} + +EXPORT (int) an_integer = 42; + +EXPORT(int) get_an_integer(void) +{ + return an_integer; +} + +EXPORT(char) a_string[16] = "0123456789abcdef"; + +EXPORT(int) get_a_string_char(int index) +{ + return a_string[index]; +} + +EXPORT(double) +integrate(double a, double b, double (*f)(double), long nstep) +{ + double x, sum=0.0, dx=(b-a)/(double)nstep; + for(x=a+0.5*dx; (b-x)*(x-a)>0.0; x+=dx) + { + double y = f(x); + printf("f(x)=%.1f\n", y); + sum += f(x); + } + return sum/(double)nstep; +} + +typedef struct { + void (*initialize)(void *(*)(int), void(*)(void *)); +} xxx_library; + +static void _xxx_init(void *(*Xalloc)(int), void (*Xfree)(void *)) +{ + void *ptr; + + printf("_xxx_init got %p %p\n", Xalloc, Xfree); + printf("calling\n"); + ptr = Xalloc(32); + Xfree(ptr); + printf("calls done, ptr was %p\n", ptr); +} + +xxx_library _xxx_lib = { + _xxx_init +}; + +EXPORT(xxx_library) *library_get(void) +{ + return &_xxx_lib; +} + +#ifdef MS_WIN32 +/* See Don Box (german), pp 79ff. */ +EXPORT(void) GetString(BSTR *pbstr) +{ + *pbstr = SysAllocString(L"Goodbye!"); +} +#endif + +EXPORT(void) _py_func_si(char *s, int i) +{ +} + +EXPORT(void) _py_func(void) +{ +} + +EXPORT(LONG_LONG) last_tf_arg_s = 0; +EXPORT(unsigned LONG_LONG) last_tf_arg_u = 0; + +struct BITS { + int A: 1, B:2, C:3, D:4, E: 5, F: 6, G: 7, H: 8, I: 9; + short M: 1, N: 2, O: 3, P: 4, Q: 5, R: 6, S: 7; +}; + +EXPORT(void) set_bitfields(struct BITS *bits, char name, int value) +{ + switch (name) { + case 'A': bits->A = value; break; + case 'B': bits->B = value; break; + case 'C': bits->C = value; break; + case 'D': bits->D = value; break; + case 'E': bits->E = value; break; + case 'F': bits->F = value; break; + case 'G': bits->G = value; break; + case 'H': bits->H = value; break; + case 'I': bits->I = value; break; + + case 'M': bits->M = value; break; + case 'N': bits->N = value; break; + case 'O': bits->O = value; break; + case 'P': bits->P = value; break; + case 'Q': bits->Q = value; break; + case 'R': bits->R = value; break; + case 'S': bits->S = value; break; + } +} + +EXPORT(int) unpack_bitfields(struct BITS *bits, char name) +{ + switch (name) { + case 'A': return bits->A; + case 'B': return bits->B; + case 'C': return bits->C; + case 'D': return bits->D; + case 'E': return bits->E; + case 'F': return bits->F; + case 'G': return bits->G; + case 'H': return bits->H; + case 'I': return bits->I; + + case 'M': return bits->M; + case 'N': return bits->N; + case 'O': return bits->O; + case 'P': return bits->P; + case 'Q': return bits->Q; + case 'R': return bits->R; + case 'S': return bits->S; + } + return 0; +} + +#define S last_tf_arg_s = (LONG_LONG)c +#define U last_tf_arg_u = (unsigned LONG_LONG)c + +EXPORT(signed char) tf_b(signed char c) { S; return c/3; } +EXPORT(unsigned char) tf_B(unsigned char c) { U; return c/3; } +EXPORT(short) tf_h(short c) { S; return c/3; } +EXPORT(unsigned short) tf_H(unsigned short c) { U; return c/3; } +EXPORT(int) tf_i(int c) { S; return c/3; } +EXPORT(unsigned int) tf_I(unsigned int c) { U; return c/3; } +EXPORT(long) tf_l(long c) { S; return c/3; } +EXPORT(unsigned long) tf_L(unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) tf_q(LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) tf_Q(unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) tf_f(float c) { S; return c/3; } +EXPORT(double) tf_d(double c) { S; return c/3; } + +#ifdef MS_WIN32 +EXPORT(signed char) __stdcall s_tf_b(signed char c) { S; return c/3; } +EXPORT(unsigned char) __stdcall s_tf_B(unsigned char c) { U; return c/3; } +EXPORT(short) __stdcall s_tf_h(short c) { S; return c/3; } +EXPORT(unsigned short) __stdcall s_tf_H(unsigned short c) { U; return c/3; } +EXPORT(int) __stdcall s_tf_i(int c) { S; return c/3; } +EXPORT(unsigned int) __stdcall s_tf_I(unsigned int c) { U; return c/3; } +EXPORT(long) __stdcall s_tf_l(long c) { S; return c/3; } +EXPORT(unsigned long) __stdcall s_tf_L(unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) __stdcall s_tf_q(LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) __stdcall s_tf_Q(unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) __stdcall s_tf_f(float c) { S; return c/3; } +EXPORT(double) __stdcall s_tf_d(double c) { S; return c/3; } +#endif +/*******/ + +EXPORT(signed char) tf_bb(signed char x, signed char c) { S; return c/3; } +EXPORT(unsigned char) tf_bB(signed char x, unsigned char c) { U; return c/3; } +EXPORT(short) tf_bh(signed char x, short c) { S; return c/3; } +EXPORT(unsigned short) tf_bH(signed char x, unsigned short c) { U; return c/3; } +EXPORT(int) tf_bi(signed char x, int c) { S; return c/3; } +EXPORT(unsigned int) tf_bI(signed char x, unsigned int c) { U; return c/3; } +EXPORT(long) tf_bl(signed char x, long c) { S; return c/3; } +EXPORT(unsigned long) tf_bL(signed char x, unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) tf_bq(signed char x, LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) tf_bQ(signed char x, unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) tf_bf(signed char x, float c) { S; return c/3; } +EXPORT(double) tf_bd(signed char x, double c) { S; return c/3; } +EXPORT(void) tv_i(int c) { S; return; } + +#ifdef MS_WIN32 +EXPORT(signed char) __stdcall s_tf_bb(signed char x, signed char c) { S; return c/3; } +EXPORT(unsigned char) __stdcall s_tf_bB(signed char x, unsigned char c) { U; return c/3; } +EXPORT(short) __stdcall s_tf_bh(signed char x, short c) { S; return c/3; } +EXPORT(unsigned short) __stdcall s_tf_bH(signed char x, unsigned short c) { U; return c/3; } +EXPORT(int) __stdcall s_tf_bi(signed char x, int c) { S; return c/3; } +EXPORT(unsigned int) __stdcall s_tf_bI(signed char x, unsigned int c) { U; return c/3; } +EXPORT(long) __stdcall s_tf_bl(signed char x, long c) { S; return c/3; } +EXPORT(unsigned long) __stdcall s_tf_bL(signed char x, unsigned long c) { U; return c/3; } +EXPORT(LONG_LONG) __stdcall s_tf_bq(signed char x, LONG_LONG c) { S; return c/3; } +EXPORT(unsigned LONG_LONG) __stdcall s_tf_bQ(signed char x, unsigned LONG_LONG c) { U; return c/3; } +EXPORT(float) __stdcall s_tf_bf(signed char x, float c) { S; return c/3; } +EXPORT(double) __stdcall s_tf_bd(signed char x, double c) { S; return c/3; } +EXPORT(void) __stdcall s_tv_i(int c) { S; return; } +#endif + +/********/ + +#ifndef MS_WIN32 + +typedef struct { + long x; + long y; +} POINT; + +typedef struct { + long left; + long top; + long right; + long bottom; +} RECT; + +#endif + +EXPORT(int) PointInRect(RECT *prc, POINT pt) +{ + if (pt.x < prc->left) + return 0; + if (pt.x > prc->right) + return 0; + if (pt.y < prc->top) + return 0; + if (pt.y > prc->bottom) + return 0; + return 1; +} + +typedef struct { + short x; + short y; +} S2H; + +EXPORT(S2H) ret_2h_func(S2H inp) +{ + inp.x *= 2; + inp.y *= 3; + return inp; +} + +typedef struct { + int a, b, c, d, e, f, g, h; +} S8I; + + + +typedef int (*CALLBACK_RECT)(RECT rect); + +EXPORT(int) call_callback_with_rect(CALLBACK_RECT cb, RECT rect) +{ + return cb(rect); +} + + +EXPORT(S8I) ret_8i_func(S8I inp) +{ + inp.a *= 2; + inp.b *= 3; + inp.c *= 4; + inp.d *= 5; + inp.e *= 6; + inp.f *= 7; + inp.g *= 8; + inp.h *= 9; + return inp; +} + +EXPORT(int) GetRectangle(int flag, RECT *prect) +{ + if (flag == 0) + return 0; + prect->left = (int)flag; + prect->top = (int)flag + 1; + prect->right = (int)flag + 2; + prect->bottom = (int)flag + 3; + return 1; +} + +EXPORT(void) TwoOutArgs(int a, int *pi, int b, int *pj) +{ + *pi += a; + *pj += b; +} + +#ifdef MS_WIN32 +EXPORT(S2H) __stdcall s_ret_2h_func(S2H inp) { return ret_2h_func(inp); } +EXPORT(S8I) __stdcall s_ret_8i_func(S8I inp) { return ret_8i_func(inp); } +#endif + +#ifdef MS_WIN32 +/* Should port this */ +#include +#include + +EXPORT (HRESULT) KeepObject(IUnknown *punk) +{ + static IUnknown *pobj; + if (punk) + punk->lpVtbl->AddRef(punk); + if (pobj) + pobj->lpVtbl->Release(pobj); + pobj = punk; + return S_OK; +} + +#endif + +typedef union { + short x; + long y; +} UN; + +EXPORT(UN) ret_un_func(UN inp) +{ + inp.y = inp.x * 10000; + return inp; +} + +EXPORT(int) my_unused_function(void) +{ + return 42; +} + +EXPORT(int) test_errno(void) +{ + int result = errno; + errno = result + 1; + return result; +} + +EXPORT(int *) test_issue1655(char const *tag, int *len) +{ + static int data[] = { -1, -2, -3, -4 }; + *len = -42; + if (strcmp(tag, "testing!") != 0) + return NULL; + *len = sizeof(data) / sizeof(data[0]); + return data; +} diff --git a/vm/src/stdlib/ctypes/_ctypes_test.h b/vm/src/stdlib/ctypes/_ctypes_test.h new file mode 100644 index 0000000000..9ecba887ef --- /dev/null +++ b/vm/src/stdlib/ctypes/_ctypes_test.h @@ -0,0 +1 @@ +extern int _testfunc_i_bhilfd(char b, short h, int i, long l, float f, double d); \ No newline at end of file From 577c5ae219cf6ab07bbe7083ed5787b08676d3b0 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 8 Aug 2021 09:10:44 -0300 Subject: [PATCH 53/56] Add suggestions and bump dependencies --- Cargo.lock | 86 +++++++++--------------------- jit/Cargo.toml | 2 +- vm/Cargo.toml | 4 +- vm/src/stdlib/ctypes/array.rs | 20 +++---- vm/src/stdlib/ctypes/basics.rs | 44 +++++++-------- vm/src/stdlib/ctypes/function.rs | 24 +++++---- vm/src/stdlib/ctypes/primitive.rs | 6 +-- vm/src/stdlib/ctypes/shared_lib.rs | 2 +- 8 files changed, 73 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca45c92d9e..ed7e628ffc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,12 +189,6 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "caseless" version = "0.2.1" @@ -335,9 +329,9 @@ dependencies = [ [[package]] name = "cranelift" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7af7d6fd6291870ac6da1aa31acbc3b95e48d5372c0596a304ac7b81bd5d5fd" +checksum = "f499639a3d140f366a329a35b0739063e5587a33c625219139698e9436203dfc" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -345,20 +339,19 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841476ab6d3530136b5162b64a2c6969d68141843ad2fd59126e5ea84fd9b5fe" +checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5619cef8d19530298301f91e9a0390d369260799a3d8dd01e28fc88e53637a" +checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" dependencies = [ - "byteorder", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", @@ -367,14 +360,13 @@ dependencies = [ "regalloc", "smallvec", "target-lexicon", - "thiserror", ] [[package]] name = "cranelift-codegen-meta" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a319709b8267939155924114ea83f2a5b5af65ece3ac6f703d4735f3c66bb0d" +checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -382,21 +374,21 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15925b23cd3a448443f289d85a8f53f3cf7a80f0137aa53c8e3b01ae8aefaef7" +checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-entity" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610cf464396c89af0f9f7c64b5aa90aa9e8812ac84084098f1565b40051bc415" +checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-frontend" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d20c8bd4a1c41ded051734f0e33ad1d843a0adc98b9bd975ee6657e2c70cdc9" +checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log", @@ -406,16 +398,15 @@ dependencies = [ [[package]] name = "cranelift-jit" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d006da9619d4d11b6b6a0422dc75449d6b341a7974818ee29b466bda3d0fa6" +checksum = "c6f2d58575eff238e2554a5df7ea8dc52a2825269539617bd32ee44abaecf373" dependencies = [ "anyhow", "cranelift-codegen", "cranelift-entity", "cranelift-module", "cranelift-native", - "errno", "libc", "log", "region", @@ -425,24 +416,24 @@ dependencies = [ [[package]] name = "cranelift-module" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184e6ee262afdecfb6e8c49b8bc2c565aeacd2a63461ade3b0e65c9ac21f57ec" +checksum = "2e241d0b091e80f41cac341fd51a80619b344add0e168e0587ba9e368d01d2c1" dependencies = [ "anyhow", "cranelift-codegen", "cranelift-entity", "log", - "thiserror", ] [[package]] name = "cranelift-native" -version = "0.72.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304e100df41f34a5a15291b37bfe0fd7abd0427a2c84195cc69578b4137f9099" +checksum = "4c04d1fe6a5abb5bb0edc78baa8ef238370fb8e389cc88b6d153f7c3e9680425" dependencies = [ "cranelift-codegen", + "libc", "target-lexicon", ] @@ -685,27 +676,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "errno" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - [[package]] name = "error-code" version = "2.3.0" @@ -808,12 +778,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" version = "0.14.4" @@ -1055,9 +1019,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -2310,9 +2274,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" +checksum = "b0652da4c4121005e9ed22b79f6c5f2d9e2752906b53a33e9490489ba421a6fb" [[package]] name = "term" diff --git a/jit/Cargo.toml b/jit/Cargo.toml index 5e354273e3..be6f31de3a 100644 --- a/jit/Cargo.toml +++ b/jit/Cargo.toml @@ -14,7 +14,7 @@ cranelift = "0.72.0" cranelift-module = "0.72.0" cranelift-jit = "0.72.0" num-traits = "0.2" -libffi = "1.0" +libffi = "1.0.1" rustpython-bytecode = { path = "../bytecode", version = "0.1.2" } thiserror = "1.0" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 1df2e49a1b..dba11e905d 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -131,8 +131,8 @@ openssl-probe = { version = "0.1", optional = true } which = "4.0" foreign-types-shared = { version = "0.1", optional = true } # ctypes -libloading = "0.6.5" -libffi = "1.0.0" +libloading = "0.7.0" +libffi = "1.0.1" widestring = "0.4.3" [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 40fbbefccf..6b945b05f3 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -114,15 +114,16 @@ pub fn make_array_with_length( .get_attribute(outer_type.clone(), "_type_") .map_err(|_| vm.new_type_error("_type_ must have storage info".to_string()))?; let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); - let _ = length + let capacity = length .checked_mul(itemsize) .ok_or_else(|| vm.new_overflow_error("Array size too big".to_string()))?; + // FIXME change this initialization Ok(PyCArray { _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(vm), _length_: length, _buffer: PyRwLock::new(RawBuffer { - inner: Vec::with_capacity(length * itemsize).as_mut_ptr(), - size: length * itemsize, + inner: Vec::with_capacity(capacity).as_mut_ptr(), + size: capacity, }), } .into_ref_with_type(vm, cls)?) @@ -480,6 +481,7 @@ impl PyCArray { self._length_ } + // FIXME: change k_or_idx to SequenceIndex #[pymethod(magic)] fn getitem( zelf: PyRef, @@ -574,17 +576,11 @@ impl PyCArray { } impl PyCDataFunctions for PyCArray { - fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - Ok(vm.new_pyobj( - zelf._length_ - * usize::try_from_object( - vm, - PyCDataFunctions::size_of_instances(zelf._type_.clone(), vm)?, - )?, - )) + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(zelf._length_ * PyCDataFunctions::size_of_instances(zelf._type_.clone(), vm)?) } - fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { PyCDataFunctions::alignment_of_instances(zelf._type_.clone(), vm) } diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 107dc83002..df8cb8528d 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -92,6 +92,7 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult PyResult { +pub fn default_from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { //@TODO: check if this behaves like it should if vm.isinstance(&value, &cls)? { Ok(value) @@ -183,17 +179,16 @@ pub fn default_from_param( #[pyimpl] pub trait PyCDataFunctions: PyValue { #[pymethod] - fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; #[pymethod] - fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult; #[pymethod] - fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) - -> PyResult; + fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult; #[pymethod] - fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult; + fn address_of(zelf: PyRef, vm: &VirtualMachine) -> PyResult; } #[pyimpl] pub trait PyCDataMethods: PyValue { @@ -208,11 +203,7 @@ pub trait PyCDataMethods: PyValue { // PyCFuncPtrType_Type #[pymethod] - fn from_param( - zelf: PyRef, - value: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult; + fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; #[pyclassmethod] fn from_address( @@ -285,7 +276,7 @@ pub trait PyCDataSequenceMethods: PyValue { #[pymethod(name = "__mul__")] #[pymethod(name = "__rmul__")] - fn mul(zelf: PyRef, length: isize, vm: &VirtualMachine) -> PyResult { + fn mul(zelf: PyRef, length: isize, vm: &VirtualMachine) -> PyResult { if length < 0 { Err(vm.new_value_error(format!("Array length must be >= 0, not {} length", length))) } else { @@ -381,6 +372,7 @@ where pub options: BufferOptions, } +// FIXME: Change this implementation pub struct RawBuffer { pub inner: *mut u8, pub size: usize, @@ -447,29 +439,33 @@ impl PyCData { pub fn setstate(zelf: PyRef) {} } -pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> PyResult { +// FIXME: this function is too hacky, work a better way of doing it +pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> PyResult { match tp { Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { let zelf = new_simple_type(Either::B(&type_), vm)?; PyCDataFunctions::size_of_instances(zelf.into_ref(vm), vm) } Either::B(obj) if obj.has_class_attr("size_of_instances") => { - let size_of = vm.get_attribute(obj, "size_of_instances").unwrap(); - vm.invoke(&size_of, ()) + let size_of_method = vm.get_attribute(obj, "size_of_instances").unwrap(); + let size_of_return = vm.invoke(&size_of_method, ())?; + Ok(usize::try_from_object(vm, size_of_return)?) } _ => Err(vm.new_type_error("this type has no size".to_string())), } } -pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { +// FIXME: this function is too hacky, work a better way of doing it +pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { match tp { Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { let zelf = new_simple_type(Either::B(&type_), vm)?; PyCDataFunctions::alignment_of_instances(zelf.into_ref(vm), vm) } Either::B(obj) if obj.has_class_attr("alignment_of_instances") => { - let size_of = vm.get_attribute(obj, "alignment_of_instances").unwrap(); - vm.invoke(&size_of, ()) + let alignment_of_m = vm.get_attribute(obj, "alignment_of_instances")?; + let alignment_of_r = vm.invoke(&alignment_of_m, ())?; + usize::try_from_object(vm, alignment_of_m) } _ => Err(vm.new_type_error("no alignment info".to_string())), } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index eea765aa63..80db516f33 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -278,8 +278,15 @@ impl PyCFuncPtr { #[pyproperty(name = "_argtypes_", setter)] fn set_argtypes(&self, argtypes: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - if vm.isinstance(&argtypes, &vm.ctx.types.list_type).is_ok() - || vm.isinstance(&argtypes, &vm.ctx.types.tuple_type).is_ok() + if vm + .isinstance(&argtypes, &vm.ctx.types.list_type) + .and_then(|_| vm.isinstance(&argtypes, &vm.ctx.types.tuple_type)) + .map_err(|e| { + vm.new_type_error(format!( + "_argtypes_ must be a sequence of types, {} found.", + argtypes.to_string() + )) + })? { let args = vm.extract_elements(&argtypes)?; let c_args_res: PyResult> = args @@ -287,9 +294,9 @@ impl PyCFuncPtr { .enumerate() .map(|(idx, inner_obj)| { match vm.isinstance(inner_obj, PySimpleType::static_type()) { - // @TODO: checks related to _type_ are temporary + // FIXME: checks related to _type_ are temporary // it needs to check for from_param method, instead - Ok(_) => Ok(vm.get_attribute(inner_obj.clone(), "_type_").unwrap()), + Ok(_) => vm.get_attribute(inner_obj.clone(), "_type_"), _ => Err(vm.new_type_error(format!( "item {} in _argtypes_ must be subclass of _SimpleType, but type {} found", idx, @@ -310,14 +317,9 @@ impl PyCFuncPtr { let mut fn_ptr = self._f.write(); fn_ptr.set_args(str_types); - - Ok(()) - } else { - Err(vm.new_type_error(format!( - "_argtypes_ must be a sequence of types, {} found.", - argtypes.to_string() - ))) } + + Ok(()) } #[pyproperty(name = "_restype_", setter)] diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 5dfdde23fd..adf9b5c541 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -448,11 +448,11 @@ impl PySimpleType { } impl PyCDataFunctions for PySimpleType { - fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - Ok(vm.new_pyobj(get_size(zelf._type_.as_str()))) + fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + Ok(get_size(zelf._type_.as_str())) } - fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + fn alignment_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { Self::size_of_instances(zelf, vm) } diff --git a/vm/src/stdlib/ctypes/shared_lib.rs b/vm/src/stdlib/ctypes/shared_lib.rs index 96e73bfc3d..65d908d1b5 100644 --- a/vm/src/stdlib/ctypes/shared_lib.rs +++ b/vm/src/stdlib/ctypes/shared_lib.rs @@ -32,7 +32,7 @@ impl PyValue for SharedLibrary { impl SharedLibrary { pub fn new(name: &str) -> Result { Ok(SharedLibrary { - lib: AtomicCell::new(Some(Library::new(name.to_string())?)), + lib: AtomicCell::new(Some(unsafe { Library::new(name.to_string())? })), }) } From 6eb0794e238ff2211fbf72cd29ac0a47bc97f67a Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 8 Aug 2021 11:57:05 -0300 Subject: [PATCH 54/56] Starting to add Metas to primitive and array ctypes --- Cargo.lock | 82 +++++++++---- vm/src/stdlib/ctypes/array.rs | 58 ++++++--- vm/src/stdlib/ctypes/basics.rs | 8 +- vm/src/stdlib/ctypes/function.rs | 8 +- vm/src/stdlib/ctypes/mod.rs | 9 +- vm/src/stdlib/ctypes/primitive.rs | 188 ++++++++++++++---------------- 6 files changed, 202 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed7e628ffc..790987bfde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,12 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "caseless" version = "0.2.1" @@ -329,9 +335,9 @@ dependencies = [ [[package]] name = "cranelift" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f499639a3d140f366a329a35b0739063e5587a33c625219139698e9436203dfc" +checksum = "c7af7d6fd6291870ac6da1aa31acbc3b95e48d5372c0596a304ac7b81bd5d5fd" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -339,19 +345,20 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "841476ab6d3530136b5162b64a2c6969d68141843ad2fd59126e5ea84fd9b5fe" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "2b5619cef8d19530298301f91e9a0390d369260799a3d8dd01e28fc88e53637a" dependencies = [ + "byteorder", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", @@ -360,13 +367,14 @@ dependencies = [ "regalloc", "smallvec", "target-lexicon", + "thiserror", ] [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "2a319709b8267939155924114ea83f2a5b5af65ece3ac6f703d4735f3c66bb0d" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -374,21 +382,21 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "15925b23cd3a448443f289d85a8f53f3cf7a80f0137aa53c8e3b01ae8aefaef7" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "610cf464396c89af0f9f7c64b5aa90aa9e8812ac84084098f1565b40051bc415" [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "4d20c8bd4a1c41ded051734f0e33ad1d843a0adc98b9bd975ee6657e2c70cdc9" dependencies = [ "cranelift-codegen", "log", @@ -398,15 +406,16 @@ dependencies = [ [[package]] name = "cranelift-jit" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f2d58575eff238e2554a5df7ea8dc52a2825269539617bd32ee44abaecf373" +checksum = "79d006da9619d4d11b6b6a0422dc75449d6b341a7974818ee29b466bda3d0fa6" dependencies = [ "anyhow", "cranelift-codegen", "cranelift-entity", "cranelift-module", "cranelift-native", + "errno", "libc", "log", "region", @@ -416,24 +425,24 @@ dependencies = [ [[package]] name = "cranelift-module" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e241d0b091e80f41cac341fd51a80619b344add0e168e0587ba9e368d01d2c1" +checksum = "184e6ee262afdecfb6e8c49b8bc2c565aeacd2a63461ade3b0e65c9ac21f57ec" dependencies = [ "anyhow", "cranelift-codegen", "cranelift-entity", "log", + "thiserror", ] [[package]] name = "cranelift-native" -version = "0.76.0" +version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c04d1fe6a5abb5bb0edc78baa8ef238370fb8e389cc88b6d153f7c3e9680425" +checksum = "304e100df41f34a5a15291b37bfe0fd7abd0427a2c84195cc69578b4137f9099" dependencies = [ "cranelift-codegen", - "libc", "target-lexicon", ] @@ -676,6 +685,27 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "error-code" version = "2.3.0" @@ -778,6 +808,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.14.4" @@ -2274,9 +2310,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0652da4c4121005e9ed22b79f6c5f2d9e2752906b53a33e9490489ba421a6fb" +checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" [[package]] name = "term" diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 6b945b05f3..2800773c38 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -1,11 +1,11 @@ use super::{ pointer::PyCPointer, - primitive::{new_simple_type, PySimpleType}, + primitive::{new_simple_type, PySimpleMeta, PyCSimple}, }; use crate::builtins::{ self, memory::{try_buffer_from_object, Buffer}, - slice::PySlice, + slice::PySlice, PyType, PyBytes, PyInt, PyList, PyStr, PyTypeRef, }; use crate::common::borrow::BorrowedValueMut; @@ -138,7 +138,7 @@ fn set_array_value( vm: &VirtualMachine, ) -> PyResult<()> { if !obj.class().issubclass(PyCData::static_type()) { - let value = PyCDataMethods::from_param(zelf._type_.clone(), obj, vm)?; + let value = PySimpleMeta::from_param(zelf._type_.clone_class(), obj, vm)?; let v_buffer = try_buffer_from_object(vm, &value)?; let v_buffer_bytes = v_buffer.obj_bytes_mut(); @@ -191,13 +191,30 @@ fn array_get_slice_inner( Ok((step, start, stop)) } -#[pyclass(module = "_ctypes", name = "Array", base = "PyCData")] +#[pyclass(module = "_ctypes", name = "PyCArrayType", base = "PyType")] +pub struct PyCArrayMeta {} + +#[pyclass( + module = "_ctypes", + name = "Array", + base = "PyCData", + metaclass = "PyCArrayMeta" +)] pub struct PyCArray { - _type_: PyRef, + _type_: PyRef, _length_: usize, _buffer: PyRwLock, } +impl fmt::Debug for PyCArrayMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PyCArrayMeta", + ) + } +} + impl fmt::Debug for PyCArray { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( @@ -209,6 +226,12 @@ impl fmt::Debug for PyCArray { } } +impl PyValue for PyCArrayMeta { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + impl PyValue for PyCArray { fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() @@ -233,9 +256,9 @@ impl BufferProtocol for PyCArray { } } -impl PyCDataMethods for PyCArray { +impl PyCDataMethods for PyCArrayMeta { fn from_param( - zelf: PyRef, + tp: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { @@ -243,6 +266,8 @@ impl PyCDataMethods for PyCArray { return Ok(value); } + let zelf = tp.as_object().downcast_exact::(vm).unwrap(); + if vm.obj_len(&value)? > zelf._length_ { return Err(vm.new_value_error("value has size greater than the array".to_string())); } @@ -272,13 +297,10 @@ impl PyCDataMethods for PyCArray { } } -#[pyimpl( - flags(BASETYPE), - with(BufferProtocol, PyCDataFunctions, PyCDataMethods) -)] -impl PyCArray { +#[pyimpl(with(PyCDataMethods), flags(BASETYPE))] +impl PyCArrayMeta { #[pyslot] - fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult> { + fn tp_new(cls: PyTypeRef, vm: &VirtualMachine) -> PyResult { let length_obj = vm .get_attribute(cls.as_object().to_owned(), "_length_") .map_err(|_| { @@ -297,9 +319,15 @@ impl PyCArray { ) }?; - make_array_with_length(cls, length, vm) + Ok(make_array_with_length(cls, length, vm)?.as_object().clone()) } +} +#[pyimpl( + flags(BASETYPE), + with(BufferProtocol, PyCDataFunctions) +)] +impl PyCArray { #[pymethod(magic)] pub fn init(zelf: PyRef, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { value.map_or(Ok(()), |value| { @@ -605,4 +633,4 @@ impl PyCDataFunctions for PyCArray { } } -impl PyCDataSequenceMethods for PyCArray {} +impl PyCDataSequenceMethods for PyCArrayMeta {} diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index df8cb8528d..8ada8869f9 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -18,7 +18,7 @@ use crate::VirtualMachine; use crate::stdlib::ctypes::array::make_array_with_length; use crate::stdlib::ctypes::dll::dlsym; -use crate::stdlib::ctypes::primitive::{new_simple_type, PySimpleType}; +use crate::stdlib::ctypes::primitive::{new_simple_type, PyCSimple}; use crossbeam_utils::atomic::AtomicCell; @@ -203,7 +203,7 @@ pub trait PyCDataMethods: PyValue { // PyCFuncPtrType_Type #[pymethod] - fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; + fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; #[pyclassmethod] fn from_address( @@ -442,7 +442,7 @@ impl PyCData { // FIXME: this function is too hacky, work a better way of doing it pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> PyResult { match tp { - Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { + Either::A(type_) if type_.issubclass(PyCSimple::static_type()) => { let zelf = new_simple_type(Either::B(&type_), vm)?; PyCDataFunctions::size_of_instances(zelf.into_ref(vm), vm) } @@ -458,7 +458,7 @@ pub fn sizeof_func(tp: Either, vm: &VirtualMachine) -> P // FIXME: this function is too hacky, work a better way of doing it pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyResult { match tp { - Either::A(type_) if type_.issubclass(PySimpleType::static_type()) => { + Either::A(type_) if type_.issubclass(PyCSimple::static_type()) => { let zelf = new_simple_type(Either::B(&type_), vm)?; PyCDataFunctions::alignment_of_instances(zelf.into_ref(vm), vm) } diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 80db516f33..9752cf1438 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -15,7 +15,7 @@ use crate::pyobject::{ use crate::VirtualMachine; use crate::stdlib::ctypes::basics::PyCData; -use crate::stdlib::ctypes::primitive::PySimpleType; +use crate::stdlib::ctypes::primitive::PyCSimple; use crate::slots::Callable; use crate::stdlib::ctypes::dll::dlsym; @@ -293,7 +293,7 @@ impl PyCFuncPtr { .iter() .enumerate() .map(|(idx, inner_obj)| { - match vm.isinstance(inner_obj, PySimpleType::static_type()) { + match vm.isinstance(inner_obj, PyCSimple::static_type()) { // FIXME: checks related to _type_ are temporary // it needs to check for from_param method, instead Ok(_) => vm.get_attribute(inner_obj.clone(), "_type_"), @@ -324,7 +324,7 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_", setter)] fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - match vm.isinstance(&restype, PySimpleType::static_type()) { + match vm.isinstance(&restype, PyCSimple::static_type()) { // @TODO: checks related to _type_ are temporary Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { Ok(_type_) => { @@ -421,7 +421,7 @@ impl Callable for PyCFuncPtr { .enumerate() .map(|(idx, obj)| { if vm - .issubclass(&obj.clone_class(), PySimpleType::static_type()) + .issubclass(&obj.clone_class(), PyCSimple::static_type()) .is_ok() { Ok(vm.get_attribute(obj.clone(), "value")?) diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 3f99c74963..104626df1a 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -10,17 +10,18 @@ mod primitive; mod shared_lib; mod structure; -use array::PyCArray; +use array::{PyCArray,PyCArrayMeta}; use basics::{addressof, alignment, byref, sizeof_func, PyCData}; use function::PyCFuncPtr; use pointer::{pointer_fn, PyCPointer, POINTER}; -use primitive::{PyCSimpleType, PySimpleType}; +use primitive::{PySimpleMeta, PyCSimple}; use structure::PyCStructure; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; PyCData::make_class(ctx); - PyCSimpleType::make_class(ctx); + PySimpleMeta::make_class(ctx); + PyCArrayMeta::make_class(ctx); let module = py_module!(vm, "_ctypes", { "__version__" => ctx.new_str("1.1.0"), @@ -35,7 +36,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { "_pointer_type_cache" => ctx.new_dict(), "CFuncPtr" => PyCFuncPtr::make_class(ctx), - "_SimpleCData" => PySimpleType::make_class(ctx), + "_SimpleCData" => PyCSimple::make_class(ctx), "_Pointer" => PyCPointer::make_class(ctx), "Array" => PyCArray::make_class(ctx), "Struct" => PyCStructure::make_class(ctx) diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index adf9b5c541..dbf6cfc444 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -139,13 +139,12 @@ fn generic_xxx_p_from_param( if vm.isinstance(value, &vm.ctx.types.str_type)? || vm.isinstance(value, &vm.ctx.types.bytes_type)? { - Ok(PySimpleType { + Ok(PyCSimple { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: cls.is(PySimpleType::static_type()), } .into_object(vm)) - } else if vm.isinstance(value, PySimpleType::static_type())? + } else if vm.isinstance(value, PyCSimple::static_type())? && (type_str == "z" || type_str == "Z" || type_str == "P") { Ok(value.clone()) @@ -217,10 +216,9 @@ fn from_param_void_p( Err(vm.new_attribute_error("class has no from_address method".to_string())) } } else if vm.isinstance(value, &vm.ctx.types.int_type)? { - Ok(PySimpleType { + Ok(PyCSimple { _type_: type_str.to_string(), value: AtomicCell::new(value.clone()), - __abstract__: cls.is(PySimpleType::static_type()), } .into_object(vm)) } else { @@ -232,19 +230,14 @@ fn from_param_void_p( pub fn new_simple_type( cls: Either<&PyObjectRef, &PyTypeRef>, vm: &VirtualMachine, -) -> PyResult { +) -> PyResult { let cls = match cls { Either::A(obj) => obj, Either::B(typ) => typ.as_object(), }; - let is_abstract = cls.is(PySimpleType::static_type()); - - if is_abstract { - return Err(vm.new_type_error("abstract class".to_string())); - } - match vm.get_attribute(cls.clone(), "_type_") { - Ok(_type_) if vm.isinstance(&_type_, &vm.ctx.types.str_type)? => { + if let Ok(_type_) = vm.get_attribute(cls.clone(), "_type_") { + if vm.isinstance(&_type_, &vm.ctx.types.str_type)? { let tp_str = _type_.downcast_exact::(vm).unwrap().to_string(); if tp_str.len() != 1 { @@ -255,45 +248,48 @@ pub fn new_simple_type( } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) } else { - Ok(PySimpleType { + Ok(PyCSimple { _type_: tp_str, value: AtomicCell::new(vm.ctx.none()), - __abstract__: is_abstract, }) } - } - Ok(_) => { + } else { Err(vm.new_type_error("class must define a '_type_' string attribute".to_string())) } - _ => Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())), + } else { + Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string())) } } -#[pyclass(module = "_ctypes", name = "CSimpleType", base = "PyType")] -pub struct PyCSimpleType {} +#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")] +pub struct PySimpleMeta{} -#[pyimpl(flags(BASETYPE))] -impl PyCSimpleType {} +#[pyimpl(with(PyCDataMethods), flags(BASETYPE))] +impl PySimpleMeta { + #[pyslot] + fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(new_simple_type(Either::B(&cls), vm)?.into_ref_with_type(vm, cls)?.as_object().clone()) + } +} #[pyclass( module = "_ctypes", name = "_SimpleCData", base = "PyCData", - metaclass = "PyCSimpleType" + metaclass = "PySimpleMeta" )] -pub struct PySimpleType { +pub struct PyCSimple { pub _type_: String, value: AtomicCell, - __abstract__: bool, } -impl fmt::Debug for PySimpleType { +impl fmt::Debug for PyCSimple { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let value = unsafe { (*self.value.as_ptr()).to_string() }; write!( f, - "PySimpleType {{ + "PyCSimple {{ _type_: {}, value: {}, }}", @@ -303,102 +299,92 @@ impl fmt::Debug for PySimpleType { } } -impl PyValue for PySimpleType { +impl fmt::Debug for PySimpleMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PySimpleMeta" + ) + } +} + +impl PyValue for PyCSimple { fn class(_vm: &VirtualMachine) -> &PyTypeRef { Self::static_type() } } -impl PyCDataMethods for PySimpleType { +impl PyValue for PySimpleMeta { + fn class(_vm: &VirtualMachine) -> &PyTypeRef { + Self::static_type() + } +} + +impl PyCDataMethods for PySimpleMeta { // From PyCSimpleType_Type PyCSimpleType_methods fn from_param( - zelf: PyRef, + tp: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - let cls = zelf.clone_class(); - if cls.is(PySimpleType::static_type()) { + let cls = tp.clone_class(); + if cls.is(PyCSimple::static_type()) { Err(vm.new_type_error("abstract class".to_string())) } else if vm.isinstance(&value, &cls)? { Ok(value) } else { - match vm.get_attribute(zelf.as_object().clone(), "_type_") { - Ok(tp_obj) if vm.isinstance(&tp_obj, &vm.ctx.types.str_type)? => { - let _type_ = tp_obj.downcast_exact::(vm).unwrap(); - let tp_str = _type_.as_ref(); - - match tp_str { - "z" | "Z" => from_param_char_p(&cls, &value, vm), - "P" => from_param_void_p(&cls, &value, vm), - _ => { - match new_simple_type(Either::B(&cls), vm) { - Ok(obj) => Ok(obj.into_object(vm)), - Err(e) - if vm.isinstance( - &e.clone().into_object(), - &vm.ctx.exceptions.type_error, - )? || vm.isinstance( - &e.clone().into_object(), - &vm.ctx.exceptions.value_error, - )? => - { - if let Some(my_base) = cls.base.clone() { - if let Some(from_param) = - vm.get_method(my_base.as_object().clone(), "from_param") - { - Ok(vm.invoke( - &from_param?, - (my_base.clone_class(), value), - )?) - } else { - // @TODO: Make sure of what goes here - Err(vm.new_attribute_error( - "class has no from_param method".to_string(), - )) - } - } else { - // @TODO: This should be unreachable - Err(vm.new_type_error("class has no base".to_string())) - } - } - Err(e) => Err(e), + let tp_obj = vm.get_attribute(tp.as_object().clone(), "_type_").unwrap(); + let _type_ = tp_obj.downcast_exact::(vm).unwrap(); + match _type_.as_ref() { + "z" | "Z" => from_param_char_p(&cls, &value, vm), + "P" => from_param_void_p(&cls, &value, vm), + _ => { + match new_simple_type(Either::B(&cls), vm) { + Ok(obj) => Ok(obj.into_object(vm)), + Err(e) => { + if vm.isinstance( + &e.clone().into_object(), + &vm.ctx.exceptions.type_error, + )? || vm.isinstance( + &e.clone().into_object(), + &vm.ctx.exceptions.value_error, + )? { + if vm.isinstance(&value,&tp)?{ + return Ok(value); + } + + let as_parameter = vm + .get_attribute(tp.as_object().clone(), "_as_parameter_") + .map_err(|_| { + vm.new_type_error(format!("expected {} instance instead of {}", tp.class().name, value.class().name)) + })?; + + return PySimpleMeta::from_param(tp, value, &vm); } + Err(e) } } } - // @TODO: Sanity check, this should be unreachable - _ => { - Err(vm - .new_attribute_error("class must define a '_type_' attribute".to_string())) - } } } } } -#[pyimpl(with(PyCDataFunctions, PyCDataMethods), flags(BASETYPE))] -impl PySimpleType { - #[pyslot] - fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult> { - new_simple_type(Either::B(&cls), vm)?.into_ref_with_type(vm, cls) - } - +#[pyimpl(with(PyCDataFunctions), flags(BASETYPE))] +impl PyCSimple { #[pymethod(magic)] pub fn init(&self, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { - match value.into_option() { - Some(ref v) => { - let content = set_primitive(self._type_.as_str(), v, vm)?; - self.value.store(content); - } - _ => { - self.value.store(match self._type_.as_str() { - "c" | "u" => vm.ctx.new_bytes(vec![0]), - "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), - "f" | "d" | "g" => vm.ctx.new_float(0.0), - "?" => vm.ctx.new_bool(false), - _ => vm.ctx.none(), // "z" | "Z" | "P" - }); - } + if let Some(ref v) = value.into_option() { + let content = set_primitive(self._type_.as_str(), v, vm)?; + self.value.store(content); + } else { + self.value.store(match self._type_.as_str() { + "c" | "u" => vm.ctx.new_bytes(vec![0]), + "b" | "B" | "h" | "H" | "i" | "I" | "l" | "q" | "L" | "Q" => vm.ctx.new_int(0), + "f" | "d" | "g" => vm.ctx.new_float(0.0), + "?" => vm.ctx.new_bool(false), + _ => vm.ctx.none(), // "z" | "Z" | "P" + }); } Ok(()) } @@ -419,7 +405,7 @@ impl PySimpleType { #[pymethod(magic)] pub fn ctypes_from_outparam(zelf: PyRef, vm: &VirtualMachine) -> PyResult { if let Some(base) = zelf.class().base.clone() { - if vm.bool_eq(&base.as_object(), PySimpleType::static_type().as_object())? { + if vm.bool_eq(&base.as_object(), PyCSimple::static_type().as_object())? { return Ok(zelf.as_object().clone()); } } @@ -447,7 +433,7 @@ impl PySimpleType { } } -impl PyCDataFunctions for PySimpleType { +impl PyCDataFunctions for PyCSimple { fn size_of_instances(zelf: PyRef, vm: &VirtualMachine) -> PyResult { Ok(get_size(zelf._type_.as_str())) } @@ -469,4 +455,4 @@ impl PyCDataFunctions for PySimpleType { } } -impl PyCDataSequenceMethods for PySimpleType {} +impl PyCDataSequenceMethods for PySimpleMeta {} From 59162185da8b5a5db033f0cd221cb6efc0b091bb Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sun, 15 Aug 2021 19:04:11 -0300 Subject: [PATCH 55/56] Fixing some terribly wrong impls and bugs. Adding more meta impls. --- extra_tests/snippets/ctypes_tests/conftest.py | 102 ---- vm/src/stdlib/ctypes/array.rs | 453 ++++++++++-------- vm/src/stdlib/ctypes/basics.rs | 24 +- vm/src/stdlib/ctypes/function.rs | 6 +- vm/src/stdlib/ctypes/mod.rs | 4 +- vm/src/stdlib/ctypes/primitive.rs | 72 ++- 6 files changed, 313 insertions(+), 348 deletions(-) delete mode 100644 extra_tests/snippets/ctypes_tests/conftest.py diff --git a/extra_tests/snippets/ctypes_tests/conftest.py b/extra_tests/snippets/ctypes_tests/conftest.py deleted file mode 100644 index 05283895a9..0000000000 --- a/extra_tests/snippets/ctypes_tests/conftest.py +++ /dev/null @@ -1,102 +0,0 @@ -import py -import pytest -import sys -import os - -# XXX: copied from pypy/tool/cpyext/extbuild.py -if os.name != 'nt': - so_ext = 'so' -else: - so_ext = 'dll' - -def _build(cfilenames, outputfilename, compile_extra, link_extra, - include_dirs, libraries, library_dirs): - try: - # monkeypatch distutils for some versions of msvc compiler - import setuptools - except ImportError: - # XXX if this fails and is required, - # we must call pypy -mensurepip after translation - pass - from distutils.ccompiler import new_compiler - from distutils import sysconfig - - # XXX for Darwin running old versions of CPython 2.7.x - sysconfig.get_config_vars() - - compiler = new_compiler(force=1) - sysconfig.customize_compiler(compiler) # XXX - objects = [] - for cfile in cfilenames: - cfile = py.path.local(cfile) - old = cfile.dirpath().chdir() - try: - res = compiler.compile([cfile.basename], - include_dirs=include_dirs, extra_preargs=compile_extra) - assert len(res) == 1 - cobjfile = py.path.local(res[0]) - assert cobjfile.check() - objects.append(str(cobjfile)) - finally: - old.chdir() - - compiler.link_shared_object( - objects, str(outputfilename), - libraries=libraries, - extra_preargs=link_extra, - library_dirs=library_dirs) - -def c_compile(cfilenames, outputfilename, - compile_extra=None, link_extra=None, - include_dirs=None, libraries=None, library_dirs=None): - compile_extra = compile_extra or [] - link_extra = link_extra or [] - include_dirs = include_dirs or [] - libraries = libraries or [] - library_dirs = library_dirs or [] - if sys.platform == 'win32': - link_extra = link_extra + ['/DEBUG'] # generate .pdb file - if sys.platform == 'darwin': - # support Fink & Darwinports - for s in ('/sw/', '/opt/local/'): - if (s + 'include' not in include_dirs - and os.path.exists(s + 'include')): - include_dirs.append(s + 'include') - if s + 'lib' not in library_dirs and os.path.exists(s + 'lib'): - library_dirs.append(s + 'lib') - - outputfilename = py.path.local(outputfilename).new(ext=so_ext) - saved_environ = os.environ.copy() - try: - _build( - cfilenames, outputfilename, - compile_extra, link_extra, - include_dirs, libraries, library_dirs) - finally: - # workaround for a distutils bugs where some env vars can - # become longer and longer every time it is used - for key, value in saved_environ.items(): - if os.environ.get(key) != value: - os.environ[key] = value - return outputfilename -# end copy - -def compile_so_file(udir): - cfile = py.path.local(__file__).dirpath().join("_ctypes_test.c") - - if sys.platform == 'win32': - libraries = ['oleaut32'] - else: - libraries = [] - - return c_compile([cfile], str(udir / '_ctypes_test'), libraries=libraries) - -@pytest.fixture(scope='session') -def sofile(tmpdir_factory): - udir = tmpdir_factory.mktemp('_ctypes_test') - return str(compile_so_file(udir)) - -@pytest.fixture -def dll(sofile): - from ctypes import CDLL - return CDLL(str(sofile)) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index 2800773c38..d3b832c112 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -1,19 +1,20 @@ use super::{ pointer::PyCPointer, - primitive::{new_simple_type, PySimpleMeta, PyCSimple}, + primitive::{new_simple_type, PyCSimple}, }; use crate::builtins::{ self, memory::{try_buffer_from_object, Buffer}, - slice::PySlice, PyType, - PyBytes, PyInt, PyList, PyStr, PyTypeRef, + slice::PySlice, + PyBytes, PyInt, PyList, PyRange, PyStr, PyType, PyTypeRef, }; -use crate::common::borrow::BorrowedValueMut; use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, + IdProtocol, ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + TypeProtocol, }; +use crate::sliceable::SequenceIndex; use crate::slots::BufferProtocol; use crate::stdlib::ctypes::basics::{ default_from_param, generic_get_buffer, get_size, BorrowValue as BorrowValueCData, @@ -26,14 +27,21 @@ use std::convert::TryInto; use std::fmt; use widestring::WideCString; -fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { - if ty == "u" { +// TODO: make sure that this is correct +fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { + let res = if ty == "u" { vm.new_pyobj(if cfg!(windows) { - let chunk: [u8; 2] = b.try_into().unwrap(); - u16::from_ne_bytes(chunk) as u32 + u16::from_ne_bytes( + b.try_into().map_err(|_| { + vm.new_value_error("buffer does not fit widestring".to_string()) + })?, + ) as u32 } else { - let chunk: [u8; 4] = b.try_into().unwrap(); - u32::from_ne_bytes(chunk) + u32::from_ne_bytes( + b.try_into().map_err(|_| { + vm.new_value_error("buffer does not fit widestring".to_string()) + })?, + ) }) } else { macro_rules! byte_match_type { @@ -46,8 +54,7 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { $( $( t if t == $type => { - let chunk: [u8; std::mem::size_of::<$body>()] = b.try_into().unwrap(); - vm.new_pyobj($body::from_ne_bytes(chunk)) + vm.new_pyobj($body::from_ne_bytes(b.try_into().map_err(|_| vm.new_value_error(format!("buffer does not fit type '{}'",ty)))?)) } )+ )+ @@ -68,37 +75,8 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyObjectRef { "?" | "B" => u8 "P" | "z" | "Z" => usize ) - } -} - -fn slice_adjust_size(length: isize, start: &mut isize, stop: &mut isize, step: isize) -> isize { - if *start < 0 { - *start += length; - if *start < 0 { - *start = if step < 0 { -1 } else { 0 }; - } - } else if *start >= length { - *start = if step < 0 { length - 1 } else { length }; - } - - if *stop < 0 { - *stop += length; - if *stop < 0 { - *stop = if step < 0 { -1 } else { 0 }; - } - } else if *stop >= length { - *stop = if step < 0 { length - 1 } else { length }; - } - - if step < 0 { - if *stop < *start { - return (*start - *stop - 1) / (-step) + 1; - } - } else if *start < *stop { - return (*stop - *start - 1) / step + 1; - } - - 0 + }; + Ok(res) } pub fn make_array_with_length( @@ -116,7 +94,7 @@ pub fn make_array_with_length( let itemsize = get_size(_type_.downcast::().unwrap().to_string().as_str()); let capacity = length .checked_mul(itemsize) - .ok_or_else(|| vm.new_overflow_error("Array size too big".to_string()))?; + .ok_or_else(|| vm.new_overflow_error("array too large".to_string()))?; // FIXME change this initialization Ok(PyCArray { _type_: new_simple_type(Either::A(&outer_type), vm)?.into_ref(vm), @@ -128,67 +106,186 @@ pub fn make_array_with_length( } .into_ref_with_type(vm, cls)?) } - +// TODO: finish implementation fn set_array_value( - zelf: &PyRef, - dst_buffer: &mut BorrowedValueMut<[u8]>, + zelf: &PyObjectRef, + dst_buffer: &mut [u8], idx: usize, - offset: usize, + size: usize, obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - if !obj.class().issubclass(PyCData::static_type()) { - let value = PySimpleMeta::from_param(zelf._type_.clone_class(), obj, vm)?; + let self_cls = zelf.clone_class(); + + if !self_cls.issubclass(PyCData::static_type()) { + return Err(vm.new_type_error("not a ctype instance".to_string())); + } - let v_buffer = try_buffer_from_object(vm, &value)?; - let v_buffer_bytes = v_buffer.obj_bytes_mut(); + let obj_cls = obj.clone_class(); - dst_buffer[idx..idx + offset].copy_from_slice(&v_buffer_bytes[..]); - } else if vm.isinstance(&obj, &zelf._type_.clone_class())? { + if !obj_cls.issubclass(PyCData::static_type()) { + // TODO: Fill here + } + + if vm.isinstance(&obj, &self_cls)? { let o_buffer = try_buffer_from_object(vm, &obj)?; - let src_buffer = o_buffer.obj_bytes_mut(); + let src_buffer = o_buffer.obj_bytes(); + + assert!(dst_buffer.len() == size && src_buffer.len() >= size); - dst_buffer[idx..idx + offset].copy_from_slice(&src_buffer[idx..idx + offset]); - } else if vm.isinstance(zelf._type_.as_object(), PyCPointer::static_type())? - && vm.isinstance(&obj, PyCArray::static_type())? - { - //@TODO: Fill here once CPointer is done + dst_buffer.copy_from_slice(&src_buffer[..size]); + } + + if self_cls.is(PyCPointer::static_type()) && obj_cls.is(PyCArray::static_type()) { + //TODO: Fill here } else { return Err(vm.new_type_error(format!( "incompatible types, {} instance instead of {} instance", - obj.class().name, - zelf.class().name + obj_cls.name, self_cls.name ))); } + Ok(()) } -fn array_get_slice_inner( +fn array_get_slice_params( slice: PyRef, + length: Option, vm: &VirtualMachine, ) -> PyResult<(isize, isize, isize)> { - let step = slice - .step - .as_ref() - .map(|o| isize::try_from_object(vm, o.clone())) - .transpose()? // FIXME: unnessessary clone - .unwrap_or(1); - - assert!(step != 0); - assert!(step >= -isize::MAX); - - let start = slice - .start - .clone() // FIXME: unnessessary clone - .map_or(Ok(0), |o| isize::try_from_object(vm, o))?; - - if vm.is_none(&slice.stop) { - return Err(vm.new_value_error("slice stop is required".to_string())); + if let Some(len) = length { + let indices = vm.get_method(slice.as_object().clone(), "indices").unwrap().unwrap(); + let tuple = vm.invoke(&indices, ())?; + + let (start, stop, step) = ( + tuple.get_item(0, vm)?, + tuple.get_item(1, vm)?, + tuple.get_item(2, vm)?, + ); + + Ok(( + isize::try_from_object(vm, step)?, + isize::try_from_object(vm, start)?, + isize::try_from_object(vm, stop)?, + )) + } else { + let step = slice + .step + .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + + let start = slice.start.map_or_else( + || { + if step > 0 { + Ok(0) + } else { + Err(vm.new_value_error("slice start is required for step < 0".to_string())) + } + }, + |o| isize::try_from_object(vm, o), + )?; + + let stop = isize::try_from_object(vm, slice.stop)?; + + Ok((step, start, stop)) + } +} + +fn array_slice_getitem( + zelf: PyObjectRef, + buffer_bytes: &mut [u8], + slice: PyRef, + size: usize, + vm: &VirtualMachine, +) -> PyResult { + let length = vm + .get_attribute(zelf.clone(), "_length_") + .map(|c_l| usize::try_from_object(vm, c_l))??; + + let tp = vm.get_attribute(zelf, "_type_")?; + let _type_ = tp.downcast::().unwrap().to_string().as_str(); + let (step, start, stop) = array_get_slice_params(slice, Some(vm.ctx.new_int(length)), vm)?; + + let _range = PyRange { + start: PyInt::from(start).into_ref(vm), + stop: PyInt::from(stop).into_ref(vm), + step: PyInt::from(step).into_ref(vm), + }; + + let obj_vec = Vec::new(); + let mut offset; + + for curr in _range { + let idx = fix_index(isize::try_from_object(vm, curr)?, length, vm)? as usize; + offset = idx * size; + + obj_vec.push(byte_to_pyobj( + _type_, + buffer_bytes[offset..offset + size].as_ref(), + vm, + )?); } - // FIXME: unnessessary clone - let stop = isize::try_from_object(vm, slice.stop.clone())?; - Ok((step, start, stop)) + Ok(vm.new_pyobj(PyList::from(obj_vec))) +} + +fn array_slice_setitem( + zelf: PyObjectRef, + slice: PyRef, + buffer_bytes: &mut [u8], + obj: PyObjectRef, + length: Option, + vm: &VirtualMachine, +) -> PyResult<()> { + let (step, start, stop) = array_get_slice_params(slice, length, vm)?; + + let slice_length = if (step < 0 && stop >= start) || (step > 0 && start >= stop) { + 0 + } else if step < 0 { + (stop - start + 1) / step + 1 + } else { + (stop - start - 1) / step + 1 + }; + + if slice_length != vm.obj_len(&obj)? as isize { + return Err(vm.new_value_error("Can only assign sequence of same size".to_string())); + } + + let _range = PyRange { + start: PyInt::from(start).into_ref(vm), + stop: PyInt::from(stop).into_ref(vm), + step: PyInt::from(step).into_ref(vm), + }; + + //FIXME: this function should be called for pointer too (length should be None), + //thus, needs to make sure the size. + //Right now I'm setting one + let size = length.map_or(Ok(1), |v| usize::try_from_object(vm, v))?; + + for (i, curr) in _range.enumerate() { + let idx = fix_index(isize::try_from_object(vm, curr)?, size, vm)? as usize; + let offset = idx * size; + let item = obj.get_item(i, vm)?; + let buffer_slice = &mut buffer_bytes[offset..offset + size]; + + set_array_value(&zelf, buffer_slice, idx, size, item, vm)?; + } + + Ok(()) +} + +#[inline(always)] +fn fix_index(index: isize, length: usize, vm: &VirtualMachine) -> PyResult { + let index = if index < 0 { + index + length as isize + } else { + index + }; + + if 0 <= index && index <= length as isize { + Ok(index) + } else { + Err(vm.new_index_error("invalid index".to_string())) + } } #[pyclass(module = "_ctypes", name = "PyCArrayType", base = "PyType")] @@ -208,10 +305,7 @@ pub struct PyCArray { impl fmt::Debug for PyCArrayMeta { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "PyCArrayMeta", - ) + write!(f, "PyCArrayMeta",) } } @@ -258,42 +352,60 @@ impl BufferProtocol for PyCArray { impl PyCDataMethods for PyCArrayMeta { fn from_param( - tp: PyTypeRef, + zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - if vm.isinstance(&value, PyCArray::static_type())? { + let mut value = value; + let cls = zelf.clone_class(); + + if vm.isinstance(&value, &cls)? { return Ok(value); } - let zelf = tp.as_object().downcast_exact::(vm).unwrap(); + let length = vm + .get_attribute(zelf.as_object().clone(), "_length_") + .map(|c_l| usize::try_from_object(vm, c_l))??; - if vm.obj_len(&value)? > zelf._length_ { - return Err(vm.new_value_error("value has size greater than the array".to_string())); - } + let value_len = vm.obj_len(&value)?; - if zelf._type_._type_.as_str() == "c" - && value.clone().downcast_exact::(vm).is_err() - { - return Err(vm.new_value_error(format!("expected bytes, {} found", value.class().name))); - } + if let Ok(tp) = vm.get_attribute(zelf.as_object().clone(), "_type_") { + let _type = tp.downcast::().unwrap(); - if zelf._type_._type_.as_str() == "u" && value.clone().downcast_exact::(vm).is_err() - { - return Err(vm.new_value_error(format!( - "expected unicode string, {} found", - value.class().name - ))); + if _type._type_.as_str() == "c" { + if vm.isinstance(&value, &vm.ctx.types.bytes_type).is_ok() { + if value_len > length { + return Err(vm.new_value_error("Invalid length".to_string())); + } + value = make_array_with_length(cls, length, vm)?.as_object().clone(); + } else if vm.isinstance(&value, &cls).is_err() { + return Err( + vm.new_type_error(format!("expected bytes, {} found", value.class().name)) + ); + } + } else if _type._type_.as_str() == "u" { + if vm.isinstance(&value, &vm.ctx.types.str_type).is_ok() { + if value_len > length { + return Err(vm.new_value_error("Invalid length".to_string())); + } + value = make_array_with_length(cls, length, vm)?.as_object().clone(); + } else if vm.isinstance(&value, &cls).is_err() { + return Err(vm.new_type_error(format!( + "expected unicode string, {} found", + value.class().name + ))); + } + } } - if !vm.isinstance(&value, &vm.ctx.types.tuple_type)? { - //@TODO: make sure what goes here - return Err(vm.new_type_error("Invalid type".to_string())); + if vm.isinstance(&value, &vm.ctx.types.tuple_type).is_ok() { + if value_len > length { + return Err(vm.new_runtime_error("Invalid length".to_string())); + } + value = make_array_with_length(cls, length, vm)?.as_object().clone(); } - PyCArray::init(zelf.clone(), OptionalArg::Present(value), vm)?; - - default_from_param(zelf.clone_class(), zelf.as_object().clone(), vm) + default_from_param(zelf, value, vm) } } @@ -304,7 +416,7 @@ impl PyCArrayMeta { let length_obj = vm .get_attribute(cls.as_object().to_owned(), "_length_") .map_err(|_| { - vm.new_attribute_error("class must define a '_type_' _length_".to_string()) + vm.new_attribute_error("class must define a '_length_' attribute".to_string()) })?; let length_int = length_obj.downcast_exact::(vm).map_err(|_| { vm.new_type_error("The '_length_' attribute must be an integer".to_string()) @@ -323,10 +435,7 @@ impl PyCArrayMeta { } } -#[pyimpl( - flags(BASETYPE), - with(BufferProtocol, PyCDataFunctions) -)] +#[pyimpl(flags(BASETYPE), with(BufferProtocol, PyCDataFunctions))] impl PyCArray { #[pymethod(magic)] pub fn init(zelf: PyRef, value: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { @@ -336,11 +445,11 @@ impl PyCArray { if value_length < zelf._length_ { let value_vec: Vec = vm.extract_elements(&value)?; for (i, v) in value_vec.iter().enumerate() { - Self::setitem(zelf.clone(), Either::A(i as isize), v.clone(), vm)? + Self::setitem(zelf.clone(), SequenceIndex::Int(i as isize), v.clone(), vm)? } Ok(()) } else if value_length == zelf._length_ { - let py_slice = Either::B( + let py_slice = SequenceIndex::Slice( PySlice { start: Some(vm.new_pyobj(0)), stop: vm.new_pyobj(zelf._length_), @@ -358,6 +467,7 @@ impl PyCArray { #[pyproperty] pub fn value(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // TODO: make sure that this is correct let obj = zelf.as_object(); let buffer = try_buffer_from_object(vm, obj)?; @@ -437,6 +547,7 @@ impl PyCArray { bytes[my_size] = 0; } } else { + // TODO: make sure that this is correct // unicode string zelf._type_ == "u" let value = value.downcast_exact::(vm).map_err(|value| { vm.new_value_error(format!( @@ -509,57 +620,34 @@ impl PyCArray { self._length_ } - // FIXME: change k_or_idx to SequenceIndex #[pymethod(magic)] - fn getitem( - zelf: PyRef, - k_or_idx: Either>, - vm: &VirtualMachine, - ) -> PyResult { + fn getitem(zelf: PyRef, k_or_idx: SequenceIndex, vm: &VirtualMachine) -> PyResult { let buffer = try_buffer_from_object(vm, zelf.as_object())?; let buffer_size = buffer.get_options().len; let buffer_bytes = buffer.obj_bytes(); - let offset = buffer_size / zelf.len(); - - let res = match k_or_idx { - Either::A(idx) => { - if idx < 0 { - Err(vm.new_index_error("invalid index".to_string())) - } else if idx as usize > zelf._length_ { - Err(vm.new_index_error("index out of bounds".to_string())) - } else { - let idx = idx as usize; - let buffer_slice = buffer_bytes[idx..idx + offset].as_ref(); - Ok(byte_to_pyobj(zelf._type_._type_.as_str(), buffer_slice, vm)) - }? - } - Either::B(slice) => { - let (step, mut start, mut stop) = array_get_slice_inner(slice, vm)?; - - let slice_length = - slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; - - let mut obj_vec = Vec::with_capacity(slice_length); + let size = buffer_size / zelf._length_; - for i in (start as usize..stop as usize).step_by(step as usize) { - obj_vec.push(byte_to_pyobj( - zelf._type_._type_.as_str(), - buffer_bytes[i..i + offset].as_ref(), - vm, - )); - } - - PyList::from(obj_vec).into_object(vm) + match k_or_idx { + SequenceIndex::Int(idx) => { + let idx = fix_index(idx, zelf._length_, vm)? as usize; + let offset = idx * size; + let buffer_slice = buffer_bytes[offset..offset + size].as_ref(); + byte_to_pyobj(zelf._type_._type_.as_str(), buffer_slice, vm) } - }; - - Ok(res) + SequenceIndex::Slice(slice) => array_slice_getitem( + zelf.as_object().clone(), + &mut buffer_bytes[..], + slice, + size, + vm, + ), + } } #[pymethod(magic)] fn setitem( zelf: PyRef, - k_or_idx: Either>, + k_or_idx: SequenceIndex, obj: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { @@ -567,38 +655,23 @@ impl PyCArray { let buffer_size = buffer.get_options().len; let mut buffer_bytes = buffer.obj_bytes_mut(); - let offset = buffer_size / zelf.len(); + let size = buffer_size / zelf._length_; match k_or_idx { - Either::A(idx) => { - if idx < 0 { - Err(vm.new_index_error("invalid index".to_string())) - } else if idx as usize > zelf._length_ { - Err(vm.new_index_error("index out of bounds".to_string())) - } else { - set_array_value(&zelf, &mut buffer_bytes, idx as usize, offset, obj, vm) - } - } - Either::B(slice) => { - let (step, mut start, mut stop) = array_get_slice_inner(slice, vm)?; - - let slice_length = - slice_adjust_size(zelf._length_ as isize, &mut start, &mut stop, step) as usize; - - let values: Vec = vm.extract_elements(&obj)?; - - if values.len() != slice_length { - Err(vm.new_value_error("can only assign sequence of same size".to_string())) - } else { - let mut cur = start as usize; - - for v in values { - set_array_value(&zelf, &mut buffer_bytes, cur, offset, v, vm)?; - cur += step as usize; - } - Ok(()) - } + SequenceIndex::Int(idx) => { + let idx = fix_index(idx, zelf._length_, vm)? as usize; + let offset = idx * size; + let buffer_slice = &mut buffer_bytes[offset..offset + size]; + set_array_value(&zelf.as_object().clone(), buffer_slice, idx, size, obj, vm) } + SequenceIndex::Slice(slice) => array_slice_setitem( + zelf.as_object().clone(), + slice, + &mut buffer_bytes[..], + obj, + Some(vm.ctx.new_int(zelf._length_)), + vm, + ), } } } @@ -613,15 +686,15 @@ impl PyCDataFunctions for PyCArray { } fn ref_to(zelf: PyRef, offset: OptionalArg, vm: &VirtualMachine) -> PyResult { - let off_set = offset + let offset = offset .into_option() .map_or(Ok(0), |o| usize::try_from_object(vm, o))?; - if off_set > zelf._length_ * get_size(zelf._type_._type_.as_str()) { + if offset > zelf._length_ * get_size(zelf._type_._type_.as_str()) { Err(vm.new_index_error("offset out of bounds".to_string())) } else { let guard = zelf.borrow_value(); - let ref_at: *mut u8 = unsafe { guard.inner.add(off_set) }; + let ref_at: *mut u8 = unsafe { guard.inner.add(offset) }; Ok(vm.new_pyobj(ref_at as *mut _ as *mut usize as usize)) } diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 8ada8869f9..6f1924a6ef 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -84,10 +84,10 @@ fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult Err(vm.new_type_error("abstract class".to_string())), - // @TODO: A sanity check + // FIXME: A sanity check Err(_) => Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())), }, - // @TODO: I think it's unreachable + // FIXME: I think it's unreachable Err(_) => Err(vm.new_attribute_error("abstract class".to_string())), } } @@ -107,7 +107,7 @@ fn buffer_copy( let buffer = try_buffer_from_object(vm, &obj)?; let opts = buffer.get_options().clone(); - // @TODO: Fix the way the size of stored + // TODO: Fix the way the size of stored // Would this be the a proper replacement? // vm.call_method(cls.as_object().to_owned(), "size", ())?. let cls_size = vm @@ -152,22 +152,26 @@ fn buffer_copy( } Ok(_) => Err(vm.new_type_error("abstract class".to_string())), Err(_) => { - // @TODO: A sanity check + // TODO: A sanity check Err(vm.new_type_error("attribute '__abstract__' must be bool".to_string())) } } } - // @TODO: I think this is unreachable... + // TODO: I think this is unreachable... Err(_) => Err(vm.new_type_error("abstract class".to_string())), } } -pub fn default_from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - //@TODO: check if this behaves like it should +pub fn default_from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult +where + T: PyCDataMethods + PyValue, +{ + //TODO: check if this behaves like it should + let cls = zelf.as_object().clone_class(); if vm.isinstance(&value, &cls)? { Ok(value) } else if let Ok(parameter) = vm.get_attribute(value.clone(), "_as_parameter_") { - default_from_param(cls, parameter, vm) + T::from_param(zelf, parameter, vm) } else { Err(vm.new_attribute_error(format!( "expected {} instance instead of {}", @@ -203,7 +207,7 @@ pub trait PyCDataMethods: PyValue { // PyCFuncPtrType_Type #[pymethod] - fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; + fn from_param(zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult; #[pyclassmethod] fn from_address( @@ -472,7 +476,7 @@ pub fn alignment(tp: Either, vm: &VirtualMachine) -> PyR } pub fn byref(tp: PyObjectRef, vm: &VirtualMachine) -> PyResult { - //@TODO: Return a Pointer when Pointer implementation is ready + //TODO: Return a Pointer when Pointer implementation is ready let class = tp.clone_class(); if class.issubclass(PyCData::static_type()) { diff --git a/vm/src/stdlib/ctypes/function.rs b/vm/src/stdlib/ctypes/function.rs index 9752cf1438..d833413c55 100644 --- a/vm/src/stdlib/ctypes/function.rs +++ b/vm/src/stdlib/ctypes/function.rs @@ -325,10 +325,10 @@ impl PyCFuncPtr { #[pyproperty(name = "_restype_", setter)] fn set_restype(&self, restype: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { match vm.isinstance(&restype, PyCSimple::static_type()) { - // @TODO: checks related to _type_ are temporary + // TODO: checks related to _type_ are temporary Ok(_) => match vm.get_attribute(restype.clone(), "_type_") { Ok(_type_) => { - // @TODO: restype must be a type, a callable, or None + // TODO: restype must be a type, a callable, or None self._restype_.store(restype.clone()); let mut fn_ptr = self._f.write(); fn_ptr.set_ret(vm.to_str(&_type_)?.as_ref()); @@ -345,7 +345,7 @@ impl PyCFuncPtr { } } - // @TODO: Needs to check and implement other forms of new + // TODO: Needs to check and implement other forms of new #[pyslot] fn tp_new( cls: PyTypeRef, diff --git a/vm/src/stdlib/ctypes/mod.rs b/vm/src/stdlib/ctypes/mod.rs index 104626df1a..51a8179fce 100644 --- a/vm/src/stdlib/ctypes/mod.rs +++ b/vm/src/stdlib/ctypes/mod.rs @@ -10,11 +10,11 @@ mod primitive; mod shared_lib; mod structure; -use array::{PyCArray,PyCArrayMeta}; +use array::{PyCArray, PyCArrayMeta}; use basics::{addressof, alignment, byref, sizeof_func, PyCData}; use function::PyCFuncPtr; use pointer::{pointer_fn, PyCPointer, POINTER}; -use primitive::{PySimpleMeta, PyCSimple}; +use primitive::{PyCSimple, PySimpleMeta}; use structure::PyCStructure; pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index dbf6cfc444..0793a16a6e 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -13,7 +13,8 @@ use crate::pyobject::{ }; use crate::stdlib::ctypes::array::PyCArray; use crate::stdlib::ctypes::basics::{ - get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, PyCDataSequenceMethods, + default_from_param, get_size, BorrowValueMut, PyCData, PyCDataFunctions, PyCDataMethods, + PyCDataSequenceMethods, }; use crate::stdlib::ctypes::function::PyCFuncPtr; use crate::stdlib::ctypes::pointer::PyCPointer; @@ -149,7 +150,7 @@ fn generic_xxx_p_from_param( { Ok(value.clone()) } else { - // @TODO: better message + // TODO: better message Err(vm.new_type_error("wrong type".to_string())) } } @@ -175,7 +176,7 @@ fn from_param_char_p( { Ok(value.clone()) } else { - // @TODO: Make sure of what goes here + // TODO: Make sure of what goes here Err(vm.new_type_error("some error".to_string())) } } @@ -200,7 +201,7 @@ fn from_param_void_p( } else if vm.isinstance(value, PyCFuncPtr::static_type())? || vm.isinstance(value, PyCPointer::static_type())? { - // @TODO: Is there a better way of doing this? + // TODO: Is there a better way of doing this? if let Some(from_address) = vm.get_method(cls.as_object().clone(), "from_address") { if let Ok(cdata) = value.clone().downcast::() { let buffer_guard = cdata.borrow_value_mut(); @@ -208,11 +209,11 @@ fn from_param_void_p( Ok(vm.invoke(&from_address?, (cls.clone_class(), addr))?) } else { - // @TODO: Make sure of what goes here + // TODO: Make sure of what goes here Err(vm.new_type_error("value should be an instance of _CData".to_string())) } } else { - // @TODO: Make sure of what goes here + // TODO: Make sure of what goes here Err(vm.new_attribute_error("class has no from_address method".to_string())) } } else if vm.isinstance(value, &vm.ctx.types.int_type)? { @@ -222,7 +223,7 @@ fn from_param_void_p( } .into_object(vm)) } else { - // @TODO: Make sure of what goes here + // TODO: Make sure of what goes here Err(vm.new_type_error("some error".to_string())) } } @@ -246,7 +247,7 @@ pub fn new_simple_type( .to_string(), )) } else if !SIMPLE_TYPE_CHARS.contains(tp_str.as_str()) { - Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) + Err(vm.new_attribute_error(format!("class must define a '_type_' attribute which must be\n a single character string containing one of {}.",SIMPLE_TYPE_CHARS))) } else { Ok(PyCSimple { _type_: tp_str, @@ -262,13 +263,16 @@ pub fn new_simple_type( } #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = "PyType")] -pub struct PySimpleMeta{} +pub struct PySimpleMeta {} #[pyimpl(with(PyCDataMethods), flags(BASETYPE))] impl PySimpleMeta { #[pyslot] fn tp_new(cls: PyTypeRef, _: OptionalArg, vm: &VirtualMachine) -> PyResult { - Ok(new_simple_type(Either::B(&cls), vm)?.into_ref_with_type(vm, cls)?.as_object().clone()) + Ok(new_simple_type(Either::B(&cls), vm)? + .into_ref_with_type(vm, cls)? + .as_object() + .clone()) } } @@ -301,10 +305,7 @@ impl fmt::Debug for PyCSimple { impl fmt::Debug for PySimpleMeta { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "PySimpleMeta" - ) + write!(f, "PySimpleMeta") } } @@ -323,48 +324,37 @@ impl PyValue for PySimpleMeta { impl PyCDataMethods for PySimpleMeta { // From PyCSimpleType_Type PyCSimpleType_methods fn from_param( - tp: PyTypeRef, + zelf: PyRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - let cls = tp.clone_class(); + let cls = zelf.clone_class(); if cls.is(PyCSimple::static_type()) { Err(vm.new_type_error("abstract class".to_string())) } else if vm.isinstance(&value, &cls)? { Ok(value) } else { - let tp_obj = vm.get_attribute(tp.as_object().clone(), "_type_").unwrap(); - let _type_ = tp_obj.downcast_exact::(vm).unwrap(); - match _type_.as_ref() { + let tp = vm.get_attribute(zelf.as_object().clone(), "_type_")?; + let _type_ = tp.downcast::().unwrap().to_string().as_str(); + + match _type_ { "z" | "Z" => from_param_char_p(&cls, &value, vm), "P" => from_param_void_p(&cls, &value, vm), - _ => { - match new_simple_type(Either::B(&cls), vm) { - Ok(obj) => Ok(obj.into_object(vm)), - Err(e) => { - if vm.isinstance( - &e.clone().into_object(), - &vm.ctx.exceptions.type_error, - )? || vm.isinstance( + _ => match new_simple_type(Either::B(&cls), vm) { + Ok(obj) => Ok(obj.into_object(vm)), + Err(e) => { + if vm.isinstance(&e.clone().into_object(), &vm.ctx.exceptions.type_error)? + || vm.isinstance( &e.clone().into_object(), &vm.ctx.exceptions.value_error, - )? { - if vm.isinstance(&value,&tp)?{ - return Ok(value); - } - - let as_parameter = vm - .get_attribute(tp.as_object().clone(), "_as_parameter_") - .map_err(|_| { - vm.new_type_error(format!("expected {} instance instead of {}", tp.class().name, value.class().name)) - })?; - - return PySimpleMeta::from_param(tp, value, &vm); - } + )? + { + default_from_param(zelf, value.clone(), vm) + } else { Err(e) } } - } + }, } } } From c2ba1160ef66fc9c1bb32c9d9b3329a2228b2d56 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Sat, 21 Aug 2021 16:37:21 -0300 Subject: [PATCH 56/56] Fix compilation --- vm/src/stdlib/ctypes/array.rs | 72 +++++++++++++++---------------- vm/src/stdlib/ctypes/basics.rs | 53 ++++++++--------------- vm/src/stdlib/ctypes/primitive.rs | 4 +- 3 files changed, 54 insertions(+), 75 deletions(-) diff --git a/vm/src/stdlib/ctypes/array.rs b/vm/src/stdlib/ctypes/array.rs index d3b832c112..c37f981cfc 100644 --- a/vm/src/stdlib/ctypes/array.rs +++ b/vm/src/stdlib/ctypes/array.rs @@ -11,7 +11,7 @@ use crate::builtins::{ use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::function::OptionalArg; use crate::pyobject::{ - IdProtocol, ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, + IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, TypeProtocol, }; use crate::sliceable::SequenceIndex; @@ -27,10 +27,10 @@ use std::convert::TryInto; use std::fmt; use widestring::WideCString; -// TODO: make sure that this is correct -fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { - let res = if ty == "u" { - vm.new_pyobj(if cfg!(windows) { +// TODO: make sure that this is correct wrt windows and unix wstr +fn slice_to_obj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { + if ty == "u" { + Ok(vm.new_pyobj(if cfg!(windows) { u16::from_ne_bytes( b.try_into().map_err(|_| { vm.new_value_error("buffer does not fit widestring".to_string()) @@ -42,7 +42,7 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { vm.new_value_error("buffer does not fit widestring".to_string()) })?, ) - }) + })) } else { macro_rules! byte_match_type { ( @@ -54,7 +54,7 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { $( $( t if t == $type => { - vm.new_pyobj($body::from_ne_bytes(b.try_into().map_err(|_| vm.new_value_error(format!("buffer does not fit type '{}'",ty)))?)) + Ok(vm.new_pyobj($body::from_ne_bytes(b.try_into().map_err(|_| vm.new_value_error(format!("buffer does not fit type '{}'",ty)))?))) } )+ )+ @@ -75,8 +75,7 @@ fn byte_to_pyobj(ty: &str, b: &[u8], vm: &VirtualMachine) -> PyResult { "?" | "B" => u8 "P" | "z" | "Z" => usize ) - }; - Ok(res) + } } pub fn make_array_with_length( @@ -150,13 +149,13 @@ fn set_array_value( fn array_get_slice_params( slice: PyRef, - length: Option, + length: &Option, vm: &VirtualMachine, ) -> PyResult<(isize, isize, isize)> { - if let Some(len) = length { + if let Some(ref len) = length { let indices = vm.get_method(slice.as_object().clone(), "indices").unwrap().unwrap(); - let tuple = vm.invoke(&indices, ())?; - + let tuple = vm.invoke(&indices, (len.clone(),))?; + let (start, stop, step) = ( tuple.get_item(0, vm)?, tuple.get_item(1, vm)?, @@ -169,41 +168,40 @@ fn array_get_slice_params( isize::try_from_object(vm, stop)?, )) } else { - let step = slice - .step - .map_or(Ok(1), |o| isize::try_from_object(vm, o))?; + let step = slice.step.as_ref() + .map_or(Ok(1), |o| isize::try_from_object(vm, o.clone()))?; - let start = slice.start.map_or_else( - || { + let start = slice.start.as_ref() + .map_or_else(|| { if step > 0 { Ok(0) } else { Err(vm.new_value_error("slice start is required for step < 0".to_string())) } }, - |o| isize::try_from_object(vm, o), + |o| isize::try_from_object(vm, o.clone()), )?; - let stop = isize::try_from_object(vm, slice.stop)?; + let stop = isize::try_from_object(vm, slice.stop.clone())?; Ok((step, start, stop)) } } -fn array_slice_getitem( +fn array_slice_getitem<'a>( zelf: PyObjectRef, - buffer_bytes: &mut [u8], + buffer_bytes: &'a [u8], slice: PyRef, size: usize, - vm: &VirtualMachine, + vm: &'a VirtualMachine, ) -> PyResult { let length = vm .get_attribute(zelf.clone(), "_length_") .map(|c_l| usize::try_from_object(vm, c_l))??; - let tp = vm.get_attribute(zelf, "_type_")?; - let _type_ = tp.downcast::().unwrap().to_string().as_str(); - let (step, start, stop) = array_get_slice_params(slice, Some(vm.ctx.new_int(length)), vm)?; + let tp = vm.get_attribute(zelf, "_type_")?.downcast::().unwrap().to_string(); + let _type_ = tp.as_str(); + let (step, start, stop) = array_get_slice_params(slice, &Some(vm.ctx.new_int(length)), vm)?; let _range = PyRange { start: PyInt::from(start).into_ref(vm), @@ -211,14 +209,14 @@ fn array_slice_getitem( step: PyInt::from(step).into_ref(vm), }; - let obj_vec = Vec::new(); + let mut obj_vec = Vec::new(); let mut offset; - for curr in _range { - let idx = fix_index(isize::try_from_object(vm, curr)?, length, vm)? as usize; + for curr in PyIterable::try_from_object(vm,_range.into_object(vm))?.iter(vm)? { + let idx = fix_index(isize::try_from_object(vm, curr?)?, length, vm)? as usize; offset = idx * size; - obj_vec.push(byte_to_pyobj( + obj_vec.push(slice_to_obj( _type_, buffer_bytes[offset..offset + size].as_ref(), vm, @@ -236,7 +234,7 @@ fn array_slice_setitem( length: Option, vm: &VirtualMachine, ) -> PyResult<()> { - let (step, start, stop) = array_get_slice_params(slice, length, vm)?; + let (step, start, stop) = array_get_slice_params(slice, &length, vm)?; let slice_length = if (step < 0 && stop >= start) || (step > 0 && start >= stop) { 0 @@ -261,8 +259,8 @@ fn array_slice_setitem( //Right now I'm setting one let size = length.map_or(Ok(1), |v| usize::try_from_object(vm, v))?; - for (i, curr) in _range.enumerate() { - let idx = fix_index(isize::try_from_object(vm, curr)?, size, vm)? as usize; + for (i, curr) in PyIterable::try_from_object(vm,_range.into_object(vm))?.iter(vm)?.enumerate() { + let idx = fix_index(isize::try_from_object(vm, curr?)?, size, vm)? as usize; let offset = idx * size; let item = obj.get_item(i, vm)?; let buffer_slice = &mut buffer_bytes[offset..offset + size]; @@ -377,7 +375,7 @@ impl PyCDataMethods for PyCArrayMeta { if value_len > length { return Err(vm.new_value_error("Invalid length".to_string())); } - value = make_array_with_length(cls, length, vm)?.as_object().clone(); + value = make_array_with_length(cls.clone(), length, vm)?.as_object().clone(); } else if vm.isinstance(&value, &cls).is_err() { return Err( vm.new_type_error(format!("expected bytes, {} found", value.class().name)) @@ -388,7 +386,7 @@ impl PyCDataMethods for PyCArrayMeta { if value_len > length { return Err(vm.new_value_error("Invalid length".to_string())); } - value = make_array_with_length(cls, length, vm)?.as_object().clone(); + value = make_array_with_length(cls.clone(), length, vm)?.as_object().clone(); } else if vm.isinstance(&value, &cls).is_err() { return Err(vm.new_type_error(format!( "expected unicode string, {} found", @@ -632,11 +630,11 @@ impl PyCArray { let idx = fix_index(idx, zelf._length_, vm)? as usize; let offset = idx * size; let buffer_slice = buffer_bytes[offset..offset + size].as_ref(); - byte_to_pyobj(zelf._type_._type_.as_str(), buffer_slice, vm) + slice_to_obj(zelf._type_._type_.as_str(), buffer_slice, vm) } SequenceIndex::Slice(slice) => array_slice_getitem( zelf.as_object().clone(), - &mut buffer_bytes[..], + &buffer_bytes[..], slice, size, vm, diff --git a/vm/src/stdlib/ctypes/basics.rs b/vm/src/stdlib/ctypes/basics.rs index 6f1924a6ef..8759ce23e0 100644 --- a/vm/src/stdlib/ctypes/basics.rs +++ b/vm/src/stdlib/ctypes/basics.rs @@ -22,43 +22,24 @@ use crate::stdlib::ctypes::primitive::{new_simple_type, PyCSimple}; use crossbeam_utils::atomic::AtomicCell; -macro_rules! os_match_type { - ( - $kind: expr, - - $( - $($type: literal)|+ => $body: ident - )+ - ) => { - match $kind { - $( - $( - t if t == $type => { mem::size_of::<$body>() } - )+ - )+ - _ => unreachable!() - } - } -} - pub fn get_size(ty: &str) -> usize { - os_match_type!( - ty, - "u" => WideChar - "c" | "b" => c_schar - "h" => c_short - "H" => c_ushort - "i" => c_int - "I" => c_uint - "l" => c_long - "q" => c_longlong - "L" => c_ulong - "Q" => c_ulonglong - "f" => c_float - "d" | "g" => c_double - "?" | "B" => c_uchar - "P" | "z" | "Z" => usize - ) + match ty { + "u" => mem::size_of::(), + "c" | "b" => mem::size_of::(), + "h" => mem::size_of::(), + "H" => mem::size_of::(), + "i" => mem::size_of::(), + "I" => mem::size_of::(), + "l" => mem::size_of::(), + "q" => mem::size_of::(), + "L" => mem::size_of::(), + "Q" => mem::size_of::(), + "f" => mem::size_of::(), + "d" | "g" => mem::size_of::(), + "?" | "B" => mem::size_of::(), + "P" | "z" | "Z" => mem::size_of::(), + _ => unreachable!(), + } } fn at_address(cls: &PyTypeRef, buf: usize, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/stdlib/ctypes/primitive.rs b/vm/src/stdlib/ctypes/primitive.rs index 0793a16a6e..1c3615317c 100644 --- a/vm/src/stdlib/ctypes/primitive.rs +++ b/vm/src/stdlib/ctypes/primitive.rs @@ -334,8 +334,8 @@ impl PyCDataMethods for PySimpleMeta { } else if vm.isinstance(&value, &cls)? { Ok(value) } else { - let tp = vm.get_attribute(zelf.as_object().clone(), "_type_")?; - let _type_ = tp.downcast::().unwrap().to_string().as_str(); + let tp = vm.get_attribute(zelf.as_object().clone(), "_type_")?.downcast::().unwrap().to_string(); + let _type_ = tp.as_str(); match _type_ { "z" | "Z" => from_param_char_p(&cls, &value, vm),