use boa_gc::Gc;
use boa_parser::Source;
use crate::{
Context, JsObject, JsResult, JsValue,
builtins::{OrdinaryObject, function::OrdinaryFunction},
js_string,
object::{
ObjectInitializer, internal_methods::InternalMethodPropertyContext,
shape::slot::SlotAttributes,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
vm::CodeBlock,
};
#[test]
fn get_own_property_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get_own_property__(&property, context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an owned property, this should be cacheable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn get_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get__(&property, o.clone().into(), context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an owned property, this should be cacheable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn get_internal_method_in_prototype() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
let prototype = context.intrinsics().constructors().object().prototype();
prototype
.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__get__(&property, o.clone().into(), context)
.expect("should not fail");
assert!(
context.slot().in_prototype(),
"Since it's an prototype property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an prototype property, this should be cacheable"
);
let shape = prototype.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn define_own_property_internal_method_non_existent_property() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
context,
)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an owned property, this should be cacheable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn define_own_property_internal_method_existing_property_property() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
&mut context.into(),
)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__define_own_property__(
&property,
PropertyDescriptor::builder()
.value(value + 100)
.writable(true)
.configurable(true)
.enumerable(true)
.build(),
context,
)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an owned property, this should be cacheable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
#[test]
fn set_internal_method() {
let context = &mut Context::default();
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(OrdinaryObject, Vec::default());
let property: PropertyKey = js_string!("prop").into();
let value = 100;
o.set(property.clone(), value, true, context)
.expect("should not fail");
let context = &mut InternalMethodPropertyContext::new(context);
assert_eq!(context.slot().index, 0);
assert_eq!(context.slot().attributes, SlotAttributes::empty());
o.__set__(property.clone(), value.into(), o.clone().into(), context)
.expect("should not fail");
assert!(
!context.slot().in_prototype(),
"Since it's an owned property, the prototype bit should not be set"
);
assert!(
context.slot().is_cacheable(),
"Since it's an owned property, this should be cacheable"
);
let shape = o.borrow().shape().clone();
let slot = shape.lookup(&property);
assert!(slot.is_some(), "the property should be found in the object");
let slot = slot.expect("the property should be found in the object");
assert_eq!(context.slot().index, slot.index);
}
fn get_codeblock(value: &JsValue) -> Option<(JsObject, Gc<CodeBlock>)> {
let object = value.as_object()?.clone();
let code = object.downcast_ref::<OrdinaryFunction>()?.code.clone();
Some((object, code))
}
#[test]
fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { return o.test; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
.build();
let o_shape = o.borrow().shape().clone();
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
assert_eq!(code.ic[0].entries.borrow().len(), 1);
assert_eq!(
code.ic[0].entries.borrow()[0]
.shape
.upgrade()
.unwrap()
.to_addr_usize(),
o_shape.to_addr_usize()
);
Ok(())
}
#[test]
fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { o.test = 30; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
.build();
let o_shape = o.borrow().shape().clone();
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
assert_eq!(code.ic[0].entries.borrow().len(), 1);
assert_eq!(
code.ic[0].entries.borrow()[0]
.shape
.upgrade()
.unwrap()
.to_addr_usize(),
o_shape.to_addr_usize()
);
Ok(())
}
#[test]
fn test_polymorphic_inline_cache() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { return o.test; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert!(!code.ic[0].megamorphic.get());
let shapes = vec![
ObjectInitializer::new(context)
.property(js_string!("test"), 1, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("a"), 2, Attribute::all())
.property(js_string!("test"), 3, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("b"), 4, Attribute::all())
.property(js_string!("test"), 5, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("c"), 6, Attribute::all())
.property(js_string!("test"), 7, Attribute::all())
.build(),
];
for o in &shapes {
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
}
assert_eq!(code.ic[0].entries.borrow().len(), 4);
assert!(!code.ic[0].megamorphic.get());
Ok(())
}
#[test]
fn test_megamorphic_inline_cache() -> JsResult<()> {
let context = &mut Context::default();
let function = context.eval(Source::from_bytes("(function (o) { return o.test; })"))?;
let (function, code) = get_codeblock(&function).unwrap();
let shapes = vec![
ObjectInitializer::new(context)
.property(js_string!("test"), 1, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("a"), 1, Attribute::all())
.property(js_string!("test"), 1, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("b"), 1, Attribute::all())
.property(js_string!("test"), 1, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("c"), 1, Attribute::all())
.property(js_string!("test"), 1, Attribute::all())
.build(),
ObjectInitializer::new(context)
.property(js_string!("d"), 1, Attribute::all())
.property(js_string!("test"), 1, Attribute::all())
.build(),
];
for o in &shapes {
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
}
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert!(code.ic[0].megamorphic.get());
let o6 = ObjectInitializer::new(context)
.property(js_string!("e"), 1, Attribute::all())
.property(js_string!("test"), 1, Attribute::all())
.build();
function.call(&JsValue::undefined(), &[o6.clone().into()], context)?;
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert!(code.ic[0].megamorphic.get());
Ok(())
}