forked from Rust-related/RustPython
Initial _ctypes implementation (#5519)
* initial _ctypes implementation with _CData, get_errno, and set_errno Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2258,6 +2258,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"crossbeam-utils",
|
||||
"errno",
|
||||
"exitcode",
|
||||
"flame",
|
||||
"flamer",
|
||||
|
||||
105
extra_tests/snippets/builtins_ctypes.py
Normal file
105
extra_tests/snippets/builtins_ctypes.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from _ctypes import sizeof
|
||||
from _ctypes import _SimpleCData
|
||||
from struct import calcsize as _calcsize
|
||||
|
||||
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"
|
||||
_check_size(c_ushort)
|
||||
|
||||
class c_long(_SimpleCData):
|
||||
_type_ = "l"
|
||||
_check_size(c_long)
|
||||
|
||||
class c_ulong(_SimpleCData):
|
||||
_type_ = "L"
|
||||
_check_size(c_ulong)
|
||||
|
||||
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"
|
||||
_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"
|
||||
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"
|
||||
## def from_param(cls, val):
|
||||
## return ('d', float(val), val)
|
||||
## from_param = classmethod(from_param)
|
||||
_check_size(c_ulonglong)
|
||||
|
||||
class c_ubyte(_SimpleCData):
|
||||
_type_ = "B"
|
||||
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
|
||||
# backward compatibility:
|
||||
##c_uchar = 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_ = "?"
|
||||
_check_size(c_bool)
|
||||
@@ -99,6 +99,8 @@ uname = "0.1.1"
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rustyline = { workspace = true }
|
||||
which = "6"
|
||||
errno = "0.3"
|
||||
widestring = { workspace = true }
|
||||
|
||||
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
|
||||
num_cpus = "1.13.1"
|
||||
@@ -106,7 +108,6 @@ num_cpus = "1.13.1"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
junction = { workspace = true }
|
||||
schannel = { workspace = true }
|
||||
widestring = { workspace = true }
|
||||
winreg = "0.52"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
|
||||
107
vm/src/stdlib/ctypes.rs
Normal file
107
vm/src/stdlib/ctypes.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
pub(crate) mod base;
|
||||
|
||||
use crate::builtins::PyModule;
|
||||
use crate::{PyRef, VirtualMachine};
|
||||
|
||||
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
|
||||
let module = _ctypes::make_module(vm);
|
||||
base::extend_module_nodes(vm, &module);
|
||||
module
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
pub(crate) mod _ctypes {
|
||||
use super::base::PyCSimple;
|
||||
use crate::builtins::PyTypeRef;
|
||||
use crate::class::StaticType;
|
||||
use crate::function::Either;
|
||||
use crate::{AsObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use std::ffi::{
|
||||
c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong,
|
||||
c_ulonglong,
|
||||
};
|
||||
use std::mem;
|
||||
use widestring::WideChar;
|
||||
|
||||
pub fn get_size(ty: &str) -> usize {
|
||||
match ty {
|
||||
"u" => mem::size_of::<WideChar>(),
|
||||
"c" | "b" => mem::size_of::<c_schar>(),
|
||||
"h" => mem::size_of::<c_short>(),
|
||||
"H" => mem::size_of::<c_short>(),
|
||||
"i" => mem::size_of::<c_int>(),
|
||||
"I" => mem::size_of::<c_uint>(),
|
||||
"l" => mem::size_of::<c_long>(),
|
||||
"q" => mem::size_of::<c_longlong>(),
|
||||
"L" => mem::size_of::<c_ulong>(),
|
||||
"Q" => mem::size_of::<c_ulonglong>(),
|
||||
"f" => mem::size_of::<c_float>(),
|
||||
"d" | "g" => mem::size_of::<c_double>(),
|
||||
"?" | "B" => mem::size_of::<c_uchar>(),
|
||||
"P" | "z" | "Z" => mem::size_of::<usize>(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
const SIMPLE_TYPE_CHARS: &str = "cbBhHiIlLdfguzZPqQ?";
|
||||
|
||||
pub fn new_simple_type(
|
||||
cls: Either<&PyObjectRef, &PyTypeRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyCSimple> {
|
||||
let cls = match cls {
|
||||
Either::A(obj) => obj,
|
||||
Either::B(typ) => typ.as_object(),
|
||||
};
|
||||
|
||||
if let Ok(_type_) = cls.get_attr("_type_", vm) {
|
||||
if _type_.is_instance((&vm.ctx.types.str_type).as_ref(), vm)? {
|
||||
let tp_str = _type_.str(vm)?.to_string();
|
||||
|
||||
if tp_str.len() != 1 {
|
||||
Err(vm.new_value_error(
|
||||
format!("class must define a '_type_' attribute which must be a string of length 1, str: {tp_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\n a single character string containing one of {SIMPLE_TYPE_CHARS}, currently it is {tp_str}.")))
|
||||
} else {
|
||||
Ok(PyCSimple {
|
||||
_type_: tp_str,
|
||||
_value: AtomicCell::new(vm.ctx.none()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(vm.new_type_error("class must define a '_type_' string attribute".to_string()))
|
||||
}
|
||||
} else {
|
||||
Err(vm.new_attribute_error("class must define a '_type_' attribute".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction(name = "sizeof")]
|
||||
pub fn size_of(tp: Either<PyTypeRef, PyObjectRef>, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
match tp {
|
||||
Either::A(type_) if type_.fast_issubclass(PyCSimple::static_type()) => {
|
||||
let zelf = new_simple_type(Either::B(&type_), vm)?;
|
||||
Ok(get_size(zelf._type_.as_str()))
|
||||
}
|
||||
Either::B(obj) if obj.has_attr("size_of_instances", vm)? => {
|
||||
let size_of_method = obj.get_attr("size_of_instances", vm)?;
|
||||
let size_of_return = size_of_method.call(vec![], vm)?;
|
||||
Ok(usize::try_from_object(vm, size_of_return)?)
|
||||
}
|
||||
_ => Err(vm.new_type_error("this type has no size".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_errno() -> i32 {
|
||||
errno::errno().0
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn set_errno(value: i32) {
|
||||
errno::set_errno(errno::Errno(value));
|
||||
}
|
||||
}
|
||||
161
vm/src/stdlib/ctypes/base.rs
Normal file
161
vm/src/stdlib/ctypes/base.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::builtins::{PyBytes, PyFloat, PyInt, PyModule, PyNone, PyStr};
|
||||
use crate::class::PyClassImpl;
|
||||
use crate::{Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use num_traits::ToPrimitive;
|
||||
use rustpython_common::lock::PyRwLock;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_primitive(_type_: &str, value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
match _type_ {
|
||||
"c" => {
|
||||
if value
|
||||
.clone()
|
||||
.downcast_exact::<PyBytes>(vm)
|
||||
.is_ok_and(|v| v.len() == 1)
|
||||
|| value
|
||||
.clone()
|
||||
.downcast_exact::<PyBytes>(vm)
|
||||
.is_ok_and(|v| v.len() == 1)
|
||||
|| value
|
||||
.clone()
|
||||
.downcast_exact::<PyInt>(vm)
|
||||
.map_or(Ok(false), |v| {
|
||||
let n = v.as_bigint().to_i64();
|
||||
if let Some(n) = n {
|
||||
Ok((0..=255).contains(&n))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})?
|
||||
{
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(
|
||||
"one character bytes, bytearray or integer expected".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
"u" => {
|
||||
if let Ok(b) = value.str(vm).map(|v| v.to_string().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::<PyInt>(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::<PyFloat>(vm).is_ok() {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error(format!("must be real number, not {}", value.class().name())))
|
||||
}
|
||||
}
|
||||
"?" => Ok(PyObjectRef::from(
|
||||
vm.ctx.new_bool(value.clone().try_to_bool(vm)?),
|
||||
)),
|
||||
"B" => {
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok() {
|
||||
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())))
|
||||
}
|
||||
}
|
||||
"z" => {
|
||||
if value.clone().downcast_exact::<PyInt>(vm).is_ok()
|
||||
|| value.clone().downcast_exact::<PyBytes>(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::<PyStr>(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::<PyInt>(vm).is_ok()
|
||||
|| value.clone().downcast_exact::<PyNone>(vm).is_ok()
|
||||
{
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(vm.new_type_error("cannot be converted to pointer".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawBuffer {
|
||||
#[allow(dead_code)]
|
||||
pub inner: Box<[u8]>,
|
||||
#[allow(dead_code)]
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[pyclass(name = "_CData", module = "_ctypes")]
|
||||
pub struct PyCData {
|
||||
_objects: AtomicCell<Vec<PyObjectRef>>,
|
||||
_buffer: PyRwLock<RawBuffer>,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
impl PyCData {}
|
||||
|
||||
#[pyclass(
|
||||
name = "_SimpleCData",
|
||||
base = "PyCData",
|
||||
module = "_ctypes"
|
||||
// TODO: metaclass
|
||||
)]
|
||||
#[derive(PyPayload)]
|
||||
pub struct PyCSimple {
|
||||
pub _type_: String,
|
||||
pub _value: AtomicCell<PyObjectRef>,
|
||||
}
|
||||
|
||||
impl Debug for PyCSimple {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PyCSimple")
|
||||
.field("_type_", &self._type_)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(flags(BASETYPE))]
|
||||
impl PyCSimple {}
|
||||
|
||||
pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
|
||||
let ctx = &vm.ctx;
|
||||
extend_module!(vm, module, {
|
||||
"_CData" => PyCData::make_class(ctx),
|
||||
"_SimpleCData" => PyCSimple::make_class(ctx),
|
||||
})
|
||||
}
|
||||
@@ -37,6 +37,8 @@ pub mod posix;
|
||||
#[path = "posix_compat.rs"]
|
||||
pub mod posix;
|
||||
|
||||
#[cfg(any(target_family = "unix", target_family = "windows"))]
|
||||
mod ctypes;
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod msvcrt;
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
|
||||
@@ -124,5 +126,9 @@ pub fn get_module_inits() -> StdlibMap {
|
||||
"_winapi" => winapi::make_module,
|
||||
"winreg" => winreg::make_module,
|
||||
}
|
||||
#[cfg(any(target_family = "unix", target_family = "windows"))]
|
||||
{
|
||||
"_ctypes" => ctypes::make_module,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user