Browse Source

Implement non-erased `JsObject`s (#3618)

pull/3622/head
José Julián Espina 4 months ago committed by GitHub
parent
commit
08722382d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      core/engine/src/builtins/array_buffer/mod.rs
  2. 8
      core/engine/src/builtins/array_buffer/utils.rs
  3. 32
      core/engine/src/builtins/typed_array/builtin.rs
  4. 63
      core/engine/src/object/builtins/jsarraybuffer.rs
  5. 18
      core/engine/src/object/builtins/jsdataview.rs
  6. 4
      core/engine/src/object/internal_methods/mod.rs
  7. 406
      core/engine/src/object/jsobject.rs
  8. 8
      core/engine/src/object/mod.rs
  9. 12
      core/gc/src/internals/ephemeron_box.rs
  10. 8
      core/gc/src/internals/gc_box.rs
  11. 4
      core/gc/src/pointers/ephemeron.rs
  12. 14
      core/gc/src/pointers/gc.rs
  13. 6
      core/gc/src/pointers/mod.rs
  14. 5
      examples/src/bin/jsarraybuffer.rs

8
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<JsObject> {
) -> JsResult<JsObject<ArrayBuffer>> {
// 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 {

8
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<JsObject> {
pub(crate) fn clone(&self, context: &mut Context) -> JsResult<JsObject<ArrayBuffer>> {
// 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::<ArrayBuffer>()
.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).

32
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,
))
}
/// <https://tc39.es/ecma262/#sec-initializetypedarrayfromlist>
@ -3209,11 +3217,12 @@ impl BuiltinTypedArray {
context,
)?;
{
let mut data = data_obj
.downcast_mut::<ArrayBuffer>()
.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.

63
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<ArrayBuffer>,
}
// 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<Self> {
if object.is::<ArrayBuffer>() {
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not an ArrayBuffer")
.into())
}
object
.downcast::<ArrayBuffer>()
.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<Vec<u8>> {
self.inner
.downcast_mut::<ArrayBuffer>()
.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<GcRef<'_, [u8]>> {
debug_assert!(
self.inner.is::<ArrayBuffer>(),
"inner must be an ArrayBuffer"
);
GcRef::try_map(self.inner.downcast_ref::<ArrayBuffer>()?, 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<GcRefMut<'_, ErasedObject, [u8]>> {
debug_assert!(
self.inner.is::<ArrayBuffer>(),
"inner must be an ArrayBuffer"
);
GcRefMut::try_map(
self.inner.downcast_mut::<ArrayBuffer>()?,
ArrayBuffer::data_mut,
)
pub fn data_mut(&self) -> Option<GcRefMut<'_, Object<ArrayBuffer>, [u8]>> {
GcRefMut::try_map(self.inner.borrow_mut(), |o| o.data.data_mut())
}
}
impl From<JsArrayBuffer> for JsObject {
#[inline]
fn from(o: JsArrayBuffer) -> Self {
o.inner.clone()
o.inner.upcast()
}
}
impl From<JsArrayBuffer> 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<ArrayBuffer>;
#[inline]
fn deref(&self) -> &Self::Target {

18
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<u64>,
byte_length: Option<u64>,
context: &mut Context,
) -> JsResult<Self> {
let (byte_offset, byte_length) = {
let buffer = array_buffer.downcast_ref::<ArrayBuffer>().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,
},

4
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,

406
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<dyn NativeObject>;
pub(crate) type ErasedVTableObject = VTableObject<dyn NativeObject>;
/// Garbage collected `Object`.
#[derive(Trace, Finalize, Clone)]
pub struct JsObject {
inner: Gc<VTableObject<dyn NativeObject>>,
#[derive(Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct JsObject<T: NativeObject + ?Sized = dyn NativeObject> {
inner: Gc<VTableObject<T>>,
}
impl<T: NativeObject + ?Sized> Clone for JsObject<T> {
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<T: NativeObject>(self) -> Result<JsObject<T>, Self> {
if self.borrow().is::<T>() {
let ptr: NonNull<GcBox<VTableObject<dyn NativeObject>>> = 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::<GcBox<VTableObject<T>>>()
);
assert_eq!(
std::mem::align_of_val(erased),
std::mem::align_of::<GcBox<VTableObject<T>>>()
);
}
let ptr: NonNull<GcBox<VTableObject<T>>> = 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<T: NativeObject>(&self) -> Option<Ref<'_, T>> {
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<T: NativeObject>(&self) -> Option<RefMut<'_, ErasedObject, T>> {
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<Ref<'_, ErasedObject>, BorrowError> {
self.inner.object.try_borrow().map_err(|_| BorrowError)
#[must_use]
#[track_caller]
pub fn is<T: NativeObject>(&self) -> bool {
self.borrow().is::<T>()
}
/// 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<RefMut<'_, ErasedObject, ErasedObject>, BorrowMutError> {
self.inner
.object
.try_borrow_mut()
.map_err(|_| BorrowMutError)
#[must_use]
#[track_caller]
pub fn is_ordinary(&self) -> bool {
self.is::<OrdinaryObject>()
}
/// 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<T: NativeObject>(&self) -> Option<Ref<'_, T>> {
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<T: NativeObject>(&self) -> Option<RefMut<'_, ErasedObject, T>> {
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<T: NativeObject>(&self) -> bool {
self.borrow().is::<T>()
}
/// 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::<OrdinaryObject>()
}
/// 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<T: NativeObject + ?Sized> JsObject<T> {
/// 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<O: Into<Option<JsObject>>>(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<T>> {
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<T>, Object<T>> {
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<Ref<'_, Object<T>>, 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<RefMut<'_, Object<T>, Object<T>>, 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<VTableObject<dyn NativeObject>> {
pub(crate) const fn inner(&self) -> &Gc<VTableObject<T>> {
&self.inner
}
@ -646,29 +730,29 @@ Cannot both specify accessors and a value or writable attribute",
}
}
impl AsRef<GcRefCell<ErasedObject>> for JsObject {
impl<T: NativeObject + ?Sized> AsRef<GcRefCell<Object<T>>> for JsObject<T> {
#[inline]
fn as_ref(&self) -> &GcRefCell<ErasedObject> {
fn as_ref(&self) -> &GcRefCell<Object<T>> {
&self.inner.object
}
}
impl From<Gc<VTableObject<dyn NativeObject>>> for JsObject {
impl<T: NativeObject + ?Sized> From<Gc<VTableObject<T>>> for JsObject<T> {
#[inline]
fn from(inner: Gc<VTableObject<dyn NativeObject>>) -> Self {
fn from(inner: Gc<VTableObject<T>>) -> Self {
Self { inner }
}
}
impl PartialEq for JsObject {
impl<T: NativeObject + ?Sized> PartialEq for JsObject<T> {
fn eq(&self, other: &Self) -> bool {
Self::equals(self, other)
}
}
impl Eq for JsObject {}
impl<T: NativeObject + ?Sized> Eq for JsObject<T> {}
impl Hash for JsObject {
impl<T: NativeObject + ?Sized> Hash for JsObject<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::ptr::hash(self.as_ref(), state);
}
@ -782,7 +866,7 @@ impl RecursionLimiter {
}
}
impl Debug for JsObject {
impl<T: NativeObject + ?Sized> Debug for JsObject<T> {
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::<OrdinaryFunction>() {
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<T: NativeObject>(ptr: Gc<VTableObject<T>>) -> Gc<VTableObject<dyn NativeObject>> {
fn coerce_gc<T: NativeObject>(ptr: Gc<VTableObject<T>>) -> Gc<VTableObject<dyn NativeObject>> {
// SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by
// https://github.com/rust-lang/rust/issues/18598
unsafe {

8
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<JsValue> + Into<JsObject> + Deref<Target = JsObject>
{
}
pub(crate) trait JsObjectType: Into<JsValue> + Into<JsObject> {}
/// 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<T: ?Sized> {
/// The `[[PrivateElements]]` internal slot.
private_elements: ThinVec<(PrivateName, PrivateElement)>,
/// The inner object data
data: T,
pub(crate) data: T,
}
impl<T: Default> Default for Object<T> {

12
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<K: Trace + ?Sized, V: Trace> EphemeronBox<K, V> {
}
}
/// 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

8
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<T: Trace> GcBox<T> {
}
impl<T: Trace + ?Sized> GcBox<T> {
/// 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

4
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<K: Trace + ?Sized, V: Trace> Ephemeron<K, V> {
/// 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<EphemeronBox<K, V>> {

14
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<T: Trace + ?Sized + 'static> {
pub(crate) marker: PhantomData<Rc<T>>,
}
impl<T: Trace> Gc<T> {
impl<T: Trace + ?Sized> Gc<T> {
/// Constructs a new `Gc<T>` 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<T: Trace> Gc<T> {
pub fn new_cyclic<F>(data_fn: F) -> Self
where
F: FnOnce(&WeakGc<T>) -> 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<T: Trace> Gc<T> {
std::mem::forget(this);
ptr
}
}
impl<T: Trace + ?Sized> Gc<T> {
/// 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<T>` from a raw pointer.

6
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<T: ?Sized, U: ?Sized>(p: *const T, q: *const U) -> bool {
(p as *const ()) == (q as *const ())
}

5
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)?;

Loading…
Cancel
Save