Skip to content
Closed
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
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,4 @@ suspicious = { level = "warn", priority = -1 }
style = { level = "warn", priority = -1 }
complexity = { level = "warn", priority = -1 }
perf = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
5 changes: 2 additions & 3 deletions core/engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version.workspace = true

[features]
default = ["float16", "xsum", "temporal"]
trace = ["js"] # Enable Boa's VM instruction tracing

embedded_lz4 = ["boa_macros/embedded_lz4", "lz4_flex"]

Expand Down Expand Up @@ -64,9 +65,6 @@ fuzz = ["boa_ast/arbitrary", "boa_interner/arbitrary"]
# Enable Boa's VM instruction flowgraph generator.
flowgraph = []

# Enable Boa's VM instruction tracing.
trace = ["js"]

# Enable Boa's additional ECMAScript features for web browsers.
annex-b = ["boa_ast/annex-b", "boa_parser/annex-b"]

Expand All @@ -92,6 +90,7 @@ xsum = ["dep:xsum"]
native-backtrace = []

[dependencies]
tracing = { version = "0.1", features = ["log"] }
tag_ptr.workspace = true
boa_interner.workspace = true
boa_gc = { workspace = true, features = ["thin-vec", "boa_string", "arrayvec"] }
Expand Down
96 changes: 36 additions & 60 deletions core/engine/src/object/internal_methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
#[cfg(feature = "trace")]
use tracing::trace;

use std::ops::{Deref, DerefMut};

Expand Down Expand Up @@ -280,16 +282,6 @@ impl JsObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
pub(crate) fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
(self.vtable().__set__)(self, key, value, receiver, context)
}

/// Internal method `[[Delete]]`
///
/// Delete the specified own property of this object.
Expand Down Expand Up @@ -532,8 +524,16 @@ pub(crate) fn ordinary_get_prototype_of(
pub(crate) fn ordinary_set_prototype_of(
obj: &JsObject,
val: JsPrototype,
_: &mut Context,
context: &mut Context,
) -> JsResult<bool> {
#[cfg(feature = "trace")]
{
println!(
"[TRACE] SET PROTOTYPE -> object: {:?}, new prototype: {:?}",
obj, val
);
}

// 1. Assert: Either Type(V) is Object or Type(V) is Null.
// 2. Let current be O.[[Prototype]].
let current = obj.prototype();
Expand Down Expand Up @@ -578,7 +578,6 @@ pub(crate) fn ordinary_set_prototype_of(
// 10. Return true.
Ok(true)
}

/// Abstract operation `OrdinaryIsExtensible`.
///
/// More information:
Expand Down Expand Up @@ -709,6 +708,8 @@ pub(crate) fn ordinary_get(
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<JsValue> {
#[cfg(feature = "trace")]
println!("[trace:object] GET property '{}'", key);
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let desc be ? O.[[GetOwnProperty]](P).
match obj.__get_own_property__(key, context)? {
Expand Down Expand Up @@ -812,40 +813,23 @@ pub(crate) fn ordinary_set(
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).

// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor

let mut has_own_desc = false;

// 1. Assert: IsPropertyKey(P) is true.
// Get own property descriptor
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? {
has_own_desc = true;
desc
}
// 2. If ownDesc is undefined, then
// a. Let parent be ? O.[[GetPrototypeOf]]().
// b. If parent is not null, then
else if let Some(parent) = obj.__get_prototype_of__(context)? {
} else if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cacheable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;

// i. Return ? parent.[[Set]](P, V, Receiver).
return parent.__set__(key, value, receiver, context);
}
// c. Else,
else {
// It's not on prototype chain.
} else {
context
.slot()
.attributes
.remove(SlotAttributes::PROTOTYPE | SlotAttributes::NOT_CACHEABLE);

// i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
// [[Enumerable]]: true, [[Configurable]]: true }.
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
Expand All @@ -854,79 +838,71 @@ pub(crate) fn ordinary_set(
.build()
};

// 3. If IsDataDescriptor(ownDesc) is true, then
// Handle data descriptor
if own_desc.is_data_descriptor() {
// a. If ownDesc.[[Writable]] is false, return false.
if !own_desc.expect_writable() {
return Ok(false);
}

// b. If Type(Receiver) is not Object, return false.
let Some(receiver) = receiver.as_object() else {
return Ok(false);
};

let obj_is_receiver = JsObject::equals(obj, &receiver);

// NOTE(HaledOdat): If the object and receiver are not the same then it's not inline cacheable for now.
context
.slot()
.attributes
.set(SlotAttributes::NOT_CACHEABLE, !obj_is_receiver);

// OPTIMIZATION: If obj and receiver are the same, there's no need to call [[GetOwnProperty]](P)
// again because it was already performed above.
let existing_descriptor = if has_own_desc && obj_is_receiver {
Some(own_desc)
} else {
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
receiver.__get_own_property__(&key, context)?
};

// d. If existingDescriptor is not undefined, then
// ✅ Clone key/value for trace only
#[cfg(feature = "trace")]
let (key_for_trace, value_for_trace) = (key.clone(), value.clone());

if let Some(ref existing_desc) = existing_descriptor {
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}

// ii. If existingDescriptor.[[Writable]] is false, return false.
if !existing_desc.expect_writable() {
return Ok(false);
}

// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
return receiver.__define_own_property__(
let res = receiver.__define_own_property__(
&key,
PropertyDescriptor::builder().value(value).build(),
PropertyDescriptor::builder().value(value.clone()).build(),
context,
);
)?;

#[cfg(feature = "trace")]
tracing::trace!(?key_for_trace, ?value_for_trace, "SET -> existing descriptor");

return Ok(res);
}

// e. Else
// i. Assert: Receiver does not currently have a property P.
// ii. Return ? CreateDataProperty(Receiver, P, V).
return receiver.create_data_property_with_slot(key, value, context);
let res = receiver.create_data_property_with_slot(key.clone(), value.clone(), context)?;

#[cfg(feature = "trace")]
tracing::trace!(?key_for_trace, ?value_for_trace, "SET -> new property");

return Ok(res);
}

// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
// Handle accessor descriptors
debug_assert!(own_desc.is_accessor_descriptor());

// 5. Let setter be ownDesc.[[Set]].
match own_desc.set() {
Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »).
set.call(&receiver, &[value], context)?;

// 8. Return true.
Ok(true)
}
// 6. If setter is undefined, return false.
_ => Ok(false),
}
}

/// Abstract operation `OrdinaryDelete`.
///
/// More information:
Expand Down
15 changes: 14 additions & 1 deletion core/engine/src/object/jsobject.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! This module implements the `JsObject` structure.
//!
//! The `JsObject` is a garbage collected Object.

use crate::object::internal_methods::InternalMethodPropertyContext;
use super::{
JsPrototype, NativeObject, Object, ObjectData, PrivateName, PropertyMap,
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
Expand Down Expand Up @@ -82,7 +82,20 @@ pub(crate) struct VTableObject<T: NativeObject + ?Sized> {
vtable: &'static InternalObjectMethods,
object: GcRefCell<Object<T>>,
}
impl JsObject {
pub(crate) fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
#[cfg(feature = "trace")]
tracing::trace!(?key, ?value, "JSObject::__set__ called");

(self.vtable().__set__)(self, key, value, receiver, context)
}
}
impl JsObject {
/// Converts the `JsObject` into a raw pointer to its inner `GcBox<ErasedVTableObject>`.
#[cfg(not(feature = "jsvalue-enum"))]
Expand Down
Loading