ctypes struct/union/array#6309
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughReworks _ctypes: adds metaclass-driven Structure/Union types, converts C arrays to buffer-backed, sequence-enabled types, implements descriptor-based PyCField read/write, changes byref to validate and return a new CArgObject, and exposes a platform-aware _wstring_at_addr attr. Changes
Sequence DiagramsequenceDiagram
participant User as User code
participant Meta as PyCStructType / PyCUnionType
participant Cls as Structure/Union Class
participant Field as PyCField (descriptor)
participant Inst as Structure/Union Instance
participant Core as _ctypes module
User->>Meta: define class with _fields_
Meta->>Meta: process_fields(_fields_)
Meta->>Field: create PyCField descriptors (compute size/offset)
Meta->>Cls: register descriptors and fields metadata
Meta-->>User: return created class
User->>Cls: instantiate(args/kwargs)
Cls->>Inst: py_new -> allocate buffer (Vec<u8>) and fields map
Inst->>Inst: populate buffer from args/kwargs
Inst-->>User: instance returned
User->>Inst: read attribute
Inst->>Field: descriptor __get__ (reads buffer[offset...])
Field->>Field: bytes_to_value -> PyObject
Field-->>User: value
User->>Inst: write attribute
Inst->>Field: descriptor __set__ (value_to_bytes -> bytes)
Field->>Inst: write bytes into buffer[offset...]
Field-->>User: done
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
1e983b1 to
df369d0
Compare
November 29, 2025 01:56
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/ctypes/field.rs (1)
68-76: Repr displays boolean as size value.
bitfield_sizeis abool(line 29), but the repr formatbit_size={bitfield_size}suggests it should be a numeric size. This will displaybit_size=trueinstead of an actual bit count.Either rename the field to
is_bitfieldor change its type to store the actual bit size:- pub(super) bitfield_size: bool, + pub(super) bitfield_size: Option<usize>, // None if not a bitfield, Some(n) for n-bit field
🧹 Nitpick comments (3)
crates/vm/src/stdlib/ctypes/union.rs (1)
85-109: Consider extracting shared size calculation logic.This
get_field_sizelogic duplicates the element size derivation inbase.rs(lines 311-324) andarray.rs. Consider extracting this into a shared helper function in_ctypesto reduce duplication and ensure consistent behavior.Example shared helper:
// In _ctypes module pub fn get_field_size_from_type(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { // ... shared implementation }crates/vm/src/stdlib/ctypes/array.rs (1)
374-382: Silent failure when value is not bytes.The
set_valuesetter silently ignores non-PyBytesvalues and returnsOk(()). This could mask user errors. Consider raising aTypeErrorfor consistency withset_raw.#[pygetset(setter)] fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if let Some(bytes) = value.downcast_ref::<PyBytes>() { let mut buffer = self.buffer.write(); let src = bytes.as_bytes(); let len = std::cmp::min(src.len(), buffer.len()); buffer[..len].copy_from_slice(&src[..len]); + Ok(()) + } else { + Err(vm.new_type_error("expected bytes".to_owned())) } - Ok(()) }crates/vm/src/stdlib/ctypes/structure.rs (1)
233-242: Inconsistent error handling: silently skipping malformed fields.
process_fieldsreturns errors for malformed field tuples, butpy_newsilently skips them withcontinue. This inconsistency could cause hard-to-debug issues where a structure appears to work but has missing fields.Consider reusing validation logic or returning errors consistently:
- let Some(field_tuple) = field.downcast_ref::<PyTuple>() else { - continue; - }; - if field_tuple.len() < 2 { - continue; - } - let Some(name) = field_tuple.first().unwrap().downcast_ref::<PyStr>() else { - continue; - }; + let field_tuple = field + .downcast_ref::<PyTuple>() + .ok_or_else(|| vm.new_type_error("_fields_ must contain tuples".to_string()))?; + if field_tuple.len() < 2 { + return Err(vm.new_type_error("_fields_ tuple must have at least 2 elements".to_string())); + } + let name = field_tuple.first().unwrap() + .downcast_ref::<PyStr>() + .ok_or_else(|| vm.new_type_error("field name must be a string".to_string()))?;
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
crates/vm/src/stdlib/ctypes.rs(5 hunks)crates/vm/src/stdlib/ctypes/array.rs(5 hunks)crates/vm/src/stdlib/ctypes/base.rs(1 hunks)crates/vm/src/stdlib/ctypes/field.rs(4 hunks)crates/vm/src/stdlib/ctypes/pointer.rs(1 hunks)crates/vm/src/stdlib/ctypes/structure.rs(1 hunks)crates/vm/src/stdlib/ctypes/union.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style in Rust code (cargo fmtto format)
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management
Files:
crates/vm/src/stdlib/ctypes/base.rscrates/vm/src/stdlib/ctypes/pointer.rscrates/vm/src/stdlib/ctypes/union.rscrates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rscrates/vm/src/stdlib/ctypes/field.rscrates/vm/src/stdlib/ctypes/structure.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use the macro system (
pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/vm/src/stdlib/ctypes/base.rscrates/vm/src/stdlib/ctypes/pointer.rscrates/vm/src/stdlib/ctypes/union.rscrates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rscrates/vm/src/stdlib/ctypes/field.rscrates/vm/src/stdlib/ctypes/structure.rs
🧠 Learnings (2)
📚 Learning: 2025-11-25T11:05:02.899Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T11:05:02.899Z
Learning: Applies to **/src/**/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
Applied to files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Applied to files:
crates/vm/src/stdlib/ctypes/array.rs
🧬 Code graph analysis (6)
crates/vm/src/stdlib/ctypes/base.rs (1)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
crates/vm/src/stdlib/ctypes/pointer.rs (1)
crates/vm/src/stdlib/ctypes.rs (16)
mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
crates/vm/src/stdlib/ctypes/union.rs (3)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)crates/vm/src/stdlib/ctypes/field.rs (1)
type_(271-273)crates/vm/src/stdlib/ctypes/structure.rs (1)
field_type(247-248)
crates/vm/src/stdlib/ctypes/array.rs (1)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
crates/vm/src/stdlib/ctypes/field.rs (2)
crates/vm/src/stdlib/ctypes/base.rs (13)
new(169-175)value(44-46)value(48-50)value(52-54)value(84-84)value(94-94)value(104-104)value(111-111)value(112-112)value(123-123)value(134-134)value(135-135)value(287-292)crates/vm/src/stdlib/ctypes/array.rs (5)
index(324-324)index(341-341)value(367-371)value(375-375)value(392-392)
crates/vm/src/stdlib/ctypes/structure.rs (2)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)crates/vm/src/stdlib/ctypes/field.rs (2)
name(266-268)size(246-248)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
🔇 Additional comments (14)
crates/vm/src/stdlib/ctypes/pointer.rs (1)
27-34: LGTM!The pointer size calculation using
std::mem::size_of::<usize>()is correct for pointer types, and the initialization ofelement_sizeandbufferfields aligns with the newPyCArraystructure. The empty buffer is appropriate since this creates a type object, not an instance.crates/vm/src/stdlib/ctypes/base.rs (2)
306-324: LGTM!The element size derivation logic correctly extracts the type code from the
_type_attribute and usesget_sizefor single-character codes, with appropriate fallbacks tosize_of::<usize>()for unknown or complex types.
325-334: LGTM!The
PyCArrayTypeinitialization is consistent with the new buffer-backed array structure introduced across the ctypes module.crates/vm/src/stdlib/ctypes/union.rs (2)
16-36: LGTM!The metaclass constructor correctly delegates to
PyType::py_newand processes_fields_only when defined directly on the class (not inherited), which is the expected behavior for ctypes Union types.
41-83: LGTM!The
process_fieldsimplementation correctly implements union semantics where all fields start at offset 0. The validation of_fields_format (list/tuple of tuples with at least 2 elements) is thorough.crates/vm/src/stdlib/ctypes.rs (2)
24-25: LGTM!The metaclass registrations for
PyCStructTypeandPyCUnionTypeare correctly placed before the module extension, ensuring the metaclasses are available when creating the base classes.
63-78: LGTM!The
CArgObjectimplementation correctly follows thepyclassmacro conventions. The_objgetter provides access to the wrapped object as expected by ctypes semantics.crates/vm/src/stdlib/ctypes/array.rs (3)
33-65: LGTM!The
Callableimplementation correctly creates array instances from positional arguments, initializing the buffer with proper element sizing. The bounds checking (if i >= length) prevents overflow when more arguments than array length are provided.
87-116: LGTM!The
__mul__implementation correctly creates nested array types where the element size is the total size of the inner array. This enables expressions like(c_int * 5) * 3to create a 2D array structure.
248-256: LGTM!The
int_to_byteshelper correctly uses native endianness (to_ne_bytes) which matches platform ABI expectations for ctypes. The size handling covers common C integer sizes.crates/vm/src/stdlib/ctypes/structure.rs (2)
21-41: LGTM!The metaclass constructor correctly delegates to
PyType::py_newand processes_fields_only when defined directly on the class (avoiding inherited fields).
56-104: LGTM!Field processing correctly validates structure, extracts field metadata, and registers CField descriptors with proper offsets.
crates/vm/src/stdlib/ctypes/field.rs (2)
34-53: LGTM!Clean constructor with sensible defaults for optional fields.
102-134: LGTM!The descriptor correctly returns itself on class access and reads from the structure buffer on instance access with proper bounds checking.
Sorry, something went wrong.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
crates/vm/src/stdlib/ctypes/array.rs (2)
258-277: Potential panic if buffer slice is too short.The
bytes_to_intfunction directly indexesbytes[0],bytes[1], etc. without verifying slice length. If called with an undersized slice, this will panic.Add a length guard at the start:
fn bytes_to_int(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyObjectRef { + if bytes.len() < size { + return vm.ctx.new_int(0).into(); + } match size { 1 => vm.ctx.new_int(bytes[0] as i8).into(),
404-412: Lifetime concern:Arg::new(&ptr)references a stack-local pointer.The
ptrvariable is local to this function. WhileArg::new(&ptr)takes a reference to the pointer value itself (not the buffer data), this still creates anArgthat references memory that will be invalid after the function returns and the read guard is dropped.This requires architectural changes to ensure the buffer outlives the FFI call. Consider storing the buffer pointer in a way that persists for the duration of the FFI call, or restructuring how arguments are passed to FFI.
crates/vm/src/stdlib/ctypes/structure.rs (2)
106-134: Potential panic with unrecognized single-character type codes.The
get_size()function usesunreachable!()for unknown type strings. While thes.len() == 1check filters multi-char strings, an unrecognized single character (e.g.,"x") will still trigger a panic.Validate the type code before calling
get_size:.and_then(|type_str| { let s = type_str.to_string(); - (s.len() == 1).then(|| get_size(&s)) + const VALID_TYPES: &str = "cbBhHiIlqLQfdg?PzZOu"; + (s.len() == 1 && VALID_TYPES.contains(&s)).then(|| get_size(&s)) })
136-155: Element size is hardcoded to pointer size instead of actual structure size.The
element_sizeshould be calculated from the structure's fields, not hardcoded tosize_of::<usize>(). This will cause incorrect array sizing when creating arrays of structures.Consider computing the actual structure size from
_fields_:fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult { use super::array::{PyCArray, PyCArrayType}; if n < 0 { return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}"))); } - // TODO: Calculate element size properly - // For structures, element size is the structure size (sum of field sizes) - let element_size = std::mem::size_of::<usize>(); // Default, should calculate from fields + // Calculate element size from _fields_ + let element_size = if let Ok(fields_attr) = cls.as_object().get_attr("_fields_", vm) { + Self::calculate_struct_size_from_fields(&fields_attr, vm).unwrap_or(std::mem::size_of::<usize>()) + } else { + std::mem::size_of::<usize>() + };You'll need to add a helper method or reuse logic from
process_fieldsto sum field sizes.
🧹 Nitpick comments (1)
crates/vm/src/stdlib/ctypes/structure.rs (1)
308-312:_fields_getter returns None instead of actual fields.The
_fields_property always returnsvm.ctx.none()instead of retrieving the actual_fields_from the class. While the comment suggests this is intentional, this may break code that expects to read_fields_from an instance.Consider returning the class's
_fields_attribute:#[pygetset] fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return the _fields_ from the class, not instance - vm.ctx.none() + // Return the _fields_ from the class + self.class() + .get_attr(vm.ctx.intern_str("_fields_"), vm) + .unwrap_or_else(|_| vm.ctx.none()) }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
crates/vm/src/stdlib/ctypes.rs(5 hunks)crates/vm/src/stdlib/ctypes/array.rs(5 hunks)crates/vm/src/stdlib/ctypes/structure.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style in Rust code (cargo fmtto format)
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management
Files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/structure.rscrates/vm/src/stdlib/ctypes/array.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use the macro system (
pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/structure.rscrates/vm/src/stdlib/ctypes/array.rs
🧠 Learnings (3)
📚 Learning: 2025-11-10T06:27:41.954Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6243
File: vm/src/function/buffer.rs:123-129
Timestamp: 2025-11-10T06:27:41.954Z
Learning: In vm/src/function/buffer.rs, line 36 in the try_rw_bytes_like method intentionally uses TypeError (not BufferError) for the "buffer is not a read-write bytes-like object" error case, even though a similar error message in ArgMemoryBuffer::try_from_borrowed_object uses BufferError. The TypeError is the intended exception type for this specific code path.
Applied to files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rs
📚 Learning: 2025-11-25T11:05:02.899Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T11:05:02.899Z
Learning: Applies to **/src/**/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
Applied to files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Applied to files:
crates/vm/src/stdlib/ctypes/array.rs
🧬 Code graph analysis (1)
crates/vm/src/stdlib/ctypes/array.rs (1)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run tests under miri
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (8)
crates/vm/src/stdlib/ctypes.rs (3)
63-78: LGTM! Clean CArgObject implementation.The
CArgObjectstruct follows the RustPython macro conventions correctly with#[pyclass]and#[pygetset]. The#[allow(dead_code)]onoffsetis appropriate since it's stored for future use but not yet exposed.
345-367: LGTM! byref error message now includes actual type name.The fix correctly uses
obj.class().name()to provide helpful error messages showing the actual type passed, addressing the previous review feedback.
436-439:_cast_addrusestodo!()which will panic at runtime.Similar to
_wstring_at_addr, this will panic if called. If this is intentional for tracking unimplemented features, consider using a more explicit approach.Is this intentional as a development placeholder? If users might call this, consider returning a Python exception instead.
crates/vm/src/stdlib/ctypes/array.rs (2)
33-65: LGTM! Clean Callable implementation for PyCArrayType.The implementation correctly creates array instances from the type metadata, bounds-checks the positional arguments, and initializes the buffer appropriately.
87-116: LGTM! Well-structured__mul__for nested array types.The implementation correctly validates negative lengths and creates nested array types with proper element size calculation. The approach of using the current array type as the element type for the new outer array is correct for ctypes semantics.
crates/vm/src/stdlib/ctypes/structure.rs (3)
21-41: LGTM! Clean metaclass constructor.The
py_newcorrectly delegates toPyType::py_newand processes_fields_only when defined directly on the new class (not inherited), which is the correct behavior for Structure metaclasses.
56-104: Well-structured field processing with proper offset tracking.The
process_fieldsmethod correctly validates_fields_format, computes offsets, and createsPyCFielddescriptors. Usingset_attrto register descriptors on the class is the right approach.
220-265: Good: IndexMap preserves insertion order for positional arguments.The use of
IndexMapinstead ofHashMapensures thatfields_map.keys()returns field names in declaration order, so positional arguments are correctly assigned. This addresses the concern from the previous review.Also applies to: 287-297
Sorry, something went wrong.
39e6885 to
10fe041
Compare
November 29, 2025 02:55
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/ctypes/base.rs (1)
44-52: Duplicate condition check - likely copy-paste error.The same
PyByteswith length 1 check appears twice. The second check (lines 48-51) should likely check a different type (perhapsPyByteArray?).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::<PyByteArray>(vm) + .is_ok_and(|v| v.len() == 1) || value .clone() .downcast_exact::<PyInt>(vm)
♻️ Duplicate comments (8)
crates/vm/src/stdlib/ctypes.rs (1)
422-434: Windowstodo!()will panic at runtime.This was flagged in a previous review. The
todo!()macro will cause a runtime panic on Windows. Consider returning 0 with a TODO comment, or raising a proper Python exception.#[cfg(target_os = "windows")] { - todo!("On Windows, use wcslen from ucrt") + // TODO: RUSTPYTHON - On Windows, should use wcslen from ucrt + // Returning 0 as placeholder until Windows support is added + 0 }crates/vm/src/stdlib/ctypes/field.rs (2)
136-157: Type conversion only handles signed integers.This was flagged in a previous review. The implementation always interprets bytes as signed integers, but ctypes supports unsigned types (
c_uint,c_ulong) and floating-point types (c_float,c_double). Consider using the field'sprototype information to determine the correct interpretation.
159-186: Silent truncation on integer overflow.This was flagged in a previous review. Using
unwrap_or(0)silently handles values that don't fit, whereas CPython ctypes raisesOverflowError. For example, assigning300to a 1-byte field silently becomes0.1 => { - let val = i.to_i8().unwrap_or(0); + let val = i.to_i8().ok_or_else(|| { + vm.new_overflow_error("int too large to convert".to_owned()) + })?; Ok(val.to_ne_bytes().to_vec()) }crates/vm/src/stdlib/ctypes/array.rs (3)
258-277: Potential panic if buffer is too short.This was flagged in a previous review. The
bytes_to_intfunction accesses array indices without validating slice length. If called with an undersized slice, this will panic.fn bytes_to_int(bytes: &[u8], size: usize, vm: &VirtualMachine) -> PyObjectRef { + if bytes.len() < size { + return vm.ctx.new_int(0).into(); + } match size { 1 => vm.ctx.new_int(bytes[0] as i8).into(), 2 => { - let val = i16::from_ne_bytes([bytes[0], bytes[1]]); + let val = i16::from_ne_bytes(bytes[..2].try_into().unwrap()); vm.ctx.new_int(val).into() }
373-382:set_valuesilently ignores non-bytes input.This was flagged in a previous review. When
valueis notPyBytes, the function returnsOk(())without any indication that the value wasn't set.fn set_value(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { if let Some(bytes) = value.downcast_ref::<PyBytes>() { let mut buffer = self.buffer.write(); let src = bytes.as_bytes(); let len = std::cmp::min(src.len(), buffer.len()); buffer[..len].copy_from_slice(&src[..len]); + Ok(()) + } else { + Err(vm.new_type_error("expected bytes".to_owned())) } - Ok(()) }
404-412: Lifetime issue withto_arg- pointer may dangle after read lock is released.This was flagged in a previous review. The
buffer.read()guard is dropped at the end of the function, butptrstill references that memory. TheArgreturned will point to potentially invalid memory when used.The TODO comment acknowledges this, but this is a use-after-free that will cause undefined behavior. Consider:
- Storing a persistent buffer pointer in
PyCArraythat outlives FFI calls- Using
Arc<RwLock<Vec<u8>>>and keeping the guard alive during FFI- Copying the buffer to a location that's guaranteed to outlive the call
crates/vm/src/stdlib/ctypes/structure.rs (2)
106-134: Guardget_sizeagainst unknown_type_codes to avoid VM panics
get_field_sizecallsget_size(&s)for any single‑char_type_value without validating the code. If_type_is a single unknown character (e.g. user‑defined or corrupted),get_size’s internalunreachable!()will panic the VM.Consider explicitly validating the code before calling
get_size, or delegating to a non‑panicking helper in_ctypes:- .and_then(|type_str| { - let s = type_str.to_string(); - (s.len() == 1).then(|| get_size(&s)) - }) + .and_then(|type_str| { + let s = type_str.to_string(); + if s.len() != 1 { + return None; + } + // Keep this set in sync with `_ctypes::get_size`. + const VALID_TYPES: &[&str] = &[ + "u", "c", "b", "h", "H", "i", "I", "l", "q", "L", "Q", "f", "d", "g", "?", + "B", "P", "z", "Z", "O", + ]; + if !VALID_TYPES.contains(&s.as_str()) { + return None; + } + Some(get_size(&s)) + })This keeps
get_field_sizetotal‑failure‑free and avoids panics even if_type_is malformed.
136-175:Structure * nuses pointer size instead of structure size for arrays
PyCStructType::__mul__sets:// TODO: Calculate element size properly let element_size = std::mem::size_of::<usize>();For arrays of structures (
Point * 10), this will allocaten * sizeof(usize)bytes instead ofn * sizeof(struct). Any field descriptor that uses struct offsets larger thansizeof(usize)risks indexing past the end of the array buffer and panicking.You should compute the real structure size from
_fields_, ideally reusing the same logic as inPyCStructure::py_new:- // TODO: Calculate element size properly - // For structures, element size is the structure size (sum of field sizes) - let element_size = std::mem::size_of::<usize>(); // Default, should calculate from fields + // Element size is the structure size derived from the class's `_fields_` + let element_size = { + let fields_attr = cls + .as_object() + .get_attr("_fields_", vm) + .map_err(|_| vm.new_type_error("Structure has no _fields_".to_owned()))?; + // Factor this out of `PyCStructure::py_new` into a shared helper + PyCStructType::get_struct_size_from_fields(&fields_attr, vm)? + };…and add a
get_struct_size_from_fieldshelper that mirrors the size/offset accumulation inPyCStructure::py_new, so arrays and instances share one definition of layout.Until this is fixed, arrays of non‑trivial
Structureare very likely incorrect and fragile.
🧹 Nitpick comments (3)
crates/vm/src/stdlib/ctypes/union.rs (1)
38-110: Code duplication withPyCStructType- consider extracting shared logic.The
process_fieldsandget_field_sizemethods are nearly identical to those instructure.rs(only differing in offset calculation - always 0 for unions vs. cumulative for structs). Consider extracting the common field parsing and size calculation into a shared helper module.For example, a shared helper could be:
// In a common module pub fn parse_fields(fields_attr: &PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<(String, PyObjectRef, usize)>> { ... } pub fn get_field_size(field_type: &PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> { ... }crates/vm/src/stdlib/ctypes/array.rs (1)
248-256:int_to_bytessilently truncates on overflow (same issue as field.rs).Using
unwrap_or(0)silently handles values that don't fit. Consider returning aPyResult<Vec<u8>>and raisingOverflowErrorfor consistency with CPython.crates/vm/src/stdlib/ctypes/structure.rs (1)
306-313: Implement_fields_getter for instances (currently always returnsNone)The
_fields_getter onPyCStructurecurrently returnsNone, despite the comment saying it should expose the class’s_fields_. This breaks introspection likeinstance._fields_and diverges from expectations aroundctypes.Structure.Now that you store
fields: IndexMap<String, FieldInfo>and the class already has a_fields_attribute, consider:
- Either returning the class attribute:
#[pygetset] fn _fields_(&self, vm: &VirtualMachine) -> PyObjectRef { - // Return the _fields_ from the class, not instance - vm.ctx.none() + self.as_object() + .class() + .get_attr("_fields_", vm) + .unwrap_or_else(|_| vm.ctx.none()) }
- Or reconstructing
_fields_fromself.fieldsif you want the instance to be the single source of truth.Either way, returning
Nonepermanently is likely to surprise users.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
crates/vm/src/stdlib/ctypes.rs(5 hunks)crates/vm/src/stdlib/ctypes/array.rs(5 hunks)crates/vm/src/stdlib/ctypes/base.rs(1 hunks)crates/vm/src/stdlib/ctypes/field.rs(4 hunks)crates/vm/src/stdlib/ctypes/pointer.rs(1 hunks)crates/vm/src/stdlib/ctypes/structure.rs(1 hunks)crates/vm/src/stdlib/ctypes/union.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/vm/src/stdlib/ctypes/pointer.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style in Rust code (cargo fmtto format)
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management
Files:
crates/vm/src/stdlib/ctypes/union.rscrates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/structure.rscrates/vm/src/stdlib/ctypes/field.rscrates/vm/src/stdlib/ctypes/base.rscrates/vm/src/stdlib/ctypes/array.rs
**/src/**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use the macro system (
pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/vm/src/stdlib/ctypes/union.rscrates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/structure.rscrates/vm/src/stdlib/ctypes/field.rscrates/vm/src/stdlib/ctypes/base.rscrates/vm/src/stdlib/ctypes/array.rs
🧠 Learnings (6)
📚 Learning: 2025-11-10T06:27:41.954Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6243
File: vm/src/function/buffer.rs:123-129
Timestamp: 2025-11-10T06:27:41.954Z
Learning: In vm/src/function/buffer.rs, line 36 in the try_rw_bytes_like method intentionally uses TypeError (not BufferError) for the "buffer is not a read-write bytes-like object" error case, even though a similar error message in ArgMemoryBuffer::try_from_borrowed_object uses BufferError. The TypeError is the intended exception type for this specific code path.
Applied to files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/field.rscrates/vm/src/stdlib/ctypes/array.rs
📚 Learning: 2025-11-25T11:05:02.899Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T11:05:02.899Z
Learning: Applies to Lib/**/*.py : When modifying files in Lib/, add a `# TODO: RUSTPYTHON` comment to mark modifications, or use `unittest.skip("TODO: RustPython <reason>")` or `unittest.expectedFailure` with a comment
Applied to files:
crates/vm/src/stdlib/ctypes.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.
Applied to files:
crates/vm/src/stdlib/ctypes.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.
Applied to files:
crates/vm/src/stdlib/ctypes.rs
📚 Learning: 2025-11-25T11:05:02.899Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T11:05:02.899Z
Learning: Applies to **/src/**/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
Applied to files:
crates/vm/src/stdlib/ctypes.rscrates/vm/src/stdlib/ctypes/array.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Applied to files:
crates/vm/src/stdlib/ctypes/array.rs
🧬 Code graph analysis (5)
crates/vm/src/stdlib/ctypes/union.rs (3)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)crates/vm/src/stdlib/ctypes/field.rs (1)
type_(271-273)crates/vm/src/stdlib/ctypes/structure.rs (1)
field_type(248-249)
crates/vm/src/stdlib/ctypes.rs (3)
crates/vm/src/stdlib/ctypes/field.rs (4)
name(266-268)obj(120-120)obj(205-205)offset(276-278)crates/vm/src/stdlib/ctypes/function.rs (2)
obj(351-351)f(273-274)crates/vm/src/frame.rs (1)
class(122-124)
crates/vm/src/stdlib/ctypes/field.rs (2)
crates/vm/src/stdlib/ctypes/array.rs (5)
index(324-324)index(341-341)value(367-371)value(375-375)value(392-392)crates/vm/src/stdlib/ctypes.rs (1)
obj(337-337)
crates/vm/src/stdlib/ctypes/base.rs (1)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
crates/vm/src/stdlib/ctypes/array.rs (1)
crates/vm/src/stdlib/ctypes.rs (17)
get_size(140-159)mem(142-142)mem(143-143)mem(144-144)mem(145-145)mem(146-146)mem(147-147)mem(148-148)mem(149-149)mem(150-150)mem(151-151)mem(152-152)mem(153-153)mem(154-154)mem(155-155)mem(156-156)size_of(197-210)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run tests under miri
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (9)
crates/vm/src/stdlib/ctypes/base.rs (1)
306-334: LGTM - element_size computation with proper fallbacks.The logic correctly derives element size from the
_type_attribute when available, with sensible fallback tosize_of::<usize>(). The initialization ofPyCArraywithelement_sizeand emptybufferaligns with the new buffer-backed design.crates/vm/src/stdlib/ctypes/union.rs (1)
16-36: LGTM - Clean metaclass constructor pattern.The
py_newimplementation correctly creates the type viaPyType::py_new, then processes_fields_only when directly defined on the new class (not inherited). This follows proper ctypes semantics.crates/vm/src/stdlib/ctypes.rs (2)
63-78: LGTM - Clean CArgObject wrapper for byref().The
CArgObjectstruct properly wraps the ctypes object reference with an offset, and the_objgetter provides access to the underlying object. This aligns with CPython's ctypes behavior.
344-367: LGTM - Proper ctypes instance validation in byref().The implementation correctly checks for both
PyCDataandPyCSimpleinstances, uses a dynamic error message with the actual type name (addressing the previous review), and properly wraps the result inCArgObject.crates/vm/src/stdlib/ctypes/field.rs (1)
34-52: LGTM - Clean constructor for PyCField.The
newconstructor properly initializes all fields with sensible defaults for non-bitfield cases (anonymous: false,bitfield_size: false,bit_offset: 0).crates/vm/src/stdlib/ctypes/array.rs (3)
33-65: LGTM - Clean Callable implementation for array instantiation.The implementation correctly initializes the buffer based on element size and length, and properly handles positional argument initialization with bounds checking.
87-117: LGTM - Proper nested array type creation.The
__mul__implementation correctly creates nested array types where the element size becomes the total size of the inner array. The conversion fromPyObjectReftoPyTypeis handled appropriately.
225-244: LGTM - Clean AsSequence implementation.The sequence methods properly delegate to the helper functions with appropriate error handling for deletion attempts.
crates/vm/src/stdlib/ctypes/structure.rs (1)
16-104: Metaclass wiring and_fields_processing look coherentThe
PyCStructTypemetaclass plusprocess_fieldspipeline for_fields_(including direct-attr checks in bothpy_newand__init_subclass__) is consistent and keeps field layout logic centralized. This is a solid base for the newStructuremodel.
Sorry, something went wrong.
10fe041 to
711ea51
Compare
November 29, 2025 03:13
4fcce4d
into
RustPython:main
Nov 29, 2025
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.