Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecmascript): Implement ArrayBuffer Object sub-type #44

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nova_vm/src/ecmascript/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod ordinary;

pub(crate) use array::ArrayHeapData;
pub use array::{Array, ArrayConstructor};
pub use array_buffer::ArrayBuffer;
pub(crate) use array_buffer::ArrayBufferHeapData;
pub use builtin_function::{
create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs,
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/builtins/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl OrdinaryObjectInternalSlots for Array {
fn set_prototype(self, agent: &mut Agent, prototype: Option<Object>) {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).set_prototype(agent, prototype)
} else if prototype != Some(agent.current_realm().intrinsics().array_prototype()) {
} else {
// Create array base object with custom prototype
todo!()
}
Expand Down
220 changes: 214 additions & 6 deletions nova_vm/src/ecmascript/builtins/array_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
//! ## [25.1 ArrayBuffer Objects](https://tc39.es/ecma262/#sec-arraybuffer-objects)

mod data;

use self::data::InternalBuffer;
use super::{ordinary::ordinary_set_prototype_of_check_loop, Array};
use crate::{
ecmascript::{
abstract_operations::operations_on_objects::get,
abstract_operations::{
operations_on_objects::get, testing_and_comparison::same_value_non_number,
},
execution::{agent::JsError, Agent, JsResult},
types::{DataBlock, Function, InternalMethods, Number, Object, PropertyKey, Value},
types::{
DataBlock, Function, InternalMethods, Number, Object, OrdinaryObject,
OrdinaryObjectInternalSlots, PropertyDescriptor, PropertyKey, Value,
},
},
heap::{indexes::ArrayBufferIndex, GetHeapData},
Heap,
};

pub use data::ArrayBufferHeapData;

use self::data::InternalBuffer;
use std::ops::Deref;

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ArrayBuffer(ArrayBufferIndex);

impl From<ArrayBufferIndex> for ArrayBuffer {
fn from(value: ArrayBufferIndex) -> Self {
ArrayBuffer(value)
}
sno2 marked this conversation as resolved.
Show resolved Hide resolved
}

impl From<ArrayBuffer> for Object {
fn from(value: ArrayBuffer) -> Self {
Self::ArrayBuffer(value.0)
}
}

impl From<ArrayBuffer> for Value {
fn from(value: ArrayBuffer) -> Self {
Self::ArrayBuffer(value.0)
}
}

impl Deref for ArrayBuffer {
type Target = ArrayBufferIndex;

fn deref(&self) -> &Self::Target {
&self.0
}
}

pub struct DetachKey {}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
Expand Down Expand Up @@ -478,3 +508,181 @@ impl ArrayBuffer {
// 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian).
}
}

fn create_array_buffer_base_object(agent: &mut Agent, array_buffer: ArrayBuffer) -> OrdinaryObject {
// TODO: An issue crops up if multiple realms are in play:
// The prototype should not be dependent on the realm we're operating in
// but should instead be bound to the realm the object was created in.
// We'll have to cross this bridge at a later point, likely be designating
// a "default realm" and making non-default realms always initialize ObjectHeapData.
let prototype = agent.current_realm().intrinsics().array_buffer_prototype();
let object_index = agent.heap.create_object_with_prototype(prototype.into());
agent.heap.get_mut(*array_buffer).object_index = Some(object_index);
OrdinaryObject::from(object_index)
}

impl OrdinaryObjectInternalSlots for ArrayBuffer {
fn extensible(self, agent: &Agent) -> bool {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).extensible(agent)
} else {
true
}
}

fn set_extensible(self, agent: &mut Agent, value: bool) {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).set_extensible(agent, value)
} else {
debug_assert!(!value);
create_array_buffer_base_object(agent, self).set_extensible(agent, value)
}
}

fn prototype(self, agent: &Agent) -> Option<Object> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).prototype(agent)
} else {
Some(
agent
.current_realm()
.intrinsics()
.array_buffer_prototype()
.into(),
)
}
}

