Browse Source

Improve typing of `DataView` and related objects (#3626)

pull/3633/head
José Julián Espina 10 months ago committed by GitHub
parent
commit
20dba13ae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 114
      core/engine/src/builtins/array_buffer/mod.rs
  2. 12
      core/engine/src/builtins/array_buffer/shared.rs
  3. 18
      core/engine/src/builtins/array_buffer/utils.rs
  4. 37
      core/engine/src/builtins/atomics/mod.rs
  5. 59
      core/engine/src/builtins/dataview/mod.rs
  6. 93
      core/engine/src/builtins/typed_array/builtin.rs
  7. 6
      core/engine/src/builtins/typed_array/element/mod.rs
  8. 124
      core/engine/src/builtins/typed_array/mod.rs
  9. 136
      core/engine/src/builtins/typed_array/object.rs
  10. 16
      core/engine/src/object/builtins/jsarraybuffer.rs
  11. 91
      core/engine/src/object/builtins/jsdataview.rs
  12. 49
      core/engine/src/object/builtins/jssharedarraybuffer.rs
  13. 60
      core/engine/src/object/jsobject.rs
  14. 28
      core/engine/src/object/mod.rs

114
core/engine/src/builtins/array_buffer/mod.rs

@ -16,6 +16,8 @@ pub(crate) mod utils;
#[cfg(test)]
mod tests;
use std::ops::{Deref, DerefMut};
pub use shared::SharedArrayBuffer;
use crate::{
@ -23,7 +25,7 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject},
object::{internal_methods::get_prototype_from_constructor, JsObject, Object},
property::Attribute,
realm::Realm,
string::common::StaticJsStrings,
@ -31,7 +33,7 @@ use crate::{
value::IntegerOrInfinity,
Context, JsArgs, JsData, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_gc::{Finalize, GcRef, GcRefMut, Trace};
use boa_profiler::Profiler;
use self::utils::{SliceRef, SliceRefMut};
@ -41,16 +43,20 @@ use super::{
};
#[derive(Debug, Clone, Copy)]
pub(crate) enum BufferRef<'a> {
Buffer(&'a ArrayBuffer),
SharedBuffer(&'a SharedArrayBuffer),
pub(crate) enum BufferRef<B, S> {
Buffer(B),
SharedBuffer(S),
}
impl BufferRef<'_> {
impl<B, S> BufferRef<B, S>
where
B: Deref<Target = ArrayBuffer>,
S: Deref<Target = SharedArrayBuffer>,
{
pub(crate) fn data(&self) -> Option<SliceRef<'_>> {
match self {
Self::Buffer(buf) => buf.data().map(SliceRef::Slice),
Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.data())),
Self::Buffer(buf) => buf.deref().data().map(SliceRef::Slice),
Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().data())),
}
}
@ -60,16 +66,96 @@ impl BufferRef<'_> {
}
#[derive(Debug)]
pub(crate) enum BufferRefMut<'a> {
Buffer(&'a mut ArrayBuffer),
SharedBuffer(&'a mut SharedArrayBuffer),
pub(crate) enum BufferRefMut<B, S> {
Buffer(B),
SharedBuffer(S),
}
impl BufferRefMut<'_> {
impl<B, S> BufferRefMut<B, S>
where
B: DerefMut<Target = ArrayBuffer>,
S: DerefMut<Target = SharedArrayBuffer>,
{
pub(crate) fn data_mut(&mut self) -> Option<SliceRefMut<'_>> {
match self {
Self::Buffer(buf) => buf.data_mut().map(SliceRefMut::Slice),
Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.data())),
Self::Buffer(buf) => buf.deref_mut().data_mut().map(SliceRefMut::Slice),
Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.deref_mut().data())),
}
}
}
/// A `JsObject` containing a bytes buffer as its inner data.
#[derive(Debug, Clone, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub(crate) enum BufferObject {
Buffer(JsObject<ArrayBuffer>),
SharedBuffer(JsObject<SharedArrayBuffer>),
}
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.data().as_ptr(), rhs.data.data().as_ptr())
}
_ => false,
}
}
}

12
core/engine/src/builtins/array_buffer/shared.rs

@ -125,7 +125,9 @@ impl BuiltInConstructor for SharedArrayBuffer {
let byte_length = args.get_or_undefined(0).to_index(context)?;
// 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength).
Ok(Self::allocate(new_target, byte_length, context)?.into())
Ok(Self::allocate(new_target, byte_length, context)?
.upcast()
.into())
}
}
@ -256,7 +258,7 @@ impl SharedArrayBuffer {
constructor: &JsValue,
byte_length: u64,
context: &mut Context,
) -> JsResult<JsObject> {
) -> JsResult<JsObject<SharedArrayBuffer>> {
// TODO:
// 1. Let slots be « [[ArrayBufferData]] ».
// 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer
@ -291,11 +293,7 @@ impl SharedArrayBuffer {
// 10. Else,
// a. Set obj.[[ArrayBufferByteLength]] to byteLength.
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
Self { data },
);
let obj = JsObject::new(context.root_shape(), prototype, Self { data });
// 11. Return obj.
Ok(obj)

18
core/engine/src/builtins/array_buffer/utils.rs

@ -3,7 +3,6 @@
use std::{ptr, slice::SliceIndex, sync::atomic};
use portable_atomic::AtomicU8;
use sptr::Strict;
use crate::{
builtins::typed_array::{ClampedU8, Element, TypedArrayElement, TypedArrayKind},
@ -41,7 +40,9 @@ impl SliceRef<'_> {
}
/// 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(),
@ -68,7 +69,8 @@ impl SliceRef<'_> {
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= std::mem::size_of::<T>());
assert_eq!(buffer.addr() % std::mem::align_of::<T>(), 0);
}
@ -178,6 +180,7 @@ pub(crate) enum SliceRefMut<'a> {
impl SliceRefMut<'_> {
/// Gets the byte length of this `SliceRefMut`.
#[cfg(debug_assertions)]
pub(crate) fn len(&self) -> usize {
match self {
Self::Slice(buf) => buf.len(),
@ -201,7 +204,9 @@ impl SliceRefMut<'_> {
}
/// 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(),
@ -236,7 +241,8 @@ impl SliceRefMut<'_> {
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= std::mem::size_of::<T>());
assert_eq!(buffer.addr() % std::mem::align_of::<T>(), 0);
}
@ -345,7 +351,8 @@ unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], c
/// (you cannot borrow and mutably borrow a slice at the same time), but cannot be guaranteed
/// for atomic slices.
pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usize) {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(src.len() >= count);
assert!(dest.len() >= count);
let src_range = src.addr()..src.addr() + src.len();
@ -387,7 +394,8 @@ pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usi
/// - `buffer` must contain at least `from + count` bytes to be read.
/// - `buffer` must contain at least `to + count` bytes to be written.
pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, count: usize) {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(from + count <= buffer.len());
assert!(to + count <= buffer.len());
}

