Skip to main content

rustpython_vm/function/
buffer.rs

1use crate::{
2    AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject,
3    VirtualMachine,
4    builtins::{PyStr, PyStrRef},
5    common::borrow::{BorrowedValue, BorrowedValueMut},
6    protocol::{BufferFlags, PyBuffer},
7};
8
9// Python/getargs.c
10
11/// any bytes-like object. Like the `y*` format code for `PyArg_Parse` in CPython.
12#[derive(Debug, Traverse)]
13pub struct ArgBytesLike(PyBuffer);
14
15impl PyObject {
16    pub fn try_bytes_like<F, R>(&self, vm: &VirtualMachine, f: F) -> PyResult<R>
17    where
18        F: FnOnce(&[u8]) -> R,
19    {
20        let buffer = PyBuffer::from_object(vm, self, BufferFlags::SIMPLE)?;
21        buffer
22            .as_contiguous()
23            .map(|x| f(&x))
24            .ok_or_else(|| vm.new_buffer_error("non-contiguous buffer is not a bytes-like object"))
25    }
26
27    pub fn try_rw_bytes_like<F, R>(&self, vm: &VirtualMachine, f: F) -> PyResult<R>
28    where
29        F: FnOnce(&mut [u8]) -> R,
30    {
31        let buffer = PyBuffer::from_object(vm, self, BufferFlags::WRITABLE)?;
32        buffer
33            .as_contiguous_mut()
34            .map(|mut x| f(&mut x))
35            .ok_or_else(|| vm.new_type_error("buffer is not a read-write bytes-like object"))
36    }
37}
38
39impl ArgBytesLike {
40    #[must_use]
41    pub fn borrow_buf(&self) -> BorrowedValue<'_, [u8]> {
42        unsafe { self.0.contiguous_unchecked() }
43    }
44
45    pub fn with_ref<F, R>(&self, f: F) -> R
46    where
47        F: FnOnce(&[u8]) -> R,
48    {
49        f(&self.borrow_buf())
50    }
51
52    /// The bytes to hand to an operation that may wait, and whatever keeps
53    /// them readable while it does.
54    ///
55    /// `borrow_buf` may answer with a lock that every other thread writing to
56    /// the same object waits on, and a thread waiting on a lock never reaches
57    /// a safepoint, so keeping one across a wait for a peer, a pipe or a
58    /// signal stops the world from being stopped at all. Bytes reached that
59    /// way are copied out first. Bytes that lock nothing -- an immutable
60    /// object's -- are borrowed where they lie, which is all CPython holds in
61    /// either case.
62    pub fn borrow_buf_unlocked(&self, vm: &VirtualMachine) -> PyResult<UnlockedBuf<'_>> {
63        let borrowed = self.borrow_buf();
64        if !borrowed.is_locked() {
65            return Ok(UnlockedBuf::Borrowed(borrowed));
66        }
67        let mut copy = Vec::new();
68        copy.try_reserve_exact(borrowed.len())
69            .map_err(|_| vm.no_memory_error())?;
70        copy.extend_from_slice(&borrowed);
71        Ok(UnlockedBuf::Copied(copy))
72    }
73
74    #[must_use]
75    pub const fn len(&self) -> usize {
76        self.0.desc.len
77    }
78
79    /// The width of one item. Callers that read the buffer as bytes rather
80    /// than as whatever it holds have to ask, since a contiguous buffer of
81    /// wider items is contiguous all the same.
82    #[must_use]
83    pub const fn itemsize(&self) -> usize {
84        self.0.desc.itemsize
85    }
86
87    #[must_use]
88    pub const fn is_empty(&self) -> bool {
89        self.len() == 0
90    }
91
92    #[must_use]
93    pub fn as_object(&self) -> &PyObject {
94        &self.0.obj
95    }
96
97    /// The object whose storage is borrowed while this buffer is read: a view
98    /// borrows the object it looks at, not itself.
99    #[must_use]
100    pub fn source_object(&self) -> &PyObject {
101        self.0
102            .obj
103            .downcast_ref::<crate::builtins::PyMemoryView>()
104            .map_or(&self.0.obj, |view| view.viewed_object())
105    }
106}
107
108impl From<ArgBytesLike> for PyBuffer {
109    fn from(buffer: ArgBytesLike) -> Self {
110        buffer.0
111    }
112}
113
114impl From<ArgBytesLike> for PyObjectRef {
115    fn from(buffer: ArgBytesLike) -> Self {
116        buffer.as_object().to_owned()
117    }
118}
119
120impl ArgBytesLike {
121    fn from_request(vm: &VirtualMachine, obj: &PyObject, flags: BufferFlags) -> PyResult<Self> {
122        let buffer = PyBuffer::from_object(vm, obj, flags)?;
123        if buffer.desc.is_contiguous() {
124            Ok(Self(buffer))
125        } else {
126            Err(vm.new_buffer_error("non-contiguous buffer is not a bytes-like object"))
127        }
128    }
129}
130
131impl<'a> TryFromBorrowedObject<'a> for ArgBytesLike {
132    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
133        Self::from_request(vm, obj, BufferFlags::SIMPLE)
134    }
135}
136
137/// A bytes-like object asked for as `PyBUF_CONTIG_RO`, which is what a shape is
138/// requested with rather than assumed.
139#[derive(Debug, Traverse)]
140pub struct ArgContiguousBytesLike(ArgBytesLike);
141
142impl core::ops::Deref for ArgContiguousBytesLike {
143    type Target = ArgBytesLike;
144
145    fn deref(&self) -> &Self::Target {
146        &self.0
147    }
148}
149
150impl<'a> TryFromBorrowedObject<'a> for ArgContiguousBytesLike {
151    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
152        ArgBytesLike::from_request(vm, obj, BufferFlags::CONTIG_RO).map(Self)
153    }
154}
155
156/// Bytes that stay readable across a wait, from [`ArgBytesLike::borrow_buf_unlocked`].
157#[derive(Debug)]
158pub enum UnlockedBuf<'a> {
159    Borrowed(BorrowedValue<'a, [u8]>),
160    Copied(Vec<u8>),
161}
162
163impl core::ops::Deref for UnlockedBuf<'_> {
164    type Target = [u8];
165
166    fn deref(&self) -> &[u8] {
167        match self {
168            Self::Borrowed(b) => b,
169            Self::Copied(v) => v,
170        }
171    }
172}
173
174/// A memory buffer, read-write access. Like the `w*` format code for `PyArg_Parse` in CPython.
175#[derive(Debug, Traverse)]
176pub struct ArgMemoryBuffer(PyBuffer);
177
178impl ArgMemoryBuffer {
179    #[must_use]
180    pub fn borrow_buf_mut(&self) -> BorrowedValueMut<'_, [u8]> {
181        unsafe { self.0.contiguous_mut_unchecked() }
182    }
183
184    pub fn with_ref<F, R>(&self, f: F) -> R
185    where
186        F: FnOnce(&mut [u8]) -> R,
187    {
188        f(&mut self.borrow_buf_mut())
189    }
190
191    #[must_use]
192    pub const fn len(&self) -> usize {
193        self.0.desc.len
194    }
195
196    #[must_use]
197    pub const fn is_empty(&self) -> bool {
198        self.len() == 0
199    }
200
201    /// The object whose storage is borrowed while this buffer is written: a
202    /// view borrows the object it looks at, not itself.
203    #[must_use]
204    pub fn source_object(&self) -> &PyObject {
205        self.0
206            .obj
207            .downcast_ref::<crate::builtins::PyMemoryView>()
208            .map_or(&self.0.obj, |view| view.viewed_object())
209    }
210}
211
212impl From<ArgMemoryBuffer> for PyBuffer {
213    fn from(buffer: ArgMemoryBuffer) -> Self {
214        buffer.0
215    }
216}
217
218impl<'a> TryFromBorrowedObject<'a> for ArgMemoryBuffer {
219    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
220        let buffer = PyBuffer::from_object(vm, obj, BufferFlags::WRITABLE).map_err(|exc| {
221            if obj.check_buffer() {
222                // An exporter that cannot serve the request leaves the argument
223                // simply the wrong kind of object, as `PyArg_Parse` reports it.
224                vm.new_type_error("buffer is not a read-write bytes-like object")
225            } else {
226                exc
227            }
228        })?;
229        if !buffer.desc.is_contiguous() {
230            Err(vm.new_buffer_error("non-contiguous buffer is not a bytes-like object"))
231        } else if buffer.desc.readonly {
232            Err(vm.new_type_error("buffer is not a read-write bytes-like object"))
233        } else {
234            Ok(Self(buffer))
235        }
236    }
237}
238
239/// A text string or bytes-like object. Like the `s*` format code for `PyArg_Parse` in CPython.
240pub enum ArgStrOrBytesLike {
241    Buf(ArgBytesLike),
242    Str(PyStrRef),
243}
244
245impl ArgStrOrBytesLike {
246    #[must_use]
247    pub fn as_object(&self) -> &PyObject {
248        match self {
249            Self::Buf(b) => b.as_object(),
250            Self::Str(s) => s.as_object(),
251        }
252    }
253}
254
255impl TryFromObject for ArgStrOrBytesLike {
256    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
257        obj.downcast()
258            .map(Self::Str)
259            .or_else(|obj| ArgBytesLike::try_from_object(vm, obj).map(Self::Buf))
260    }
261}
262
263impl ArgStrOrBytesLike {
264    #[must_use]
265    pub fn borrow_bytes(&self) -> BorrowedValue<'_, [u8]> {
266        match self {
267            Self::Buf(b) => b.borrow_buf(),
268            Self::Str(s) => s.as_bytes().into(),
269        }
270    }
271}
272
273#[derive(Debug)]
274pub enum ArgAsciiBuffer {
275    String(PyStrRef),
276    Buffer(ArgBytesLike),
277}
278
279impl TryFromObject for ArgAsciiBuffer {
280    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
281        match obj.downcast::<PyStr>() {
282            Ok(string) => {
283                if string.as_wtf8().is_ascii() {
284                    Ok(Self::String(string))
285                } else {
286                    Err(vm.new_value_error("string argument should contain only ASCII characters"))
287                }
288            }
289            Err(obj) => ArgBytesLike::try_from_object(vm, obj).map(ArgAsciiBuffer::Buffer),
290        }
291    }
292}
293
294impl ArgAsciiBuffer {
295    #[must_use]
296    pub fn len(&self) -> usize {
297        match self {
298            Self::String(s) => s.as_wtf8().len(),
299            Self::Buffer(buffer) => buffer.len(),
300        }
301    }
302
303    #[must_use]
304    pub fn is_empty(&self) -> bool {
305        self.len() == 0
306    }
307
308    #[inline]
309    pub fn with_ref<F, R>(&self, f: F) -> R
310    where
311        F: FnOnce(&[u8]) -> R,
312    {
313        match self {
314            Self::String(s) => f(s.as_bytes()),
315            Self::Buffer(buffer) => buffer.with_ref(f),
316        }
317    }
318}