fn set_prototype(self, agent: &mut Agent, prototype: Option<Object>) {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).set_prototype(agent, prototype)
} else {
// Create ArrayBuffer base object with custom prototype
let object_index = if let Some(prototype) = prototype {
debug_assert!(ordinary_set_prototype_of_check_loop(
agent,
prototype,
Some(self.into())
));
agent.heap.create_object_with_prototype(prototype)
} else {
agent.heap.create_null_object(vec![])
};
agent.heap.get_mut(*self).object_index = Some(object_index);
}
}
}

impl InternalMethods for ArrayBuffer {
fn get_prototype_of(self, agent: &mut Agent) -> JsResult<Option<Object>> {
Ok(self.prototype(agent))
}

fn set_prototype_of(self, agent: &mut Agent, prototype: Option<Object>) -> JsResult<bool> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).set_prototype_of(agent, prototype)
} else {
// If we're setting %ArrayBuffer.prototype% then we can still avoid creating the ObjectHeapData.
let current = agent.current_realm().intrinsics().array_buffer_prototype();
if let Some(v) = prototype {
if same_value_non_number(agent, v, current.into()) {
return Ok(true);
}
};
if ordinary_set_prototype_of_check_loop(agent, current.into(), prototype) {
// OrdinarySetPrototypeOf 7.b.i: Setting prototype would cause a loop to occur.
return Ok(false);
}
self.set_prototype(agent, prototype);
Ok(true)
}
}

fn is_extensible(self, agent: &mut Agent) -> JsResult<bool> {
Ok(self.extensible(agent))
}

fn prevent_extensions(self, agent: &mut Agent) -> JsResult<bool> {
self.set_extensible(agent, false);
Ok(true)
}

fn get_own_property(
self,
agent: &mut Agent,
property_key: PropertyKey,
) -> JsResult<Option<PropertyDescriptor>> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).get_own_property(agent, property_key)
} else {
Ok(None)
}
}

fn define_own_property(
self,
agent: &mut Agent,
property_key: PropertyKey,
property_descriptor: crate::ecmascript::types::PropertyDescriptor,
) -> JsResult<bool> {
create_array_buffer_base_object(agent, self).define_own_property(
agent,
property_key,
property_descriptor,
)
}

fn has_property(self, agent: &mut Agent, property_key: PropertyKey) -> JsResult<bool> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).has_property(agent, property_key)
} else {
agent
.current_realm()
.intrinsics()
.array_buffer_prototype()
.has_property(agent, property_key)
}
}

fn get(self, agent: &mut Agent, property_key: PropertyKey, receiver: Value) -> JsResult<Value> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).get(agent, property_key, receiver)
} else {
agent
.current_realm()
.intrinsics()
.array_buffer_prototype()
.get(agent, property_key, receiver)
}
}

fn set(
self,
agent: &mut Agent,
property_key: PropertyKey,
value: Value,
receiver: Value,
) -> JsResult<bool> {
create_array_buffer_base_object(agent, self).set(agent, property_key, value, receiver)
}

fn delete(self, agent: &mut Agent, property_key: PropertyKey) -> JsResult<bool> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).delete(agent, property_key)
} else {
// OrdinaryDelete essentially returns "didn't exist or was deleted":
// We know properties didn't exist in this branch.
Ok(true)
}
}

fn own_property_keys(self, agent: &mut Agent) -> JsResult<Vec<PropertyKey>> {
if let Some(object_index) = agent.heap.get(*self).object_index {
OrdinaryObject::from(object_index).own_property_keys(agent)
} else {
// OrdinaryDelete essentially returns "didn't exist or was deleted":
// We know properties didn't exist in this branch.
Ok(vec![])
}
}
}
72 changes: 43 additions & 29 deletions nova_vm/src/ecmascript/builtins/ordinary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,45 @@ pub(crate) fn ordinary_get_prototype_of(agent: &mut Agent, object: Object) -> Op
object.prototype(agent)
}

