Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ctypes
  • Loading branch information
youknowone committed Dec 14, 2025
commit d7a563bf4b54ccf04fc13dd54524bfb2e70b7517
11 changes: 5 additions & 6 deletions crates/vm/src/stdlib/ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::builtins::PyModule;
use crate::class::PyClassImpl;
use crate::{Py, PyRef, VirtualMachine};

pub use crate::stdlib::ctypes::base::{PyCData, PyCSimple, PyCSimpleType};
pub use crate::stdlib::ctypes::base::{CDataObject, PyCData, PyCSimple, PyCSimpleType};

pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py<PyModule>) {
let ctx = &vm.ctx;
Expand Down Expand Up @@ -47,7 +47,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {

#[pymodule]
pub(crate) mod _ctypes {
use super::base::{CDataObject, PyCSimple};
use super::base::{CDataObject, PyCData, PyCSimple};
use crate::builtins::PyTypeRef;
use crate::class::StaticType;
use crate::convert::ToPyObject;
Expand Down Expand Up @@ -369,13 +369,12 @@ pub(crate) mod _ctypes {
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 {
let size = get_size(&tp_str);
let cdata = CDataObject::from_bytes(vec![0u8; size], None);
Ok(PyCSimple {
_base: PyCData::new(cdata.clone()),
_type_: tp_str,
value: AtomicCell::new(vm.ctx.none()),
cdata: rustpython_common::lock::PyRwLock::new(CDataObject::from_bytes(
vec![0u8; size],
None,
)),
cdata: rustpython_common::lock::PyRwLock::new(cdata),
})
}
Comment on lines 371 to 379

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

cdata.clone() deep-copies the buffer; base/derived will diverge.
If the goal is “one underlying C data backing store”, current wiring creates two independent CDataObjects (because Vec<u8> clones). Anything operating via the base (PyCData) can see different bytes/objects than code operating via the derived field.

Actionable direction: make the backing storage shared (e.g., Arc<PyRwLock<CDataObject>> inside PyCData, and have derived types hold only _base: PyCData and access self._base.cdata), or remove the duplicated cdata field from derived types entirely and standardize on the base field.

🤖 Prompt for AI Agents
In crates/vm/src/stdlib/ctypes.rs around lines 371–379, the code clones the
CDataObject into both PyCData (base) and the derived PyCSimple, creating two
independent buffers; instead make the backing storage shared so base and derived
view the same bytes. Fix by removing the duplicated cdata field from derived
types (or change PyCData to hold an Arc<PyRwLock<CDataObject>> and have derived
types keep only _base: PyCData and access the shared cdata via self._base), and
stop calling .clone() to create separate Vecs; initialize a single CDataObject
wrapped in the shared container and use that same handle for both base and
derived.

} else {
Expand Down
33 changes: 22 additions & 11 deletions crates/vm/src/stdlib/ctypes/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use rustpython_vm::stdlib::ctypes::base::PyCData;
/// PyCArrayType - metatype for Array types
/// CPython stores array info (type, length) in StgInfo via type_data
#[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")]
#[derive(Debug, Default)]
pub struct PyCArrayType {}
#[derive(Debug)]
#[repr(transparent)]
pub struct PyCArrayType(PyType);

/// Create a new Array type with StgInfo stored in type_data (CPython style)
pub fn create_array_type_with_stg_info(stg_info: StgInfo, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -194,11 +195,13 @@ impl PyCArrayType {
};

// Create instance
let cdata = CDataObject::from_bytes(data, None);
let instance = PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
cdata: PyRwLock::new(cdata),
}
.into_pyobject(vm);

Expand Down Expand Up @@ -236,6 +239,7 @@ impl AsNumber for PyCArrayType {
module = "_ctypes"
)]
pub struct PyCArray {
_base: PyCData,
/// Element type - can be a simple type (c_int) or an array type (c_int * 5)
pub(super) typ: PyRwLock<PyObjectRef>,
pub(super) length: AtomicCell<usize>,
Expand Down Expand Up @@ -300,11 +304,13 @@ impl Constructor for PyCArray {
}
}

let cdata = CDataObject::from_bytes(buffer, None);
PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)),
cdata: PyRwLock::new(cdata),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
Expand Down Expand Up @@ -529,11 +535,13 @@ impl PyCArray {
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };

let cdata = CDataObject::from_bytes(bytes.to_vec(), None);
Ok(PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(bytes.to_vec(), None)),
cdata: PyRwLock::new(cdata),
}
.into_pyobject(vm))
}
Expand Down Expand Up @@ -595,14 +603,13 @@ impl PyCArray {
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };

