mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2402 lines
94 KiB
2402 lines
94 KiB
pub mod array_buffer { |
|
//! Boa's implementation of ECMAScript's global `ArrayBuffer` and `SharedArrayBuffer` objects |
|
//! |
|
//! More information: |
|
//! - [ECMAScript reference][spec] |
|
//! - [MDN documentation][mdn] |
|
//! |
|
//! [spec]: https://tc39.es/ecma262/#sec-arraybuffer-objects |
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer |
|
#![deny(unsafe_op_in_unsafe_fn)] |
|
#![deny(clippy::undocumented_unsafe_blocks)] |
|
pub(crate) mod shared { |
|
#![allow(unstable_name_collisions)] |
|
use std::{alloc, sync::{atomic::Ordering, Arc}}; |
|
use boa_profiler::Profiler; |
|
use portable_atomic::{AtomicU8, AtomicUsize}; |
|
use boa_gc::{Finalize, Trace}; |
|
use sptr::Strict; |
|
use crate::{ |
|
builtins::{ |
|
Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, |
|
}, |
|
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, |
|
js_string, object::internal_methods::get_prototype_from_constructor, |
|
property::Attribute, realm::Realm, string::StaticJsStrings, Context, JsArgs, |
|
JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, |
|
}; |
|
use super::{get_max_byte_len, utils::copy_shared_to_shared}; |
|
/// The internal representation of a `SharedArrayBuffer` object. |
|
/// |
|
/// This struct implements `Send` and `Sync`, meaning it can be shared between threads |
|
/// running different JS code at the same time. |
|
pub struct SharedArrayBuffer { |
|
#[unsafe_ignore_trace] |
|
data: Arc<Inner>, |
|
} |
|
#[automatically_derived] |
|
impl ::core::fmt::Debug for SharedArrayBuffer { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
::core::fmt::Formatter::debug_struct_field1_finish( |
|
f, |
|
"SharedArrayBuffer", |
|
"data", |
|
&&self.data, |
|
) |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::clone::Clone for SharedArrayBuffer { |
|
#[inline] |
|
fn clone(&self) -> SharedArrayBuffer { |
|
SharedArrayBuffer { |
|
data: ::core::clone::Clone::clone(&self.data), |
|
} |
|
} |
|
} |
|
const _: () = { |
|
unsafe impl ::boa_gc::Trace for SharedArrayBuffer { |
|
#[inline] |
|
unsafe fn trace(&self, tracer: &mut ::boa_gc::Tracer) { |
|
#[expect(dead_code)] |
|
let mut mark = |it: &dyn ::boa_gc::Trace| { |
|
unsafe { |
|
::boa_gc::Trace::trace(it, tracer); |
|
} |
|
}; |
|
match *self { |
|
SharedArrayBuffer { .. } => {} |
|
} |
|
} |
|
#[inline] |
|
unsafe fn trace_non_roots(&self) { |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::trace_non_roots(it); |
|
} |
|
} |
|
match *self { |
|
SharedArrayBuffer { .. } => {} |
|
} |
|
} |
|
#[inline] |
|
fn run_finalizer(&self) { |
|
::boa_gc::Finalize::finalize(self); |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::run_finalizer(it); |
|
} |
|
} |
|
match *self { |
|
SharedArrayBuffer { .. } => {} |
|
} |
|
} |
|
} |
|
}; |
|
const _: () = { |
|
impl ::core::ops::Drop for SharedArrayBuffer { |
|
#[expect(clippy::inline_always)] |
|
#[inline(always)] |
|
fn drop(&mut self) { |
|
if ::boa_gc::finalizer_safe() { |
|
::boa_gc::Finalize::finalize(self); |
|
} |
|
} |
|
} |
|
}; |
|
const _: () = { |
|
impl ::boa_gc::Finalize for SharedArrayBuffer {} |
|
}; |
|
const _: () = { |
|
impl ::boa_engine::JsData for SharedArrayBuffer {} |
|
}; |
|
struct Inner { |
|
buffer: Box<[AtomicU8]>, |
|
current_len: Option<AtomicUsize>, |
|
} |
|
#[automatically_derived] |
|
impl ::core::fmt::Debug for Inner { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
::core::fmt::Formatter::debug_struct_field2_finish( |
|
f, |
|
"Inner", |
|
"buffer", |
|
&self.buffer, |
|
"current_len", |
|
&&self.current_len, |
|
) |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::default::Default for Inner { |
|
#[inline] |
|
fn default() -> Inner { |
|
Inner { |
|
buffer: ::core::default::Default::default(), |
|
current_len: ::core::default::Default::default(), |
|
} |
|
} |
|
} |
|
impl SharedArrayBuffer { |
|
/// Creates a `SharedArrayBuffer` with an empty buffer. |
|
#[must_use] |
|
pub fn empty() -> Self { |
|
Self { data: Arc::default() } |
|
} |
|
/// Gets the length of this `SharedArrayBuffer`. |
|
pub(crate) fn len(&self, ordering: Ordering) -> usize { |
|
self.data |
|
.current_len |
|
.as_ref() |
|
.map_or_else(|| self.data.buffer.len(), |len| len.load(ordering)) |
|
} |
|
/// Gets the inner bytes of this `SharedArrayBuffer`. |
|
pub(crate) fn bytes(&self, ordering: Ordering) -> &[AtomicU8] { |
|
&self.data.buffer[..self.len(ordering)] |
|
} |
|
/// Gets the inner data of the buffer without accessing the current atomic length. |
|
#[track_caller] |
|
pub(crate) fn bytes_with_len(&self, len: usize) -> &[AtomicU8] { |
|
&self.data.buffer[..len] |
|
} |
|
/// Gets a pointer to the internal shared buffer. |
|
pub(crate) fn as_ptr(&self) -> *const AtomicU8 { |
|
(*self.data.buffer).as_ptr() |
|
} |
|
pub(crate) fn is_fixed_len(&self) -> bool { |
|
self.data.current_len.is_none() |
|
} |
|
} |
|
impl IntrinsicObject for SharedArrayBuffer { |
|
fn init(realm: &Realm) { |
|
let _timer = Profiler::global() |
|
.start_event(std::any::type_name::<Self>(), "init"); |
|
let flag_attributes = Attribute::CONFIGURABLE |
|
| Attribute::NON_ENUMERABLE; |
|
let get_species = BuiltInBuilder::callable(realm, Self::get_species) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
91, |
|
83, |
|
121, |
|
109, |
|
98, |
|
111, |
|
108, |
|
46, |
|
115, |
|
112, |
|
101, |
|
99, |
|
105, |
|
101, |
|
115, |
|
93, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_byte_length = BuiltInBuilder::callable( |
|
realm, |
|
Self::get_byte_length, |
|
) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
98, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_growable = BuiltInBuilder::callable(realm, Self::get_growable) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[103, 101, 116, 32, 103, 114, 111, 119, 97, 98, 108, 101] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_max_byte_length = BuiltInBuilder::callable( |
|
realm, |
|
Self::get_max_byte_length, |
|
) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
109, |
|
97, |
|
120, |
|
66, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
BuiltInBuilder::from_standard_constructor::<Self>(realm) |
|
.static_accessor( |
|
JsSymbol::species(), |
|
Some(get_species), |
|
None, |
|
Attribute::CONFIGURABLE, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[98, 121, 116, 101, 76, 101, 110, 103, 116, 104].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_byte_length), |
|
None, |
|
flag_attributes, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[103, 114, 111, 119, 97, 98, 108, 101].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_growable), |
|
None, |
|
flag_attributes, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
109, |
|
97, |
|
120, |
|
66, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_max_byte_length), |
|
None, |
|
flag_attributes, |
|
) |
|
.method( |
|
Self::slice, |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[115, 108, 105, 99, 101].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
2, |
|
) |
|
.method( |
|
Self::grow, |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[103, 114, 111, 119].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
1, |
|
) |
|
.property( |
|
JsSymbol::to_string_tag(), |
|
Self::NAME, |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE |
|
| Attribute::CONFIGURABLE, |
|
) |
|
.build(); |
|
} |
|
fn get(intrinsics: &Intrinsics) -> JsObject { |
|
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() |
|
} |
|
} |
|
impl BuiltInObject for SharedArrayBuffer { |
|
const NAME: JsString = StaticJsStrings::SHARED_ARRAY_BUFFER; |
|
} |
|
impl BuiltInConstructor for SharedArrayBuffer { |
|
const LENGTH: usize = 1; |
|
const STANDARD_CONSTRUCTOR: fn( |
|
&StandardConstructors, |
|
) -> &StandardConstructor = StandardConstructors::shared_array_buffer; |
|
/// `25.1.3.1 SharedArrayBuffer ( length [ , options ] )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-constructor |
|
fn constructor( |
|
new_target: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
if new_target.is_undefined() { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.constructor called with undefined new target", |
|
) |
|
.into(), |
|
); |
|
} |
|
let byte_len = args.get_or_undefined(0).to_index(context)?; |
|
let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; |
|
Ok( |
|
Self::allocate(new_target, byte_len, max_byte_len, context)? |
|
.upcast() |
|
.into(), |
|
) |
|
} |
|
} |
|
impl SharedArrayBuffer { |
|
/// `get SharedArrayBuffer [ @@species ]` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species |
|
|
|
fn get_species( |
|
this: &JsValue, |
|
_: &[JsValue], |
|
_: &mut Context, |
|
) -> JsResult<JsValue> { |
|
Ok(this.clone()) |
|
} |
|
/// `get SharedArrayBuffer.prototype.byteLength` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength |
|
pub(crate) fn get_byte_length( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"SharedArrayBuffer.byteLength called with invalid value", |
|
) |
|
})?; |
|
let len = buf.bytes(Ordering::SeqCst).len() as u64; |
|
Ok(len.into()) |
|
} |
|
/// [`get SharedArrayBuffer.prototype.growable`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.growable |
|
pub(crate) fn get_growable( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"get SharedArrayBuffer.growable called with invalid `this`", |
|
) |
|
})?; |
|
Ok(JsValue::from(!buf.is_fixed_len())) |
|
} |
|
/// [`get SharedArrayBuffer.prototype.maxByteLength`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.maxbytelength |
|
pub(crate) fn get_max_byte_length( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"get SharedArrayBuffer.maxByteLength called with invalid value", |
|
) |
|
})?; |
|
Ok(buf.data.buffer.len().into()) |
|
} |
|
/// [`SharedArrayBuffer.prototype.grow ( newLength )`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/sec-sharedarraybuffer.prototype.grow |
|
pub(crate) fn grow( |
|
this: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let Some(buf) = this |
|
.as_object() |
|
.and_then(|o| o.clone().downcast::<Self>().ok()) else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"SharedArrayBuffer.grow called with non-object value", |
|
) |
|
.into(), |
|
); |
|
}; |
|
if buf.borrow().data.is_fixed_len() { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"SharedArrayBuffer.grow: cannot grow a fixed-length buffer", |
|
) |
|
.into(), |
|
); |
|
} |
|
let new_byte_len = args.get_or_undefined(0).to_index(context)?; |
|
let buf = buf.borrow(); |
|
let buf = &buf.data; |
|
if new_byte_len > buf.data.buffer.len() as u64 { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"SharedArrayBuffer.grow: new length cannot be bigger than `maxByteLength`", |
|
) |
|
.into(), |
|
); |
|
} |
|
let new_byte_len = new_byte_len as usize; |
|
let atomic_len = buf |
|
.data |
|
.current_len |
|
.as_ref() |
|
.expect("already checked that the buffer is not fixed-length"); |
|
atomic_len |
|
.fetch_update( |
|
Ordering::SeqCst, |
|
Ordering::SeqCst, |
|
|prev_byte_len| { |
|
(prev_byte_len <= new_byte_len).then_some(new_byte_len) |
|
}, |
|
) |
|
.map_err(|_| { |
|
JsNativeError::range() |
|
.with_message( |
|
"SharedArrayBuffer.grow: failed to grow buffer to new length", |
|
) |
|
})?; |
|
Ok(JsValue::undefined()) |
|
} |
|
/// `SharedArrayBuffer.prototype.slice ( start, end )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice |
|
fn slice( |
|
this: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(|o| o.clone().downcast::<Self>().ok()) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"SharedArrayBuffer.slice called with invalid `this` value", |
|
) |
|
})?; |
|
let len = buf.borrow().data.len(Ordering::SeqCst); |
|
let first = Array::get_relative_start( |
|
context, |
|
args.get_or_undefined(0), |
|
len as u64, |
|
)?; |
|
let final_ = Array::get_relative_end( |
|
context, |
|
args.get_or_undefined(1), |
|
len as u64, |
|
)?; |
|
let new_len = final_.saturating_sub(first); |
|
let ctor = buf |
|
.clone() |
|
.upcast() |
|
.species_constructor( |
|
StandardConstructors::shared_array_buffer, |
|
context, |
|
)?; |
|
let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; |
|
{ |
|
let buf = buf.borrow(); |
|
let buf = &buf.data; |
|
let new = new |
|
.downcast_ref::<Self>() |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"SharedArrayBuffer constructor returned invalid object", |
|
) |
|
})?; |
|
if std::ptr::eq(buf.as_ptr(), new.as_ptr()) { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"cannot reuse the same SharedArrayBuffer for a slice operation", |
|
) |
|
.into(), |
|
); |
|
} |
|
if (new.len(Ordering::SeqCst) as u64) < new_len { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"invalid size of constructed SharedArrayBuffer", |
|
) |
|
.into(), |
|
); |
|
} |
|
let first = first as usize; |
|
let new_len = new_len as usize; |
|
let from_buf = &buf.bytes_with_len(len)[first..]; |
|
let to_buf = new; |
|
if true { |
|
if !(from_buf.len() >= new_len) { |
|
::core::panicking::panic( |
|
"assertion failed: from_buf.len() >= new_len", |
|
) |
|
} |
|
} |
|
unsafe { |
|
copy_shared_to_shared( |
|
from_buf.as_ptr(), |
|
to_buf.as_ptr(), |
|
new_len, |
|
) |
|
} |
|
} |
|
Ok(new.into()) |
|
} |
|
/// `AllocateSharedArrayBuffer ( constructor, byteLength [ , maxByteLength ] )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-allocatesharedarraybuffer |
|
pub(crate) fn allocate( |
|
constructor: &JsValue, |
|
byte_len: u64, |
|
max_byte_len: Option<u64>, |
|
context: &mut Context, |
|
) -> JsResult<JsObject<SharedArrayBuffer>> { |
|
if let Some(max_byte_len) = max_byte_len { |
|
if byte_len > max_byte_len { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"`length` cannot be bigger than `maxByteLength`", |
|
) |
|
.into(), |
|
); |
|
} |
|
} |
|
let prototype = get_prototype_from_constructor( |
|
constructor, |
|
StandardConstructors::shared_array_buffer, |
|
context, |
|
)?; |
|
let alloc_len = max_byte_len.unwrap_or(byte_len); |
|
let block = create_shared_byte_data_block(alloc_len, context)?; |
|
let current_len = max_byte_len |
|
.map(|_| AtomicUsize::new(byte_len as usize)); |
|
let obj = JsObject::new( |
|
context.root_shape(), |
|
prototype, |
|
Self { |
|
data: Arc::new(Inner { |
|
buffer: block, |
|
current_len, |
|
}), |
|
}, |
|
); |
|
Ok(obj) |
|
} |
|
} |
|
/// [`CreateSharedByteDataBlock ( size )`][spec] abstract operation. |
|
/// |
|
/// Creates a new `Arc<Vec<AtomicU8>>` that can be used as a backing buffer for a [`SharedArrayBuffer`]. |
|
/// |
|
/// For more information, check the [spec][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-createsharedbytedatablock |
|
pub(crate) fn create_shared_byte_data_block( |
|
size: u64, |
|
context: &mut Context, |
|
) -> JsResult<Box<[AtomicU8]>> { |
|
if size > context.host_hooks().max_buffer_size(context) { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"cannot allocate a buffer that exceeds the maximum buffer size" |
|
.to_string(), |
|
) |
|
.into(), |
|
); |
|
} |
|
let size = size |
|
.try_into() |
|
.map_err(|e| { |
|
JsNativeError::range() |
|
.with_message( |
|
::alloc::__export::must_use({ |
|
let res = ::alloc::fmt::format( |
|
format_args!("couldn\'t allocate the data block: {0}", e), |
|
); |
|
res |
|
}), |
|
) |
|
})?; |
|
if size == 0 { |
|
return Ok(Box::default()); |
|
} |
|
let layout = alloc::Layout::array::<AtomicU8>(size) |
|
.map_err(|e| { |
|
JsNativeError::range() |
|
.with_message( |
|
::alloc::__export::must_use({ |
|
let res = ::alloc::fmt::format( |
|
format_args!("couldn\'t allocate the data block: {0}", e), |
|
); |
|
res |
|
}), |
|
) |
|
})?; |
|
let ptr: *mut AtomicU8 = unsafe { alloc::alloc_zeroed(layout).cast() }; |
|
if ptr.is_null() { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message("memory allocator failed to allocate buffer") |
|
.into(), |
|
); |
|
} |
|
let buffer = unsafe { |
|
Box::from_raw(std::slice::from_raw_parts_mut(ptr, size)) |
|
}; |
|
match (&(buffer.as_ptr().addr() % align_of::<u64>()), &0) { |
|
(left_val, right_val) => { |
|
if !(*left_val == *right_val) { |
|
let kind = ::core::panicking::AssertKind::Eq; |
|
::core::panicking::assert_failed( |
|
kind, |
|
&*left_val, |
|
&*right_val, |
|
::core::option::Option::None, |
|
); |
|
} |
|
} |
|
}; |
|
Ok(buffer) |
|
} |
|
} |
|
pub(crate) mod utils { |
|
#![allow(unstable_name_collisions)] |
|
use std::{ptr, slice::SliceIndex, sync::atomic::Ordering}; |
|
use portable_atomic::AtomicU8; |
|
use crate::{ |
|
builtins::typed_array::{ |
|
ClampedU8, Element, TypedArrayElement, TypedArrayKind, |
|
}, |
|
Context, JsObject, JsResult, |
|
}; |
|
use super::ArrayBuffer; |
|
pub(crate) enum BytesConstPtr { |
|
Bytes(*const u8), |
|
AtomicBytes(*const AtomicU8), |
|
} |
|
#[automatically_derived] |
|
impl ::core::clone::Clone for BytesConstPtr { |
|
#[inline] |
|
fn clone(&self) -> BytesConstPtr { |
|
let _: ::core::clone::AssertParamIsClone<*const u8>; |
|
let _: ::core::clone::AssertParamIsClone<*const AtomicU8>; |
|
*self |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::marker::Copy for BytesConstPtr {} |
|
pub(crate) enum BytesMutPtr { |
|
Bytes(*mut u8), |
|
AtomicBytes(*const AtomicU8), |
|
} |
|
#[automatically_derived] |
|
impl ::core::clone::Clone for BytesMutPtr { |
|
#[inline] |
|
fn clone(&self) -> BytesMutPtr { |
|
let _: ::core::clone::AssertParamIsClone<*mut u8>; |
|
let _: ::core::clone::AssertParamIsClone<*const AtomicU8>; |
|
*self |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::marker::Copy for BytesMutPtr {} |
|
pub(crate) enum SliceRef<'a> { |
|
Slice(&'a [u8]), |
|
AtomicSlice(&'a [AtomicU8]), |
|
} |
|
#[automatically_derived] |
|
impl<'a> ::core::fmt::Debug for SliceRef<'a> { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
match self { |
|
SliceRef::Slice(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"Slice", |
|
&__self_0, |
|
) |
|
} |
|
SliceRef::AtomicSlice(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"AtomicSlice", |
|
&__self_0, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
#[automatically_derived] |
|
impl<'a> ::core::clone::Clone for SliceRef<'a> { |
|
#[inline] |
|
fn clone(&self) -> SliceRef<'a> { |
|
let _: ::core::clone::AssertParamIsClone<&'a [u8]>; |
|
let _: ::core::clone::AssertParamIsClone<&'a [AtomicU8]>; |
|
*self |
|
} |
|
} |
|
#[automatically_derived] |
|
impl<'a> ::core::marker::Copy for SliceRef<'a> {} |
|
impl SliceRef<'_> { |
|
/// Gets the byte length of this `SliceRef`. |
|
pub(crate) fn len(&self) -> usize { |
|
match self { |
|
Self::Slice(buf) => buf.len(), |
|
Self::AtomicSlice(buf) => buf.len(), |
|
} |
|
} |
|
/// Gets a subslice of this `SliceRef`. |
|
pub(crate) fn subslice<I>(&self, index: I) -> SliceRef<'_> |
|
where |
|
I: SliceIndex<[u8], Output = [u8]> |
|
+ SliceIndex<[AtomicU8], Output = [AtomicU8]>, |
|
{ |
|
match self { |
|
Self::Slice(buffer) => { |
|
SliceRef::Slice(buffer.get(index).expect("index out of bounds")) |
|
} |
|
Self::AtomicSlice(buffer) => { |
|
SliceRef::AtomicSlice( |
|
buffer.get(index).expect("index out of bounds"), |
|
) |
|
} |
|
} |
|
} |
|
/// Gets the starting address of this `SliceRef`. |
|
#[cfg(debug_assertions)] |
|
pub(crate) fn addr(&self) -> usize { |
|
use sptr::Strict; |
|
match self { |
|
Self::Slice(buf) => buf.as_ptr().addr(), |
|
Self::AtomicSlice(buf) => buf.as_ptr().addr(), |
|
} |
|
} |
|
/// Gets a pointer to the underlying slice. |
|
pub(crate) fn as_ptr(&self) -> BytesConstPtr { |
|
match self { |
|
SliceRef::Slice(s) => BytesConstPtr::Bytes(s.as_ptr()), |
|
SliceRef::AtomicSlice(s) => BytesConstPtr::AtomicBytes(s.as_ptr()), |
|
} |
|
} |
|
/// [`GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`][spec] |
|
/// |
|
/// The start offset is determined by the input buffer instead of a `byteIndex` parameter. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - There must be enough bytes in `buffer` to read an element from an array with type `TypedArrayKind`. |
|
/// - `buffer` must be aligned to the alignment of said element. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer |
|
pub(crate) unsafe fn get_value( |
|
&self, |
|
kind: TypedArrayKind, |
|
order: Ordering, |
|
) -> TypedArrayElement { |
|
unsafe fn read_elem<T: Element>( |
|
buffer: SliceRef<'_>, |
|
order: Ordering, |
|
) -> T { |
|
#[cfg(debug_assertions)] |
|
{ |
|
if !(buffer.len() >= size_of::<T>()) { |
|
::core::panicking::panic( |
|
"assertion failed: buffer.len() >= size_of::<T>()", |
|
) |
|
} |
|
match (&(buffer.addr() % align_of::<T>()), &0) { |
|
(left_val, right_val) => { |
|
if !(*left_val == *right_val) { |
|
let kind = ::core::panicking::AssertKind::Eq; |
|
::core::panicking::assert_failed( |
|
kind, |
|
&*left_val, |
|
&*right_val, |
|
::core::option::Option::None, |
|
); |
|
} |
|
} |
|
}; |
|
} |
|
unsafe { T::read(buffer).load(order) } |
|
} |
|
let buffer = *self; |
|
unsafe { |
|
match kind { |
|
TypedArrayKind::Int8 => read_elem::<i8>(buffer, order).into(), |
|
TypedArrayKind::Uint8 => read_elem::<u8>(buffer, order).into(), |
|
TypedArrayKind::Uint8Clamped => { |
|
read_elem::<ClampedU8>(buffer, order).into() |
|
} |
|
TypedArrayKind::Int16 => read_elem::<i16>(buffer, order).into(), |
|
TypedArrayKind::Uint16 => read_elem::<u16>(buffer, order).into(), |
|
TypedArrayKind::Int32 => read_elem::<i32>(buffer, order).into(), |
|
TypedArrayKind::Uint32 => read_elem::<u32>(buffer, order).into(), |
|
TypedArrayKind::BigInt64 => { |
|
read_elem::<i64>(buffer, order).into() |
|
} |
|
TypedArrayKind::BigUint64 => { |
|
read_elem::<u64>(buffer, order).into() |
|
} |
|
TypedArrayKind::Float32 => read_elem::<f32>(buffer, order).into(), |
|
TypedArrayKind::Float64 => read_elem::<f64>(buffer, order).into(), |
|
} |
|
} |
|
} |
|
/// [`CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )`][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer |
|
pub(crate) fn clone( |
|
&self, |
|
context: &mut Context, |
|
) -> JsResult<JsObject<ArrayBuffer>> { |
|
let target_buffer = ArrayBuffer::allocate( |
|
&context |
|
.realm() |
|
.intrinsics() |
|
.constructors() |
|
.array_buffer() |
|
.constructor() |
|
.into(), |
|
self.len() as u64, |
|
None, |
|
context, |
|
)?; |
|
{ |
|
let mut target_buffer = target_buffer.borrow_mut(); |
|
let target_block = target_buffer |
|
.data |
|
.bytes_mut() |
|
.expect("ArrayBuffer cannot be detached here"); |
|
unsafe { |
|
memcpy( |
|
self.as_ptr(), |
|
BytesMutPtr::Bytes(target_block.as_mut_ptr()), |
|
self.len(), |
|
); |
|
} |
|
} |
|
Ok(target_buffer) |
|
} |
|
} |
|
impl<'a> From<&'a [u8]> for SliceRef<'a> { |
|
fn from(value: &'a [u8]) -> Self { |
|
Self::Slice(value) |
|
} |
|
} |
|
impl<'a> From<&'a [AtomicU8]> for SliceRef<'a> { |
|
fn from(value: &'a [AtomicU8]) -> Self { |
|
Self::AtomicSlice(value) |
|
} |
|
} |
|
pub(crate) enum SliceRefMut<'a> { |
|
Slice(&'a mut [u8]), |
|
AtomicSlice(&'a [AtomicU8]), |
|
} |
|
#[automatically_derived] |
|
impl<'a> ::core::fmt::Debug for SliceRefMut<'a> { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
match self { |
|
SliceRefMut::Slice(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"Slice", |
|
&__self_0, |
|
) |
|
} |
|
SliceRefMut::AtomicSlice(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"AtomicSlice", |
|
&__self_0, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
impl SliceRefMut<'_> { |
|
/// Gets the byte length of this `SliceRefMut`. |
|
pub(crate) fn len(&self) -> usize { |
|
match self { |
|
Self::Slice(buf) => buf.len(), |
|
Self::AtomicSlice(buf) => buf.len(), |
|
} |
|
} |
|
/// Gets a mutable subslice of this `SliceRefMut`. |
|
pub(crate) fn subslice_mut<I>(&mut self, index: I) -> SliceRefMut<'_> |
|
where |
|
I: SliceIndex<[u8], Output = [u8]> |
|
+ SliceIndex<[AtomicU8], Output = [AtomicU8]>, |
|
{ |
|
match self { |
|
Self::Slice(buffer) => { |
|
SliceRefMut::Slice( |
|
buffer.get_mut(index).expect("index out of bounds"), |
|
) |
|
} |
|
Self::AtomicSlice(buffer) => { |
|
SliceRefMut::AtomicSlice( |
|
buffer.get(index).expect("index out of bounds"), |
|
) |
|
} |
|
} |
|
} |
|
/// Gets the starting address of this `SliceRefMut`. |
|
#[cfg(debug_assertions)] |
|
pub(crate) fn addr(&self) -> usize { |
|
use sptr::Strict; |
|
match self { |
|
Self::Slice(buf) => buf.as_ptr().addr(), |
|
Self::AtomicSlice(buf) => buf.as_ptr().addr(), |
|
} |
|
} |
|
/// Gets a pointer to the underlying slice. |
|
pub(crate) fn as_ptr(&mut self) -> BytesMutPtr { |
|
match self { |
|
Self::Slice(s) => BytesMutPtr::Bytes(s.as_mut_ptr()), |
|
Self::AtomicSlice(s) => BytesMutPtr::AtomicBytes(s.as_ptr()), |
|
} |
|
} |
|
/// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` |
|
/// |
|
/// The start offset is determined by the input buffer instead of a `byteIndex` parameter. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - There must be enough bytes in `buffer` to write the `TypedArrayElement`. |
|
/// - `buffer` must be aligned to the alignment of the `TypedArrayElement`. |
|
/// |
|
/// # Panics |
|
/// |
|
/// - Panics if the type of `value` is not equal to the content of `kind`. |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer |
|
pub(crate) unsafe fn set_value( |
|
&mut self, |
|
value: TypedArrayElement, |
|
order: Ordering, |
|
) { |
|
unsafe fn write_elem<T: Element>( |
|
buffer: SliceRefMut<'_>, |
|
value: T, |
|
order: Ordering, |
|
) { |
|
#[cfg(debug_assertions)] |
|
{ |
|
if !(buffer.len() >= size_of::<T>()) { |
|
::core::panicking::panic( |
|
"assertion failed: buffer.len() >= size_of::<T>()", |
|
) |
|
} |
|
match (&(buffer.addr() % align_of::<T>()), &0) { |
|
(left_val, right_val) => { |
|
if !(*left_val == *right_val) { |
|
let kind = ::core::panicking::AssertKind::Eq; |
|
::core::panicking::assert_failed( |
|
kind, |
|
&*left_val, |
|
&*right_val, |
|
::core::option::Option::None, |
|
); |
|
} |
|
} |
|
}; |
|
} |
|
unsafe { |
|
T::read_mut(buffer).store(value, order); |
|
} |
|
} |
|
let buffer = match self { |
|
SliceRefMut::Slice(buf) => SliceRefMut::Slice(buf), |
|
SliceRefMut::AtomicSlice(buf) => SliceRefMut::AtomicSlice(buf), |
|
}; |
|
unsafe { |
|
match value { |
|
TypedArrayElement::Int8(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Uint8(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Uint8Clamped(e) => { |
|
write_elem(buffer, e, order) |
|
} |
|
TypedArrayElement::Int16(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Uint16(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Int32(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Uint32(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::BigInt64(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::BigUint64(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Float32(e) => write_elem(buffer, e, order), |
|
TypedArrayElement::Float64(e) => write_elem(buffer, e, order), |
|
} |
|
} |
|
} |
|
} |
|
impl<'a> From<&'a mut [u8]> for SliceRefMut<'a> { |
|
fn from(value: &'a mut [u8]) -> Self { |
|
Self::Slice(value) |
|
} |
|
} |
|
impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> { |
|
fn from(value: &'a [AtomicU8]) -> Self { |
|
Self::AtomicSlice(value) |
|
} |
|
} |
|
/// Copies `count` bytes from `src` into `dest` using atomic relaxed loads and stores. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - Both `src` and `dest` must have at least `count` bytes to read and write, |
|
/// respectively. |
|
pub(super) unsafe fn copy_shared_to_shared( |
|
src: *const AtomicU8, |
|
dest: *const AtomicU8, |
|
count: usize, |
|
) { |
|
for i in 0..count { |
|
unsafe { |
|
(*dest.add(i)) |
|
.store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); |
|
} |
|
} |
|
} |
|
/// Copies `count` bytes backwards from `src` into `dest` using atomic relaxed loads and stores. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - Both `src` and `dest` must have at least `count` bytes to read and write, |
|
/// respectively. |
|
unsafe fn copy_shared_to_shared_backwards( |
|
src: *const AtomicU8, |
|
dest: *const AtomicU8, |
|
count: usize, |
|
) { |
|
for i in (0..count).rev() { |
|
unsafe { |
|
(*dest.add(i)) |
|
.store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); |
|
} |
|
} |
|
} |
|
/// Copies `count` bytes from the buffer `src` into the buffer `dest`, using the atomic ordering |
|
/// `Ordering::Relaxed` if any of the buffers are atomic. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - Both `src` and `dest` must have at least `count` bytes to read and write, respectively. |
|
/// - The region of memory referenced by `src` must not overlap with the region of memory |
|
/// referenced by `dest`. |
|
pub(crate) unsafe fn memcpy( |
|
src: BytesConstPtr, |
|
dest: BytesMutPtr, |
|
count: usize, |
|
) { |
|
match (src, dest) { |
|
(BytesConstPtr::Bytes(src), BytesMutPtr::Bytes(dest)) => { |
|
unsafe { |
|
ptr::copy_nonoverlapping(src, dest, count); |
|
} |
|
} |
|
(BytesConstPtr::Bytes(src), BytesMutPtr::AtomicBytes(dest)) => { |
|
unsafe { |
|
for i in 0..count { |
|
(*dest.add(i)).store(*src.add(i), Ordering::Relaxed); |
|
} |
|
} |
|
} |
|
(BytesConstPtr::AtomicBytes(src), BytesMutPtr::Bytes(dest)) => { |
|
unsafe { |
|
for i in 0..count { |
|
*dest.add(i) = (*src.add(i)).load(Ordering::Relaxed); |
|
} |
|
} |
|
} |
|
(BytesConstPtr::AtomicBytes(src), BytesMutPtr::AtomicBytes(dest)) => { |
|
unsafe { |
|
copy_shared_to_shared(src, dest, count); |
|
} |
|
} |
|
} |
|
} |
|
/// Copies `count` bytes from the position `from` to the position `to` in `buffer`. |
|
/// |
|
/// # Safety |
|
/// |
|
/// - `ptr` must be valid from the offset `ptr + from` for `count` reads of bytes. |
|
/// - `ptr` must be valid from the offset `ptr + to` for `count` writes of bytes. |
|
pub(crate) unsafe fn memmove( |
|
ptr: BytesMutPtr, |
|
from: usize, |
|
to: usize, |
|
count: usize, |
|
) { |
|
match ptr { |
|
BytesMutPtr::Bytes(ptr) => { |
|
unsafe { |
|
let src = ptr.add(from); |
|
let dest = ptr.add(to); |
|
ptr::copy(src, dest, count); |
|
} |
|
} |
|
BytesMutPtr::AtomicBytes(ptr) => { |
|
unsafe { |
|
let src = ptr.add(from); |
|
let dest = ptr.add(to); |
|
if src < dest { |
|
copy_shared_to_shared_backwards(src, dest, count); |
|
} else { |
|
copy_shared_to_shared(src, dest, count); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
use std::ops::{Deref, DerefMut}; |
|
pub use shared::SharedArrayBuffer; |
|
use std::sync::atomic::Ordering; |
|
use crate::{ |
|
builtins::BuiltInObject, |
|
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, |
|
error::JsNativeError, js_string, |
|
object::{internal_methods::get_prototype_from_constructor, JsObject, Object}, |
|
property::Attribute, realm::Realm, string::StaticJsStrings, symbol::JsSymbol, |
|
Context, JsArgs, JsData, JsResult, JsString, JsValue, |
|
}; |
|
use boa_gc::{Finalize, GcRef, GcRefMut, Trace}; |
|
use boa_profiler::Profiler; |
|
use self::utils::{SliceRef, SliceRefMut}; |
|
use super::{ |
|
typed_array::TypedArray, Array, BuiltInBuilder, BuiltInConstructor, DataView, |
|
IntrinsicObject, |
|
}; |
|
pub(crate) enum BufferRef<B, S> { |
|
Buffer(B), |
|
SharedBuffer(S), |
|
} |
|
#[automatically_derived] |
|
impl<B: ::core::fmt::Debug, S: ::core::fmt::Debug> ::core::fmt::Debug |
|
for BufferRef<B, S> { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
match self { |
|
BufferRef::Buffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"Buffer", |
|
&__self_0, |
|
) |
|
} |
|
BufferRef::SharedBuffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"SharedBuffer", |
|
&__self_0, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
#[automatically_derived] |
|
impl<B: ::core::clone::Clone, S: ::core::clone::Clone> ::core::clone::Clone |
|
for BufferRef<B, S> { |
|
#[inline] |
|
fn clone(&self) -> BufferRef<B, S> { |
|
match self { |
|
BufferRef::Buffer(__self_0) => { |
|
BufferRef::Buffer(::core::clone::Clone::clone(__self_0)) |
|
} |
|
BufferRef::SharedBuffer(__self_0) => { |
|
BufferRef::SharedBuffer(::core::clone::Clone::clone(__self_0)) |
|
} |
|
} |
|
} |
|
} |
|
#[automatically_derived] |
|
impl<B: ::core::marker::Copy, S: ::core::marker::Copy> ::core::marker::Copy |
|
for BufferRef<B, S> {} |
|
impl<B, S> BufferRef<B, S> |
|
where |
|
B: Deref<Target = ArrayBuffer>, |
|
S: Deref<Target = SharedArrayBuffer>, |
|
{ |
|
/// Gets the inner data of the buffer. |
|
pub(crate) fn bytes(&self, ordering: Ordering) -> Option<SliceRef<'_>> { |
|
match self { |
|
Self::Buffer(buf) => buf.deref().bytes().map(SliceRef::Slice), |
|
Self::SharedBuffer(buf) => { |
|
Some(SliceRef::AtomicSlice(buf.deref().bytes(ordering))) |
|
} |
|
} |
|
} |
|
/// Gets the inner data of the buffer without accessing the current atomic length. |
|
/// |
|
/// Returns `None` if the buffer is detached or if the provided `len` is bigger than |
|
/// the allocated buffer. |
|
#[track_caller] |
|
pub(crate) fn bytes_with_len(&self, len: usize) -> Option<SliceRef<'_>> { |
|
match self { |
|
Self::Buffer(buf) => buf.deref().bytes_with_len(len).map(SliceRef::Slice), |
|
Self::SharedBuffer(buf) => { |
|
Some(SliceRef::AtomicSlice(buf.deref().bytes_with_len(len))) |
|
} |
|
} |
|
} |
|
pub(crate) fn is_fixed_len(&self) -> bool { |
|
match self { |
|
Self::Buffer(buf) => buf.is_fixed_len(), |
|
Self::SharedBuffer(buf) => buf.is_fixed_len(), |
|
} |
|
} |
|
} |
|
pub(crate) enum BufferRefMut<B, S> { |
|
Buffer(B), |
|
SharedBuffer(S), |
|
} |
|
#[automatically_derived] |
|
impl<B: ::core::fmt::Debug, S: ::core::fmt::Debug> ::core::fmt::Debug |
|
for BufferRefMut<B, S> { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
match self { |
|
BufferRefMut::Buffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"Buffer", |
|
&__self_0, |
|
) |
|
} |
|
BufferRefMut::SharedBuffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"SharedBuffer", |
|
&__self_0, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
impl<B, S> BufferRefMut<B, S> |
|
where |
|
B: DerefMut<Target = ArrayBuffer>, |
|
S: DerefMut<Target = SharedArrayBuffer>, |
|
{ |
|
pub(crate) fn bytes(&mut self, ordering: Ordering) -> Option<SliceRefMut<'_>> { |
|
match self { |
|
Self::Buffer(buf) => buf.deref_mut().bytes_mut().map(SliceRefMut::Slice), |
|
Self::SharedBuffer(buf) => { |
|
Some(SliceRefMut::AtomicSlice(buf.deref_mut().bytes(ordering))) |
|
} |
|
} |
|
} |
|
/// Gets the mutable inner data of the buffer without accessing the current atomic length. |
|
/// |
|
/// Returns `None` if the buffer is detached or if the provided `len` is bigger than |
|
/// the allocated buffer. |
|
pub(crate) fn bytes_with_len(&mut self, len: usize) -> Option<SliceRefMut<'_>> { |
|
match self { |
|
Self::Buffer(buf) => { |
|
buf.deref_mut().bytes_with_len_mut(len).map(SliceRefMut::Slice) |
|
} |
|
Self::SharedBuffer(buf) => { |
|
Some(SliceRefMut::AtomicSlice(buf.deref_mut().bytes_with_len(len))) |
|
} |
|
} |
|
} |
|
} |
|
/// A `JsObject` containing a bytes buffer as its inner data. |
|
#[boa_gc(unsafe_no_drop)] |
|
pub(crate) enum BufferObject { |
|
Buffer(JsObject<ArrayBuffer>), |
|
SharedBuffer(JsObject<SharedArrayBuffer>), |
|
} |
|
#[automatically_derived] |
|
impl ::core::fmt::Debug for BufferObject { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
match self { |
|
BufferObject::Buffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"Buffer", |
|
&__self_0, |
|
) |
|
} |
|
BufferObject::SharedBuffer(__self_0) => { |
|
::core::fmt::Formatter::debug_tuple_field1_finish( |
|
f, |
|
"SharedBuffer", |
|
&__self_0, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::clone::Clone for BufferObject { |
|
#[inline] |
|
fn clone(&self) -> BufferObject { |
|
match self { |
|
BufferObject::Buffer(__self_0) => { |
|
BufferObject::Buffer(::core::clone::Clone::clone(__self_0)) |
|
} |
|
BufferObject::SharedBuffer(__self_0) => { |
|
BufferObject::SharedBuffer(::core::clone::Clone::clone(__self_0)) |
|
} |
|
} |
|
} |
|
} |
|
const _: () = { |
|
unsafe impl ::boa_gc::Trace for BufferObject { |
|
#[inline] |
|
unsafe fn trace(&self, tracer: &mut ::boa_gc::Tracer) { |
|
#[expect(dead_code)] |
|
let mut mark = |it: &dyn ::boa_gc::Trace| { |
|
unsafe { |
|
::boa_gc::Trace::trace(it, tracer); |
|
} |
|
}; |
|
match *self { |
|
BufferObject::Buffer(ref __binding_0) => { |
|
::boa_gc::Trace::trace(__binding_0, tracer) |
|
} |
|
BufferObject::SharedBuffer(ref __binding_0) => { |
|
::boa_gc::Trace::trace(__binding_0, tracer) |
|
} |
|
} |
|
} |
|
#[inline] |
|
unsafe fn trace_non_roots(&self) { |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::trace_non_roots(it); |
|
} |
|
} |
|
match *self { |
|
BufferObject::Buffer(ref __binding_0) => mark(__binding_0), |
|
BufferObject::SharedBuffer(ref __binding_0) => mark(__binding_0), |
|
} |
|
} |
|
#[inline] |
|
fn run_finalizer(&self) { |
|
::boa_gc::Finalize::finalize(self); |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::run_finalizer(it); |
|
} |
|
} |
|
match *self { |
|
BufferObject::Buffer(ref __binding_0) => mark(__binding_0), |
|
BufferObject::SharedBuffer(ref __binding_0) => mark(__binding_0), |
|
} |
|
} |
|
} |
|
}; |
|
const _: () = { |
|
impl ::boa_gc::Finalize for BufferObject {} |
|
}; |
|
impl From<BufferObject> for JsObject { |
|
fn from(value: BufferObject) -> Self { |
|
match value { |
|
BufferObject::Buffer(buf) => buf.upcast(), |
|
BufferObject::SharedBuffer(buf) => buf.upcast(), |
|
} |
|
} |
|
} |
|
impl From<BufferObject> for JsValue { |
|
fn from(value: BufferObject) -> Self { |
|
JsValue::from(JsObject::from(value)) |
|
} |
|
} |
|
impl BufferObject { |
|
/// Gets the buffer data of the object. |
|
#[inline] |
|
#[must_use] |
|
pub(crate) fn as_buffer( |
|
&self, |
|
) -> BufferRef<GcRef<'_, ArrayBuffer>, GcRef<'_, SharedArrayBuffer>> { |
|
match self { |
|
Self::Buffer(buf) => { |
|
BufferRef::Buffer(GcRef::map(buf.borrow(), |o| &o.data)) |
|
} |
|
Self::SharedBuffer(buf) => { |
|
BufferRef::SharedBuffer(GcRef::map(buf.borrow(), |o| &o.data)) |
|
} |
|
} |
|
} |
|
/// Gets the mutable buffer data of the object |
|
#[inline] |
|
pub(crate) fn as_buffer_mut( |
|
&self, |
|
) -> BufferRefMut< |
|
GcRefMut<'_, Object<ArrayBuffer>, ArrayBuffer>, |
|
GcRefMut<'_, Object<SharedArrayBuffer>, SharedArrayBuffer>, |
|
> { |
|
match self { |
|
Self::Buffer(buf) => { |
|
BufferRefMut::Buffer( |
|
GcRefMut::map(buf.borrow_mut(), |o| &mut o.data), |
|
) |
|
} |
|
Self::SharedBuffer(buf) => { |
|
BufferRefMut::SharedBuffer( |
|
GcRefMut::map(buf.borrow_mut(), |o| &mut o.data), |
|
) |
|
} |
|
} |
|
} |
|
/// Returns `true` if the buffer objects point to the same buffer. |
|
#[inline] |
|
pub(crate) fn equals(lhs: &Self, rhs: &Self) -> bool { |
|
match (lhs, rhs) { |
|
(BufferObject::Buffer(lhs), BufferObject::Buffer(rhs)) => { |
|
JsObject::equals(lhs, rhs) |
|
} |
|
(BufferObject::SharedBuffer(lhs), BufferObject::SharedBuffer(rhs)) => { |
|
if JsObject::equals(lhs, rhs) { |
|
return true; |
|
} |
|
let lhs = lhs.borrow(); |
|
let rhs = rhs.borrow(); |
|
std::ptr::eq(lhs.data.as_ptr(), rhs.data.as_ptr()) |
|
} |
|
_ => false, |
|
} |
|
} |
|
} |
|
/// The internal representation of an `ArrayBuffer` object. |
|
pub struct ArrayBuffer { |
|
/// The `[[ArrayBufferData]]` internal slot. |
|
data: Option<Vec<u8>>, |
|
/// The `[[ArrayBufferMaxByteLength]]` internal slot. |
|
max_byte_len: Option<u64>, |
|
/// The `[[ArrayBufferDetachKey]]` internal slot. |
|
detach_key: JsValue, |
|
} |
|
#[automatically_derived] |
|
impl ::core::fmt::Debug for ArrayBuffer { |
|
#[inline] |
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
|
::core::fmt::Formatter::debug_struct_field3_finish( |
|
f, |
|
"ArrayBuffer", |
|
"data", |
|
&self.data, |
|
"max_byte_len", |
|
&self.max_byte_len, |
|
"detach_key", |
|
&&self.detach_key, |
|
) |
|
} |
|
} |
|
#[automatically_derived] |
|
impl ::core::clone::Clone for ArrayBuffer { |
|
#[inline] |
|
fn clone(&self) -> ArrayBuffer { |
|
ArrayBuffer { |
|
data: ::core::clone::Clone::clone(&self.data), |
|
max_byte_len: ::core::clone::Clone::clone(&self.max_byte_len), |
|
detach_key: ::core::clone::Clone::clone(&self.detach_key), |
|
} |
|
} |
|
} |
|
const _: () = { |
|
unsafe impl ::boa_gc::Trace for ArrayBuffer { |
|
#[inline] |
|
unsafe fn trace(&self, tracer: &mut ::boa_gc::Tracer) { |
|
#[expect(dead_code)] |
|
let mut mark = |it: &dyn ::boa_gc::Trace| { |
|
unsafe { |
|
::boa_gc::Trace::trace(it, tracer); |
|
} |
|
}; |
|
match *self { |
|
ArrayBuffer { |
|
data: ref __binding_0, |
|
max_byte_len: ref __binding_1, |
|
detach_key: ref __binding_2, |
|
} => { |
|
{ ::boa_gc::Trace::trace(__binding_0, tracer) } |
|
{ ::boa_gc::Trace::trace(__binding_1, tracer) } |
|
{ ::boa_gc::Trace::trace(__binding_2, tracer) } |
|
} |
|
} |
|
} |
|
#[inline] |
|
unsafe fn trace_non_roots(&self) { |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::trace_non_roots(it); |
|
} |
|
} |
|
match *self { |
|
ArrayBuffer { |
|
data: ref __binding_0, |
|
max_byte_len: ref __binding_1, |
|
detach_key: ref __binding_2, |
|
} => { |
|
{ mark(__binding_0) } |
|
{ mark(__binding_1) } |
|
{ mark(__binding_2) } |
|
} |
|
} |
|
} |
|
#[inline] |
|
fn run_finalizer(&self) { |
|
::boa_gc::Finalize::finalize(self); |
|
#[expect(dead_code)] |
|
fn mark<T: ::boa_gc::Trace + ?Sized>(it: &T) { |
|
unsafe { |
|
::boa_gc::Trace::run_finalizer(it); |
|
} |
|
} |
|
match *self { |
|
ArrayBuffer { |
|
data: ref __binding_0, |
|
max_byte_len: ref __binding_1, |
|
detach_key: ref __binding_2, |
|
} => { |
|
{ mark(__binding_0) } |
|
{ mark(__binding_1) } |
|
{ mark(__binding_2) } |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
const _: () = { |
|
impl ::core::ops::Drop for ArrayBuffer { |
|
#[expect(clippy::inline_always)] |
|
#[inline(always)] |
|
fn drop(&mut self) { |
|
if ::boa_gc::finalizer_safe() { |
|
::boa_gc::Finalize::finalize(self); |
|
} |
|
} |
|
} |
|
}; |
|
const _: () = { |
|
impl ::boa_gc::Finalize for ArrayBuffer {} |
|
}; |
|
const _: () = { |
|
impl ::boa_engine::JsData for ArrayBuffer {} |
|
}; |
|
impl ArrayBuffer { |
|
pub(crate) fn from_data(data: Vec<u8>, detach_key: JsValue) -> Self { |
|
Self { |
|
data: Some(data), |
|
max_byte_len: None, |
|
detach_key, |
|
} |
|
} |
|
pub(crate) fn len(&self) -> usize { |
|
self.data.as_ref().map_or(0, Vec::len) |
|
} |
|
pub(crate) fn bytes(&self) -> Option<&[u8]> { |
|
self.data.as_deref() |
|
} |
|
pub(crate) fn bytes_mut(&mut self) -> Option<&mut [u8]> { |
|
self.data.as_deref_mut() |
|
} |
|
pub(crate) fn vec_mut(&mut self) -> Option<&mut Vec<u8>> { |
|
self.data.as_mut() |
|
} |
|
/// Gets the inner bytes of the buffer without accessing the current atomic length. |
|
#[track_caller] |
|
pub(crate) fn bytes_with_len(&self, len: usize) -> Option<&[u8]> { |
|
if let Some(s) = self.data.as_deref() { Some(&s[..len]) } else { None } |
|
} |
|
/// Gets the mutable inner bytes of the buffer without accessing the current atomic length. |
|
#[track_caller] |
|
pub(crate) fn bytes_with_len_mut(&mut self, len: usize) -> Option<&mut [u8]> { |
|
if let Some(s) = self.data.as_deref_mut() { |
|
Some(&mut s[..len]) |
|
} else { |
|
None |
|
} |
|
} |
|
/// Detaches the inner data of this `ArrayBuffer`, returning the original buffer if still |
|
/// present. |
|
/// |
|
/// # Errors |
|
/// |
|
/// Throws an error if the provided detach key is invalid. |
|
pub fn detach(&mut self, key: &JsValue) -> JsResult<Option<Vec<u8>>> { |
|
if !JsValue::same_value(&self.detach_key, key) { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message("Cannot detach array buffer with different key") |
|
.into(), |
|
); |
|
} |
|
Ok(self.data.take()) |
|
} |
|
/// `IsDetachedBuffer ( arrayBuffer )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer |
|
pub(crate) const fn is_detached(&self) -> bool { |
|
self.data.is_none() |
|
} |
|
pub(crate) fn is_fixed_len(&self) -> bool { |
|
self.max_byte_len.is_none() |
|
} |
|
} |
|
impl IntrinsicObject for ArrayBuffer { |
|
fn init(realm: &Realm) { |
|
let _timer = Profiler::global() |
|
.start_event(std::any::type_name::<Self>(), "init"); |
|
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; |
|
let get_species = BuiltInBuilder::callable(realm, Self::get_species) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
91, |
|
83, |
|
121, |
|
109, |
|
98, |
|
111, |
|
108, |
|
46, |
|
115, |
|
112, |
|
101, |
|
99, |
|
105, |
|
101, |
|
115, |
|
93, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
98, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
114, |
|
101, |
|
115, |
|
105, |
|
122, |
|
97, |
|
98, |
|
108, |
|
101, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let get_max_byte_length = BuiltInBuilder::callable( |
|
realm, |
|
Self::get_max_byte_length, |
|
) |
|
.name({ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
103, |
|
101, |
|
116, |
|
32, |
|
109, |
|
97, |
|
120, |
|
66, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}) |
|
.build(); |
|
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm) |
|
.static_accessor( |
|
JsSymbol::species(), |
|
Some(get_species), |
|
None, |
|
Attribute::CONFIGURABLE, |
|
) |
|
.static_method( |
|
Self::is_view, |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[105, 115, 86, 105, 101, 119].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
1, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[98, 121, 116, 101, 76, 101, 110, 103, 116, 104].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_byte_length), |
|
None, |
|
flag_attributes, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[114, 101, 115, 105, 122, 97, 98, 108, 101].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_resizable), |
|
None, |
|
flag_attributes, |
|
) |
|
.accessor( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
109, |
|
97, |
|
120, |
|
66, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
Some(get_max_byte_length), |
|
None, |
|
flag_attributes, |
|
) |
|
.method( |
|
Self::resize, |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[114, 101, 115, 105, 122, 101].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
1, |
|
) |
|
.method( |
|
Self::slice, |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[115, 108, 105, 99, 101].as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
2, |
|
) |
|
.property( |
|
JsSymbol::to_string_tag(), |
|
Self::NAME, |
|
Attribute::READONLY | Attribute::NON_ENUMERABLE |
|
| Attribute::CONFIGURABLE, |
|
); |
|
builder.build(); |
|
} |
|
fn get(intrinsics: &Intrinsics) -> JsObject { |
|
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() |
|
} |
|
} |
|
impl BuiltInObject for ArrayBuffer { |
|
const NAME: JsString = StaticJsStrings::ARRAY_BUFFER; |
|
} |
|
impl BuiltInConstructor for ArrayBuffer { |
|
const LENGTH: usize = 1; |
|
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::array_buffer; |
|
/// `ArrayBuffer ( length )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length |
|
fn constructor( |
|
new_target: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
if new_target.is_undefined() { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.constructor called with undefined new target", |
|
) |
|
.into(), |
|
); |
|
} |
|
let byte_len = args.get_or_undefined(0).to_index(context)?; |
|
let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; |
|
Ok( |
|
Self::allocate(new_target, byte_len, max_byte_len, context)? |
|
.upcast() |
|
.into(), |
|
) |
|
} |
|
} |
|
impl ArrayBuffer { |
|
/// `ArrayBuffer.isView ( arg )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview |
|
|
|
fn is_view( |
|
_: &JsValue, |
|
args: &[JsValue], |
|
_context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
Ok( |
|
args |
|
.get_or_undefined(0) |
|
.as_object() |
|
.is_some_and(|obj| obj.is::<TypedArray>() || obj.is::<DataView>()) |
|
.into(), |
|
) |
|
} |
|
/// `get ArrayBuffer [ @@species ]` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species |
|
|
|
fn get_species( |
|
this: &JsValue, |
|
_: &[JsValue], |
|
_: &mut Context, |
|
) -> JsResult<JsValue> { |
|
Ok(this.clone()) |
|
} |
|
/// `get ArrayBuffer.prototype.byteLength` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength |
|
pub(crate) fn get_byte_length( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"get ArrayBuffer.prototype.byteLength called with invalid `this`", |
|
) |
|
})?; |
|
Ok(buf.len().into()) |
|
} |
|
/// [`get ArrayBuffer.prototype.maxByteLength`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength |
|
pub(crate) fn get_max_byte_length( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"get ArrayBuffer.prototype.maxByteLength called with invalid `this`", |
|
) |
|
})?; |
|
let Some(data) = buf.bytes() else { |
|
return Ok(JsValue::from(0)); |
|
}; |
|
Ok(buf.max_byte_len.unwrap_or(data.len() as u64).into()) |
|
} |
|
/// [`get ArrayBuffer.prototype.resizable`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable |
|
pub(crate) fn get_resizable( |
|
this: &JsValue, |
|
_args: &[JsValue], |
|
_context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(JsObject::downcast_ref::<Self>) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"get ArrayBuffer.prototype.resizable called with invalid `this`", |
|
) |
|
})?; |
|
Ok(JsValue::from(!buf.is_fixed_len())) |
|
} |
|
/// [`ArrayBuffer.prototype.resize ( newLength )`][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize |
|
pub(crate) fn resize( |
|
this: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(|o| o.clone().downcast::<Self>().ok()) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.prototype.resize called with invalid `this`", |
|
) |
|
})?; |
|
let Some(max_byte_len) = buf.borrow().data.max_byte_len else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.resize: cannot resize a fixed-length buffer", |
|
) |
|
.into(), |
|
); |
|
}; |
|
let new_byte_length = args.get_or_undefined(0).to_index(context)?; |
|
let mut buf = buf.borrow_mut(); |
|
let Some(buf) = buf.data.vec_mut() else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.resize: cannot resize a detached buffer", |
|
) |
|
.into(), |
|
); |
|
}; |
|
if new_byte_length > max_byte_len { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length", |
|
) |
|
.into(), |
|
); |
|
} |
|
buf.resize(new_byte_length as usize, 0); |
|
Ok(JsValue::undefined()) |
|
} |
|
/// `ArrayBuffer.prototype.slice ( start, end )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice |
|
fn slice( |
|
this: &JsValue, |
|
args: &[JsValue], |
|
context: &mut Context, |
|
) -> JsResult<JsValue> { |
|
let buf = this |
|
.as_object() |
|
.and_then(|o| o.clone().downcast::<Self>().ok()) |
|
.ok_or_else(|| { |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.slice called with invalid `this` value", |
|
) |
|
})?; |
|
let len = { |
|
let buf = buf.borrow(); |
|
if buf.data.is_detached() { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer.slice called with detached buffer", |
|
) |
|
.into(), |
|
); |
|
} |
|
buf.data.len() as u64 |
|
}; |
|
let first = Array::get_relative_start( |
|
context, |
|
args.get_or_undefined(0), |
|
len, |
|
)?; |
|
let final_ = Array::get_relative_end( |
|
context, |
|
args.get_or_undefined(1), |
|
len, |
|
)?; |
|
let new_len = final_.saturating_sub(first); |
|
let ctor = buf |
|
.clone() |
|
.upcast() |
|
.species_constructor(StandardConstructors::array_buffer, context)?; |
|
let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; |
|
let Ok(new) = new.downcast::<Self>() else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message("ArrayBuffer constructor returned invalid object") |
|
.into(), |
|
); |
|
}; |
|
if JsObject::equals(&buf, &new) { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message("new ArrayBuffer is the same as this ArrayBuffer") |
|
.into(), |
|
); |
|
} |
|
{ |
|
let mut new = new.borrow_mut(); |
|
let Some(to_buf) = new.data.bytes_mut() else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer constructor returned detached ArrayBuffer", |
|
) |
|
.into(), |
|
); |
|
}; |
|
if (to_buf.len() as u64) < new_len { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message("new ArrayBuffer length too small") |
|
.into(), |
|
); |
|
} |
|
let buf = buf.borrow(); |
|
let Some(from_buf) = buf.data.bytes() else { |
|
return Err( |
|
JsNativeError::typ() |
|
.with_message( |
|
"ArrayBuffer detached while ArrayBuffer.slice was running", |
|
) |
|
.into(), |
|
); |
|
}; |
|
let first = first as usize; |
|
let new_len = new_len as usize; |
|
to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]); |
|
} |
|
Ok(new.upcast().into()) |
|
} |
|
/// `AllocateArrayBuffer ( constructor, byteLength )` |
|
/// |
|
/// More information: |
|
/// - [ECMAScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer |
|
pub(crate) fn allocate( |
|
constructor: &JsValue, |
|
byte_len: u64, |
|
max_byte_len: Option<u64>, |
|
context: &mut Context, |
|
) -> JsResult<JsObject<ArrayBuffer>> { |
|
if let Some(max_byte_len) = max_byte_len { |
|
if byte_len > max_byte_len { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"`length` cannot be bigger than `maxByteLength`", |
|
) |
|
.into(), |
|
); |
|
} |
|
} |
|
let prototype = get_prototype_from_constructor( |
|
constructor, |
|
StandardConstructors::array_buffer, |
|
context, |
|
)?; |
|
let block = create_byte_data_block(byte_len, max_byte_len, context)?; |
|
let obj = JsObject::new( |
|
context.root_shape(), |
|
prototype, |
|
Self { |
|
data: Some(block), |
|
max_byte_len, |
|
detach_key: JsValue::Undefined, |
|
}, |
|
); |
|
Ok(obj) |
|
} |
|
} |
|
/// Abstract operation [`GetArrayBufferMaxByteLengthOption ( options )`][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption |
|
fn get_max_byte_len( |
|
options: &JsValue, |
|
context: &mut Context, |
|
) -> JsResult<Option<u64>> { |
|
let Some(options) = options.as_object() else { |
|
return Ok(None); |
|
}; |
|
let max_byte_len = options |
|
.get( |
|
{ |
|
const LITERAL: &crate::string::StaticJsString = &crate::string::StaticJsString::new( |
|
::boa_engine::string::JsStr::latin1( |
|
[ |
|
109, |
|
97, |
|
120, |
|
66, |
|
121, |
|
116, |
|
101, |
|
76, |
|
101, |
|
110, |
|
103, |
|
116, |
|
104, |
|
] |
|
.as_slice(), |
|
), |
|
); |
|
crate::string::JsString::from_static_js_string(LITERAL) |
|
}, |
|
context, |
|
)?; |
|
if max_byte_len.is_undefined() { |
|
return Ok(None); |
|
} |
|
max_byte_len.to_index(context).map(Some) |
|
} |
|
/// `CreateByteDataBlock ( size )` abstract operation. |
|
/// |
|
/// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative |
|
/// integer). For more information, check the [spec][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock |
|
pub(crate) fn create_byte_data_block( |
|
size: u64, |
|
max_buffer_size: Option<u64>, |
|
context: &mut Context, |
|
) -> JsResult<Vec<u8>> { |
|
let alloc_size = max_buffer_size.unwrap_or(size); |
|
if !(size <= alloc_size) { |
|
::core::panicking::panic("assertion failed: size <= alloc_size") |
|
} |
|
if alloc_size > context.host_hooks().max_buffer_size(context) { |
|
return Err( |
|
JsNativeError::range() |
|
.with_message( |
|
"cannot allocate a buffer that exceeds the maximum buffer size", |
|
) |
|
.into(), |
|
); |
|
} |
|
let alloc_size = alloc_size |
|
.try_into() |
|
.map_err(|e| { |
|
JsNativeError::range() |
|
.with_message( |
|
::alloc::__export::must_use({ |
|
let res = ::alloc::fmt::format( |
|
format_args!("couldn\'t allocate the data block: {0}", e), |
|
); |
|
res |
|
}), |
|
) |
|
})?; |
|
let mut data_block = Vec::new(); |
|
data_block |
|
.try_reserve_exact(alloc_size) |
|
.map_err(|e| { |
|
JsNativeError::range() |
|
.with_message( |
|
::alloc::__export::must_use({ |
|
let res = ::alloc::fmt::format( |
|
format_args!("couldn\'t allocate the data block: {0}", e), |
|
); |
|
res |
|
}), |
|
) |
|
})?; |
|
let size = size as usize; |
|
data_block.resize(size, 0); |
|
Ok(data_block) |
|
} |
|
}
|
|
|