From 08722382d1d537a7fd0431582c204403512dc106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Mon, 29 Jan 2024 02:52:01 +0000 Subject: [PATCH] Implement non-erased `JsObject`s (#3618) --- core/engine/src/builtins/array_buffer/mod.rs | 8 +- .../engine/src/builtins/array_buffer/utils.rs | 8 +- .../src/builtins/typed_array/builtin.rs | 32 +- .../src/object/builtins/jsarraybuffer.rs | 63 ++- core/engine/src/object/builtins/jsdataview.rs | 18 +- .../engine/src/object/internal_methods/mod.rs | 4 +- core/engine/src/object/jsobject.rs | 406 +++++++++++------- core/engine/src/object/mod.rs | 8 +- core/gc/src/internals/ephemeron_box.rs | 12 +- core/gc/src/internals/gc_box.rs | 8 - core/gc/src/pointers/ephemeron.rs | 4 +- core/gc/src/pointers/gc.rs | 14 +- core/gc/src/pointers/mod.rs | 6 + examples/src/bin/jsarraybuffer.rs | 5 +- 14 files changed, 335 insertions(+), 261 deletions(-) diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index 65689c08047..0b9a358271b 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -207,7 +207,9 @@ impl BuiltInConstructor for ArrayBuffer { let byte_length = args.get_or_undefined(0).to_index(context)?; // 3. Return ? AllocateArrayBuffer(NewTarget, byteLength). - Ok(Self::allocate(new_target, byte_length, context)?.into()) + Ok(Self::allocate(new_target, byte_length, context)? + .upcast() + .into()) } } @@ -384,7 +386,7 @@ impl ArrayBuffer { constructor: &JsValue, byte_length: u64, context: &mut Context, - ) -> JsResult { + ) -> JsResult> { // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). let prototype = get_prototype_from_constructor( constructor, @@ -397,7 +399,7 @@ impl ArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - let obj = JsObject::from_proto_and_data_with_shared_shape( + let obj = JsObject::new( context.root_shape(), prototype, Self { diff --git a/core/engine/src/builtins/array_buffer/utils.rs b/core/engine/src/builtins/array_buffer/utils.rs index 7c381ce3c8f..85c3076e6c9 100644 --- a/core/engine/src/builtins/array_buffer/utils.rs +++ b/core/engine/src/builtins/array_buffer/utils.rs @@ -120,7 +120,7 @@ impl SliceRef<'_> { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer - pub(crate) fn clone(&self, context: &mut Context) -> JsResult { + pub(crate) fn clone(&self, context: &mut Context) -> JsResult> { // 1. Assert: IsDetachedBuffer(srcBuffer) is false. // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). @@ -140,12 +140,10 @@ impl SliceRef<'_> { // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. { - let mut target_buffer = target_buffer - .downcast_mut::() - .expect("This must be an ArrayBuffer"); + let mut target_buffer = target_buffer.borrow_mut(); let target_block = target_buffer .data - .as_deref_mut() + .data_mut() .expect("ArrayBuffer cannot be detached here"); // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 078393f1e60..decef0dd02b 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -2096,7 +2096,8 @@ impl BuiltinTypedArray { .subslice(src_byte_offset..src_byte_offset + src_byte_length) .clone(context)? }; - src_buffer_obj = s; + // TODO: skip this upcast + src_buffer_obj = s.upcast(); // d. Let srcByteIndex be 0. 0 @@ -3068,7 +3069,14 @@ impl BuiltinTypedArray { // 9. Set O.[[ArrayLength]] to length. // 10. Return O. - Ok(TypedArray::new(data, T::ERASED, 0, byte_length, length)) + // TODO: skip this upcast. + Ok(TypedArray::new( + data.upcast(), + T::ERASED, + 0, + byte_length, + length, + )) } /// @@ -3209,11 +3217,12 @@ impl BuiltinTypedArray { context, )?; { - let mut data = data_obj - .downcast_mut::() - .expect("Must be ArrayBuffer"); - let mut data = - SliceRefMut::Slice(data.data_mut().expect("a new buffer cannot be detached")); + let mut data = data_obj.borrow_mut(); + let mut data = SliceRefMut::Slice( + data.data + .data_mut() + .expect("a new buffer cannot be detached"), + ); // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. if src_type.content_type() != element_type.content_type() { @@ -3279,10 +3288,17 @@ 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, element_type, 0, byte_length, element_length), + TypedArray::new( + new_buffer.upcast(), + element_type, + 0, + byte_length, + element_length, + ), ); // 16. Return unused. diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index 2f638ca4d55..90f1075cd42 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -3,9 +3,7 @@ use crate::{ builtins::array_buffer::ArrayBuffer, context::intrinsics::StandardConstructors, error::JsNativeError, - object::{ - internal_methods::get_prototype_from_constructor, ErasedObject, JsObject, JsObjectType, - }, + object::{internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, Object}, value::TryFromJs, Context, JsResult, JsValue, }; @@ -14,8 +12,9 @@ use std::ops::Deref; /// `JsArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object #[derive(Debug, Clone, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] pub struct JsArrayBuffer { - inner: JsObject, + inner: JsObject, } // TODO: Add constructors that also take the `detach_key` as argument. @@ -103,7 +102,7 @@ impl JsArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - let obj = JsObject::from_proto_and_data_with_shared_shape( + let obj = JsObject::new( context.root_shape(), prototype, ArrayBuffer::from_data(block, JsValue::Undefined), @@ -117,13 +116,14 @@ impl JsArrayBuffer { /// This does not clone the fields of the array buffer, it only does a shallow clone of the object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is::() { - Ok(Self { inner: object }) - } else { - Err(JsNativeError::typ() - .with_message("object is not an ArrayBuffer") - .into()) - } + object + .downcast::() + .map(|inner| Self { inner }) + .map_err(|_| { + JsNativeError::typ() + .with_message("object is not an ArrayBuffer") + .into() + }) } /// Returns the byte length of the array buffer. @@ -141,18 +141,16 @@ impl JsArrayBuffer { /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; /// /// // Take the inner buffer - /// let buffer_length = array_buffer.byte_length(context); + /// let buffer_length = array_buffer.byte_length(); /// /// assert_eq!(buffer_length, 5); /// # Ok(()) /// # } /// ``` #[inline] - pub fn byte_length(&self, context: &mut Context) -> usize { - ArrayBuffer::get_byte_length(&self.inner.clone().into(), &[], context) - .expect("it should not throw") - .as_number() - .expect("expected a number") as usize + #[must_use] + pub fn byte_length(&self) -> usize { + self.inner.borrow().data.len() } /// Take the inner `ArrayBuffer`'s `array_buffer_data` field and replace it with `None` @@ -188,8 +186,8 @@ impl JsArrayBuffer { #[inline] pub fn detach(&self, detach_key: &JsValue) -> JsResult> { self.inner - .downcast_mut::() - .expect("inner must be an ArrayBuffer") + .borrow_mut() + .data .detach(detach_key)? .ok_or_else(|| { JsNativeError::typ() @@ -224,12 +222,7 @@ impl JsArrayBuffer { #[inline] #[must_use] pub fn data(&self) -> Option> { - debug_assert!( - self.inner.is::(), - "inner must be an ArrayBuffer" - ); - - GcRef::try_map(self.inner.downcast_ref::()?, ArrayBuffer::data) + GcRef::try_map(self.inner.borrow(), |o| o.data.data()) } /// Get a mutable reference to the [`JsArrayBuffer`]'s data. @@ -261,35 +254,27 @@ impl JsArrayBuffer { /// ``` #[inline] #[must_use] - pub fn data_mut(&self) -> Option> { - debug_assert!( - self.inner.is::(), - "inner must be an ArrayBuffer" - ); - - GcRefMut::try_map( - self.inner.downcast_mut::()?, - ArrayBuffer::data_mut, - ) + pub fn data_mut(&self) -> Option, [u8]>> { + GcRefMut::try_map(self.inner.borrow_mut(), |o| o.data.data_mut()) } } impl From for JsObject { #[inline] fn from(o: JsArrayBuffer) -> Self { - o.inner.clone() + o.inner.upcast() } } impl From for JsValue { #[inline] fn from(o: JsArrayBuffer) -> Self { - o.inner.clone().into() + o.inner.upcast().into() } } impl Deref for JsArrayBuffer { - type Target = JsObject; + type Target = JsObject; #[inline] fn deref(&self) -> &Self::Target { diff --git a/core/engine/src/object/builtins/jsdataview.rs b/core/engine/src/object/builtins/jsdataview.rs index be8d214136d..d242fc7ab98 100644 --- a/core/engine/src/object/builtins/jsdataview.rs +++ b/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::{array_buffer::ArrayBuffer, DataView}, + builtins::DataView, context::intrinsics::StandardConstructors, object::{ internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType, @@ -27,7 +27,7 @@ use std::ops::Deref; /// /// // Create a new Dataview from pre-existing ArrayBuffer /// let data_view = -/// JsDataView::from_js_array_buffer(&array_buffer, None, None, context)?; +/// JsDataView::from_js_array_buffer(array_buffer, None, None, context)?; /// /// # Ok(()) /// # } @@ -40,26 +40,23 @@ pub struct JsDataView { impl JsDataView { /// Create a new `JsDataView` object from an existing `JsArrayBuffer`. pub fn from_js_array_buffer( - array_buffer: &JsArrayBuffer, + array_buffer: JsArrayBuffer, offset: Option, byte_length: Option, context: &mut Context, ) -> JsResult { let (byte_offset, byte_length) = { - let buffer = array_buffer.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("buffer must be an ArrayBuffer") - })?; - + let buffer = array_buffer.borrow(); let provided_offset = offset.unwrap_or(0_u64); // Check if buffer is detached. - if buffer.is_detached() { + if buffer.data.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); }; - let array_buffer_length = buffer.len() as u64; + let array_buffer_length = buffer.data.len() as u64; if provided_offset > array_buffer_length { return Err(JsNativeError::range() @@ -97,7 +94,8 @@ impl JsDataView { context.root_shape(), prototype, DataView { - viewed_array_buffer: (**array_buffer).clone(), + // TODO: Remove upcast. + viewed_array_buffer: array_buffer.into(), byte_length, byte_offset, }, diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index bb37c9ca8a4..37db4c3c29d 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -1043,7 +1043,7 @@ where Ok(default(realm.intrinsics().constructors()).prototype()) } -pub(crate) fn non_existant_call( +fn non_existant_call( _obj: &JsObject, _argument_count: usize, context: &mut Context, @@ -1054,7 +1054,7 @@ pub(crate) fn non_existant_call( .into()) } -pub(crate) fn non_existant_construct( +fn non_existant_construct( _obj: &JsObject, _argument_count: usize, context: &mut Context, diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 3aa2fba6da3..364dae04f1b 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -3,16 +3,12 @@ //! The `JsObject` is a garbage collected Object. use super::{ - internal_methods::{ - non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods, - }, + internal_methods::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, shape::RootShape, JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; use crate::{ - builtins::{ - array::ARRAY_EXOTIC_INTERNAL_METHODS, function::OrdinaryFunction, object::OrdinaryObject, - }, + builtins::{array::ARRAY_EXOTIC_INTERNAL_METHODS, object::OrdinaryObject}, context::intrinsics::Intrinsics, error::JsNativeError, js_string, @@ -45,9 +41,18 @@ pub type ErasedObject = Object; pub(crate) type ErasedVTableObject = VTableObject; /// Garbage collected `Object`. -#[derive(Trace, Finalize, Clone)] -pub struct JsObject { - inner: Gc>, +#[derive(Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] +pub struct JsObject { + inner: Gc>, +} + +impl Clone for JsObject { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } } /// An `Object` that has an additional `vtable` with its internal methods. @@ -79,7 +84,9 @@ impl JsObject { vtable, }); - Self { inner: upcast(gc) } + Self { + inner: coerce_gc(gc), + } } /// Creates a new ordinary object with its prototype set to the `Object` prototype. @@ -131,7 +138,9 @@ impl JsObject { vtable: internal_methods, }); - Self { inner: upcast(gc) } + Self { + inner: coerce_gc(gc), + } } /// Creates a new object with the provided prototype and object data. @@ -160,70 +169,115 @@ impl JsObject { vtable: internal_methods, }); - Self { inner: upcast(gc) } + Self { + inner: coerce_gc(gc), + } } - /// Immutably borrows the `Object`. + /// Downcasts the object's inner data if the object is of type `T`. /// - /// The borrow lasts until the returned `Ref` exits scope. - /// Multiple immutable borrows can be taken out at the same time. + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + pub fn downcast(self) -> Result, Self> { + if self.borrow().is::() { + let ptr: NonNull>> = Gc::into_raw(self.inner); + + // SAFETY: the rooted `Gc` ensures we can read the inner `GcBox` in a sound way. + #[cfg(debug_assertions)] + unsafe { + let erased = ptr.as_ref(); + + // Some sanity checks to ensure we're doing the correct cast. + assert_eq!( + std::mem::size_of_val(erased), + std::mem::size_of::>>() + ); + assert_eq!( + std::mem::align_of_val(erased), + std::mem::align_of::>>() + ); + } + + let ptr: NonNull>> = ptr.cast(); + + // SAFETY: The conversion between an `Any` and its downcasted type must be valid. + // The pointer returned by `Gc::into_raw` is the same one that is passed to `Gc::from_raw`, + // just downcasted to the type `T`. + let inner = unsafe { Gc::from_raw(ptr) }; + + Ok(JsObject { inner }) + } else { + Err(self) + } + } + + /// Downcasts a reference to the object, + /// if the object is of type `T`. /// /// # Panics /// /// Panics if the object is currently mutably borrowed. - #[inline] #[must_use] #[track_caller] - pub fn borrow(&self) -> Ref<'_, ErasedObject> { - self.try_borrow().expect("Object already mutably borrowed") + pub fn downcast_ref(&self) -> Option> { + Ref::try_map(self.borrow(), ErasedObject::downcast_ref) } - /// Mutably borrows the Object. - /// - /// The borrow lasts until the returned `RefMut` exits scope. - /// The object cannot be borrowed while this borrow is active. + /// Downcasts a mutable reference to the object, + /// if the object is type native object type `T`. /// /// # Panics + /// /// Panics if the object is currently borrowed. - #[inline] #[must_use] #[track_caller] - pub fn borrow_mut(&self) -> RefMut<'_, ErasedObject, ErasedObject> { - self.try_borrow_mut().expect("Object already borrowed") + pub fn downcast_mut(&self) -> Option> { + RefMut::try_map(self.borrow_mut(), ErasedObject::downcast_mut) } - /// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed. + /// Checks if this object is an instance of a certain `NativeObject`. /// - /// The borrow lasts until the returned `GcCellRef` exits scope. - /// Multiple immutable borrows can be taken out at the same time. + /// # Panics /// - /// This is the non-panicking variant of [`borrow`](#method.borrow). + /// Panics if the object is currently mutably borrowed. #[inline] - pub fn try_borrow(&self) -> StdResult, BorrowError> { - self.inner.object.try_borrow().map_err(|_| BorrowError) + #[must_use] + #[track_caller] + pub fn is(&self) -> bool { + self.borrow().is::() } - /// Mutably borrows the object, returning an error if the value is currently borrowed. + /// Checks if it's an ordinary object. /// - /// The borrow lasts until the returned `GcCellRefMut` exits scope. - /// The object be borrowed while this borrow is active. + /// # Panics /// - /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + /// Panics if the object is currently mutably borrowed. #[inline] - pub fn try_borrow_mut( - &self, - ) -> StdResult, BorrowMutError> { - self.inner - .object - .try_borrow_mut() - .map_err(|_| BorrowMutError) + #[must_use] + #[track_caller] + pub fn is_ordinary(&self) -> bool { + self.is::() } - /// Checks if the garbage collected memory is the same. + /// Checks if it's an `Array` object. + #[inline] #[must_use] + #[track_caller] + pub fn is_array(&self) -> bool { + 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] - pub fn equals(lhs: &Self, rhs: &Self) -> bool { - std::ptr::eq(lhs.as_ref(), rhs.as_ref()) + #[must_use] + #[track_caller] + pub fn is_buffer(&self) -> bool { + self.borrow().as_buffer().is_some() } /// Converts an object to a primitive. @@ -301,107 +355,6 @@ impl JsObject { .into()) } - /// Downcast a reference to the object, - /// if the object is of type `T`. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[must_use] - #[track_caller] - pub fn downcast_ref(&self) -> Option> { - Ref::try_map(self.borrow(), ErasedObject::downcast_ref) - } - - /// Downcast a mutable reference to the object, - /// if the object is type native object type `T`. - /// - /// # Panics - /// - /// Panics if the object is currently borrowed. - #[must_use] - #[track_caller] - pub fn downcast_mut(&self) -> Option> { - RefMut::try_map(self.borrow_mut(), ErasedObject::downcast_mut) - } - - /// Get the prototype of the object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn prototype(&self) -> JsPrototype { - self.borrow().prototype() - } - - /// Get the extensibility of the object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - pub(crate) fn extensible(&self) -> bool { - self.borrow().extensible - } - - /// Set the prototype of the object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed - #[inline] - #[track_caller] - #[allow(clippy::must_use_candidate)] - pub fn set_prototype(&self, prototype: JsPrototype) -> bool { - self.borrow_mut().set_prototype(prototype) - } - - /// Checks if this object is an instance of a certain `NativeObject`. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is(&self) -> bool { - self.borrow().is::() - } - - /// Checks if it's an ordinary object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_ordinary(&self) -> bool { - self.is::() - } - - /// Checks if it's an `Array` object. - #[inline] - #[must_use] - #[track_caller] - pub fn is_array(&self) -> bool { - 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() - } - /// The abstract operation `ToPropertyDescriptor`. /// /// More information: @@ -582,6 +535,139 @@ Cannot both specify accessors and a value or writable attribute", } None } +} + +impl JsObject { + /// Creates a new `JsObject` from its root shape, prototype, and data. + /// + /// Note that the returned object will not be erased to be convertible to a + /// `JsValue`. To erase the pointer, call [`JsObject::upcast`]. + pub fn new>>(root_shape: &RootShape, prototype: O, data: T) -> Self + where + T: Sized, + { + let internal_methods = data.internal_methods(); + let inner = Gc::new(VTableObject { + object: GcRefCell::new(Object { + data, + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), + extensible: true, + private_elements: ThinVec::new(), + }), + vtable: internal_methods, + }); + + Self { inner } + } + + /// Upcasts this object's inner data from a specific type `T` to an erased type + /// `dyn NativeObject`. + #[must_use] + pub fn upcast(self) -> JsObject + where + T: Sized, + { + JsObject { + inner: coerce_gc(self.inner), + } + } + + /// Immutably borrows the `Object`. + /// + /// The borrow lasts until the returned `Ref` exits scope. + /// Multiple immutable borrows can be taken out at the same time. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn borrow(&self) -> Ref<'_, Object> { + self.try_borrow().expect("Object already mutably borrowed") + } + + /// Mutably borrows the Object. + /// + /// The borrow lasts until the returned `RefMut` exits scope. + /// The object cannot be borrowed while this borrow is active. + /// + /// # Panics + /// Panics if the object is currently borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn borrow_mut(&self) -> RefMut<'_, Object, Object> { + self.try_borrow_mut().expect("Object already borrowed") + } + + /// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed. + /// + /// The borrow lasts until the returned `GcCellRef` exits scope. + /// Multiple immutable borrows can be taken out at the same time. + /// + /// This is the non-panicking variant of [`borrow`](#method.borrow). + #[inline] + pub fn try_borrow(&self) -> StdResult>, BorrowError> { + self.inner.object.try_borrow().map_err(|_| BorrowError) + } + + /// Mutably borrows the object, returning an error if the value is currently borrowed. + /// + /// The borrow lasts until the returned `GcCellRefMut` exits scope. + /// The object be borrowed while this borrow is active. + /// + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + #[inline] + pub fn try_borrow_mut(&self) -> StdResult, Object>, BorrowMutError> { + self.inner + .object + .try_borrow_mut() + .map_err(|_| BorrowMutError) + } + + /// Checks if the garbage collected memory is the same. + #[must_use] + #[inline] + pub fn equals(lhs: &Self, rhs: &Self) -> bool { + Gc::ptr_eq(lhs.inner(), rhs.inner()) + } + + /// Get the prototype of the object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn prototype(&self) -> JsPrototype { + self.borrow().prototype() + } + + /// Get the extensibility of the object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + pub(crate) fn extensible(&self) -> bool { + self.borrow().extensible + } + + /// Set the prototype of the object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed + #[inline] + #[track_caller] + #[allow(clippy::must_use_candidate)] + pub fn set_prototype(&self, prototype: JsPrototype) -> bool { + self.borrow_mut().set_prototype(prototype) + } /// Helper function for property insertion. #[track_caller] @@ -613,9 +699,8 @@ Cannot both specify accessors and a value or writable attribute", /// [spec]: https://tc39.es/ecma262/#sec-iscallable #[inline] #[must_use] - #[allow(clippy::fn_address_comparisons)] pub fn is_callable(&self) -> bool { - self.inner.vtable.__call__ != non_existant_call + self.inner.vtable.__call__ != ORDINARY_INTERNAL_METHODS.__call__ } /// It determines if Object is a function object with a `[[Construct]]` internal method. @@ -626,16 +711,15 @@ Cannot both specify accessors and a value or writable attribute", /// [spec]: https://tc39.es/ecma262/#sec-isconstructor #[inline] #[must_use] - #[allow(clippy::fn_address_comparisons)] pub fn is_constructor(&self) -> bool { - self.inner.vtable.__construct__ != non_existant_construct + self.inner.vtable.__construct__ != ORDINARY_INTERNAL_METHODS.__construct__ } pub(crate) fn vtable(&self) -> &'static InternalObjectMethods { self.inner.vtable } - pub(crate) const fn inner(&self) -> &Gc> { + pub(crate) const fn inner(&self) -> &Gc> { &self.inner } @@ -646,29 +730,29 @@ Cannot both specify accessors and a value or writable attribute", } } -impl AsRef> for JsObject { +impl AsRef>> for JsObject { #[inline] - fn as_ref(&self) -> &GcRefCell { + fn as_ref(&self) -> &GcRefCell> { &self.inner.object } } -impl From>> for JsObject { +impl From>> for JsObject { #[inline] - fn from(inner: Gc>) -> Self { + fn from(inner: Gc>) -> Self { Self { inner } } } -impl PartialEq for JsObject { +impl PartialEq for JsObject { fn eq(&self, other: &Self) -> bool { Self::equals(self, other) } } -impl Eq for JsObject {} +impl Eq for JsObject {} -impl Hash for JsObject { +impl Hash for JsObject { fn hash(&self, state: &mut H) { std::ptr::hash(self.as_ref(), state); } @@ -782,7 +866,7 @@ impl RecursionLimiter { } } -impl Debug for JsObject { +impl Debug for JsObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { let limiter = RecursionLimiter::new(self.as_ref()); @@ -797,7 +881,7 @@ impl Debug for JsObject { let ptr = ptr.cast::<()>(); let obj = self.borrow(); let kind = obj.data.type_name_of_value(); - if obj.is::() { + if self.is_callable() { let name_prop = obj .properties() .get(&PropertyKey::String(JsString::from("name"))); @@ -821,7 +905,7 @@ impl Debug for JsObject { } /// Upcasts the reference to an object from a specific type `T` to an erased type `dyn NativeObject`. -fn upcast(ptr: Gc>) -> Gc> { +fn coerce_gc(ptr: Gc>) -> Gc> { // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by // https://github.com/rust-lang/rust/issues/18598 unsafe { diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index 459c624b3a8..04831e9d89b 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -31,7 +31,6 @@ use boa_gc::{Finalize, Trace}; use std::{ any::{Any, TypeId}, fmt::Debug, - ops::Deref, }; #[cfg(test)] @@ -51,10 +50,7 @@ pub(crate) use builtins::*; pub use datatypes::JsData; pub use jsobject::*; -pub(crate) trait JsObjectType: - Into + Into + Deref -{ -} +pub(crate) trait JsObjectType: Into + Into {} /// Const `constructor`, usually set on prototypes as a key to point to their respective constructor object. pub const CONSTRUCTOR: &[u16] = utf16!("constructor"); @@ -189,7 +185,7 @@ pub struct Object { /// The `[[PrivateElements]]` internal slot. private_elements: ThinVec<(PrivateName, PrivateElement)>, /// The inner object data - data: T, + pub(crate) data: T, } impl Default for Object { diff --git a/core/gc/src/internals/ephemeron_box.rs b/core/gc/src/internals/ephemeron_box.rs index 0e7fe2fb321..e7ed830930e 100644 --- a/core/gc/src/internals/ephemeron_box.rs +++ b/core/gc/src/internals/ephemeron_box.rs @@ -1,8 +1,5 @@ use crate::{trace::Trace, Gc, GcBox, Tracer}; -use std::{ - cell::UnsafeCell, - ptr::{self, NonNull}, -}; +use std::{cell::UnsafeCell, ptr::NonNull}; use super::GcHeader; @@ -37,13 +34,6 @@ impl EphemeronBox { } } - /// Returns `true` if the two references refer to the same `EphemeronBox`. - pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool { - // Use .header to ignore fat pointer vtables, to work around - // https://github.com/rust-lang/rust/issues/46139 - ptr::eq(&this.header, &other.header) - } - /// Returns a reference to the ephemeron's value or None. /// /// # Safety diff --git a/core/gc/src/internals/gc_box.rs b/core/gc/src/internals/gc_box.rs index 7331472b506..185a61ec6c1 100644 --- a/core/gc/src/internals/gc_box.rs +++ b/core/gc/src/internals/gc_box.rs @@ -1,5 +1,4 @@ use crate::Trace; -use std::ptr::{self}; use super::{vtable_of, DropFn, GcHeader, RunFinalizerFn, TraceFn, TraceNonRootsFn, VTable}; @@ -25,13 +24,6 @@ impl GcBox { } impl GcBox { - /// Returns `true` if the two references refer to the same `GcBox`. - pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool { - // Use .header to ignore fat pointer vtables, to work around - // https://github.com/rust-lang/rust/issues/46139 - ptr::eq(&this.header, &other.header) - } - /// Returns a reference to the `GcBox`'s value. pub(crate) const fn value(&self) -> &T { &self.value diff --git a/core/gc/src/pointers/ephemeron.rs b/core/gc/src/pointers/ephemeron.rs index 04119d011cf..f00969ef2a2 100644 --- a/core/gc/src/pointers/ephemeron.rs +++ b/core/gc/src/pointers/ephemeron.rs @@ -6,6 +6,8 @@ use crate::{ }; use std::ptr::NonNull; +use super::addr_eq; + /// A key-value pair where the value becomes unaccesible when the key is garbage collected. /// /// You can read more about ephemerons on: @@ -70,7 +72,7 @@ impl Ephemeron { /// Returns `true` if the two `Ephemeron`s point to the same allocation. #[must_use] pub fn ptr_eq(this: &Self, other: &Self) -> bool { - EphemeronBox::ptr_eq(this.inner(), other.inner()) + addr_eq(this.inner(), other.inner()) } pub(crate) fn inner_ptr(&self) -> NonNull> { diff --git a/core/gc/src/pointers/gc.rs b/core/gc/src/pointers/gc.rs index fb610babcee..04d773b3d98 100644 --- a/core/gc/src/pointers/gc.rs +++ b/core/gc/src/pointers/gc.rs @@ -14,6 +14,8 @@ use std::{ rc::Rc, }; +use super::addr_eq; + /// Zero sized struct that is used to ensure that we do not call trace methods, /// call its finalization method or drop it. /// @@ -53,10 +55,13 @@ pub struct Gc { pub(crate) marker: PhantomData>, } -impl Gc { +impl Gc { /// Constructs a new `Gc` with the given value. #[must_use] - pub fn new(value: T) -> Self { + pub fn new(value: T) -> Self + where + T: Sized, + { // Create GcBox and allocate it to heap. // // Note: Allocator can cause Collector to run @@ -78,6 +83,7 @@ impl Gc { pub fn new_cyclic(data_fn: F) -> Self where F: FnOnce(&WeakGc) -> T, + T: Sized, { // SAFETY: The newly allocated ephemeron is only live here, meaning `Ephemeron` is the // sole owner of the allocation after passing it to `from_raw`, making this operation safe. @@ -106,13 +112,11 @@ impl Gc { std::mem::forget(this); ptr } -} -impl Gc { /// Returns `true` if the two `Gc`s point to the same allocation. #[must_use] pub fn ptr_eq(this: &Self, other: &Self) -> bool { - GcBox::ptr_eq(this.inner(), other.inner()) + addr_eq(this.inner(), other.inner()) } /// Constructs a `Gc` from a raw pointer. diff --git a/core/gc/src/pointers/mod.rs b/core/gc/src/pointers/mod.rs index b5651297834..dd6d1d58367 100644 --- a/core/gc/src/pointers/mod.rs +++ b/core/gc/src/pointers/mod.rs @@ -12,3 +12,9 @@ pub use weak_map::WeakMap; pub(crate) use gc::NonTraceable; pub(crate) use weak_map::RawWeakMap; + +// Replace with std::ptr::addr_eq when 1.76 releases +#[allow(clippy::ptr_as_ptr, clippy::ptr_eq)] +fn addr_eq(p: *const T, q: *const U) -> bool { + (p as *const ()) == (q as *const ()) +} diff --git a/examples/src/bin/jsarraybuffer.rs b/examples/src/bin/jsarraybuffer.rs index 2614ffd7e74..5b321a50069 100644 --- a/examples/src/bin/jsarraybuffer.rs +++ b/examples/src/bin/jsarraybuffer.rs @@ -29,7 +29,7 @@ fn main() -> JsResult<()> { let array_buffer = JsArrayBuffer::from_byte_block(blob_of_data, context)?; // This the byte length of the new array buffer will be the length of block of data. - let byte_length = array_buffer.byte_length(context); + let byte_length = array_buffer.byte_length(); assert_eq!(byte_length, 256); // We can now create an typed array to access the data. @@ -40,7 +40,8 @@ fn main() -> JsResult<()> { } // We can create a Dataview from a JsArrayBuffer - let dataview = JsDataView::from_js_array_buffer(&array_buffer, None, Some(100_u64), context)?; + let dataview = + JsDataView::from_js_array_buffer(array_buffer.clone(), None, Some(100_u64), context)?; let dataview_length = dataview.byte_length(context)?;