Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
127 changes: 127 additions & 0 deletions crates/capi/src/bytearrayobject.rs
Original file line number Diff line number Diff line change
@@ -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) };
Comment on lines +26 to +30

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this safe? to be safe, buffer must be written before read. this code doesn't seem to ensure that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is unsafe indeed, but it is the responsibility of the caller to write the data before reading it.

See for example this snipped in PyO3.
https://github.com/PyO3/pyo3/blob/b2163a0916db9fe1baef3bcd3e224531dce508fe/src/types/bytearray.rs#L78-L97

    pub fn new_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyByteArray>>
    where
        F: FnOnce(&mut [u8]) -> PyResult<()>,
    {
        unsafe {
            // Allocate buffer and check for an error
            let pybytearray: Bound<'_, Self> =
                ffi::PyByteArray_FromStringAndSize(core::ptr::null(), len as ffi::Py_ssize_t)
                    .assume_owned_or_err(py)?
                    .cast_into_unchecked();

            let buffer: *mut u8 = ffi::PyByteArray_AsString(pybytearray.as_ptr()).cast();
            debug_assert!(!buffer.is_null());
            // Zero-initialise the uninitialised bytearray
            core::ptr::write_bytes(buffer, 0u8, len);
            // (Further) Initialise the bytearray in init
            // If init returns an Err, pypybytearray will automatically deallocate the buffer
            init(core::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytearray)
        }
    }

We could also initialise it with zero ourselves, but this writes the data 2 times, which is wasteful in my eyes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a # Safety section to this function about this decision. The section needs to include:

  • This is memory unsafe in certain conditions.
    • And describe the conditions.
  • Why we decide take this decision.

data
Comment thread
bschoenmaeckers marked this conversation as resolved.
} else {
unsafe { core::slice::from_raw_parts(bytes.cast::<u8>(), len) }.to_vec()
};
Comment thread
bschoenmaeckers marked this conversation as resolved.

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::<PyByteArray>(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::<PyByteArray>(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::<PyByteArray>(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");
})
}
}
1 change: 1 addition & 0 deletions crates/capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions crates/capi/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/bytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
Expand Down
Loading