Skip to main content

nova_vm/ecmascript/builtins/
array_buffer.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! ## [25.1 ArrayBuffer Objects](https://tc39.es/ecma262/#sec-arraybuffer-objects)
6
7mod abstract_operations;
8mod data;
9
10use std::collections::hash_map::Entry;
11
12pub(crate) use abstract_operations::*;
13pub(crate) use data::*;
14
15#[cfg(feature = "shared-array-buffer")]
16use super::shared_array_buffer::SharedArrayBuffer;
17#[cfg(feature = "shared-array-buffer")]
18use crate::ecmascript::types::SHARED_ARRAY_BUFFER_DISCRIMINANT;
19use crate::{
20    ecmascript::{
21        Agent, JsResult, ProtoIntrinsics,
22        types::{
23            ARRAY_BUFFER_DISCRIMINANT, InternalMethods, InternalSlots, Object, OrdinaryObject,
24            Value, Viewable, copy_data_block_bytes, create_byte_data_block,
25        },
26    },
27    engine::{Bindable, HeapRootData, NoGcScope, bindable_handle},
28    heap::{
29        ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
30        HeapIndexHandle, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
31    },
32};
33
34use ecmascript_atomics::Ordering;
35
36/// ## [25.1 ArrayBuffer Objects](https://tc39.es/ecma262/#sec-arraybuffer-objects)
37///
38/// _ArrayBuffer_ objects are byte buffers that can be allocated and accessed
39/// from JavaScript code. An [`ArrayBuffer`] cannot be shared between threads.
40/// For shareable memory, see [`SharedArrayBuffer`] objects.
41///
42/// [`ArrayBuffer`]: ArrayBuffer
43/// [`SharedArrayBuffer`]: crate::ecmascript::SharedArrayBuffer
44#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
45#[repr(transparent)]
46pub struct ArrayBuffer<'a>(BaseIndex<'a, ArrayBufferHeapData<'static>>);
47array_buffer_handle!(ArrayBuffer);
48arena_vec_access!(ArrayBuffer, 'a, ArrayBufferHeapData, array_buffers);
49
50impl<'ab> ArrayBuffer<'ab> {
51    /// Allocate a new ArrayBuffer with the given byte length.
52    pub fn new<'gc>(
53        agent: &mut Agent,
54        byte_length: usize,
55        gc: NoGcScope<'gc, '_>,
56    ) -> JsResult<'gc, ArrayBuffer<'gc>> {
57        let data_block = create_byte_data_block(agent, byte_length as u64, gc)?;
58        let block = data_block;
59        Ok(agent
60            .heap
61            .create(ArrayBufferHeapData::new_fixed_length(block))
62            .bind(gc))
63    }
64
65    /// Returns `true` if this ArrayBuffer is detached.
66    #[inline]
67    pub fn is_detached(self, agent: &Agent) -> bool {
68        self.get(agent).is_detached()
69    }
70
71    /// Returns `true` if this ArrayBuffer has the
72    /// \[\[ArrayBufferMaxByteLength]] slot.
73    #[inline]
74    pub fn is_resizable(self, agent: &Agent) -> bool {
75        self.get(agent).is_resizable()
76    }
77
78    /// Returns the \[\[ArrayBufferByteLength]] value.
79    #[inline]
80    pub fn byte_length(self, agent: &Agent) -> usize {
81        self.get(agent).byte_length()
82    }
83
84    /// Returns the \[\[ArrayBufferMaxByteLength]] value or the
85    /// \[\[ArrayBufferByteLength]] value if this ArrayBuffer is not resizable.
86    #[inline]
87    pub fn max_byte_length(self, agent: &Agent) -> usize {
88        self.get(agent).max_byte_length()
89    }
90
91    #[inline]
92    pub(crate) fn get_detach_key(self, agent: &Agent) -> Option<DetachKey> {
93        agent.heap.array_buffer_detach_keys.get(&self).copied()
94    }
95
96    /// Set the detach key of an ArrayBuffer if not yet set.
97    ///
98    /// Attempting to override an already-set key is ignored.
99    #[inline]
100    pub fn set_detach_key(self, agent: &mut Agent, key: DetachKey) {
101        match agent.heap.array_buffer_detach_keys.entry(self.unbind()) {
102            Entry::Occupied(_) => {
103                // Ignore already-set key.
104            }
105            Entry::Vacant(e) => {
106                // Set the key.
107                e.insert(key);
108                agent.heap.alloc_counter += core::mem::size_of::<(ArrayBuffer, DetachKey)>();
109            }
110        }
111    }
112
113    /// Detach the ArrayBuffer.
114    pub fn detach<'a>(
115        self,
116        agent: &mut Agent,
117        key: Option<DetachKey>,
118        gc: NoGcScope<'a, '_>,
119    ) -> JsResult<'a, ()> {
120        detach_array_buffer(agent, self, key, gc)
121    }
122
123    /// Resize a Resizable ArrayBuffer.
124    ///
125    /// `new_byte_length` must be a safe integer.
126    pub(crate) fn resize(self, agent: &mut Agent, new_byte_length: usize) {
127        self.get_mut(agent).resize(new_byte_length);
128    }
129
130    /// Get temporary access to an ArrayBuffer's backing data block as a slice
131    /// of bytes. The access can only be held while all JavaScript is paused.
132    ///
133    /// ## Safety
134    ///
135    /// The function itself has no safety implications, but the caller should
136    /// keep in mind that if JavaScript is called into the contents of the
137    /// ArrayBuffer may be rewritten or reallocated.
138    #[inline]
139    pub fn as_slice(self, agent: &'ab Agent) -> &'ab [u8] {
140        self.get(agent).get_data_block()
141    }
142
143    /// Get temporary exclusive access to an ArrayBuffer's backing data block
144    /// as a slice of bytes. The access can only be held while all JavaScript
145    /// is paused.
146    ///
147    /// ## Safety
148    ///
149    /// The function itself has no safety implications, but the caller should
150    /// keep in mind that if JavaScript is called into the contents of the
151    /// ArrayBuffer may be rewritten or reallocated.
152    #[inline]
153    pub fn as_mut_slice(self, agent: &'ab mut Agent) -> &'ab mut [u8] {
154        self.get_mut(agent).buffer.get_data_block_mut()
155    }
156
157    /// Create a T slice from an ArrayBuffer and byte offset and length values.
158    ///
159    /// This method should be used when looping over items of a TypedArray.
160    pub(crate) fn as_viewable_slice<T: Viewable>(
161        self,
162        agent: &'ab Agent,
163        byte_offset: usize,
164        byte_length: Option<usize>,
165    ) -> &'ab [T] {
166        let byte_slice = self.as_slice(agent);
167        let byte_limit = byte_length.map(|byte_length| byte_offset.saturating_add(byte_length));
168        if byte_limit.unwrap_or(byte_offset) > byte_slice.len() {
169            return &[];
170        }
171        let byte_slice = if let Some(byte_limit) = byte_limit {
172            &byte_slice[byte_offset..byte_limit]
173        } else {
174            &byte_slice[byte_offset..]
175        };
176        // SAFETY: All bytes in byte_slice are initialized, and all bitwise
177        // combinations of T are valid values. Alignment of T's is
178        // guaranteed by align_to_mut itself.
179        let (head, slice, _) = unsafe { byte_slice.align_to::<T>() };
180        if !head.is_empty() {
181            panic!("ArrayBuffer is not properly aligned for T");
182        }
183        slice
184    }
185
186    /// Create a T slice from an ArrayBuffer and byte offset and length values.
187    ///
188    /// This method should be used when looping over items of a TypedArray.
189    pub(crate) fn as_mut_viewable_slice<T: Viewable>(
190        self,
191        agent: &'ab mut Agent,
192        byte_offset: usize,
193        byte_length: Option<usize>,
194    ) -> &'ab mut [T] {
195        let byte_slice = self.as_mut_slice(agent);
196        let byte_limit = byte_length.map(|byte_length| byte_offset.saturating_add(byte_length));
197        if byte_limit.unwrap_or(byte_offset) > byte_slice.len() {
198            return &mut [];
199        }
200        let byte_slice = if let Some(byte_limit) = byte_limit {
201            &mut byte_slice[byte_offset..byte_limit]
202        } else {
203            &mut byte_slice[byte_offset..]
204        };
205        // SAFETY: All bytes in byte_slice are initialized, and all bitwise
206        // combinations of T are valid values. Alignment of T's is
207        // guaranteed by align_to_mut itself.
208        let (head, slice, _) = unsafe { byte_slice.align_to_mut::<T>() };
209        if !head.is_empty() {
210            panic!("ArrayBuffer is not properly aligned for T");
211        }
212        slice
213    }
214
215    /// Copy data from `source` ArrayBuffer to this ArrayBuffer.
216    ///
217    /// `self` and `source` must be different ArrayBuffers.
218    pub(crate) fn copy_array_buffer_data(
219        self,
220        agent: &mut Agent,
221        source: ArrayBuffer,
222        first: usize,
223        count: usize,
224    ) {
225        debug_assert_ne!(self, source);
226        let array_buffers = &mut *agent.heap.array_buffers;
227        let (source_data, target_data) = if self.get_index() > source.get_index() {
228            let (before, after) = array_buffers.split_at_mut(self.get_index());
229            (&before[source.get_index()], &mut after[0])
230        } else {
231            let (before, after) = array_buffers.split_at_mut(source.get_index());
232            (&after[0], &mut before[self.get_index()])
233        };
234        let source_data = source_data.buffer.get_data_block();
235        let target_data = target_data.buffer.get_data_block_mut();
236        copy_data_block_bytes(target_data, 0, source_data, first, count);
237    }
238}
239
240impl<'a> InternalSlots<'a> for ArrayBuffer<'a> {
241    const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::ArrayBuffer;
242
243    #[inline(always)]
244    fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
245        self.get(agent).object_index.unbind()
246    }
247
248    fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
249        assert!(
250            self.get_mut(agent)
251                .object_index
252                .replace(backing_object.unbind())
253                .is_none()
254        );
255    }
256}
257
258impl<'a> InternalMethods<'a> for ArrayBuffer<'a> {}
259
260impl HeapMarkAndSweep for ArrayBuffer<'static> {
261    fn mark_values(&self, queues: &mut WorkQueues) {
262        queues.array_buffers.push(*self);
263    }
264
265    fn sweep_values(&mut self, compactions: &CompactionLists) {
266        compactions.array_buffers.shift_index(&mut self.0);
267    }
268}
269
270impl HeapSweepWeakReference for ArrayBuffer<'static> {
271    fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
272        compactions.array_buffers.shift_weak_index(self.0).map(Self)
273    }
274}
275
276impl<'a> CreateHeapData<ArrayBufferHeapData<'a>, ArrayBuffer<'a>> for Heap {
277    fn create(&mut self, data: ArrayBufferHeapData<'a>) -> ArrayBuffer<'a> {
278        self.array_buffers.push(data.unbind());
279        self.alloc_counter += core::mem::size_of::<ArrayBufferHeapData<'static>>();
280        ArrayBuffer(BaseIndex::last(&self.array_buffers))
281    }
282}
283
284/// ## [25.1 ArrayBuffer Objects](https://tc39.es/ecma262/#sec-arraybuffer-objects)
285///
286/// An [`ArrayBuffer`] or [`SharedArrayBuffer`].
287///
288/// [`ArrayBuffer`]: crate::ecmascript::ArrayBuffer
289/// [`SharedArrayBuffer`]: crate::ecmascript::SharedArrayBuffer
290#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
291#[repr(u8)]
292pub enum AnyArrayBuffer<'a> {
293    /// ## [25.1 ArrayBuffer Objects](https://tc39.es/ecma262/#sec-arraybuffer-objects)
294    ArrayBuffer(ArrayBuffer<'a>) = ARRAY_BUFFER_DISCRIMINANT,
295    #[cfg(feature = "shared-array-buffer")]
296    /// ## [25.2 SharedArrayBuffer Objects](https://tc39.es/ecma262/#sec-sharedarraybuffer-objects)
297    SharedArrayBuffer(SharedArrayBuffer<'a>) = SHARED_ARRAY_BUFFER_DISCRIMINANT,
298}
299bindable_handle!(AnyArrayBuffer);
300
301impl<'ab> AnyArrayBuffer<'ab> {
302    /// Returns true if the ArrayBuffer is a SharedArrayBuffer.
303    #[inline(always)]
304    pub fn is_shared(self) -> bool {
305        match self {
306            Self::ArrayBuffer(_) => false,
307            #[cfg(feature = "shared-array-buffer")]
308            Self::SharedArrayBuffer(_) => true,
309        }
310    }
311
312    /// Returns true if the ArrayBuffer is detached.
313    #[inline(always)]
314    pub fn is_detached(self, agent: &Agent) -> bool {
315        match self {
316            Self::ArrayBuffer(ta) => ta.is_detached(agent),
317            #[cfg(feature = "shared-array-buffer")]
318            Self::SharedArrayBuffer(_) => false,
319        }
320    }
321
322    /// Returns true if the ArrayBuffer is resizable or growable.
323    #[inline(always)]
324    pub fn is_resizable(self, agent: &Agent) -> bool {
325        match self {
326            Self::ArrayBuffer(ta) => ta.is_resizable(agent),
327            #[cfg(feature = "shared-array-buffer")]
328            Self::SharedArrayBuffer(sta) => sta.is_growable(agent),
329        }
330    }
331
332    /// \[\[ArrayBufferByteLength]]
333    #[inline(always)]
334    pub fn byte_length(self, agent: &Agent, order: Ordering) -> usize {
335        #[cfg(not(feature = "shared-array-buffer"))]
336        let _ = order;
337        match self {
338            Self::ArrayBuffer(ta) => ta.byte_length(agent),
339            #[cfg(feature = "shared-array-buffer")]
340            Self::SharedArrayBuffer(sta) => sta.byte_length(agent, order),
341        }
342    }
343
344    /// \[\[ArrayBufferMaxByteLength]]
345    #[inline(always)]
346    pub fn max_byte_length(self, agent: &Agent) -> usize {
347        match self {
348            Self::ArrayBuffer(ta) => ta.max_byte_length(agent),
349            #[cfg(feature = "shared-array-buffer")]
350            Self::SharedArrayBuffer(sta) => sta.max_byte_length(agent),
351        }
352    }
353}
354
355impl<'a> From<AnyArrayBuffer<'a>> for Object<'a> {
356    #[inline(always)]
357    fn from(value: AnyArrayBuffer<'a>) -> Self {
358        match value {
359            AnyArrayBuffer::ArrayBuffer(dv) => Self::ArrayBuffer(dv),
360            #[cfg(feature = "shared-array-buffer")]
361            AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::SharedArrayBuffer(sdv),
362        }
363    }
364}
365
366impl<'a> From<AnyArrayBuffer<'a>> for Value<'a> {
367    #[inline(always)]
368    fn from(value: AnyArrayBuffer<'a>) -> Self {
369        match value {
370            AnyArrayBuffer::ArrayBuffer(dv) => Self::ArrayBuffer(dv),
371            #[cfg(feature = "shared-array-buffer")]
372            AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::SharedArrayBuffer(sdv),
373        }
374    }
375}
376
377impl<'a> From<AnyArrayBuffer<'a>> for HeapRootData {
378    #[inline(always)]
379    fn from(value: AnyArrayBuffer<'a>) -> Self {
380        match value {
381            AnyArrayBuffer::ArrayBuffer(dv) => Self::from(dv),
382            #[cfg(feature = "shared-array-buffer")]
383            AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::from(sdv),
384        }
385    }
386}
387
388impl<'a> TryFrom<Object<'a>> for AnyArrayBuffer<'a> {
389    type Error = ();
390
391    fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
392        match value {
393            Object::ArrayBuffer(ab) => Ok(Self::ArrayBuffer(ab)),
394            #[cfg(feature = "shared-array-buffer")]
395            Object::SharedArrayBuffer(sab) => Ok(Self::SharedArrayBuffer(sab)),
396            _ => Err(()),
397        }
398    }
399}
400
401impl<'a> TryFrom<Value<'a>> for AnyArrayBuffer<'a> {
402    type Error = ();
403
404    fn try_from(value: Value<'a>) -> Result<Self, Self::Error> {
405        match value {
406            Value::ArrayBuffer(ab) => Ok(Self::ArrayBuffer(ab)),
407            #[cfg(feature = "shared-array-buffer")]
408            Value::SharedArrayBuffer(sab) => Ok(Self::SharedArrayBuffer(sab)),
409            _ => Err(()),
410        }
411    }
412}
413
414impl TryFrom<HeapRootData> for AnyArrayBuffer<'_> {
415    type Error = ();
416
417    #[inline]
418    fn try_from(value: HeapRootData) -> Result<Self, Self::Error> {
419        match value {
420            HeapRootData::ArrayBuffer(dv) => Ok(AnyArrayBuffer::ArrayBuffer(dv)),
421            #[cfg(feature = "shared-array-buffer")]
422            HeapRootData::SharedArrayBuffer(sdv) => Ok(AnyArrayBuffer::SharedArrayBuffer(sdv)),
423            _ => Err(()),
424        }
425    }
426}
427
428macro_rules! array_buffer_handle {
429    ($name: ident) => {
430        crate::ecmascript::types::object_handle!($name);
431
432        impl<'a> From<$name<'a>> for crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a> {
433            fn from(value: $name<'a>) -> Self {
434                Self::$name(value)
435            }
436        }
437
438        impl<'a> TryFrom<crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a>>
439            for $name<'a>
440        {
441            type Error = ();
442
443            fn try_from(
444                value: crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a>,
445            ) -> Result<Self, Self::Error> {
446                match value {
447                    crate::ecmascript::builtins::array_buffer::AnyArrayBuffer::$name(data) => {
448                        Ok(data)
449                    }
450                    _ => Err(()),
451                }
452            }
453        }
454    };
455}
456pub(crate) use array_buffer_handle;