Browse Source

Refactor Property Descriptor flags (#553)

pull/582/head
HalidOdat 4 years ago committed by GitHub
parent
commit
5d4d8fe794
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .vscode/launch.json
  2. 30
      Cargo.lock
  3. 21
      boa/src/builtins/array/mod.rs
  4. 38
      boa/src/builtins/function/mod.rs
  5. 11
      boa/src/builtins/map/mod.rs
  6. 87
      boa/src/builtins/object/internal_methods.rs
  7. 2
      boa/src/builtins/object/mod.rs
  8. 145
      boa/src/builtins/property/attribute/mod.rs
  9. 167
      boa/src/builtins/property/attribute/tests.rs
  10. 172
      boa/src/builtins/property/mod.rs
  11. 37
      boa/src/builtins/value/mod.rs
  12. 33
      boa/src/environment/global_environment_record.rs
  13. 20
      boa/src/environment/object_environment_record.rs

2
.vscode/launch.json vendored

@ -47,4 +47,4 @@
"externalConsole": true "externalConsole": true
}, },
] ]
} }

30
Cargo.lock generated

@ -146,9 +146,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.57" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fde55d2a2bfaa4c9668bbc63f531fbdeee3ffe188f4662511ce2c22b3eedebe" checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -356,6 +356,15 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "hashbrown"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
@ -376,11 +385,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown",
] ]
[[package]] [[package]]
@ -436,9 +446,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.72" version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -639,9 +649,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -925,9 +935,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.34" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

21
boa/src/builtins/array/mod.rs

