use std::ops::{Deref, DerefMut};
use super::{
shape::slot::{Slot, SlotAttributes},
JsPrototype, PROTOTYPE,
};
use crate::{
context::intrinsics::{StandardConstructor, StandardConstructors},
object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue,
Context, JsNativeError, JsResult,
};
use boa_profiler::Profiler;
pub(crate) mod immutable_prototype;
pub(crate) mod string;
#[derive(Debug)]
pub(crate) struct InternalMethodContext<'ctx> {
context: &'ctx mut Context,
slot: Slot,
}
impl<'ctx> InternalMethodContext<'ctx> {
pub(crate) fn new(context: &'ctx mut Context) -> Self {
Self {
context,
slot: Slot::new(),
}
}
#[inline]
pub(crate) fn slot(&mut self) -> &mut Slot {
&mut self.slot
}
}
impl Deref for InternalMethodContext<'_> {
type Target = Context;
#[inline]
fn deref(&self) -> &Self::Target {
self.context
}
}
impl DerefMut for InternalMethodContext<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
}
}
impl<'context> From<&'context mut Context> for InternalMethodContext<'context> {
#[inline]
fn from(context: &'context mut Context) -> Self {
Self::new(context)
}
}
impl JsObject {
#[track_caller]
pub(crate) fn __get_prototype_of__(&self, context: &mut Context) -> JsResult<JsPrototype> {
let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object");
(self.vtable().__get_prototype_of__)(self, context)
}
pub(crate) fn __set_prototype_of__(
&self,
val: JsPrototype,
context: &mut Context,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object");
(self.vtable().__set_prototype_of__)(self, val, context)
}
pub(crate) fn __is_extensible__(&self, context: &mut Context) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__is_extensible__", "object");
(self.vtable().__is_extensible__)(self, context)
}
pub(crate) fn __prevent_extensions__(&self, context: &mut Context) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object");
(self.vtable().__prevent_extensions__)(self, context)
}
pub(crate) fn __get_own_property__(
&self,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::__get_own_property__", "object");
(self.vtable().__get_own_property__)(self, key, context)
}
pub(crate) fn __define_own_property__(
&self,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__define_own_property__", "object");
(self.vtable().__define_own_property__)(self, key, desc, context)
}
pub(crate) fn __has_property__(
&self,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__has_property__", "object");
(self.vtable().__has_property__)(self, key, context)
}
pub(crate) fn __try_get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
let _timer = Profiler::global().start_event("Object::__try_get__", "object");
(self.vtable().__try_get__)(self, key, receiver, context)
}
pub(crate) fn __get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__get__", "object");
(self.vtable().__get__)(self, key, receiver, context)
}
pub(crate) fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__set__", "object");
(self.vtable().__set__)(self, key, value, receiver, context)
}
pub(crate) fn __delete__(
&self,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__delete__", "object");
(self.vtable().__delete__)(self, key, context)
}
#[track_caller]
pub(crate) fn __own_property_keys__(
&self,
context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let _timer = Profiler::global().start_event("Object::__own_property_keys__", "object");
(self.vtable().__own_property_keys__)(self, context)
}
#[track_caller]
pub(crate) fn __call__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__call__,
object: self.clone(),
argument_count,
}
}
#[track_caller]
pub(crate) fn __construct__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__construct__,
object: self.clone(),
argument_count,
}
}
}
pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__get_prototype_of__: ordinary_get_prototype_of,
__set_prototype_of__: ordinary_set_prototype_of,
__is_extensible__: ordinary_is_extensible,
__prevent_extensions__: ordinary_prevent_extensions,
__get_own_property__: ordinary_get_own_property,
__define_own_property__: ordinary_define_own_property,
__has_property__: ordinary_has_property,
__try_get__: ordinary_try_get,
__get__: ordinary_get,
__set__: ordinary_set,
__delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys,
__call__: non_existant_call,
__construct__: non_existant_construct,
};
#[derive(Debug, Clone, Copy)]
#[allow(clippy::type_complexity, clippy::struct_field_names)]
pub struct InternalObjectMethods {
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsPrototype>,
pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult<bool>,
pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __get_own_property__: fn(
&JsObject,
&PropertyKey,
&mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>>,
pub(crate) __define_own_property__: fn(
&JsObject,
&PropertyKey,
PropertyDescriptor,
&mut InternalMethodContext<'_>,
) -> JsResult<bool>,
pub(crate) __has_property__:
fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult<bool>,
pub(crate) __get__:
fn(&JsObject, &PropertyKey, JsValue, &mut InternalMethodContext<'_>) -> JsResult<JsValue>,
pub(crate) __try_get__: fn(
&JsObject,
&PropertyKey,
JsValue,
&mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>>,
pub(crate) __set__: fn(
&JsObject,
PropertyKey,
JsValue,
JsValue,
&mut InternalMethodContext<'_>,
) -> JsResult<bool>,
pub(crate) __delete__:
fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult<bool>,
pub(crate) __own_property_keys__:
fn(&JsObject, context: &mut Context) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__:
fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult<CallValue>,
pub(crate) __construct__:
fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult<CallValue>,
}
pub(crate) enum CallValue {
Ready,
Pending {
func: fn(&JsObject, argument_count: usize, &mut Context) -> JsResult<CallValue>,
object: JsObject,
argument_count: usize,
},
Complete,
}
impl CallValue {
pub(crate) fn resolve(mut self, context: &mut Context) -> JsResult<bool> {
while let Self::Pending {
func,
object,
argument_count,
} = self
{
self = func(&object, argument_count, context)?;
}
Ok(matches!(self, Self::Complete))
}
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_get_prototype_of(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<JsPrototype> {
let _timer = Profiler::global().start_event("Object::ordinary_get_prototype_of", "object");
Ok(obj.prototype().clone())
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_set_prototype_of(
obj: &JsObject,
val: JsPrototype,
_: &mut Context,
) -> JsResult<bool> {
let current = obj.prototype();
if val == current {
return Ok(true);
}
if !obj.extensible() {
return Ok(false);
}
let mut p = val.clone();
while let Some(proto) = p {
if &proto == obj {
return Ok(false);
}
else if proto.vtable().__get_prototype_of__ as usize != ordinary_get_prototype_of as usize
{
break;
}
p = proto.prototype();
}
obj.set_prototype(val);
Ok(true)
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context) -> JsResult<bool> {
Ok(obj.borrow().extensible)
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_prevent_extensions(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<bool> {
obj.borrow_mut().extensible = false;
Ok(true)
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object");
Ok(obj.borrow().properties.get_with_slot(key, context.slot()))
}
pub(crate) fn ordinary_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_define_own_property", "object");
let current = obj.__get_own_property__(key, context)?;
let extensible = obj.__is_extensible__(context)?;
Ok(validate_and_apply_property_descriptor(
Some((obj, key)),
extensible,
desc,
current,
context.slot(),
))
}
pub(crate) fn ordinary_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_has_property", "object");
if obj.__get_own_property__(key, context)?.is_some() {
Ok(true)
} else {
let parent = obj.__get_prototype_of__(context)?;
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
parent
.map_or(Ok(false), |obj| obj.__has_property__(key, context))
}
}
pub(crate) fn ordinary_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::ordinary_get", "object");
match obj.__get_own_property__(key, context)? {
None => {
if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
parent.__get__(key, receiver, context)
}
else {
Ok(JsValue::undefined())
}
}
Some(ref desc) => {
match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
get.call(&receiver, &[], context)
}
_ => Ok(JsValue::undefined()),
}
}
}
}
pub(crate) fn ordinary_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
let _timer = Profiler::global().start_event("Object::ordinary_try_get", "object");
match obj.__get_own_property__(key, context)? {
None => {
if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
parent.__try_get__(key, receiver, context)
}
else {
Ok(None)
}
}
Some(ref desc) => {
match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => Ok(Some(value.clone())),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
get.call(&receiver, &[], context).map(Some)
}
_ => Ok(Some(JsValue::undefined())),
}
}
}
}
pub(crate) fn ordinary_set(
obj: &JsObject,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_set", "object");
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? {
desc
}
else if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
return parent.__set__(key, value, receiver, context);
}
else {
context
.slot()
.attributes
.remove(SlotAttributes::PROTOTYPE | SlotAttributes::NOT_CACHABLE);
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
if own_desc.is_data_descriptor() {
if !own_desc.expect_writable() {
return Ok(false);
}
let Some(receiver) = receiver.as_object() else {
return Ok(false);
};
context.slot().attributes.set(
SlotAttributes::NOT_CACHABLE,
!JsObject::equals(obj, receiver),
);
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? {
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}
if !existing_desc.expect_writable() {
return Ok(false);
}
return receiver.__define_own_property__(
&key,
PropertyDescriptor::builder().value(value).build(),
context,
);
}
return receiver.create_data_property_with_slot(key, value, context);
}
debug_assert!(own_desc.is_accessor_descriptor());
match own_desc.set() {
Some(set) if !set.is_undefined() => {
set.call(&receiver, &[value], context)?;
Ok(true)
}
_ => Ok(false),
}
}
pub(crate) fn ordinary_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_delete", "object");
Ok(
match obj.__get_own_property__(key, context)? {
Some(desc) if desc.expect_configurable() => {
obj.borrow_mut().remove(key);
true
}
Some(_) => false,
None => true,
},
)
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_own_property_keys(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let _timer = Profiler::global().start_event("Object::ordinary_own_property_keys", "object");
let mut keys = Vec::new();
let ordered_indexes = {
let mut indexes: Vec<_> = obj.borrow().properties.index_property_keys().collect();
indexes.sort_unstable();
indexes
};
keys.extend(ordered_indexes.into_iter().map(Into::into));
keys.extend(obj.borrow().properties.shape.keys());
Ok(keys)
}
pub(crate) fn is_compatible_property_descriptor(
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
) -> bool {
let _timer =
Profiler::global().start_event("Object::is_compatible_property_descriptor", "object");
validate_and_apply_property_descriptor(None, extensible, desc, current, &mut Slot::new())
}
pub(crate) fn validate_and_apply_property_descriptor(
obj_and_key: Option<(&JsObject, &PropertyKey)>,
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
slot: &mut Slot,
) -> bool {
let _timer =
Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object");
let Some(mut current) = current else {
if !extensible {
return false;
}
if let Some((obj, key)) = obj_and_key {
obj.borrow_mut().properties.insert_with_slot(
key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
}
else {
desc.into_accessor_defaulted()
},
slot,
);
}
return true;
};
if desc.is_empty() {
return true;
}
if !current.expect_configurable() {
if matches!(desc.configurable(), Some(true)) {
return false;
}
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}
if desc.is_generic_descriptor() {
}
else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.expect_configurable() {
return false;
}
if obj_and_key.is_some() {
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
}
else {
current = current.into_data_defaulted();
}
}
}
else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return false;
}
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
return true;
}
}
else if !current.expect_configurable() {
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
return true;
}
if let Some((obj, key)) = obj_and_key {
current.fill_with(&desc);
obj.borrow_mut()
.properties
.insert_with_slot(key, current, slot);
slot.attributes |= SlotAttributes::FOUND;
}
true
}
#[track_caller]
pub(crate) fn get_prototype_from_constructor<F>(
constructor: &JsValue,
default: F,
context: &mut Context,
) -> JsResult<JsObject>
where
F: FnOnce(&StandardConstructors) -> &StandardConstructor,
{
let _timer = Profiler::global().start_event("Object::get_prototype_from_constructor", "object");
let realm = if let Some(constructor) = constructor.as_object() {
if let Some(proto) = constructor.get(PROTOTYPE, context)?.as_object() {
return Ok(proto.clone());
}
constructor.get_function_realm(context)?
} else {
context.realm().clone()
};
Ok(default(realm.intrinsics().constructors()).prototype())
}
fn non_existant_call(
_obj: &JsObject,
_argument_count: usize,
context: &mut Context,
) -> JsResult<CallValue> {
Err(JsNativeError::typ()
.with_message("not a callable function")
.with_realm(context.realm().clone())
.into())
}
fn non_existant_construct(
_obj: &JsObject,
_argument_count: usize,
context: &mut Context,
) -> JsResult<CallValue> {
Err(JsNativeError::typ()
.with_message("not a constructor")
.with_realm(context.realm().clone())
.into())
}