use std::ops::{Deref, DerefMut};
use super::{
JsPrototype, PROTOTYPE,
shape::slot::{Slot, SlotAttributes},
};
use crate::{
Context, JsNativeError, JsResult,
context::intrinsics::{StandardConstructor, StandardConstructors},
object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue,
vm::source_info::NativeSourceInfo,
};
pub(crate) mod immutable_prototype;
pub(crate) mod string;
#[derive(Debug)]
pub(crate) struct InternalMethodPropertyContext<'ctx> {
context: &'ctx mut Context,
slot: Slot,
}
impl<'ctx> InternalMethodPropertyContext<'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 InternalMethodPropertyContext<'_> {
type Target = Context;
#[inline]
fn deref(&self) -> &Self::Target {
self.context
}
}
impl DerefMut for InternalMethodPropertyContext<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
}
}
impl<'context> From<&'context mut Context> for InternalMethodPropertyContext<'context> {
#[inline]
fn from(context: &'context mut Context) -> Self {
Self::new(context)
}
}
#[derive(Debug)]
pub(crate) struct InternalMethodCallContext<'ctx> {
context: &'ctx mut Context,
native_source_info: NativeSourceInfo,
}
impl<'ctx> InternalMethodCallContext<'ctx> {
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub(crate) fn new(context: &'ctx mut Context) -> Self {
Self {
context,
native_source_info: NativeSourceInfo::caller(),
}
}
#[inline]
pub(crate) fn with_native_source_info(
context: &'ctx mut Context,
native_source_info: NativeSourceInfo,
) -> Self {
Self {
context,
native_source_info,
}
}
#[inline]
pub(crate) fn context(&mut self) -> &mut Context {
self.context
}
#[inline]
pub(crate) fn native_source_info(&self) -> NativeSourceInfo {
self.native_source_info
}
}
impl Deref for InternalMethodCallContext<'_> {
type Target = Context;
#[inline]
fn deref(&self) -> &Self::Target {
self.context
}
}
impl DerefMut for InternalMethodCallContext<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
}
}
impl<'context> From<&'context mut Context> for InternalMethodCallContext<'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> {
(self.vtable().__get_prototype_of__)(self, context)
}
pub(crate) fn __set_prototype_of__(
&self,
val: JsPrototype,
context: &mut Context,
) -> JsResult<bool> {
(self.vtable().__set_prototype_of__)(self, val, context)
}
pub(crate) fn __is_extensible__(&self, context: &mut Context) -> JsResult<bool> {
(self.vtable().__is_extensible__)(self, context)
}
pub(crate) fn __prevent_extensions__(&self, context: &mut Context) -> JsResult<bool> {
(self.vtable().__prevent_extensions__)(self, context)
}
pub(crate) fn __get_own_property__(
&self,
key: &PropertyKey,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
(self.vtable().__get_own_property__)(self, key, context)
}
pub(crate) fn __define_own_property__(
&self,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
(self.vtable().__define_own_property__)(self, key, desc, context)
}
pub(crate) fn __has_property__(
&self,
key: &PropertyKey,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
(self.vtable().__has_property__)(self, key, context)
}
pub(crate) fn __try_get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<Option<JsValue>> {
(self.vtable().__try_get__)(self, key, receiver, context)
}
pub(crate) fn __get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<JsValue> {
(self.vtable().__get__)(self, key, receiver, context)
}
pub(crate) fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
(self.vtable().__set__)(self, key, value, receiver, context)
}
pub(crate) fn __delete__(
&self,
key: &PropertyKey,
context: &mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
(self.vtable().__delete__)(self, key, context)
}
#[track_caller]
pub(crate) fn __own_property_keys__(
&self,
context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
(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,
native_source_info: NativeSourceInfo::caller(),
}
}
#[track_caller]
pub(crate) fn __construct__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__construct__,
object: self.clone(),
argument_count,
native_source_info: NativeSourceInfo::caller(),
}
}
}
pub(crate) const 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 InternalMethodPropertyContext<'_>,
) -> JsResult<Option<PropertyDescriptor>>,
pub(crate) __define_own_property__: fn(
&JsObject,
&PropertyKey,
PropertyDescriptor,
&mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool>,
pub(crate) __has_property__:
fn(&JsObject, &PropertyKey, &mut InternalMethodPropertyContext<'_>) -> JsResult<bool>,
pub(crate) __get__: fn(
&JsObject,
&PropertyKey,
JsValue,
&mut InternalMethodPropertyContext<'_>,
) -> JsResult<JsValue>,
pub(crate) __try_get__: fn(
&JsObject,
&PropertyKey,
JsValue,
&mut InternalMethodPropertyContext<'_>,
) -> JsResult<Option<JsValue>>,
pub(crate) __set__: fn(
&JsObject,
PropertyKey,
JsValue,
JsValue,
&mut InternalMethodPropertyContext<'_>,
) -> JsResult<bool>,
pub(crate) __delete__:
fn(&JsObject, &PropertyKey, &mut InternalMethodPropertyContext<'_>) -> 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 InternalMethodCallContext<'_>,
) -> JsResult<CallValue>,
pub(crate) __construct__: fn(
&JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue>,
}
#[allow(variant_size_differences)]
pub(crate) enum CallValue {
Ready,
Pending {
func: fn(
&JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue>,
object: JsObject,
argument_count: usize,
native_source_info: NativeSourceInfo,
},
Complete,
}
impl CallValue {
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub(crate) fn resolve(mut self, context: &mut Context) -> JsResult<bool> {
while let Self::Pending {
func,
object,
argument_count,
native_source_info,
} = self
{
self = func(
&object,
argument_count,
&mut InternalMethodCallContext::with_native_source_info(
context,
native_source_info,
),
)?;
}
match self {
Self::Ready => Ok(false),
Self::Complete => Ok(true),
Self::Pending { .. } => unreachable!(),
}
}
}
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_get_prototype_of(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<JsPrototype> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<JsValue> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<Option<JsValue>> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
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 InternalMethodPropertyContext<'_>,
) -> JsResult<bool> {
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 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 {
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 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 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 InternalMethodCallContext<'_>,
) -> 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 InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
Err(JsNativeError::typ()
.with_message("not a constructor")
.with_realm(context.realm().clone())
.into())
}