let cdata = CDataObject::from_bytes(data.to_vec(), Some(buffer.obj.clone()));
Ok(PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(
data.to_vec(),
Some(buffer.obj.clone()),
)),
cdata: PyRwLock::new(cdata),
}
.into_pyobject(vm))
}
Expand Down Expand Up @@ -655,11 +662,13 @@ impl PyCArray {
.unwrap_or(0);
let element_size = if length > 0 { size / length } else { 0 };

let cdata = CDataObject::from_bytes(data.to_vec(), None);
Ok(PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data.to_vec(), None)),
cdata: PyRwLock::new(cdata),
}
.into_pyobject(vm))
}
Expand Down Expand Up @@ -740,11 +749,13 @@ impl PyCArray {
let element_size = if length > 0 { size / length } else { 0 };

// Create instance
let cdata = CDataObject::from_bytes(data, None);
let instance = PyCArray {
_base: PyCData::new(cdata.clone()),
typ: PyRwLock::new(element_type.into()),
length: AtomicCell::new(length),
element_size: AtomicCell::new(element_size),
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
cdata: PyRwLock::new(cdata),
}
.into_pyobject(vm);

Expand Down
18 changes: 15 additions & 3 deletions crates/vm/src/stdlib/ctypes/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ pub struct PyCData {
pub cdata: PyRwLock<CDataObject>,
}

impl PyCData {
pub fn new(cdata: CDataObject) -> Self {
Self {
cdata: PyRwLock::new(cdata),
}
}
}

#[pyclass(flags(BASETYPE))]
impl PyCData {
#[pygetset]
Expand All @@ -228,8 +236,9 @@ impl PyCData {
}

#[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)]
#[derive(Debug, Default)]
pub struct PyCSimpleType {}
#[derive(Debug)]
#[repr(transparent)]
pub struct PyCSimpleType(PyType);

#[pyclass(flags(BASETYPE), with(AsNumber))]
impl PyCSimpleType {
Expand Down Expand Up @@ -412,6 +421,7 @@ impl AsNumber for PyCSimpleType {
metaclass = "PyCSimpleType"
)]
pub struct PyCSimple {
pub _base: PyCData,
pub _type_: String,
pub value: AtomicCell<PyObjectRef>,
pub cdata: PyRwLock<CDataObject>,
Expand Down Expand Up @@ -646,10 +656,12 @@ impl Constructor for PyCSimple {
.unwrap_or(false);

let buffer = value_to_bytes_endian(&_type_, &value, swapped, vm);
let cdata = CDataObject::from_bytes(buffer, None);
PyCSimple {
_base: PyCData::new(cdata.clone()),
_type_,
value: AtomicCell::new(value),
cdata: PyRwLock::new(CDataObject::from_bytes(buffer, None)),
cdata: PyRwLock::new(cdata),
}
.into_ref_with_type(vm, cls)
.map(Into::into)
Expand Down
1 change: 1 addition & 0 deletions crates/vm/src/stdlib/ctypes/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::union::PyCUnion;
#[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")]
#[derive(Debug)]
pub struct PyCFieldType {
pub _base: PyType,
#[allow(dead_code)]
pub(super) inner: PyCField,
}
Expand Down
7 changes: 6 additions & 1 deletion crates/vm/src/stdlib/ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::builtins::{PyNone, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::FuncArgs;
use crate::stdlib::ctypes::PyCData;
use crate::stdlib::ctypes::base::{PyCSimple, ffi_type_from_str};
use crate::stdlib::ctypes::base::{CDataObject, PyCSimple, ffi_type_from_str};
use crate::stdlib::ctypes::thunk::PyCThunk;
use crate::types::Representable;
use crate::types::{Callable, Constructor};
Expand Down Expand Up @@ -163,6 +163,7 @@ impl ReturnType for PyNone {

#[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)]
pub struct PyCFuncPtr {
_base: PyCData,
pub name: PyRwLock<Option<String>>,
pub ptr: PyRwLock<Option<CodePtr>>,
#[allow(dead_code)]
Expand Down Expand Up @@ -193,6 +194,7 @@ impl Constructor for PyCFuncPtr {

if args.args.is_empty() {
return PyCFuncPtr {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
ptr: PyRwLock::new(None),
needs_free: AtomicCell::new(false),
arg_types: PyRwLock::new(None),
Expand All @@ -211,6 +213,7 @@ impl Constructor for PyCFuncPtr {
if let Ok(addr) = first_arg.try_int(vm) {
let ptr_val = addr.as_bigint().to_usize().unwrap_or(0);
return PyCFuncPtr {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
ptr: PyRwLock::new(Some(CodePtr(ptr_val as *mut _))),
needs_free: AtomicCell::new(false),
arg_types: PyRwLock::new(None),
Expand Down Expand Up @@ -270,6 +273,7 @@ impl Constructor for PyCFuncPtr {
};

return PyCFuncPtr {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
ptr: PyRwLock::new(code_ptr),
needs_free: AtomicCell::new(false),
arg_types: PyRwLock::new(None),
Expand Down Expand Up @@ -313,6 +317,7 @@ impl Constructor for PyCFuncPtr {
let thunk_ref: PyRef<PyCThunk> = thunk.into_ref(&vm.ctx);

return PyCFuncPtr {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
ptr: PyRwLock::new(Some(code_ptr)),
needs_free: AtomicCell::new(true),
arg_types: PyRwLock::new(arg_type_vec),
Expand Down
13 changes: 10 additions & 3 deletions crates/vm/src/stdlib/ctypes/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ use rustpython_common::lock::PyRwLock;
use crate::builtins::{PyType, PyTypeRef};
use crate::function::FuncArgs;
use crate::protocol::PyNumberMethods;
use crate::stdlib::ctypes::PyCData;
use crate::stdlib::ctypes::{CDataObject, PyCData};
use crate::types::{AsNumber, Constructor};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};

#[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")]
#[derive(Debug, Default)]
pub struct PyCPointerType {}
#[derive(Debug)]
#[repr(transparent)]
pub struct PyCPointerType(PyType);

#[pyclass(flags(IMMUTABLETYPE), with(AsNumber))]
impl PyCPointerType {
Expand Down Expand Up @@ -62,6 +63,7 @@ impl AsNumber for PyCPointerType {
)]
#[derive(Debug)]
pub struct PyCPointer {
_base: PyCData,
contents: PyRwLock<PyObjectRef>,
}

Expand All @@ -75,6 +77,7 @@ impl Constructor for PyCPointer {

// Create a new PyCPointer instance with the provided value
PyCPointer {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
contents: PyRwLock::new(initial_contents),
}
.into_ref_with_type(vm, cls)
Expand Down Expand Up @@ -124,6 +127,7 @@ impl PyCPointer {
}
// Pointer just stores the address value
Ok(PyCPointer {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
contents: PyRwLock::new(vm.ctx.new_int(address).into()),
}
.into_ref_with_type(vm, cls)?
Expand Down Expand Up @@ -168,6 +172,7 @@ impl PyCPointer {
let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above"));

Ok(PyCPointer {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()),
}
.into_ref_with_type(vm, cls)?
Expand Down Expand Up @@ -204,6 +209,7 @@ impl PyCPointer {
let ptr_val = usize::from_ne_bytes(ptr_bytes.try_into().expect("size is checked above"));

Ok(PyCPointer {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
contents: PyRwLock::new(vm.ctx.new_int(ptr_val).into()),
}
.into_ref_with_type(vm, cls)?
Expand Down Expand Up @@ -259,6 +265,7 @@ impl PyCPointer {

// For pointer types, we return a pointer to the symbol address
Ok(PyCPointer {
_base: PyCData::new(CDataObject::from_bytes(vec![], None)),
contents: PyRwLock::new(vm.ctx.new_int(symbol_address).into()),
}
.into_ref_with_type(vm, cls)?
Expand Down
22 changes: 16 additions & 6 deletions crates/vm/src/stdlib/ctypes/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use std::fmt::Debug;

/// PyCStructType - metaclass for Structure
#[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")]
#[derive(Debug, Default)]
pub struct PyCStructType {}
#[derive(Debug)]
#[repr(transparent)]
pub struct PyCStructType(PyType);

impl Constructor for PyCStructType {
type Args = FuncArgs;
Expand Down Expand Up @@ -219,6 +220,7 @@ pub struct FieldInfo {
metaclass = "PyCStructType"
)]
pub struct PyCStructure {
_base: PyCData,
/// Common CDataObject for memory buffer
pub(super) cdata: PyRwLock<CDataObject>,
/// Field information (name -> FieldInfo)
Expand Down Expand Up @@ -294,8 +296,10 @@ impl Constructor for PyCStructure {
// Initialize buffer with zeros
let mut stg_info = StgInfo::new(total_size, max_align);
stg_info.length = fields_map.len();
let cdata = CDataObject::from_stg_info(&stg_info);
let instance = PyCStructure {
cdata: PyRwLock::new(CDataObject::from_stg_info(&stg_info)),
_base: PyCData::new(cdata.clone()),
cdata: PyRwLock::new(cdata),
fields: PyRwLock::new(fields_map.clone()),
};

Expand Down Expand Up @@ -363,8 +367,10 @@ impl PyCStructure {
};

// Create instance
let cdata = CDataObject::from_bytes(data, None);
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
_base: PyCData::new(cdata.clone()),
cdata: PyRwLock::new(cdata),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
Expand Down Expand Up @@ -414,8 +420,10 @@ impl PyCStructure {
let data = bytes[offset..offset + size].to_vec();

// Create instance
let cdata = CDataObject::from_bytes(data, Some(source));
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, Some(source))),
_base: PyCData::new(cdata.clone()),
cdata: PyRwLock::new(cdata),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
Expand Down Expand Up @@ -457,8 +465,10 @@ impl PyCStructure {
let data = source_bytes[offset..offset + size].to_vec();

// Create instance
let cdata = CDataObject::from_bytes(data, None);
Ok(PyCStructure {
cdata: PyRwLock::new(CDataObject::from_bytes(data, None)),
_base: PyCData::new(cdata.clone()),
cdata: PyRwLock::new(cdata),
fields: PyRwLock::new(IndexMap::new()),
}
.into_ref_with_type(vm, cls)?
Expand Down
Loading