37
core/engine/src/builtins/atomics/mod.rs

@ -24,7 +24,7 @@ use boa_gc::GcRef;
use boa_profiler::Profiler;
use super::{
array_buffer::{BufferRef, SharedArrayBuffer},
array_buffer::BufferRef,
typed_array::{Atomic, ContentType, Element, TypedArray, TypedArrayElement, TypedArrayKind},
BuiltInBuilder, IntrinsicObject,
};
@ -80,10 +80,7 @@ macro_rules! atomic_op {
let value = ii.kind().get_element(value, context)?;
// revalidate
let mut buffer = ii.viewed_array_buffer().borrow_mut();
let mut buffer = buffer
.as_buffer_mut()
.expect("integer indexed object must contain a valid buffer");
let mut buffer = ii.viewed_array_buffer().as_buffer_mut();
let Some(mut data) = buffer.data_mut() else {
return Err(JsNativeError::typ()
.with_message("cannot execute atomic operation in detached buffer")
@ -168,10 +165,7 @@ impl Atomics {
let pos = validate_atomic_access(&ii, index, context)?;
// 2. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition).
let buffer = ii.viewed_array_buffer().borrow();
let buffer = buffer
.as_buffer()
.expect("integer indexed object must contain a valid buffer");
let buffer = ii.viewed_array_buffer().as_buffer();
let Some(data) = buffer.data() else {
return Err(JsNativeError::typ()
.with_message("cannot execute atomic operation in detached buffer")
@ -217,10 +211,7 @@ impl Atomics {
let value = ii.kind().get_element(&converted, context)?;
// 4. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition).
let mut buffer = ii.viewed_array_buffer().borrow_mut();
let mut buffer = buffer
.as_buffer_mut()
.expect("integer indexed object must contain a valid buffer");
let mut buffer = ii.viewed_array_buffer().as_buffer_mut();
let Some(mut buffer) = buffer.data_mut() else {
return Err(JsNativeError::typ()
.with_message("cannot execute atomic operation in detached buffer")
@ -269,10 +260,7 @@ impl Atomics {
.to_bytes();
// 6. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition).
let mut buffer = ii.viewed_array_buffer().borrow_mut();
let mut buffer = buffer
.as_buffer_mut()
.expect("integer indexed object must contain a valid buffer");
let mut buffer = ii.viewed_array_buffer().as_buffer_mut();
let Some(mut data) = buffer.data_mut() else {
return Err(JsNativeError::typ()
.with_message("cannot execute atomic operation in detached buffer")
@ -403,10 +391,7 @@ impl Atomics {
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true).
let ii = validate_integer_typed_array(array, true)?;
let buffer = ii.viewed_array_buffer().borrow();
let buffer = buffer
.as_buffer()
.expect("integer indexed object must contain a valid buffer");
let buffer = ii.viewed_array_buffer().as_buffer();
// 2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
let BufferRef::SharedBuffer(buffer) = buffer else {
@ -451,10 +436,10 @@ impl Atomics {
// SAFETY: the validity of `addr` is verified by our call to `validate_atomic_access`.
let result = unsafe {
if ii.kind() == TypedArrayKind::BigInt64 {
futex::wait(buffer, offset, value, timeout)?
futex::wait(&buffer, offset, value, timeout)?
} else {
// value must fit into `i32` since it came from an `i32` above.
futex::wait(buffer, offset, value as i32, timeout)?
futex::wait(&buffer, offset, value as i32, timeout)?
}
};
@ -496,13 +481,11 @@ impl Atomics {
// 4. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 5. Let block be buffer.[[ArrayBufferData]].
// 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽.
let buffer = ii.viewed_array_buffer();
let buffer = buffer.borrow();
let Some(shared) = buffer.downcast_ref::<SharedArrayBuffer>() else {
let BufferRef::SharedBuffer(shared) = ii.viewed_array_buffer().as_buffer() else {
return Ok(0.into());
};
let count = futex::notify(shared, offset, count)?;
let count = futex::notify(&shared, offset, count)?;
// 12. Let n be the number of elements in S.
// 13. Return 𝔽(n).

59
core/engine/src/builtins/dataview/mod.rs

@ -26,7 +26,10 @@ use boa_gc::{Finalize, Trace};
use bytemuck::{bytes_of, bytes_of_mut};
use super::{
array_buffer::utils::{memcpy, SliceRef, SliceRefMut},
array_buffer::{
utils::{memcpy, SliceRef, SliceRefMut},
BufferObject,
},
typed_array::{self, TypedArrayElement},
BuiltInBuilder, BuiltInConstructor, IntrinsicObject,
};
@ -34,7 +37,7 @@ use super::{
/// The internal representation of a `DataView` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
pub struct DataView {
pub(crate) viewed_array_buffer: JsObject,
pub(crate) viewed_array_buffer: BufferObject,
pub(crate) byte_length: u64,
pub(crate) byte_offset: u64,
}
@ -135,11 +138,6 @@ impl BuiltInConstructor for DataView {
) -> JsResult<JsValue> {
let byte_length = args.get_or_undefined(2);
let buffer_obj = args
.get_or_undefined(0)
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?;
// 1. If NewTarget is undefined, throw a TypeError exception.
if new_target.is_undefined() {
return Err(JsNativeError::typ()
@ -147,15 +145,19 @@ impl BuiltInConstructor for DataView {
.into());
}
// 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]).
let buffer_obj = args
.get_or_undefined(0)
.as_object()
.and_then(|o| o.clone().into_buffer_object().ok())
.ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?;
let (offset, view_byte_length) = {
// 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]).
let buffer_borrow = buffer_obj.borrow();
let buffer = buffer_borrow.as_buffer().ok_or_else(|| {
JsNativeError::typ().with_message("buffer must be an ArrayBuffer")
})?;
let buffer = buffer_obj.as_buffer();
// 3. Let offset be ? ToIndex(byteOffset).
let offset = args.get_or_undefined(1).to_index(context)?;
// 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
let Some(buffer) = buffer.data() else {
return Err(JsNativeError::typ()
@ -195,12 +197,7 @@ impl BuiltInConstructor for DataView {
get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?;
// 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if buffer_obj
.borrow()
.as_buffer()
.expect("already checked that `buffer_obj` was a buffer")
.is_detached()
{
if buffer_obj.as_buffer().is_detached() {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer can't be detached")
.into());
@ -211,7 +208,7 @@ impl BuiltInConstructor for DataView {
prototype,
Self {
// 11. Set O.[[ViewedArrayBuffer]] to buffer.
viewed_array_buffer: buffer_obj.clone(),
viewed_array_buffer: buffer_obj,
// 12. Set O.[[ByteLength]] to viewByteLength.
byte_length: view_byte_length,
// 13. Set O.[[ByteOffset]] to offset.
@ -277,12 +274,9 @@ impl DataView {
.ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?;
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
// 4. Let buffer be O.[[ViewedArrayBuffer]].
let buffer_borrow = view.viewed_array_buffer.borrow();
let borrow = buffer_borrow
.as_buffer()
.expect("DataView must be constructed with a Buffer");
let buffer = view.viewed_array_buffer.as_buffer();
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if borrow.is_detached() {
if buffer.is_detached() {
return Err(JsNativeError::typ()
.with_message("ArrayBuffer is detached")
.into());
@ -317,12 +311,9 @@ impl DataView {
.ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?;
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
// 4. Let buffer be O.[[ViewedArrayBuffer]].
let buffer_borrow = view.viewed_array_buffer.borrow();
let borrow = buffer_borrow
.as_buffer()
.expect("DataView must be constructed with a Buffer");
let buffer = view.viewed_array_buffer.as_buffer();
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if borrow.is_detached() {
if buffer.is_detached() {
return Err(JsNativeError::typ()
.with_message("Buffer is detached")
.into());
@ -362,9 +353,7 @@ impl DataView {
let is_little_endian = is_little_endian.to_boolean();
// 5. Let buffer be view.[[ViewedArrayBuffer]].
let buffer = &view.viewed_array_buffer;
let buffer_borrow = buffer.borrow();
let buffer = buffer_borrow.as_buffer().expect("Should be unreachable");
let buffer = view.viewed_array_buffer.as_buffer();
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
let Some(data) = buffer.data() else {
@ -676,11 +665,7 @@ impl DataView {
// 6. Set isLittleEndian to ! ToBoolean(isLittleEndian).
let is_little_endian = is_little_endian.to_boolean();
// 7. Let buffer be view.[[ViewedArrayBuffer]].
let buffer = &view.viewed_array_buffer;
let mut buffer_borrow = buffer.borrow_mut();
let mut buffer = buffer_borrow
.as_buffer_mut()
.expect("Should be unreachable");
let mut buffer = view.viewed_array_buffer.as_buffer_mut();
// 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
let Some(mut data) = buffer.data_mut() else {

93
core/engine/src/builtins/typed_array/builtin.rs

@ -1,14 +1,17 @@
use std::{cmp, ptr, sync::atomic};
use std::{cmp, sync::atomic};
use boa_macros::utf16;
use num_traits::Zero;
use super::{
object::typed_array_set_element, ContentType, TypedArray, TypedArrayKind, TypedArrayMarker,
};
use crate::{
builtins::{
array::{find_via_predicate, ArrayIterator, Direction},
array_buffer::{
utils::{memcpy, memmove, SliceRefMut},
ArrayBuffer, BufferRef,
ArrayBuffer, BufferObject,
},
iterable::iterable_to_list,
Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
@ -23,11 +26,6 @@ use crate::{
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use super::{
integer_indexed_object::integer_indexed_element_set, ContentType, TypedArray, TypedArrayKind,
TypedArrayMarker,
};
/// The JavaScript `%TypedArray%` object.
///
/// <https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object>
@ -427,7 +425,7 @@ impl BuiltinTypedArray {
// 4. Let buffer be O.[[ViewedArrayBuffer]].
// 5. Return buffer.
Ok(typed_array.viewed_array_buffer().clone().into())
Ok(JsObject::from(typed_array.viewed_array_buffer().clone()).into())
}
/// `23.2.3.3 get %TypedArray%.prototype.byteLength`
@ -574,10 +572,7 @@ impl BuiltinTypedArray {
// b. Let buffer be O.[[ViewedArrayBuffer]].
// c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
let buffer_obj = o.viewed_array_buffer();
let mut buffer_obj_borrow = buffer_obj.borrow_mut();
let mut buffer = buffer_obj_borrow
.as_buffer_mut()
.expect("Already checked for detached buffer");
let mut buffer = buffer_obj.as_buffer_mut();
let Some(buffer) = buffer.data_mut() else {
return Err(JsNativeError::typ()
.with_message("Buffer of the typed array is detached")
@ -2060,34 +2055,14 @@ impl BuiltinTypedArray {
// 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true,
// and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let
// sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false.
let same = if JsObject::equals(&src_buffer_obj, &target_buffer_obj) {
true
} else {
let src_buffer_obj = src_buffer_obj.borrow();
let src_buffer = src_buffer_obj.as_buffer().expect("Must be an array buffer");
let target_buffer_obj = target_buffer_obj.borrow();
let target_buffer = target_buffer_obj
.as_buffer()
.expect("Must be an array buffer");
match (src_buffer, target_buffer) {
(BufferRef::SharedBuffer(src), BufferRef::SharedBuffer(dest)) => {
ptr::eq(src.data(), dest.data())
}
(_, _) => false,
}
};
// 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then
let src_byte_index = if same {
let src_byte_index = if BufferObject::equals(&src_buffer_obj, &target_buffer_obj) {
// a. Let srcByteLength be source.[[ByteLength]].
let src_byte_offset = src_byte_offset as usize;
let src_byte_length = source_array.byte_length() as usize;
let s = {
let slice = src_buffer_obj.borrow();
let slice = slice.as_buffer().expect("Must be an array buffer");
let slice = src_buffer_obj.as_buffer();
let slice = slice.data().expect("Already checked for detached buffer");
// b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%).
@ -2096,8 +2071,7 @@ impl BuiltinTypedArray {
.subslice(src_byte_offset..src_byte_offset + src_byte_length)
.clone(context)?
};
// TODO: skip this upcast
src_buffer_obj = s.upcast();
src_buffer_obj = BufferObject::Buffer(s);
// d. Let srcByteIndex be 0.
0
@ -2112,16 +2086,12 @@ impl BuiltinTypedArray {
// 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset.
let target_byte_index = target_offset * target_element_size + target_byte_offset;
let src_buffer = src_buffer_obj.borrow();
let src_buffer = src_buffer.as_buffer().expect("Must be an array buffer");
let src_buffer = src_buffer_obj.as_buffer();
let src_buffer = src_buffer
.data()
.expect("Already checked for detached buffer");
let mut target_buffer = target_buffer_obj.borrow_mut();
let mut target_buffer = target_buffer
.as_buffer_mut()
.expect("Must be an array buffer");
let mut target_buffer = target_buffer_obj.as_buffer_mut();
let mut target_buffer = target_buffer
.data_mut()
.expect("Already checked for detached buffer");
@ -2255,7 +2225,7 @@ impl BuiltinTypedArray {
let target_index = target_offset + k;
// d. Perform ? IntegerIndexedElementSet(target, targetIndex, value).
integer_indexed_element_set(target, target_index as f64, &value, &mut context.into())?;
typed_array_set_element(target, target_index as f64, &value, &mut context.into())?;
// e. Set k to k + 1.
}
@ -2377,18 +2347,12 @@ impl BuiltinTypedArray {
} else {
// i. Let srcBuffer be O.[[ViewedArrayBuffer]].
let src_buffer_obj = o.viewed_array_buffer();
let src_buffer_obj_borrow = src_buffer_obj.borrow();
let src_buffer = src_buffer_obj_borrow
.as_buffer()
.expect("view must be a buffer");
let src_buffer = src_buffer_obj.as_buffer();
let src_buffer = src_buffer.data().expect("cannot be detached here");
// ii. Let targetBuffer be A.[[ViewedArrayBuffer]].
let target_buffer_obj = a_array.viewed_array_buffer();
let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut();
let mut target_buffer = target_buffer_obj_borrow
.as_buffer_mut()
.expect("view must be a buffer");
let mut target_buffer = target_buffer_obj.as_buffer_mut();
let mut target_buffer = target_buffer.data_mut().expect("cannot be detached here");
// iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType.
@ -2713,7 +2677,7 @@ impl BuiltinTypedArray {
obj,
o.kind(),
&[
buffer.clone().into(),
JsObject::from(buffer.clone()).into(),
begin_byte_offset.into(),
new_length.into(),
],
@ -3069,9 +3033,8 @@ impl BuiltinTypedArray {
// 9. Set O.[[ArrayLength]] to length.
// 10. Return O.
// TODO: skip this upcast.
Ok(TypedArray::new(
data.upcast(),
BufferObject::Buffer(data),
T::ERASED,
0,
byte_length,
@ -3153,18 +3116,14 @@ impl BuiltinTypedArray {
/// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray
pub(super) fn initialize_from_typed_array<T: TypedArrayMarker>(
proto: JsObject,
src_array: &JsObject,
src_array: &JsObject<TypedArray>,
context: &mut Context,
) -> JsResult<JsObject> {
let src_array = src_array.borrow();
let src_array = src_array
.downcast_ref::<TypedArray>()
.expect("this must be a typed array");
let src_array = &src_array.data;
let src_data = src_array.viewed_array_buffer();
let src_data = src_data.borrow();
let src_data = src_data
.as_buffer()
.expect("integer indexed must have a buffer");
let src_data = src_data.as_buffer();
// 1. Let srcData be srcArray.[[ViewedArrayBuffer]].
// 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception.
@ -3288,12 +3247,11 @@ impl BuiltinTypedArray {
// 13. Set O.[[ByteLength]] to byteLength.
// 14. Set O.[[ByteOffset]] to 0.
// 15. Set O.[[ArrayLength]] to elementLength.
// TODO: Skip this upcast.
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
TypedArray::new(
new_buffer.upcast(),
BufferObject::Buffer(new_buffer),
element_type,
0,
byte_length,
@ -3313,7 +3271,7 @@ impl BuiltinTypedArray {
/// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer
pub(super) fn initialize_from_array_buffer<T: TypedArrayMarker>(
proto: JsObject,
buffer: JsObject,
buffer: BufferObject,
byte_offset: &JsValue,
length: &JsValue,
context: &mut Context,
@ -3342,10 +3300,9 @@ impl BuiltinTypedArray {
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
// 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
let buffer_byte_length = {
let buffer_borrow = buffer.borrow();
let buffer_array = buffer_borrow.as_buffer().expect("Must be a buffer");
let buffer = buffer.as_buffer();
let Some(data) = buffer_array.data() else {
let Some(data) = buffer.data() else {
return Err(JsNativeError::typ()
.with_message("Cannot construct typed array from detached buffer")
.into());

6
core/engine/src/builtins/typed_array/element/mod.rs

@ -277,7 +277,8 @@ macro_rules! element {
}
unsafe fn read(buffer: SliceRef<'_>) -> ElementRef<'_, Self> {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= std::mem::size_of::<Self>());
assert!(buffer.addr() % std::mem::align_of::<Self>() == 0);
}
@ -293,7 +294,8 @@ macro_rules! element {
}
unsafe fn read_mut(buffer: SliceRefMut<'_>) -> ElementRefMut<'_, Self> {
if cfg!(debug_assertions) {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= std::mem::size_of::<Self>());
assert!(buffer.addr() % std::mem::align_of::<Self>() == 0);
}

124
core/engine/src/builtins/typed_array/mod.rs

@ -28,15 +28,16 @@ use crate::{
value::{JsValue, Numeric},
Context, JsArgs, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
mod builtin;
mod element;
mod integer_indexed_object;
mod object;
pub(crate) use builtin::{is_valid_integer_index, BuiltinTypedArray};
pub(crate) use element::{Atomic, ClampedU8, Element};
pub use integer_indexed_object::{IntegerIndexed, TypedArray};
pub use object::TypedArray;
pub(crate) trait TypedArrayMarker {
type Element: Element;
@ -137,18 +138,39 @@ impl<T: TypedArrayMarker> BuiltInConstructor for T {
let first_argument = &args[0];
// b. If Type(firstArgument) is Object, then
if let Some(first_argument) = first_argument.as_object() {
// i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto).
let proto =
get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?;
let Some(first_argument) = first_argument.as_object() else {
// c. Else,
// i. Assert: firstArgument is not an Object.
// Ensured by the let-else
// ii. Let elementLength be ? ToIndex(firstArgument).
let element_length = first_argument.to_index(context)?;
// iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength).
return BuiltinTypedArray::allocate::<T>(new_target, element_length, context)
.map(JsValue::from);
};
// ii. If firstArgument has a [[TypedArrayName]] internal slot, then
let o = if first_argument.is::<TypedArray>() {
let first_argument = first_argument.clone();
// i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto).
let proto = get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?;
// ii. If firstArgument has a [[TypedArrayName]] internal slot, then
let first_argument = match first_argument.downcast::<TypedArray>() {
Ok(arr) => {
// 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument).
BuiltinTypedArray::initialize_from_typed_array::<T>(proto, first_argument, context)?
} else if first_argument.is_buffer() {
// iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then
// v. Return O.
return BuiltinTypedArray::initialize_from_typed_array::<T>(proto, &arr, context)
.map(JsValue::from);
}
Err(obj) => obj,
};
// iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then
let first_argument = match first_argument.into_buffer_object() {
Ok(buf) => {
// 1. If numberOfArgs > 1, let byteOffset be args[1]; else let byteOffset be undefined.
let byte_offset = args.get_or_undefined(1);
@ -156,59 +178,46 @@ impl<T: TypedArrayMarker> BuiltInConstructor for T {
let length = args.get_or_undefined(2);
// 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length).
BuiltinTypedArray::initialize_from_array_buffer::<T>(
// v. Return O.
return BuiltinTypedArray::initialize_from_array_buffer::<T>(
proto,
first_argument.clone(),
buf,
byte_offset,
length,
context,
)?
} else {
// iv. Else,
// 1. Assert: Type(firstArgument) is Object and firstArgument does not have
// either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot.
// 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator).
let first_argument_v = JsValue::from(first_argument.clone());
let using_iterator = first_argument_v.get_method(JsSymbol::iterator(), context)?;
// 3. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(firstArgument, usingIterator).
let values =
iterable_to_list(context, &first_argument_v, Some(using_iterator))?;
// b. Perform ? InitializeTypedArrayFromList(O, values).
BuiltinTypedArray::initialize_from_list::<T>(proto, values, context)?
} else {
// 4. Else,
// a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object.
// b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument).
BuiltinTypedArray::initialize_from_array_like::<T>(
proto,
first_argument,
context,
)?
}
};
// v. Return O.
Ok(o.into())
} else {
// c. Else,
)
.map(JsValue::from);
}
Err(obj) => obj,
};
// i. Assert: firstArgument is not an Object.
assert!(!first_argument.is_object(), "firstArgument was an object");
// iv. Else,
// ii. Let elementLength be ? ToIndex(firstArgument).
let element_length = first_argument.to_index(context)?;
// 1. Assert: Type(firstArgument) is Object and firstArgument does not have
// either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot.
// iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength).
Ok(BuiltinTypedArray::allocate::<T>(new_target, element_length, context)?.into())
// 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator).
let using_iterator = first_argument.get_method(JsSymbol::iterator(), context)?;
// 3. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(firstArgument, usingIterator).
let values = iterable_to_list(context, &first_argument.into(), Some(using_iterator))?;
// b. Perform ? InitializeTypedArrayFromList(O, values).
BuiltinTypedArray::initialize_from_list::<T>(proto, values, context)
} else {
// 4. Else,
// a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object.
// b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument).
BuiltinTypedArray::initialize_from_array_like::<T>(proto, &first_argument, context)
}
.map(JsValue::from)
// v. Return O.
}
}
@ -330,7 +339,8 @@ pub(crate) enum ContentType {
}
/// List of all typed array kinds.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize)]
#[boa_gc(empty_trace)]
pub(crate) enum TypedArrayKind {
Int8,
Uint8,

136
core/engine/src/builtins/typed_array/integer_indexed_object.rs → core/engine/src/builtins/typed_array/object.rs

@ -1,9 +1,9 @@
//! This module implements the `Integer-Indexed` exotic object.
//! This module implements the `TypedArray` exotic object.
use std::sync::atomic;
use crate::{
builtins::Number,
builtins::{array_buffer::BufferObject, Number},
object::{
internal_methods::{
ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
@ -20,22 +20,16 @@ use boa_macros::utf16;
use super::{is_valid_integer_index, TypedArrayKind};
/// An `IntegerIndexed` object is just an alias for a `TypedArray` object.
pub type IntegerIndexed = TypedArray;
/// A `TypedArrayObject` is an exotic object that performs special handling of integer
/// A `TypedArray` object is an exotic object that performs special handling of integer
/// index property keys.
///
/// This is also called an `IntegerIndexed` object in the specification.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-exotic-objects
#[derive(Debug, Clone, Trace, Finalize)]
pub struct TypedArray {
viewed_array_buffer: JsObject,
#[unsafe_ignore_trace]
viewed_array_buffer: BufferObject,
kind: TypedArrayKind,
byte_offset: u64,
byte_length: u64,
@ -45,13 +39,13 @@ pub struct TypedArray {
impl JsData for TypedArray {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static METHODS: InternalObjectMethods = InternalObjectMethods {
__get_own_property__: integer_indexed_exotic_get_own_property,
__has_property__: integer_indexed_exotic_has_property,
__define_own_property__: integer_indexed_exotic_define_own_property,
__get__: integer_indexed_exotic_get,
__set__: integer_indexed_exotic_set,
__delete__: integer_indexed_exotic_delete,
__own_property_keys__: integer_indexed_exotic_own_property_keys,
__get_own_property__: typed_array_exotic_get_own_property,
__has_property__: typed_array_exotic_has_property,
__define_own_property__: typed_array_exotic_define_own_property,
__get__: typed_array_exotic_get,
__set__: typed_array_exotic_set,
__delete__: typed_array_exotic_delete,
__own_property_keys__: typed_array_exotic_own_property_keys,
..ORDINARY_INTERNAL_METHODS
};
@ -61,7 +55,7 @@ impl JsData for TypedArray {
impl TypedArray {
pub(crate) const fn new(
viewed_array_buffer: JsObject,
viewed_array_buffer: BufferObject,
kind: TypedArrayKind,
byte_offset: u64,
byte_length: u64,
@ -85,37 +79,33 @@ impl TypedArray {
///
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
pub(crate) fn is_detached(&self) -> bool {
self.viewed_array_buffer
.borrow()
.as_buffer()
.expect("Typed array must have internal array buffer object")
.is_detached()
self.viewed_array_buffer.as_buffer().is_detached()
}
/// Get the integer indexed object's byte offset.
/// Get the `TypedArray` object's byte offset.
#[must_use]
pub const fn byte_offset(&self) -> u64 {
self.byte_offset
}
/// Get the integer indexed object's typed array kind.
/// Get the `TypedArray` object's typed array kind.
pub(crate) const fn kind(&self) -> TypedArrayKind {
self.kind
}
/// Get a reference to the integer indexed object's viewed array buffer.
/// Get a reference to the `TypedArray` object's viewed array buffer.
#[must_use]
pub const fn viewed_array_buffer(&self) -> &JsObject {
pub(crate) const fn viewed_array_buffer(&self) -> &BufferObject {
&self.viewed_array_buffer
}
/// Get the integer indexed object's byte length.
/// Get the `TypedArray` object's byte length.
#[must_use]
pub const fn byte_length(&self) -> u64 {
self.byte_length
}
/// Get the integer indexed object's array length.
/// Get the `TypedArray` object's array length.
#[must_use]
pub const fn array_length(&self) -> u64 {
self.array_length
@ -146,13 +136,13 @@ fn canonical_numeric_index_string(argument: &JsString) -> Option<f64> {
None
}
/// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects.
/// `[[GetOwnProperty]]` internal method for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
pub(crate) fn integer_indexed_exotic_get_own_property(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-getownproperty
pub(crate) fn typed_array_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
@ -170,7 +160,7 @@ pub(crate) fn integer_indexed_exotic_get_own_property(
// 1.b. If numericIndex is not undefined, then
if let Some(numeric_index) = p {
// i. Let value be IntegerIndexedElementGet(O, numericIndex).
let value = integer_indexed_element_get(obj, numeric_index);
let value = typed_array_get_element(obj, numeric_index);
// ii. If value is undefined, return undefined.
// iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
@ -188,13 +178,13 @@ pub(crate) fn integer_indexed_exotic_get_own_property(
ordinary_get_own_property(obj, key, context)
}
/// `[[HasProperty]]` internal method for Integer-Indexed exotic objects.
/// `[[HasProperty]]` internal method for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
pub(crate) fn integer_indexed_exotic_has_property(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-hasproperty
pub(crate) fn typed_array_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
@ -218,13 +208,13 @@ pub(crate) fn integer_indexed_exotic_has_property(
ordinary_has_property(obj, key, context)
}
/// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects.
/// `[[DefineOwnProperty]]` internal method for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc
pub(crate) fn integer_indexed_exotic_define_own_property(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-defineownproperty
pub(crate) fn typed_array_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
@ -269,7 +259,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property(
// vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]).
if let Some(value) = desc.value() {
integer_indexed_element_set(obj, numeric_index, value, context)?;
typed_array_set_element(obj, numeric_index, value, context)?;
}
// vii. Return true.
@ -280,13 +270,13 @@ pub(crate) fn integer_indexed_exotic_define_own_property(
ordinary_define_own_property(obj, key, desc, context)
}
/// Internal method `[[Get]]` for Integer-Indexed exotic objects.
/// Internal method `[[Get]]` for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver
pub(crate) fn integer_indexed_exotic_get(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-get
pub(crate) fn typed_array_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
@ -305,20 +295,20 @@ pub(crate) fn integer_indexed_exotic_get(
// 1.b. If numericIndex is not undefined, then
if let Some(numeric_index) = p {
// i. Return IntegerIndexedElementGet(O, numericIndex).
return Ok(integer_indexed_element_get(obj, numeric_index).unwrap_or_default());
return Ok(typed_array_get_element(obj, numeric_index).unwrap_or_default());
}
// 2. Return ? OrdinaryGet(O, P, Receiver).
ordinary_get(obj, key, receiver, context)
}
/// Internal method `[[Set]]` for Integer-Indexed exotic objects.
/// Internal method `[[Set]]` for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
pub(crate) fn integer_indexed_exotic_set(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-set
pub(crate) fn typed_array_exotic_set(
obj: &JsObject,
key: PropertyKey,
value: JsValue,
@ -340,7 +330,7 @@ pub(crate) fn integer_indexed_exotic_set(
// i. If SameValue(O, Receiver) is true, then
if JsValue::same_value(&obj.clone().into(), &receiver) {
// 1. Perform ? IntegerIndexedElementSet(O, numericIndex, V).
integer_indexed_element_set(obj, numeric_index, &value, context)?;
typed_array_set_element(obj, numeric_index, &value, context)?;
// 2. Return true.
return Ok(true);
@ -356,13 +346,13 @@ pub(crate) fn integer_indexed_exotic_set(
ordinary_set(obj, key, value, receiver, context)
}
/// Internal method `[[Delete]]` for Integer-Indexed exotic objects.
/// Internal method `[[Delete]]` for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p
pub(crate) fn integer_indexed_exotic_delete(
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-delete
pub(crate) fn typed_array_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
@ -387,21 +377,21 @@ pub(crate) fn integer_indexed_exotic_delete(
ordinary_delete(obj, key, context)
}
/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects.
/// Internal method `[[OwnPropertyKeys]]` for `TypedArray` exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-ownpropertykeys
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn integer_indexed_exotic_own_property_keys(
pub(crate) fn typed_array_exotic_own_property_keys(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let obj = obj.borrow();
let inner = obj.downcast_ref::<TypedArray>().expect(
"integer indexed exotic method should only be callable from integer indexed objects",
);
let inner = obj
.downcast_ref::<TypedArray>()
.expect("TypedArray exotic method should only be callable from TypedArray objects");
// 1. Let keys be a new empty List.
let mut keys = if inner.is_detached() {
@ -424,13 +414,13 @@ pub(crate) fn integer_indexed_exotic_own_property_keys(
Ok(keys)
}
/// Abstract operation `IntegerIndexedElementGet ( O, index )`.
/// Abstract operation `TypedArrayGetElement ( O, index )`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget
fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option<JsValue> {
/// [spec]: https://tc39.es/ecma262/sec-typedarraygetelement
fn typed_array_get_element(obj: &JsObject, index: f64) -> Option<JsValue> {
// 1. If ! IsValidIntegerIndex(O, index) is false, return undefined.
if !is_valid_integer_index(obj, index) {
return None;
@ -438,10 +428,9 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option<JsValue> {
let inner = obj
.downcast_ref::<TypedArray>()
.expect("Must be an integer indexed object");
.expect("Must be an TypedArray object");
let buffer = inner.viewed_array_buffer();
let buffer = buffer.borrow();
let buffer = buffer.as_buffer().expect("Must be a buffer");
let buffer = buffer.as_buffer();
let buffer = buffer
.data()
.expect("already checked that it's not detached");
@ -461,7 +450,7 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option<JsValue> {
// 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered).
// SAFETY: The integer indexed object guarantees that the buffer is aligned.
// SAFETY: The TypedArray object guarantees that the buffer is aligned.
// The call to `is_valid_integer_index` guarantees that the index is in-bounds.
let value = unsafe {
buffer
@ -472,22 +461,22 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option<JsValue> {
Some(value.into())
}
/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`.
/// Abstract operation `TypedArraySetElement ( O, index, value )`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset
pub(crate) fn integer_indexed_element_set(
/// [spec]: https://tc39.es/ecma262/#sec-typedarraysetelement
pub(crate) fn typed_array_set_element(
obj: &JsObject,
index: f64,
value: &JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<()> {
let obj_borrow = obj.borrow();
let inner = obj_borrow.downcast_ref::<TypedArray>().expect(
"integer indexed exotic method should only be callable from integer indexed objects",
);
let inner = obj_borrow
.downcast_ref::<TypedArray>()
.expect("TypedArray exotic method should only be callable from TypedArray objects");
// 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value).
// 2. Otherwise, let numValue be ? ToNumber(value).
@ -512,15 +501,14 @@ pub(crate) fn integer_indexed_element_set(
let indexed_position = ((index as u64 * size) + offset) as usize;
let buffer = inner.viewed_array_buffer();
let mut buffer = buffer.borrow_mut();
let mut buffer = buffer.as_buffer_mut().expect("Must be a buffer");
let mut buffer = buffer.as_buffer_mut();
let mut buffer = buffer
.data_mut()
.expect("already checked that it's not detached");
// f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered).
// SAFETY: The integer indexed object guarantees that the buffer is aligned.
// SAFETY: The TypedArray object guarantees that the buffer is aligned.
// The call to `is_valid_integer_index` guarantees that the index is in-bounds.
unsafe {
buffer

16
core/engine/src/object/builtins/jsarraybuffer.rs

@ -17,6 +17,20 @@ pub struct JsArrayBuffer {
inner: JsObject<ArrayBuffer>,
}
impl From<JsArrayBuffer> for JsObject<ArrayBuffer> {
#[inline]
fn from(value: JsArrayBuffer) -> Self {
value.inner
}
}
impl From<JsObject<ArrayBuffer>> for JsArrayBuffer {
#[inline]
fn from(value: JsObject<ArrayBuffer>) -> Self {
Self { inner: value }
}
}
// TODO: Add constructors that also take the `detach_key` as argument.
impl JsArrayBuffer {
/// Create a new array buffer with byte length.
@ -157,7 +171,7 @@ impl JsArrayBuffer {
///
/// # Note
///
/// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detached
/// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detach
/// key is required. By default, the key is set to `undefined`.
///
/// ```

91
core/engine/src/object/builtins/jsdataview.rs

@ -1,6 +1,6 @@
//! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object
use crate::{
builtins::DataView,
builtins::{array_buffer::BufferObject, DataView},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType,
@ -33,8 +33,23 @@ use std::ops::Deref;
/// # }
/// ```
#[derive(Debug, Clone, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct JsDataView {
inner: JsObject,
inner: JsObject<DataView>,
}
impl From<JsDataView> for JsObject<DataView> {
#[inline]
fn from(value: JsDataView) -> Self {
value.inner
}
}
impl From<JsObject<DataView>> for JsDataView {
#[inline]
fn from(value: JsObject<DataView>) -> Self {
Self { inner: value }
}
}
impl JsDataView {
@ -90,12 +105,11 @@ impl JsDataView {
let prototype =
get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?;
let obj = JsObject::from_proto_and_data_with_shared_shape(
let obj = JsObject::new(
context.root_shape(),
prototype,
DataView {
// TODO: Remove upcast.
viewed_array_buffer: array_buffer.into(),
viewed_array_buffer: BufferObject::Buffer(array_buffer.into()),
byte_length,
byte_offset,
},
@ -107,32 +121,33 @@ impl JsDataView {
/// Create a new `JsDataView` object from an existing object.
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if object.is::<DataView>() {
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not a DataView")
.into())
}
object
.downcast::<DataView>()
.map(|inner| Self { inner })
.map_err(|_| {
JsNativeError::typ()
.with_message("object is not a DataView")
.into()
})
}
/// Returns the `viewed_array_buffer` field for [`JsDataView`]
#[inline]
pub fn buffer(&self, context: &mut Context) -> JsResult<JsValue> {
DataView::get_buffer(&self.inner.clone().into(), &[], context)
DataView::get_buffer(&self.inner.clone().upcast().into(), &[], context)
}
/// Returns the `byte_length` property of [`JsDataView`] as a u64 integer
#[inline]
pub fn byte_length(&self, context: &mut Context) -> JsResult<u64> {
DataView::get_byte_length(&self.inner.clone().into(), &[], context)
DataView::get_byte_length(&self.inner.clone().upcast().into(), &[], context)
.map(|v| v.as_number().expect("value should be a number") as u64)
}
/// Returns the `byte_offset` field property of [`JsDataView`] as a u64 integer
#[inline]
pub fn byte_offset(&self, context: &mut Context) -> JsResult<u64> {
DataView::get_byte_offset(&self.inner.clone().into(), &[], context)
DataView::get_byte_offset(&self.inner.clone().upcast().into(), &[], context)
.map(|v| v.as_number().expect("byte_offset value must be a number") as u64)
}
@ -145,7 +160,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<i64> {
DataView::get_big_int64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -161,7 +176,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<u64> {
DataView::get_big_uint64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -177,7 +192,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<f32> {
DataView::get_float32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -193,7 +208,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<f64> {
DataView::get_float64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -209,7 +224,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<i8> {
DataView::get_int8(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -225,7 +240,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<i16> {
DataView::get_int16(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -241,7 +256,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<i32> {
DataView::get_int32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -257,7 +272,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<u8> {
DataView::get_uint8(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -273,7 +288,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<u16> {
DataView::get_uint16(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -289,7 +304,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<u32> {
DataView::get_uint32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
@ -306,7 +321,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_big_int64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -322,7 +337,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_big_uint64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -338,7 +353,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_float32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -354,7 +369,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_float64(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -370,7 +385,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int8(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -386,7 +401,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int16(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -402,7 +417,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -418,7 +433,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint8(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -434,7 +449,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint16(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -450,7 +465,7 @@ impl JsDataView {
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint32(
&self.inner.clone().into(),
&self.inner.clone().upcast().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
@ -460,19 +475,19 @@ impl JsDataView {
impl From<JsDataView> for JsObject {
#[inline]
fn from(o: JsDataView) -> Self {
o.inner.clone()
o.inner.upcast()
}
}
impl From<JsDataView> for JsValue {
#[inline]
fn from(o: JsDataView) -> Self {
o.inner.clone().into()
o.inner.upcast().into()
}
}
impl Deref for JsDataView {
type Target = JsObject;
type Target = JsObject<DataView>;
#[inline]
fn deref(&self) -> &Self::Target {

49
core/engine/src/object/builtins/jssharedarraybuffer.rs

@ -11,8 +11,23 @@ use std::ops::Deref;
/// `JsSharedArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object
#[derive(Debug, Clone, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct JsSharedArrayBuffer {
inner: JsObject,
inner: JsObject<SharedArrayBuffer>,
}
impl From<JsSharedArrayBuffer> for JsObject<SharedArrayBuffer> {
#[inline]
fn from(value: JsSharedArrayBuffer) -> Self {
value.inner
}
}
impl From<JsObject<SharedArrayBuffer>> for JsSharedArrayBuffer {
#[inline]
fn from(value: JsObject<SharedArrayBuffer>) -> Self {
JsSharedArrayBuffer { inner: value }
}
}
impl JsSharedArrayBuffer {
@ -42,8 +57,7 @@ impl JsSharedArrayBuffer {
.shared_array_buffer()
.prototype();
let inner =
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buffer);
let inner = JsObject::new(context.root_shape(), proto, buffer);
Self { inner }
}
@ -55,50 +69,47 @@ impl JsSharedArrayBuffer {
/// the object.
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if object.is::<SharedArrayBuffer>() {
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not an ArrayBuffer")
.into())
}
object
.downcast::<SharedArrayBuffer>()
.map(|inner| Self { inner })
.map_err(|_| {
JsNativeError::typ()
.with_message("object is not a SharedArrayBuffer")
.into()
})
}
/// Returns the byte length of the array buffer.
#[inline]
#[must_use]
pub fn byte_length(&self) -> usize {
self.downcast_ref::<SharedArrayBuffer>()
.expect("should be an array buffer")
.len()
self.borrow().data.len()
}
/// Gets the raw buffer of this `JsSharedArrayBuffer`.
#[inline]
#[must_use]
pub fn inner(&self) -> SharedArrayBuffer {
self.downcast_ref::<SharedArrayBuffer>()
.expect("should be an array buffer")
.clone()
self.borrow().data.clone()
}
}
impl From<JsSharedArrayBuffer> for JsObject {
#[inline]
fn from(o: JsSharedArrayBuffer) -> Self {
o.inner.clone()
o.inner.upcast()
}
}
impl From<JsSharedArrayBuffer> for JsValue {
#[inline]
fn from(o: JsSharedArrayBuffer) -> Self {
o.inner.clone().into()
o.inner.upcast().into()
}
}
impl Deref for JsSharedArrayBuffer {
type Target = JsObject;
type Target = JsObject<SharedArrayBuffer>;
#[inline]
fn deref(&self) -> &Self::Target {

60
core/engine/src/object/jsobject.rs

@ -8,7 +8,11 @@ use super::{
JsPrototype, NativeObject, Object, PrivateName, PropertyMap,
};
use crate::{
builtins::{array::ARRAY_EXOTIC_INTERNAL_METHODS, object::OrdinaryObject},
builtins::{
array::ARRAY_EXOTIC_INTERNAL_METHODS,
array_buffer::{ArrayBuffer, BufferObject, SharedArrayBuffer},
object::OrdinaryObject,
},
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
@ -212,6 +216,24 @@ impl JsObject {
}
}
/// Downcasts the object's inner data to `T` without verifying the inner type of `T`.
///
/// # Safety
///
/// For this cast to be sound, `self` must contain an instance of `T` inside its inner data.
#[must_use]
pub unsafe fn downcast_unchecked<T: NativeObject>(self) -> JsObject<T> {
let ptr: NonNull<GcBox<VTableObject<T>>> = Gc::into_raw(self.inner).cast();
// SAFETY: The caller guarantees `T` is the original inner data type of the underlying
// object.
unsafe {
JsObject {
inner: Gc::from_raw(ptr),
}
}
}
/// Downcasts a reference to the object,
/// if the object is of type `T`.
///
@ -268,18 +290,6 @@ impl JsObject {
std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS)
}
/// Checks if it's an `ArrayBuffer` or `SharedArrayBuffer` object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
#[inline]
#[must_use]
#[track_caller]
pub fn is_buffer(&self) -> bool {
self.borrow().as_buffer().is_some()
}
/// Converts an object to a primitive.
///
/// Diverges from the spec to prevent a stack overflow when the object is recursive.
@ -535,6 +545,30 @@ Cannot both specify accessors and a value or writable attribute",
}
None
}
/// Casts to a `BufferObject` if the object is an `ArrayBuffer` or a `SharedArrayBuffer`.
#[inline]
pub(crate) fn into_buffer_object(self) -> Result<BufferObject, JsObject> {
let obj = self.borrow();
if obj.is::<ArrayBuffer>() {
drop(obj);
// SAFETY: We have verified that the inner data of `self` is of type `ArrayBuffer`.
return Ok(BufferObject::Buffer(unsafe {
self.downcast_unchecked::<ArrayBuffer>()
}));
}
if obj.is::<SharedArrayBuffer>() {
drop(obj);
// SAFETY: We have verified that the inner data of `self` is of type `SharedArrayBuffer`.
return Ok(BufferObject::SharedBuffer(unsafe {
self.downcast_unchecked::<SharedArrayBuffer>()
}));
}
drop(obj);
Err(self)
}
}
impl<T: NativeObject + ?Sized> JsObject<T> {

28
core/engine/src/object/mod.rs

@ -10,7 +10,6 @@ use thin_vec::ThinVec;
use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape};
use crate::{
builtins::{
array_buffer::{ArrayBuffer, BufferRef, BufferRefMut, SharedArrayBuffer},
function::{
arguments::{MappedArguments, UnmappedArguments},
ConstructorKind,
@ -352,33 +351,6 @@ impl Object<dyn NativeObject> {
self.data.downcast_mut::<T>()
}
/// Gets the buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`.
#[inline]
#[must_use]
pub(crate) fn as_buffer(&self) -> Option<BufferRef<'_>> {
if let Some(buffer) = self.downcast_ref::<ArrayBuffer>() {
return Some(BufferRef::Buffer(buffer));
}
self.downcast_ref::<SharedArrayBuffer>()
.map(BufferRef::SharedBuffer)
}
/// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`.
#[inline]
pub(crate) fn as_buffer_mut(&mut self) -> Option<BufferRefMut<'_>> {
// Workaround for Problem case 3 of the current borrow checker.
// https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions
if self.is::<ArrayBuffer>() {
match self.downcast_mut::<ArrayBuffer>() {
Some(buffer) => return Some(BufferRefMut::Buffer(buffer)),
None => unreachable!(),
}
}
self.downcast_mut::<SharedArrayBuffer>()
.map(BufferRefMut::SharedBuffer)
}
/// Checks if this object is an `Arguments` object.
pub(crate) fn is_arguments(&self) -> bool {
self.is::<UnmappedArguments>() || self.is::<MappedArguments>()

Loading…
Cancel
Save