@ -16,7 +16,7 @@ use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{ObjectData, PROTOTYPE}, object::{ObjectData, PROTOTYPE},
property::Property, property::{Attribute, Property},
value::{same_value_zero, ResultValue, Value}, value::{same_value_zero, ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
@ -75,12 +75,10 @@ impl Array {
} }
// Create length // Create length
let length = Property::new() let length = Property::data_descriptor(
.value(Value::from(array_contents.len() as i32)) array_contents.len().into(),
.writable(true) Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
.configurable(false) );
.enumerable(false);
array_obj_ptr.set_property("length".to_string(), length); array_obj_ptr.set_property("length".to_string(), length);
for (n, value) in array_contents.iter().enumerate() { for (n, value) in array_contents.iter().enumerate() {
@ -143,11 +141,10 @@ impl Array {
} }
// finally create length property // finally create length property
let length = Property::new() let length = Property::data_descriptor(
.value(Value::from(length)) length.into(),
.writable(true) Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
.configurable(false) );
.enumerable(false);
this.set_property("length".to_string(), length); this.set_property("length".to_string(), length);

38
boa/src/builtins/function/mod.rs

@ -13,10 +13,10 @@
use crate::{ use crate::{
builtins::{ builtins::{
array::Array,
object::{Object, ObjectData, PROTOTYPE}, object::{Object, ObjectData, PROTOTYPE},
property::Property, property::{Attribute, Property},
value::{RcString, ResultValue, Value}, value::{RcString, ResultValue, Value},
Array,
}, },
environment::function_environment_record::BindingStatus, environment::function_environment_record::BindingStatus,
environment::lexical_environment::{new_function_environment, Environment}, environment::lexical_environment::{new_function_environment, Environment},
@ -411,19 +411,19 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
let mut obj = Object::default(); let mut obj = Object::default();
obj.set_internal_slot("ParameterMap", Value::undefined()); obj.set_internal_slot("ParameterMap", Value::undefined());
// Set length // Set length
let mut length = Property::default(); let length = Property::data_descriptor(
length = length.writable(true).value(Value::from(len)); len.into(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
// Define length as a property // Define length as a property
obj.define_own_property("length".to_string(), length); obj.define_own_property("length".to_string(), length);
let mut index: usize = 0; let mut index: usize = 0;
while index < len { while index < len {
let val = arguments_list.get(index).expect("Could not get argument"); let val = arguments_list.get(index).expect("Could not get argument");
let mut prop = Property::default(); let prop = Property::data_descriptor(
prop = prop val.clone(),
.value(val.clone()) Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
.enumerable(true) );
.writable(true)
.configurable(true);
obj.properties_mut() obj.properties_mut()
.insert(RcString::from(index.to_string()), prop); .insert(RcString::from(index.to_string()), prop);
@ -469,18 +469,16 @@ pub fn make_constructor_fn(
let mut constructor = let mut constructor =
Object::function(function, global.get_field("Function").get_field(PROTOTYPE)); Object::function(function, global.get_field("Function").get_field(PROTOTYPE));
let length = Property::new() let length = Property::data_descriptor(
.value(Value::from(length)) length.into(),
.writable(false) Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
.configurable(false) );
.enumerable(false);
constructor.insert_property("length", length); constructor.insert_property("length", length);
let name = Property::new() let name = Property::data_descriptor(
.value(Value::from(name)) name.into(),
.writable(false) Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
.configurable(false) );
.enumerable(false);
constructor.insert_property("name", name); constructor.insert_property("name", name);
let constructor = Value::from(constructor); let constructor = Value::from(constructor);

11
boa/src/builtins/map/mod.rs

@ -4,7 +4,7 @@ use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{ObjectData, PROTOTYPE}, object::{ObjectData, PROTOTYPE},
property::Property, property::{Attribute, Property},
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
@ -26,11 +26,10 @@ impl Map {
/// Helper function to set the size property. /// Helper function to set the size property.
fn set_size(this: &Value, size: usize) { fn set_size(this: &Value, size: usize) {
let size = Property::new() let size = Property::data_descriptor(
.value(Value::from(size)) size.into(),
.writable(false) Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
.configurable(false) );
.enumerable(false);
this.set_property("size".to_string(), size); this.set_property("size".to_string(), size);
} }

87
boa/src/builtins/object/internal_methods.rs

@ -7,7 +7,7 @@
use crate::builtins::{ use crate::builtins::{
object::{Object, PROTOTYPE}, object::{Object, PROTOTYPE},
property::Property, property::{Attribute, Property},
value::{same_value, RcString, Value}, value::{same_value, RcString, Value},
}; };
use crate::BoaProfiler; use crate::BoaProfiler;
@ -73,7 +73,7 @@ impl Object {
{ {
return true; return true;
} }
if desc.configurable.expect("unable to get value") { if desc.configurable_or(false) {
self.remove_property(&prop_key.to_string()); self.remove_property(&prop_key.to_string());
return true; return true;
} }
@ -131,14 +131,14 @@ impl Object {
if !parent.is_null() { if !parent.is_null() {
// TODO: come back to this // TODO: come back to this
} }
own_desc = Property::new() own_desc = Property::data_descriptor(
.writable(true) Value::undefined(),
.enumerable(true) Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
.configurable(true); );
} }
// [3] // [3]
if own_desc.is_data_descriptor() { if own_desc.is_data_descriptor() {
if !own_desc.writable.unwrap() { if !own_desc.writable() {
return false; return false;
} }
@ -184,18 +184,12 @@ impl Object {
} }
// 4 // 4
if !current.configurable.unwrap_or(false) { if !current.configurable_or(false) {
if desc.configurable.is_some() && desc.configurable.expect("unable to get prop desc") { if desc.configurable_or(false) {
return false; return false;
} }
if desc.enumerable.is_some() if desc.enumerable_or(false) != current.enumerable_or(false) {
&& (desc.enumerable.as_ref().expect("unable to get prop desc")
!= current
.enumerable
.as_ref()
.expect("unable to get prop desc"))
{
return false; return false;
} }
} }
@ -205,14 +199,14 @@ impl Object {
// 6 // 6
} else if current.is_data_descriptor() != desc.is_data_descriptor() { } else if current.is_data_descriptor() != desc.is_data_descriptor() {
// a // a
if !current.configurable.expect("unable to get prop desc") { if !current.configurable() {
return false; return false;
} }
// b // b
if current.is_data_descriptor() { if current.is_data_descriptor() {
// Convert to accessor // Convert to accessor
current.value = None; current.value = None;
current.writable = None; current.attribute.remove(Attribute::WRITABLE);
} else { } else {
// c // c
// convert to data // convert to data
@ -224,10 +218,8 @@ impl Object {
// 7 // 7
} else if current.is_data_descriptor() && desc.is_data_descriptor() { } else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a // a
if !current.configurable.expect("unable to get prop desc") if !current.configurable() && !current.writable() {
&& !current.writable.expect("unable to get prop desc") if desc.writable_or(false) {
{
if desc.writable.is_some() && desc.writable.expect("unable to get prop desc") {
return false; return false;
} }
@ -244,7 +236,7 @@ impl Object {
} }
// 8 // 8
} else { } else {
if !current.configurable.unwrap() { if !current.configurable() {
if desc.set.is_some() if desc.set.is_some()
&& !same_value(&desc.set.clone().unwrap(), &current.set.clone().unwrap()) && !same_value(&desc.set.clone().unwrap(), &current.set.clone().unwrap())
{ {
@ -279,43 +271,35 @@ impl Object {
debug_assert!(Property::is_property_key(prop)); debug_assert!(Property::is_property_key(prop));
// Prop could either be a String or Symbol // Prop could either be a String or Symbol
match *prop { match *prop {
Value::String(ref st) => { Value::String(ref st) => self.properties().get(st).map_or_else(Property::empty, |v| {
self.properties() let mut d = Property::empty();
.get(st) if v.is_data_descriptor() {
.map_or_else(Property::default, |v| { d.value = v.value.clone();
let mut d = Property::default(); } else {
if v.is_data_descriptor() { debug_assert!(v.is_accessor_descriptor());
d.value = v.value.clone(); d.get = v.get.clone();
d.writable = v.writable; d.set = v.set.clone();
} else { }
debug_assert!(v.is_accessor_descriptor()); d.attribute = v.attribute;
d.get = v.get.clone(); d
d.set = v.set.clone(); }),
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
})
}
Value::Symbol(ref symbol) => { Value::Symbol(ref symbol) => {
self.symbol_properties() self.symbol_properties()
.get(&symbol.hash()) .get(&symbol.hash())
.map_or_else(Property::default, |v| { .map_or_else(Property::empty, |v| {
let mut d = Property::default(); let mut d = Property::empty();
if v.is_data_descriptor() { if v.is_data_descriptor() {
d.value = v.value.clone(); d.value = v.value.clone();
d.writable = v.writable;
} else { } else {
debug_assert!(v.is_accessor_descriptor()); debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone(); d.get = v.get.clone();
d.set = v.set.clone(); d.set = v.set.clone();
} }
d.enumerable = v.enumerable; d.attribute = v.attribute;
d.configurable = v.configurable;
d d
}) })
} }
_ => Property::default(), _ => unreachable!("the field can only be of type string or symbol"),
} }
} }
@ -409,11 +393,10 @@ impl Object {
{ {
self.properties.insert( self.properties.insert(
name.into(), name.into(),
Property::default() Property::data_descriptor(
.value(value) value,
.writable(true) Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
.configurable(true) ),
.enumerable(true),
) )
} }

2
boa/src/builtins/object/mod.rs

@ -571,7 +571,7 @@ pub fn property_is_enumerable(this: &Value, args: &[Value], ctx: &mut Interprete
}); });
Ok(own_property.map_or(Value::from(false), |own_prop| { Ok(own_property.map_or(Value::from(false), |own_prop| {
Value::from(own_prop.enumerable.unwrap_or(false)) Value::from(own_prop.enumerable_or(false))
})) }))
} }

145
boa/src/builtins/property/attribute/mod.rs

@ -0,0 +1,145 @@
//! This module implements the `Attribute` struct which contains the attibutes for property descriptors.
use bitflags::bitflags;
use gc::{unsafe_empty_trace, Finalize, Trace};
#[cfg(test)]
mod tests;
bitflags! {
/// This struct constains the property flags as describen in the ECMAScript specification.
///
/// It contains the following flags:
/// - `[[Writable]]` (`WRITABLE`) - If `false`, attempts by ECMAScript code to change the property's `[[Value]]` attribute using `[[Set]]` will not succeed.
/// - `[[Enumerable]]` (`ENUMERABLE`) - If the property will be enumerated by a for-in enumeration.
/// - `[[Configurable]]` (`CONFIGURABLE`) - If `false`, attempts to delete the property, change the property to be an `accessor property`, or change its attributes (other than `[[Value]]`, or changing `[[Writable]]` to `false`) will fail.
///
/// Additionaly there are flags for when the flags are defined.
#[derive(Finalize)]
pub struct Attribute: u8 {
/// None of the flags are present.
const NONE = 0b0000_0000;
/// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value.
const WRITABLE = 0b0000_0011;
/// The property is not writable.
const READONLY = 0b0000_0010;
/// Is the `Writable` flag defined.
const HAS_WRITABLE = 0b0000_0010;
/// If the property can be enumerated by a `for-in` loop.
const ENUMERABLE = 0b0000_1100;
/// The property can not be enumerated in a `for-in` loop.
const NON_ENUMERABLE = 0b0000_1000;
/// Is the `Enumerable` flag defined.
const HAS_ENUMERABLE = 0b0000_1000;
/// If the property descriptor can be changed later.
const CONFIGURABLE = 0b0011_0000;
/// The property descriptor cannot be changed.
const PERMANENT = 0b0010_0000;
/// Is the `Configurable` flag defined.
const HAS_CONFIGURABLE = 0b0010_0000;
}
}
// We implement `Trace` manualy rather that wih derive, beacuse `rust-gc`,
// derive `Trace` does not allow `Copy` and `Trace` to be both implemented.
//
// SAFETY: The `Attribute` struct only contains an `u8`
// and therefore it should be safe to implement an empty trace.
unsafe impl Trace for Attribute {
unsafe_empty_trace!();
}
impl Attribute {
/// Clear all flags.
#[inline]
pub fn clear(&mut self) {
self.bits = 0;
}
/// Is the `writable` flag defined.
#[inline]
pub fn has_writable(self) -> bool {
self.contains(Self::HAS_WRITABLE)
}
/// Sets the `writable` flag.
#[inline]
pub fn set_writable(&mut self, value: bool) {
if value {
*self |= Self::WRITABLE;
} else {
*self |= *self & !Self::WRITABLE;
}
}
/// Gets the `writable` flag.
#[inline]
pub fn writable(self) -> bool {
debug_assert!(self.has_writable());
self.contains(Self::WRITABLE)
}
/// Is the `enumerable` flag defined.
#[inline]
pub fn has_enumerable(self) -> bool {
self.contains(Self::HAS_ENUMERABLE)
}
/// Sets the `enumerable` flag.
#[inline]
pub fn set_enumerable(&mut self, value: bool) {
if value {
*self |= Self::ENUMERABLE;
} else {
*self |= *self & !Self::ENUMERABLE;
}
}
/// Gets the `enumerable` flag.
#[inline]
pub fn enumerable(self) -> bool {
debug_assert!(self.has_enumerable());
self.contains(Self::ENUMERABLE)
}
/// Is the `configurable` flag defined.
#[inline]
pub fn has_configurable(self) -> bool {
self.contains(Self::HAS_CONFIGURABLE)
}
/// Sets the `configurable` flag.
#[inline]
pub fn set_configurable(&mut self, value: bool) {
if value {
*self |= Self::CONFIGURABLE;
} else {
*self |= *self & !Self::CONFIGURABLE;
}
}
/// Gets the `configurable` flag.
#[inline]
pub fn configurable(self) -> bool {
debug_assert!(self.has_configurable());
self.contains(Self::CONFIGURABLE)
}
}
impl Default for Attribute {
/// Returns the default flags according to the [ECMAScript specification][spec].
///
/// [spec]: https://tc39.es/ecma262/#table-default-attribute-values
fn default() -> Self {
Self::READONLY | Self::NON_ENUMERABLE | Self::PERMANENT
}
}

167
boa/src/builtins/property/attribute/tests.rs

@ -0,0 +1,167 @@
use super::Attribute;
#[test]
fn writable() {
let attribute = Attribute::WRITABLE;
assert!(attribute.has_writable());
assert!(attribute.writable());
}
#[test]
fn enumerable() {
let attribute = Attribute::ENUMERABLE;
assert!(attribute.has_enumerable());
assert!(attribute.enumerable());
}
#[test]
fn configurable() {
let attribute = Attribute::CONFIGURABLE;
assert!(attribute.has_configurable());
assert!(attribute.configurable());
}
#[test]
fn writable_and_enumerable() {
let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE;
assert!(attribute.has_writable());
assert!(attribute.writable());
assert!(attribute.has_enumerable());
assert!(attribute.enumerable());
assert!(!attribute.has_configurable());
}
#[test]
fn enumerable_configurable() {
let attribute = Attribute::ENUMERABLE | Attribute::CONFIGURABLE;
assert!(!attribute.has_writable());
assert!(attribute.has_enumerable());
assert!(attribute.enumerable());
assert!(attribute.has_configurable());
assert!(attribute.configurable());
}
#[test]
fn writable_enumerable_configurable() {
let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE;
assert!(attribute.has_writable());
assert!(attribute.writable());
assert!(attribute.has_enumerable());
assert!(attribute.enumerable());
assert!(attribute.has_configurable());
assert!(attribute.configurable());
}
#[test]
fn default() {
let attribute = Attribute::default();
assert!(attribute.has_writable());
assert!(attribute.has_enumerable());
assert!(attribute.has_configurable());
}
#[test]
fn clear() {
let mut attribute = Attribute::default();
attribute.clear();
assert!(!attribute.has_writable());
assert!(!attribute.has_enumerable());
assert!(!attribute.has_configurable());
assert!(attribute.is_empty());
}
#[test]
fn set_writable_to_true() {
let mut attribute = Attribute::default();
attribute.set_writable(true);
assert!(attribute.has_writable());
assert!(attribute.writable());
assert!(attribute.has_enumerable());
assert!(!attribute.enumerable());
assert!(attribute.has_configurable());
assert!(!attribute.configurable());
}
#[test]
fn set_writable_to_false() {
let mut attribute = Attribute::default();
attribute.set_writable(false);
assert!(attribute.has_writable());
assert!(!attribute.writable());
assert!(attribute.has_enumerable());
assert!(!attribute.enumerable());
assert!(attribute.has_configurable());
assert!(!attribute.configurable());
}
#[test]
fn set_enumerable_to_true() {
let mut attribute = Attribute::default();
attribute.set_enumerable(true);
assert!(attribute.has_writable());
assert!(!attribute.writable());
assert!(attribute.has_enumerable());
assert!(attribute.enumerable());
assert!(attribute.has_configurable());
assert!(!attribute.configurable());
}
#[test]
fn set_enumerable_to_false() {
let mut attribute = Attribute::default();
attribute.set_enumerable(false);
assert!(attribute.has_writable());
assert!(!attribute.writable());
assert!(attribute.has_enumerable());
assert!(!attribute.enumerable());
assert!(attribute.has_configurable());
assert!(!attribute.configurable());
}
#[test]
fn set_configurable_to_true() {
let mut attribute = Attribute::default();
attribute.set_configurable(true);
assert!(attribute.has_writable());
assert!(!attribute.writable());
assert!(attribute.has_enumerable());
assert!(!attribute.enumerable());
assert!(attribute.has_configurable());
assert!(attribute.configurable());
}
#[test]
fn set_configurable_to_false() {
let mut attribute = Attribute::default();
attribute.set_configurable(false);
assert!(attribute.has_writable());
assert!(!attribute.writable());
assert!(attribute.has_enumerable());
assert!(!attribute.enumerable());
assert!(attribute.has_configurable());
assert!(!attribute.configurable());
}

172
boa/src/builtins/property/mod.rs

@ -14,9 +14,15 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
//! [section]: https://tc39.es/ecma262/#sec-property-attributes //! [section]: https://tc39.es/ecma262/#sec-property-attributes
use crate::builtins::value::Value; use crate::builtins::Value;
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
pub mod attribute;
pub use attribute::Attribute;
#[cfg(test)]
mod tests;
/// This represents a Javascript Property AKA The Property Descriptor. /// This represents a Javascript Property AKA The Property Descriptor.
/// ///
/// Property descriptors present in objects come in two main flavors: /// Property descriptors present in objects come in two main flavors:
@ -37,12 +43,7 @@ use gc::{Finalize, Trace};
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Trace, Finalize, Clone, Debug)] #[derive(Trace, Finalize, Clone, Debug)]
pub struct Property { pub struct Property {
/// If the type of this can be changed and this can be deleted pub(crate) attribute: Attribute,
pub configurable: Option<bool>,
/// If the property shows up in enumeration of the object
pub enumerable: Option<bool>,
/// If this property can be changed with an assignment
pub writable: Option<bool>,
/// The value associated with the property /// The value associated with the property
pub value: Option<Value>, pub value: Option<Value>,
/// The function serving as getter /// The function serving as getter
@ -53,8 +54,9 @@ pub struct Property {
impl Property { impl Property {
/// Checks if the provided Value can be used as a property key. /// Checks if the provided Value can be used as a property key.
#[inline]
pub fn is_property_key(value: &Value) -> bool { pub fn is_property_key(value: &Value) -> bool {
value.is_string() || value.is_symbol() // Uncomment this when we are handeling symbols. value.is_string() || value.is_symbol()
} }
/// Make a new property with the given value /// Make a new property with the given value
@ -62,62 +64,126 @@ impl Property {
/// ///
/// New: zeros everything to make an empty object /// New: zeros everything to make an empty object
/// Default: Defaults according to the spec /// Default: Defaults according to the spec
#[inline]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
configurable: None, attribute: Default::default(),
enumerable: None,
writable: None,
value: None, value: None,
get: None, get: None,
set: None, set: None,
} }
} }
/// Set configurable #[inline]
pub fn configurable(mut self, configurable: bool) -> Self { pub fn empty() -> Self {
self.configurable = Some(configurable); Self {
self attribute: Attribute::NONE,
value: None,
get: None,
set: None,
}
}
#[inline]
pub fn data_descriptor(value: Value, attribute: Attribute) -> Self {
Self {
attribute,
value: Some(value),
get: None,
set: None,
}
}
/// Get the
#[inline]
pub fn configurable(&self) -> bool {
self.attribute.configurable()
}
#[inline]
pub fn set_configurable(&mut self, configurable: bool) {
self.attribute.set_configurable(configurable)
}
#[inline]
pub fn configurable_or(&self, value: bool) -> bool {
if self.attribute.has_configurable() {
self.attribute.configurable()
} else {
value
}
} }
/// Set enumerable /// Set enumerable
pub fn enumerable(mut self, enumerable: bool) -> Self { #[inline]
self.enumerable = Some(enumerable); pub fn enumerable(&self) -> bool {
self self.attribute.enumerable()
}
#[inline]
pub fn enumerable_or(&self, value: bool) -> bool {
if self.attribute.has_enumerable() {
self.attribute.enumerable()
} else {
value
}
} }
/// Set writable /// Set writable
pub fn writable(mut self, writable: bool) -> Self { #[inline]
self.writable = Some(writable); pub fn writable(&self) -> bool {
self self.attribute.writable()
}
#[inline]
pub fn writable_or(&self, value: bool) -> bool {
if self.attribute.has_writable() {
self.attribute.writable()
} else {
value
}
} }
/// Set value /// Set value
#[inline]
pub fn value(mut self, value: Value) -> Self { pub fn value(mut self, value: Value) -> Self {
self.value = Some(value); self.value = Some(value);
self self
} }
/// Set get /// Set get
#[inline]
pub fn get(mut self, get: Value) -> Self { pub fn get(mut self, get: Value) -> Self {
self.get = Some(get); self.get = Some(get);
self self
} }
#[inline]
pub fn has_get(&self) -> bool {
self.get.is_some()
}
/// Set set /// Set set
#[inline]
pub fn set(mut self, set: Value) -> Self { pub fn set(mut self, set: Value) -> Self {
self.set = Some(set); self.set = Some(set);
self self
} }
#[inline]
pub fn has_set(&self) -> bool {
self.set.is_some()
}
/// Is this an empty Property? /// Is this an empty Property?
/// ///
/// `true` if all fields are set to none /// `true` if all fields are set to none
#[inline]
pub fn is_none(&self) -> bool { pub fn is_none(&self) -> bool {
self.get.is_none() self.value.is_none()
&& self.attribute.is_empty()
&& self.get.is_none()
&& self.set.is_none() && self.set.is_none()
&& self.writable.is_none()
&& self.configurable.is_none()
&& self.enumerable.is_none()
} }
/// An accessor Property Descriptor is one that includes any fields named either [[Get]] or [[Set]]. /// An accessor Property Descriptor is one that includes any fields named either [[Get]] or [[Set]].
@ -126,6 +192,7 @@ impl Property {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor /// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor
#[inline]
pub fn is_accessor_descriptor(&self) -> bool { pub fn is_accessor_descriptor(&self) -> bool {
self.get.is_some() || self.set.is_some() self.get.is_some() || self.set.is_some()
} }
@ -136,16 +203,18 @@ impl Property {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor /// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor
#[inline]
pub fn is_data_descriptor(&self) -> bool { pub fn is_data_descriptor(&self) -> bool {
self.value.is_some() || self.writable.is_some() self.value.is_some() || self.attribute.has_writable()
} }
/// Check if a property is generic. /// Check if a property is generic descriptor.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor /// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor
#[inline]
pub fn is_generic_descriptor(&self) -> bool { pub fn is_generic_descriptor(&self) -> bool {
!self.is_accessor_descriptor() && !self.is_data_descriptor() !self.is_accessor_descriptor() && !self.is_data_descriptor()
} }
@ -158,24 +227,27 @@ impl Default for Property {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#table-default-attribute-values /// [spec]: https://tc39.es/ecma262/#table-default-attribute-values
#[inline]
fn default() -> Self { fn default() -> Self {
Self { Self::new()
configurable: None,
enumerable: None,
writable: None,
value: None,
get: None,
set: None,
}
} }
} }
impl From<&Property> for Value { impl From<&Property> for Value {
fn from(value: &Property) -> Value { fn from(value: &Property) -> Value {
let property = Value::new_object(None); let property = Value::new_object(None);
property.set_field("configurable", Value::from(value.configurable)); if value.attribute.has_writable() {
property.set_field("enumerable", Value::from(value.enumerable)); property.set_field("writable", value.attribute.writable());
property.set_field("writable", Value::from(value.writable)); }
if value.attribute.has_enumerable() {
property.set_field("enumerable", value.attribute.enumerable());
}
if value.attribute.has_configurable() {
property.set_field("configurable", value.attribute.configurable());
}
property.set_field("value", value.value.clone().unwrap_or_else(Value::null)); property.set_field("value", value.value.clone().unwrap_or_else(Value::null));
property.set_field("get", value.get.clone().unwrap_or_else(Value::null)); property.set_field("get", value.get.clone().unwrap_or_else(Value::null));
property.set_field("set", value.set.clone().unwrap_or_else(Value::null)); property.set_field("set", value.set.clone().unwrap_or_else(Value::null));
@ -187,16 +259,28 @@ impl<'a> From<&'a Value> for Property {
/// Attempt to fetch values "configurable", "enumerable", "writable" from the value, /// Attempt to fetch values "configurable", "enumerable", "writable" from the value,
/// if they're not there default to false /// if they're not there default to false
fn from(value: &Value) -> Self { fn from(value: &Value) -> Self {
let mut attribute = Attribute::empty();
let writable = value.get_field("writable");
if !writable.is_undefined() {
attribute.set_writable(bool::from(&writable));
}
let enumerable = value.get_field("enumerable");
if !enumerable.is_undefined() {
attribute.set_enumerable(bool::from(&enumerable));
}
let configurable = value.get_field("configurable");
if !configurable.is_undefined() {
attribute.set_configurable(bool::from(&configurable));
}
Self { Self {
configurable: { Some(bool::from(&value.get_field("configurable"))) }, attribute,
enumerable: { Some(bool::from(&value.get_field("enumerable"))) },
writable: { Some(bool::from(&value.get_field("writable"))) },
value: Some(value.get_field("value")), value: Some(value.get_field("value")),
get: Some(value.get_field("get")), get: Some(value.get_field("get")),
set: Some(value.get_field("set")), set: Some(value.get_field("set")),
} }
} }
} }
#[cfg(test)]
mod tests;

37
boa/src/builtins/value/mod.rs

@ -12,7 +12,7 @@ pub use crate::builtins::value::val_type::Type;
use crate::builtins::{ use crate::builtins::{
function::Function, function::Function,
object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE},
property::Property, property::{Attribute, Property},
BigInt, Symbol, BigInt, Symbol,
}; };
use crate::exec::Interpreter; use crate::exec::Interpreter;
@ -201,11 +201,10 @@ impl Value {
for (idx, json) in vs.into_iter().enumerate() { for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property( new_obj.set_property(
idx.to_string(), idx.to_string(),
Property::default() Property::data_descriptor(
.value(Self::from_json(json, interpreter)) Self::from_json(json, interpreter),
.writable(true) Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
.configurable(true) ),
.enumerable(true),
); );
} }
new_obj.set_property( new_obj.set_property(
@ -220,11 +219,10 @@ impl Value {
let value = Self::from_json(json, interpreter); let value = Self::from_json(json, interpreter);
new_obj.set_property( new_obj.set_property(
key, key,
Property::default() Property::data_descriptor(
.value(value) value,
.writable(true) Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
.configurable(true) ),
.enumerable(true),
); );
} }
new_obj new_obj
@ -495,24 +493,15 @@ impl Value {
/// update_prop will overwrite individual [Property] fields, unlike /// update_prop will overwrite individual [Property] fields, unlike
/// Set_prop, which will overwrite prop with a new Property /// Set_prop, which will overwrite prop with a new Property
///
/// Mostly used internally for now /// Mostly used internally for now
pub fn update_property( pub(crate) fn update_property(&self, field: &str, new_property: Property) {
&self,
field: &str,
value: Option<Value>,
enumerable: Option<bool>,
writable: Option<bool>,
configurable: Option<bool>,
) {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
if let Some(ref mut object) = self.as_object_mut() { if let Some(ref mut object) = self.as_object_mut() {
// Use value, or walk up the prototype chain // Use value, or walk up the prototype chain
if let Some(ref mut property) = object.properties_mut().get_mut(field) { if let Some(property) = object.properties_mut().get_mut(field) {
property.value = value; *property = new_property;
property.enumerable = enumerable;
property.writable = writable;
property.configurable = configurable;
} }
} }
} }

33
boa/src/environment/global_environment_record.rs

@ -8,7 +8,10 @@
//! More info: <https://tc39.es/ecma262/#sec-global-environment-records> //! More info: <https://tc39.es/ecma262/#sec-global-environment-records>
use crate::{ use crate::{
builtins::value::Value, builtins::{
property::{Attribute, Property},
Value,
},
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
@ -41,7 +44,7 @@ impl GlobalEnvironmentRecord {
let existing_prop = global_object.get_property(name); let existing_prop = global_object.get_property(name);
match existing_prop { match existing_prop {
Some(prop) => { Some(prop) => {
if prop.value.is_none() || prop.configurable.unwrap_or(false) { if prop.value.is_none() || prop.configurable() {
return false; return false;
} }
true true
@ -70,23 +73,19 @@ impl GlobalEnvironmentRecord {
let global_object = &mut self.object_record.bindings; let global_object = &mut self.object_record.bindings;
let existing_prop = global_object.get_property(&name); let existing_prop = global_object.get_property(&name);
if let Some(prop) = existing_prop { if let Some(prop) = existing_prop {
if prop.value.is_none() || prop.configurable.unwrap_or(false) { if prop.value.is_none() || prop.configurable_or(false) {
global_object.update_property( let mut property =
name, Property::data_descriptor(value, Attribute::WRITABLE | Attribute::ENUMERABLE);
Some(value), property.set_configurable(deletion);
Some(true),
Some(true), global_object.update_property(name, property);
Some(deletion),
);
} }
} else { } else {
global_object.update_property( let mut property =
name, Property::data_descriptor(value, Attribute::WRITABLE | Attribute::ENUMERABLE);
Some(value), property.set_configurable(deletion);
Some(true),
Some(true), global_object.update_property(name, property);
Some(deletion),
);
} }
} }
} }

20
boa/src/environment/object_environment_record.rs

@ -7,7 +7,10 @@
//! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records) //! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records)
use crate::{ use crate::{
builtins::{property::Property, value::Value}, builtins::{
property::{Attribute, Property},
value::Value,
},
environment::{ environment::{
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
lexical_environment::{Environment, EnvironmentType}, lexical_environment::{Environment, EnvironmentType},
@ -38,11 +41,11 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
// TODO: could save time here and not bother generating a new undefined object, // TODO: could save time here and not bother generating a new undefined object,
// only for it to be replace with the real value later. We could just add the name to a Vector instead // only for it to be replace with the real value later. We could just add the name to a Vector instead
let bindings = &mut self.bindings; let bindings = &mut self.bindings;
let prop = Property::default() let mut prop = Property::data_descriptor(
.value(Value::undefined()) Value::undefined(),
.writable(true) Attribute::WRITABLE | Attribute::ENUMERABLE,
.enumerable(true) );
.configurable(deletion); prop.set_configurable(deletion);
bindings.set_property(name, prop); bindings.set_property(name, prop);
} }
@ -62,8 +65,9 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) { fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) {
debug_assert!(value.is_object() || value.is_function()); debug_assert!(value.is_object() || value.is_function());
let bindings = &mut self.bindings; let mut property = Property::data_descriptor(value, Attribute::ENUMERABLE);
bindings.update_property(name, Some(value), None, None, Some(strict)); property.set_configurable(strict);
self.bindings.update_property(name, property);
} }
fn get_binding_value(&self, name: &str, strict: bool) -> Value { fn get_binding_value(&self, name: &str, strict: bool) -> Value {

Loading…
Cancel
Save