/// Implements steps 5 through 7 of OrdinarySetPrototypeOf
///
/// Returns true if a loop is detected, corresponding to substep 7.b.i. of the
/// abstract operation.
pub(crate) fn ordinary_set_prototype_of_check_loop(
agent: &mut Agent,
o: Object,
v: Option<Object>,
) -> bool {
// 5. Let p be V.
let mut p = v;
// 6. Let done be false.
// 7. Repeat, while done is false,
while let Some(p_inner) = p {
// a. If p is null, then
// i. Set done to true.

// b. Else if SameValue(p, O) is true, then
if same_value_non_number(agent, p_inner, o) {
// i. Return false.
return false;
}

// c. Else,
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1,
// set done to true.
// NOTE: At present there are two exotic objects that define their own [[GetPrototypeOf]]
// methods. Those are Proxy and Module.

// if parent_prototype.get_prototype_of != get_prototype_of {
// break;
// }

// ii. Else, set p to p.[[Prototype]].
p = p_inner.prototype(agent);
}
true
}

/// 10.1.2.1 OrdinarySetPrototypeOf ( O, V )
/// https://tc39.es/ecma262/#sec-ordinarysetprototypeof
pub(crate) fn ordinary_set_prototype_of(
Expand All @@ -131,41 +170,16 @@ pub(crate) fn ordinary_set_prototype_of(

// 4. If extensible is false, return false.
if !extensible {
// 7.b.i. Return false.
return false;
}

// 5. Let p be V.
let mut parent_prototype_outer = prototype;

// 6. Let done be false.
// 7. Repeat, while done is false,
while let Some(parent_prototype) = parent_prototype_outer {
// a. If p is null, then
// i. Set done to true.

// b. Else if SameValue(p, O) is true, then
if same_value_non_number(agent, parent_prototype, object) {
// i. Return false.
return false;
}

// c. Else,
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1,
// set done to true.
// NOTE: At present there are two exotic objects that define their own [[GetPrototypeOf]]
// methods. Those are Proxy and Module.

// if parent_prototype.get_prototype_of != get_prototype_of {
// break;
// }

// ii. Else, set p to p.[[Prototype]].
// TODO: Is this still wrong?
parent_prototype_outer = parent_prototype.prototype(agent);
if ordinary_set_prototype_of_check_loop(agent, object, prototype) {
return false;
}

// 8. Set O.[[Prototype]] to V.
object.set_prototype(agent, parent_prototype_outer);
object.set_prototype(agent, prototype);

// 9. Return true.
true
Expand Down
8 changes: 4 additions & 4 deletions nova_vm/src/ecmascript/execution/realm/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
ecmascript::types::{Function, Object},
ecmascript::types::{Function, Object, OrdinaryObject},
heap::{
indexes::{FunctionIndex, ObjectIndex},
BuiltinObjectIndexes,
Expand Down Expand Up @@ -223,7 +223,7 @@ impl Intrinsics {
) -> Object {
match intrinsic_default_proto {
ProtoIntrinsics::Array => self.array_prototype(),
ProtoIntrinsics::ArrayBuffer => self.array_buffer_prototype(),
ProtoIntrinsics::ArrayBuffer => self.array_buffer_prototype().into(),
ProtoIntrinsics::BigInt => self.big_int_prototype(),
ProtoIntrinsics::Boolean => self.boolean_prototype(),
ProtoIntrinsics::Error => self.error_prototype(),
Expand Down Expand Up @@ -257,8 +257,8 @@ impl Intrinsics {
}

/// %ArrayBuffer.prototype%
pub const fn array_buffer_prototype(&self) -> Object {
Object::Object(self.array_buffer_prototype)
pub const fn array_buffer_prototype(&self) -> OrdinaryObject {
OrdinaryObject::new(self.array_buffer_prototype)
}

/// %BigInt%
Expand Down
Loading