From f3ee5099ac5f7dff597920cdfe47ac4995e1cf8b Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:36:15 +0200 Subject: [PATCH] Add bytesarray support to the c-api (#8009) * Add bytesarray support to the c-api * Add SAFETY comment --- crates/capi/src/bytearrayobject.rs | 127 ++++++++++++++++++++++++++++ crates/capi/src/lib.rs | 1 + crates/capi/src/util.rs | 8 ++ crates/vm/src/builtins/bytearray.rs | 2 +- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 crates/capi/src/bytearrayobject.rs diff --git a/crates/capi/src/bytearrayobject.rs b/crates/capi/src/bytearrayobject.rs new file mode 100644 index 000000000..2bc56895b --- /dev/null +++ b/crates/capi/src/bytearrayobject.rs @@ -0,0 +1,127 @@ +use crate::PyObject; +use crate::object::define_py_check; +use crate::pystate::with_vm; +use core::ffi::c_char; +use rustpython_vm::builtins::PyByteArray; +use rustpython_vm::byte::bytes_from_object; + +define_py_check!(fn PyByteArray_Check, types.bytearray_type); + +/// # Safety +/// +/// If `bytes` is `NULL`, the returned bytearray may contain uninitialized +/// bytes. The caller is responsible for initializing all bytes before any read. +#[unsafe(no_mangle)] +#[allow(clippy::uninit_vec)] +pub unsafe extern "C" fn PyByteArray_FromStringAndSize( + bytes: *const c_char, + len: isize, +) -> *mut PyObject { + with_vm(|vm| { + let len: usize = len.try_into().map_err(|_| { + vm.new_system_error("Negative size passed to PyByteArray_FromStringAndSize") + })?; + + let data = if bytes.is_null() { + let mut data = Vec::with_capacity(len); + // SAFETY: `bytes == NULL` follows CPython semantics here; caller must + // initialize all bytes before any read. We keep this behavior for C-API + // compatibility and to avoid unnecessary zero-initialization overhead. + unsafe { data.set_len(len) }; + data + } else { + unsafe { core::slice::from_raw_parts(bytes.cast::(), len) }.to_vec() + }; + + Ok(vm.ctx.new_bytearray(data)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyByteArray_FromObject(obj: *mut PyObject) -> *mut PyObject { + with_vm(|vm| { + let obj = unsafe { &*obj }; + let data = bytes_from_object(vm, obj)?; + Ok(vm.ctx.new_bytearray(data)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyByteArray_Size(bytearray: *mut PyObject) -> isize { + with_vm(|vm| { + let bytearray = unsafe { &*bytearray }.try_downcast_ref::(vm)?; + Ok(bytearray.borrow_buf().len()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyByteArray_AsString(bytearray: *mut PyObject) -> *mut c_char { + with_vm(|vm| { + let bytearray = unsafe { &*bytearray }.try_downcast_ref::(vm)?; + Ok(bytearray.borrow_buf_mut().as_mut_ptr()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyByteArray_Resize(bytearray: *mut PyObject, len: isize) -> i32 { + with_vm(|vm| { + let bytearray = unsafe { &*bytearray }.try_downcast_ref::(vm)?; + bytearray.resize(len, vm)?; + Ok(()) + }) +} + +#[cfg(false)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::{PyByteArray, PyBytes}; + + #[test] + fn bytearray_size() { + Python::attach(|py| { + let bytearray = PyByteArray::new(py, b"abc"); + assert_eq!(bytearray.len(), 3); + }) + } + + #[test] + fn bytearray_resize() { + Python::attach(|py| { + let bytearray = PyByteArray::new(py, b"abcde"); + bytearray.resize(3).unwrap(); + assert_eq!(bytearray.len(), 3); + assert_eq!(bytearray.to_vec(), b"abc"); + }) + } + + #[test] + fn bytearray_from_string_and_size() { + Python::attach(|py| { + let bytearray = PyByteArray::new(py, b"hello"); + assert_eq!(bytearray.len(), 5); + assert_eq!(bytearray.to_vec(), b"hello"); + }) + } + + #[test] + fn bytearray_new_with_zero_initialized() { + Python::attach(|py| { + let bytearray = PyByteArray::new_with(py, 4, |bytes| { + bytes[..2].copy_from_slice(b"hi"); + Ok(()) + }) + .unwrap(); + assert_eq!(bytearray.len(), 4); + assert_eq!(bytearray.to_vec(), b"hi\0\0"); + }) + } + + #[test] + fn bytearray_from_object() { + Python::attach(|py| { + let source = PyBytes::new(py, b"ABC"); + let bytearray = PyByteArray::from(&source).unwrap(); + assert_eq!(bytearray.to_vec(), b"ABC"); + }) + } +} diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 15230958a..99a606783 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -10,6 +10,7 @@ extern crate alloc; pub mod abstract_; pub mod boolobject; +pub mod bytearrayobject; pub mod bytesobject; pub mod ceval; pub mod complexobject; diff --git a/crates/capi/src/util.rs b/crates/capi/src/util.rs index 6eef9163a..24d6d3d40 100644 --- a/crates/capi/src/util.rs +++ b/crates/capi/src/util.rs @@ -76,6 +76,14 @@ impl FfiResult<*mut c_char> for *const u8 { } } +impl FfiResult<*mut c_char> for *mut u8 { + const ERR_VALUE: *mut c_char = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut c_char { + self.cast() + } +} + impl FfiResult for *const c_char { const ERR_VALUE: *const c_char = core::ptr::null_mut(); diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index 5a1b8565a..63b10552f 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -548,7 +548,7 @@ impl PyByteArray { } #[pymethod] - fn resize(&self, size: isize, vm: &VirtualMachine) -> PyResult<()> { + pub fn resize(&self, size: isize, vm: &VirtualMachine) -> PyResult<()> { if size < 0 { return Err(vm.new_value_error("bytearray.resize(): new size must be >= 0")); }