mod caches;
mod shape;
pub use caches::*;
pub use shape::*;
use std::{
collections::{TryReserveError, hash_map::Entry},
ops::ControlFlow,
vec,
};
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::SharedDataViewRecord;
#[cfg(feature = "array-buffer")]
use crate::ecmascript::try_get_result_into_value;
#[cfg(feature = "temporal")]
use crate::ecmascript::{DurationRecord, InstantRecord, PlainTimeRecord};
use crate::{
ecmascript::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods,
InternalSlots, JsResult, Object, OrdinaryObject, PropertyDescriptor, PropertyKey,
ProtoIntrinsics, Realm, SetAtOffsetProps, SetResult, String, Symbol, TryError,
TryGetResult, TryHasResult, TryResult, Value, call_function, create_data_property,
get_function_realm, handle_try_get_result, same_value, try_create_data_property, try_get,
try_get_function_realm, try_result_into_js, unwrap_try,
},
engine::{Bindable, GcScope, NoGcScope, Scopable, Scoped},
heap::{
CreateHeapData, WellKnownSymbols, {ElementStorageRef, PropertyStorageRef},
},
};
#[cfg(feature = "date")]
use crate::ecmascript::DateHeapData;
#[cfg(feature = "regexp")]
use crate::ecmascript::RegExpHeapData;
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::SharedArrayBufferRecord;
#[cfg(feature = "array-buffer")]
use crate::ecmascript::{ArrayBufferHeapData, DataViewRecord, TypedArrayRecord};
use crate::ecmascript::{
ArrayHeapData, ArrayIteratorHeapData, AsyncGeneratorHeapData, ErrorHeapData,
FinalizationRegistryRecord, GeneratorHeapData, MapHeapData, MapIteratorHeapData, Module,
PrimitiveObjectRecord, PromiseHeapData, StringIteratorHeapData,
};
#[cfg(feature = "set")]
use crate::ecmascript::{SetHeapData, SetIteratorHeapData};
#[cfg(feature = "weak-refs")]
use crate::ecmascript::{WeakMapRecord, WeakRefHeapData, WeakSetHeapData};
impl<'a> InternalMethods<'a> for OrdinaryObject<'a> {
fn get_own_property_at_offset<'gc>(
self,
agent: &Agent,
offset: PropertyOffset,
gc: NoGcScope<'gc, '_>,
) -> TryGetResult<'gc> {
let offset = offset.get_property_offset();
let obj = self.bind(gc);
let data = obj.get_elements_storage(agent);
if let Some(v) = data.values[offset as usize] {
v.into()
} else {
let d = data
.descriptors
.and_then(|d| d.get(&(offset as u32)))
.unwrap();
d.getter_function(gc)
.map_or(TryGetResult::Value(Value::Undefined), TryGetResult::Get)
}
}
fn set_at_offset<'gc>(
self,
agent: &mut Agent,
props: &SetAtOffsetProps,
offset: PropertyOffset,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
ordinary_set_at_offset(agent, props, self.into(), Some(self), offset, gc)
}
}
pub(crate) fn ordinary_get_prototype_of<'a>(
agent: &mut Agent,
object: OrdinaryObject,
_: NoGcScope<'a, '_>,
) -> Option<Object<'a>> {
object.internal_prototype(agent)
}
pub(crate) fn ordinary_set_prototype_of(
agent: &mut Agent,
object: Object,
prototype: Option<Object>,
gc: NoGcScope,
) -> bool {
let current = object.internal_prototype(agent);
if prototype == current {
return true;
}
let extensible = object.internal_extensible(agent);
if !extensible {
return false;
}
let mut p = prototype;
while let Some(p_inner) = p {
if p_inner == object {
return false;
} else {
if matches!(p_inner, Object::Module(_) | Object::Proxy(_)) {
break;
} else {
p = p_inner.internal_prototype(agent);
}
}
}
if let Some(prototype) = prototype
&& !prototype.is_proxy()
&& !prototype.is_module()
{
prototype
.get_or_create_backing_object(agent)
.make_intrinsic(agent)
.expect("Should perform GC here");
}
let old_shape = object
.get_backing_object(agent)
.map(|o| o.object_shape(agent));
object.internal_set_prototype(agent, prototype);
if let Some(shape) = old_shape
&& shape.is_intrinsic(agent)
{
Caches::invalidate_caches_on_intrinsic_shape_prototype_change(
agent, object, shape, current, prototype, gc,
);
}
true
}
pub(crate) fn ordinary_is_extensible(agent: &mut Agent, object: OrdinaryObject) -> bool {
object.internal_extensible(agent)
}
pub(crate) fn ordinary_prevent_extensions(agent: &mut Agent, object: OrdinaryObject) -> bool {
object.internal_set_extensible(agent, false);
true
}
pub(crate) fn ordinary_get_own_property<'a>(
agent: &mut Agent,
object: Object,
backing_object: OrdinaryObject,
property_key: PropertyKey,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'a, '_>,
) -> Option<PropertyDescriptor<'a>> {
let (value, x, offset) = if let Some(cache) = cache
&& let shape = backing_object.object_shape(agent)
&& let Some((offset, _)) = cache.find_cached_property_offset(agent, shape)
{
if offset.is_unset() || offset.is_prototype_property() {
return None;
}
let offset = offset.get_property_offset() as u32;
let ElementStorageRef {
values,
descriptors,
} = backing_object.get_elements_storage(agent);
let value = &values[offset as usize];
let x = descriptors.and_then(|d| d.get(&offset));
(value, x, offset)
} else {
backing_object.property_storage().get(agent, property_key)?
};
let mut descriptor = PropertyDescriptor::default();
if let Some(value) = value {
descriptor.value = Some(value.bind(gc));
descriptor.writable = Some(x.is_none_or(|x| x.is_writable().unwrap()));
} else {
let x = x.unwrap();
debug_assert!(x.is_accessor_descriptor());
descriptor.get = Some(x.getter_function(gc));
descriptor.set = Some(x.setter_function(gc));
}
descriptor.enumerable = Some(x.is_none_or(|x| x.is_enumerable()));
descriptor.configurable = Some(x.is_none_or(|x| x.is_configurable()));
if let Some(CacheToPopulate {
receiver,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
let ov: Value = object.into();
let is_receiver = ov == receiver;
if is_receiver {
cache.insert_lookup_offset(agent, shape, offset);
} else {
cache.insert_prototype_lookup_offset(agent, shape, offset, object);
}
}
Some(descriptor)
}
pub(crate) fn ordinary_define_own_property<'gc>(
agent: &mut Agent,
o: Object,
backing_object: OrdinaryObject,
property_key: PropertyKey,
descriptor: PropertyDescriptor,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, bool> {
let current = ordinary_get_own_property(agent, o, backing_object, property_key, cache, gc);
let extensible = backing_object.internal_extensible(agent);
validate_and_apply_property_descriptor(
agent,
Some((o, backing_object)),
property_key,
extensible,
descriptor,
current,
gc,
)
.map_err(|err| agent.throw_allocation_exception(err, gc))
}
pub(crate) fn is_compatible_property_descriptor(
agent: &mut Agent,
extensible: bool,
descriptor: PropertyDescriptor,
current: Option<PropertyDescriptor>,
gc: NoGcScope,
) -> Result<bool, TryReserveError> {
let property_key = PropertyKey::from_str(agent, "", gc);
validate_and_apply_property_descriptor(
agent,
None,
property_key,
extensible,
descriptor,
current,
gc,
)
}
fn validate_and_apply_property_descriptor(
agent: &mut Agent,
o: Option<(Object, OrdinaryObject)>,
property_key: PropertyKey,
extensible: bool,
descriptor: PropertyDescriptor,
current: Option<PropertyDescriptor>,
gc: NoGcScope,
) -> Result<bool, TryReserveError> {
let Some(current) = current else {
if !extensible {
return Ok(false);
}
let Some((o, backing_object)) = o else {
return Ok(true);
};
if descriptor.is_accessor_descriptor() {
backing_object.property_storage().set(
agent,
o,
property_key,
PropertyDescriptor {
get: Some(descriptor.get.unwrap_or(None)),
set: Some(descriptor.set.unwrap_or(None)),
enumerable: Some(descriptor.enumerable.unwrap_or(false)),
configurable: Some(descriptor.configurable.unwrap_or(false)),
..Default::default()
},
gc,
)?;
}
else {
backing_object.property_storage().set(
agent,
o,
property_key,
PropertyDescriptor {
value: Some(descriptor.value.unwrap_or(Value::Undefined)),
writable: Some(descriptor.writable.unwrap_or(false)),
enumerable: Some(descriptor.enumerable.unwrap_or(false)),
configurable: Some(descriptor.configurable.unwrap_or(false)),
..Default::default()
},
gc,
)?;
}
return Ok(true);
};
debug_assert!(current.is_fully_populated());
if !descriptor.has_fields() {
return Ok(true);
}
if !current.configurable.unwrap() {
if descriptor.configurable == Some(true) {
return Ok(false);
}
if descriptor.enumerable.is_some() && descriptor.enumerable != current.enumerable {
return Ok(false);
}
if !descriptor.is_generic_descriptor()
&& descriptor.is_accessor_descriptor() != current.is_accessor_descriptor()
{
return Ok(false);
}
if current.is_accessor_descriptor() {
if let Some(desc_get) = descriptor.get {
if let Some(cur_get) = current.get {
if desc_get != cur_get {
return Ok(false);
}
} else {
return Ok(false);
}
}
if let Some(desc_set) = descriptor.set {
if let Some(cur_set) = current.set {
if desc_set != cur_set {
return Ok(false);
}
} else {
return Ok(false);
}
}
}
else if current.writable == Some(false) {
if descriptor.writable == Some(true) {
return Ok(false);
}
if let Some(desc_value) = descriptor.value {
if let Some(cur_value) = current.value {
if !same_value(agent, desc_value, cur_value) {
return Ok(false);
}
} else {
return Ok(false);
}
}
}
}
if let Some((o, backing_object)) = o {
if current.is_data_descriptor() && descriptor.is_accessor_descriptor() {
let configurable = descriptor
.configurable
.unwrap_or_else(|| current.configurable.unwrap());
let enumerable = descriptor
.enumerable
.unwrap_or_else(|| current.enumerable.unwrap());
backing_object.property_storage().set(
agent,
o,
property_key,
PropertyDescriptor {
get: Some(descriptor.get.unwrap_or(None)),
set: Some(descriptor.set.unwrap_or(None)),
enumerable: Some(enumerable),
configurable: Some(configurable),
..Default::default()
},
gc,
)?;
}
else if current.is_accessor_descriptor() && descriptor.is_data_descriptor() {
let configurable = descriptor
.configurable
.unwrap_or_else(|| current.configurable.unwrap());
let enumerable = descriptor
.enumerable
.unwrap_or_else(|| current.enumerable.unwrap());
backing_object.property_storage().set(
agent,
o,
property_key,
PropertyDescriptor {
value: Some(descriptor.value.unwrap_or(Value::Undefined)),
writable: Some(descriptor.writable.unwrap_or(false)),
enumerable: Some(enumerable),
configurable: Some(configurable),
..Default::default()
},
gc,
)?;
}
else {
backing_object.property_storage().set(
agent,
o,
property_key,
PropertyDescriptor {
value: descriptor.value.or(current.value),
writable: descriptor.writable.or(current.writable),
get: descriptor.get.or(current.get),
set: descriptor.set.or(current.set),
enumerable: descriptor.enumerable.or(current.enumerable),
configurable: descriptor.configurable.or(current.configurable),
},
gc,
)?;
}
}
Ok(true)
}
pub(crate) fn ordinary_try_has_property<'gc>(
agent: &mut Agent,
object: Object,
backing_object: Option<OrdinaryObject>,
property_key: PropertyKey,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, TryHasResult<'gc>> {
if let Some(cache) = cache {
let shape = if let Some(bo) = backing_object {
bo.object_shape(agent)
} else {
object.object_shape(agent)
};
if let Some((offset, prototype)) = cache.find_cached_property_offset(agent, shape) {
return if offset.is_unset() {
TryHasResult::Unset.into()
} else {
TryHasResult::Offset(
offset.get_property_offset() as u32,
prototype.unwrap_or(object).bind(gc),
)
.into()
};
}
}
let has_own = backing_object.and_then(|bo| {
bo.object_shape(agent)
.keys(&agent.heap.object_shapes, &agent.heap.elements)
.iter()
.enumerate()
.find(|(_, p)| *p == &property_key)
.map(|(i, _)| i as u32)
});
if let Some(offset) = has_own {
if let Some(CacheToPopulate {
receiver,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
let ov: Value = object.into();
let is_receiver = ov == receiver;
if is_receiver {
cache.insert_lookup_offset(agent, shape, offset);
} else {
cache.insert_prototype_lookup_offset(agent, shape, offset, object);
}
}
return TryHasResult::Offset(offset, object.bind(gc)).into();
};
let parent = match backing_object
.map_or(object, |bo| bo.into())
.try_get_prototype_of(agent, gc)
{
ControlFlow::Continue(p) => p,
ControlFlow::Break(_) => return TryError::GcError.into(),
};
if let Some(parent) = parent {
return if cache.is_some() {
let result = parent.try_has_property(agent, property_key, None, gc);
agent.heap.caches.clear_current_cache_to_populate();
result
} else {
parent.try_has_property(agent, property_key, None, gc)
};
}
if let Some(CacheToPopulate {
receiver: _,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
cache.insert_unset(agent, shape);
}
TryHasResult::Unset.into()
}
pub(crate) fn ordinary_has_property<'a>(
agent: &mut Agent,
object: Object,
backing_object: OrdinaryObject,
property_key: PropertyKey,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let backing_object = backing_object.bind(gc.nogc());
let property_key = property_key.bind(gc.nogc());
let has_own = backing_object
.object_shape(agent)
.keys(&agent.heap.object_shapes, &agent.heap.elements)
.iter()
.enumerate()
.find(|(_, p)| *p == &property_key)
.map(|(i, _)| i as u32);
if let Some(offset) = has_own {
if let Some(CacheToPopulate {
receiver,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
let ov: Value = object.into();
let is_receiver = ov == receiver;
if is_receiver {
cache.insert_lookup_offset(agent, shape, offset);
} else {
cache.insert_prototype_lookup_offset(agent, shape, offset, object);
}
}
return Ok(true);
};
let parent = backing_object.internal_prototype(agent).bind(gc.nogc());
if let Some(parent) = parent {
return parent
.unbind()
.internal_has_property(agent, property_key.unbind(), gc);
}
if let Some(CacheToPopulate {
receiver: _,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
cache.insert_unset(agent, shape);
}
Ok(false)
}
#[cfg(feature = "array-buffer")]
pub(crate) fn ordinary_has_property_entry<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, bool> {
let property_key = property_key.bind(gc.nogc());
match object.get_backing_object(agent) {
Some(backing_object) => ordinary_has_property(
agent,
object.into(),
backing_object,
property_key.unbind(),
gc,
),
None => {
let parent = unwrap_try(object.try_get_prototype_of(agent, gc.nogc()));
if let Some(parent) = parent {
parent
.unbind()
.internal_has_property(agent, property_key.unbind(), gc)
} else {
Ok(false)
}
}
}
}
pub(crate) fn ordinary_try_get<'gc>(
agent: &mut Agent,
object: Object,
backing_object: Option<OrdinaryObject>,
property_key: PropertyKey,
receiver: Value,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, TryGetResult<'gc>> {
let object = object.bind(gc);
let backing_object = backing_object.bind(gc);
let property_key = property_key.bind(gc);
let receiver = receiver.bind(gc);
if let Some(cache) = cache {
let shape = if let Some(bo) = backing_object {
bo.object_shape(agent)
} else {
object.object_shape(agent)
};
if let Some(result) = shape.get_cached(agent, property_key, object.into(), cache, gc) {
return result.into();
}
}
let Some(descriptor) = backing_object
.and_then(|bo| ordinary_get_own_property(agent, object, bo, property_key, None, gc))
else {
let parent = object.internal_prototype(agent);
let Some(parent) = parent else {
if let Some(CacheToPopulate {
receiver: _,
cache,
key: _,
shape,
}) = agent
.heap
.caches
.take_current_cache_to_populate(property_key)
{
cache.insert_unset(agent, shape);
}
return TryGetResult::Unset.into();
};
return if cache.is_some() {
let result = parent.try_get(agent, property_key, receiver, None, gc);
agent.heap.caches.clear_current_cache_to_populate();
result
} else {
parent.try_get(agent, property_key, receiver, None, gc)
};
};
if let Some(value) = descriptor.value {
debug_assert!(descriptor.is_data_descriptor());
return TryGetResult::Value(value).into();
}
debug_assert!(descriptor.is_accessor_descriptor());
let Some(Some(getter)) = descriptor.get else {
return TryGetResult::Unset.into();
};
TryGetResult::Get(getter).into()
}
pub(crate) fn ordinary_get<'gc>(
agent: &mut Agent,
object: OrdinaryObject,
property_key: PropertyKey,
receiver: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let object = object.bind(gc.nogc());
let property_key = property_key.bind(gc.nogc());
let scoped_object = object.scope(agent, gc.nogc());
let scoped_property_key = property_key.scope(agent, gc.nogc());
let Some(descriptor) = object
.unbind()
.internal_get_own_property(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
else {
let object = scoped_object.get(agent).bind(gc.nogc());
let (parent, property_key, receiver) =
if let TryResult::Continue(parent) = object.try_get_prototype_of(agent, gc.nogc()) {
let Some(parent) = parent else {
return Ok(Value::Undefined);
};
(
parent,
scoped_property_key.get(agent).bind(gc.nogc()),
receiver,
)
} else {
let receiver = receiver.scope(agent, gc.nogc());
let Some(parent) = object
.unbind()
.internal_get_prototype_of(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc())
else {
return Ok(Value::Undefined);
};
let parent = parent.unbind().bind(gc.nogc());
let receiver = receiver.get(agent);
(
parent,
scoped_property_key.get(agent).bind(gc.nogc()),
receiver,
)
};
return parent
.unbind()
.internal_get(agent, property_key.unbind(), receiver, gc);
};
if let Some(value) = descriptor.value {
debug_assert!(descriptor.is_data_descriptor());
return Ok(value.unbind());
}
debug_assert!(descriptor.is_accessor_descriptor());
let Some(Some(getter)) = descriptor.get else {
return Ok(Value::Undefined);
};
call_function(agent, getter.unbind(), receiver.unbind(), None, gc)
}
pub(crate) fn ordinary_try_set<'o, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'o>,
property_key: PropertyKey,
value: Value,
receiver: Value,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
let own_descriptor = unwrap_try(object.try_get_own_property(agent, property_key, cache, gc));
ordinary_try_set_with_own_descriptor(
agent,
object,
property_key,
value,
receiver,
own_descriptor,
cache,
gc,
)
}
pub(crate) fn ordinary_set<'a>(
agent: &mut Agent,
object: Object,
property_key: PropertyKey,
value: Value,
receiver: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let property_key = property_key.bind(gc.nogc());
let scoped_property_key = property_key.scope(agent, gc.nogc());
let own_descriptor = object
.internal_get_own_property(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
ordinary_set_with_own_descriptor(
agent,
object,
scoped_property_key,
value,
receiver,
own_descriptor.unbind(),
gc,
)
}
#[allow(clippy::too_many_arguments)]
fn ordinary_try_set_with_own_descriptor<'gc, 'o>(
agent: &mut Agent,
object: impl InternalMethods<'o>,
property_key: PropertyKey,
value: Value,
receiver: Value,
own_descriptor: Option<PropertyDescriptor>,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
let ov: Value = object.into();
let is_receiver = ov == receiver;
let own_descriptor = if let Some(own_descriptor) = own_descriptor {
own_descriptor
} else {
let parent = unwrap_try(object.try_get_prototype_of(agent, gc));
if let Some(parent) = parent {
return parent.try_set(agent, property_key, value, receiver, cache, gc);
}
else {
if is_receiver {
if !object.internal_extensible(agent) {
return SetResult::Unwritable.into();
}
if let Err(err) = object
.get_or_create_backing_object(agent)
.property_storage()
.push(agent, object.into(), property_key, Some(value), None, gc)
{
return TryError::Err(agent.throw_allocation_exception(err, gc)).into();
}
return SetResult::Done.into();
}
PropertyDescriptor::new_data_descriptor(Value::Undefined)
}
};
if own_descriptor.is_data_descriptor() {
if own_descriptor.writable == Some(false) {
return SetResult::Unwritable.into();
}
let Ok(receiver) = Object::try_from(receiver) else {
return SetResult::Unwritable.into();
};
let existing_descriptor = if is_receiver {
Some(own_descriptor)
} else {
receiver.try_get_own_property(agent, property_key, cache, gc)?
};
let result = if let Some(existing_descriptor) = existing_descriptor {
if existing_descriptor.is_accessor_descriptor() {
return SetResult::Accessor.into();
}
if existing_descriptor.writable == Some(false) {
return SetResult::Unwritable.into();
}
let value_descriptor = PropertyDescriptor {
value: Some(value.unbind()),
..Default::default()
};
receiver.try_define_own_property(agent, property_key, value_descriptor, cache, gc)
}
else {
try_create_data_property(agent, receiver, property_key, value, cache, gc)
};
return result.map_continue(|result| {
if result {
SetResult::Done
} else {
SetResult::Unwritable
}
});
}
debug_assert!(own_descriptor.is_accessor_descriptor());
let setter = own_descriptor.set.unwrap().bind(gc);
let Some(setter) = setter else {
return SetResult::Accessor.into();
};
SetResult::Set(setter).into()
}
fn ordinary_set_with_own_descriptor<'a>(
agent: &mut Agent,
object: Object,
scoped_property_key: Scoped<PropertyKey>,
value: Value,
receiver: Value,
own_descriptor: Option<PropertyDescriptor>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let mut value = value.bind(gc.nogc());
let receiver = receiver.bind(gc.nogc());
let property_key = scoped_property_key.get(agent).bind(gc.nogc());
let own_descriptor = if let Some(own_descriptor) = own_descriptor {
own_descriptor.bind(gc.nogc())
} else {
let parent = unwrap_try(object.try_get_prototype_of(agent, gc.nogc()));
if let Some(parent) = parent {
return parent.unbind().internal_set(
agent,
property_key.unbind(),
value.unbind(),
receiver.unbind(),
gc,
);
}
else {
PropertyDescriptor {
value: Some(Value::Undefined),
writable: Some(true),
enumerable: Some(true),
configurable: Some(true),
..Default::default()
}
}
};
if own_descriptor.is_data_descriptor() {
if own_descriptor.writable == Some(false) {
return Ok(false);
}
let Ok(mut receiver) = Object::try_from(receiver) else {
return Ok(false);
};
let property_key = scoped_property_key.get(agent).bind(gc.nogc());
let existing_descriptor = if let TryResult::Continue(desc) =
receiver.try_get_own_property(agent, property_key, None, gc.nogc())
{
desc
} else {
let scoped_receiver = receiver.scope(agent, gc.nogc());
let scoped_value = value.scope(agent, gc.nogc());
let desc = receiver
.unbind()
.internal_get_own_property(agent, scoped_property_key.get(agent), gc.reborrow())
.unbind()?
.bind(gc.nogc());
unsafe {
value = scoped_value.take(agent).bind(gc.nogc());
receiver = scoped_receiver.take(agent).bind(gc.nogc());
}
desc
};
if let Some(existing_descriptor) = existing_descriptor {
if existing_descriptor.is_accessor_descriptor() {
return Ok(false);
}
if existing_descriptor.writable == Some(false) {
return Ok(false);
}
let value_descriptor = PropertyDescriptor {
value: Some(value),
..Default::default()
};
return receiver.unbind().internal_define_own_property(
agent,
scoped_property_key.get(agent).unbind(),
value_descriptor.unbind(),
gc,
);
}
else {
return create_data_property(
agent,
receiver.unbind(),
scoped_property_key.get(agent),
value.unbind(),
gc,
);
}
}
debug_assert!(own_descriptor.is_accessor_descriptor());
let Some(Some(setter)) = own_descriptor.set else {
return Ok(false);
};
call_function(
agent,
setter.unbind(),
receiver.unbind(),
Some(ArgumentsList::from_mut_slice(&mut [value.unbind()])),
gc,
)?;
Ok(true)
}
pub(crate) fn ordinary_set_at_offset<'a>(
agent: &mut Agent,
props: &SetAtOffsetProps,
o: Object,
bo: Option<OrdinaryObject>,
offset: PropertyOffset,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, SetResult<'a>> {
let o = o.bind(gc);
let bo = bo.bind(gc);
let p = props.p.bind(gc);
let v = props.value.bind(gc);
let receiver = props.receiver.bind(gc);
let ov: Value = o.into();
let is_receiver = ov == receiver;
if offset.is_unset() {
if bo.is_some_and(|bo| !ordinary_is_extensible(agent, bo)) {
return SetResult::Unwritable.into();
}
if is_receiver {
let bo = bo.unwrap_or_else(|| o.get_or_create_backing_object(agent));
if let Err(err) = bo.property_storage().push(agent, o, p, Some(v), None, gc) {
return agent.throw_allocation_exception(err, gc).into();
}
let shape = bo.object_shape(agent);
if !shape.is_intrinsic(agent) {
props
.cache
.insert_lookup_offset_if_not_found(agent, shape, bo.len(agent) - 1);
}
return SetResult::Done.into();
} else {
return handle_super_set_inner(agent, p, v, receiver, None, gc);
}
}
let offset = offset.get_property_offset();
let data = bo
.expect("OrdinarySet at offset with valid offset but no backing object")
.get_elements_storage_mut(agent);
if let Some(slot) = &mut data.values[offset as usize] {
let writable = match data.descriptors {
Entry::Occupied(e) => e
.get()
.get(&(offset as u32))
.is_none_or(|d| d.is_writable().unwrap()),
Entry::Vacant(_) => true,
};
if !writable {
return SetResult::Unwritable.into();
}
if is_receiver {
*slot = v.unbind();
SetResult::Done.into()
} else {
handle_super_set_inner(agent, p, v, receiver, None, gc)
}
} else {
let Entry::Occupied(e) = data.descriptors else {
unreachable!()
};
let d = e.get().get(&(offset as u32)).unwrap();
debug_assert!(d.is_accessor_descriptor());
d.setter_function(gc)
.map_or(SetResult::Accessor, SetResult::Set)
.into()
}
}
fn handle_super_set_inner<'gc>(
agent: &mut Agent,
p: PropertyKey,
v: Value,
receiver: Value,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
let Ok(receiver) = Object::try_from(receiver) else {
return SetResult::Unwritable.into();
};
let existing_descriptor = receiver.try_get_own_property(agent, p, cache, gc)?;
let result = if let Some(existing_descriptor) = existing_descriptor {
if existing_descriptor.is_accessor_descriptor() {
return SetResult::Accessor.into();
}
if existing_descriptor.writable == Some(false) {
return SetResult::Unwritable.into();
}
let value_desc = PropertyDescriptor {
value: Some(v.unbind()),
..Default::default()
};
receiver.try_define_own_property(agent, p, value_desc, cache, gc)
}
else {
try_create_data_property(agent, receiver, p, v, cache, gc)
};
result.map_continue(|result| {
if result {
SetResult::Done
} else {
SetResult::Unwritable
}
})
}
pub(crate) fn ordinary_delete(
agent: &mut Agent,
o: Object,
backing_object: OrdinaryObject,
property_key: PropertyKey,
gc: NoGcScope,
) -> bool {
let descriptor = ordinary_get_own_property(agent, o, backing_object, property_key, None, gc);
let Some(descriptor) = descriptor else {
return true;
};
if let Some(true) = descriptor.configurable {
backing_object
.property_storage()
.remove(agent, o, property_key)
.expect("Should perform GC here");
return true;
}
false
}
pub(crate) fn ordinary_own_property_keys<'a>(
agent: &Agent,
object: OrdinaryObject<'a>,
gc: NoGcScope<'a, '_>,
) -> Vec<PropertyKey<'a>> {
let PropertyStorageRef {
keys,
values: _,
descriptors: _,
} = object.get_property_storage(agent);
let mut integer_keys = vec![];
let mut keys_vec = Vec::with_capacity(keys.len());
let mut symbol_keys = vec![];
for key in keys.iter() {
match key {
PropertyKey::Integer(integer_key) => {
let key_value = integer_key.into_i64();
if (0..u32::MAX as i64).contains(&key_value) {
integer_keys.push(key_value as u32);
} else {
keys_vec.push(key.bind(gc));
}
}
PropertyKey::Symbol(symbol) => symbol_keys.push(symbol.bind(gc)),
PropertyKey::PrivateName(_) => {}
_ => keys_vec.push(key.bind(gc)),
}
}
if !integer_keys.is_empty() {
integer_keys.sort();
keys_vec.splice(0..0, integer_keys.into_iter().map(|key| key.into()));
}
if !symbol_keys.is_empty() {
keys_vec.extend(symbol_keys.iter().map(|key| PropertyKey::Symbol(*key)));
}
keys_vec
}
pub(crate) fn ordinary_object_create_null<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
) -> OrdinaryObject<'a> {
OrdinaryObject::create_object(agent, None, &[])
.expect("Should perform GC here")
.bind(gc)
}
pub(crate) fn ordinary_object_create_with_intrinsics<'a>(
agent: &mut Agent,
proto_intrinsics: ProtoIntrinsics,
prototype: Option<Object<'a>>,
gc: NoGcScope<'a, '_>,
) -> Object<'a> {
let object = match proto_intrinsics {
ProtoIntrinsics::Array => agent.heap.create(ArrayHeapData::default()).into(),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::ArrayBuffer => agent.heap.create(ArrayBufferHeapData::default()).into(),
ProtoIntrinsics::ArrayIterator => {
agent.heap.create(ArrayIteratorHeapData::default()).into()
}
ProtoIntrinsics::BigInt => agent
.heap
.create(PrimitiveObjectRecord::new_big_int_object(0.into()))
.into(),
ProtoIntrinsics::Boolean => agent
.heap
.create(PrimitiveObjectRecord::new_boolean_object(false))
.into(),
ProtoIntrinsics::Error => agent
.heap
.create(ErrorHeapData::new(ExceptionType::Error, None, None))
.into(),
ProtoIntrinsics::EvalError => agent
.heap
.create(ErrorHeapData::new(ExceptionType::EvalError, None, None))
.into(),
#[cfg(feature = "date")]
ProtoIntrinsics::Date => agent.heap.create(DateHeapData::new_invalid()).into(),
ProtoIntrinsics::Function => todo!(),
ProtoIntrinsics::Number => agent
.heap
.create(PrimitiveObjectRecord::new_number_object(0.into()))
.into(),
ProtoIntrinsics::Object => OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.into(),
),
&[],
)
.expect("Should perform GC here")
.into(),
ProtoIntrinsics::RangeError => agent
.heap
.create(ErrorHeapData::new(ExceptionType::RangeError, None, None))
.into(),
ProtoIntrinsics::ReferenceError => agent
.heap
.create(ErrorHeapData::new(
ExceptionType::ReferenceError,
None,
None,
))
.into(),
ProtoIntrinsics::String => agent
.heap
.create(PrimitiveObjectRecord::new_string_object(
String::EMPTY_STRING,
))
.into(),
ProtoIntrinsics::StringIterator => agent
.heap
.create(StringIteratorHeapData::new(String::EMPTY_STRING))
.into(),
ProtoIntrinsics::Symbol => agent
.heap
.create(PrimitiveObjectRecord::new_symbol_object(Symbol::from(
WellKnownSymbols::AsyncIterator,
)))
.into(),
ProtoIntrinsics::SyntaxError => agent
.heap
.create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None))
.into(),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalDuration => agent.heap.create(DurationRecord::default()).into(),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalPlainTime => agent.heap.create(PlainTimeRecord::default()).into(),
ProtoIntrinsics::TypeError => agent
.heap
.create(ErrorHeapData::new(ExceptionType::TypeError, None, None))
.into(),
ProtoIntrinsics::URIError => agent
.heap
.create(ErrorHeapData::new(ExceptionType::UriError, None, None))
.into(),
ProtoIntrinsics::AggregateError => agent
.heap
.create(ErrorHeapData::new(
ExceptionType::AggregateError,
None,
None,
))
.into(),
ProtoIntrinsics::AsyncFunction => todo!(),
ProtoIntrinsics::AsyncGenerator => {
agent.heap.create(AsyncGeneratorHeapData::default()).into()
}
ProtoIntrinsics::AsyncGeneratorFunction => todo!(),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::BigInt64Array => {
Object::BigInt64Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::BigUint64Array => {
Object::BigUint64Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::DataView => agent.heap.create(DataViewRecord::default()).into(),
#[cfg(feature = "shared-array-buffer")]
ProtoIntrinsics::SharedDataView => {
agent.heap.create(SharedDataViewRecord::default()).into()
}
ProtoIntrinsics::FinalizationRegistry => agent
.heap
.create(FinalizationRegistryRecord::default())
.into(),
#[cfg(feature = "proposal-float16array")]
ProtoIntrinsics::Float16Array => {
Object::Float16Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Float32Array => {
Object::Float32Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Float64Array => {
Object::Float64Array(agent.heap.create(TypedArrayRecord::default()))
}
ProtoIntrinsics::Generator => agent.heap.create(GeneratorHeapData::default()).into(),
ProtoIntrinsics::GeneratorFunction => todo!(),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int16Array => {
Object::Int16Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int32Array => {
Object::Int32Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int8Array => {
Object::Int8Array(agent.heap.create(TypedArrayRecord::default()))
}
ProtoIntrinsics::Iterator => OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.iterator_prototype()
.into(),
),
&[],
)
.expect("Should perform GC here")
.into(),
ProtoIntrinsics::Map => agent.heap.create(MapHeapData::default()).into(),
ProtoIntrinsics::MapIterator => agent.heap.create(MapIteratorHeapData::default()).into(),
ProtoIntrinsics::Promise => agent.heap.create(PromiseHeapData::default()).into(),
#[cfg(feature = "regexp")]
ProtoIntrinsics::RegExp => agent.heap.create(RegExpHeapData::default()).into(),
#[cfg(feature = "regexp")]
ProtoIntrinsics::RegExpStringIterator => unreachable!(),
#[cfg(feature = "set")]
ProtoIntrinsics::Set => agent.heap.create(SetHeapData::default()).into(),
#[cfg(feature = "set")]
ProtoIntrinsics::SetIterator => agent.heap.create(SetIteratorHeapData::default()).into(),
#[cfg(feature = "shared-array-buffer")]
ProtoIntrinsics::SharedArrayBuffer => {
agent.heap.create(SharedArrayBufferRecord::default()).into()
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint16Array => {
Object::Uint16Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint32Array => {
Object::Uint32Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint8Array => {
Object::Uint8Array(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint8ClampedArray => {
Object::Uint8ClampedArray(agent.heap.create(TypedArrayRecord::default()))
}
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakMap => agent.heap.create(WeakMapRecord::default()).into(),
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakRef => agent.heap.create(WeakRefHeapData::default()).into(),
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakSet => agent.heap.create(WeakSetHeapData::default()).into(),
}
.bind(gc);
ordinary_object_populate_with_intrinsics(agent, object, prototype)
}
pub(crate) fn ordinary_create_from_constructor<'a>(
agent: &mut Agent,
constructor: Function,
intrinsic_default_proto: ProtoIntrinsics,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Object<'a>> {
let constructor = constructor.bind(gc.nogc());
let proto = get_prototype_from_constructor(
agent,
constructor.unbind(),
intrinsic_default_proto,
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let proto = proto.bind(gc.into_nogc());
Ok(ordinary_object_create_with_intrinsics(
agent,
intrinsic_default_proto,
proto,
gc,
))
}
fn ordinary_object_populate_with_intrinsics<'a>(
agent: &mut Agent,
object: Object<'a>,
prototype: Option<Object<'a>>,
) -> Object<'a> {
if let Some(prototype) = prototype {
if !prototype.is_proxy() && !prototype.is_module() {
prototype
.get_or_create_backing_object(agent)
.make_intrinsic(agent)
.expect("Should perform GC here");
}
object.internal_set_prototype(agent, Some(prototype));
}
object
}
pub(crate) fn ordinary_populate_from_constructor<'gc>(
agent: &mut Agent,
object: Object,
constructor: Function,
intrinsic_default_proto: ProtoIntrinsics,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Object<'gc>> {
let mut object = object.bind(gc.nogc());
let constructor = constructor.bind(gc.nogc());
let proto = if let Some(proto) = try_result_into_js(try_get_prototype_from_constructor(
agent,
constructor,
intrinsic_default_proto,
gc.nogc(),
))
.unbind()?
.bind(gc.nogc())
{
proto
} else {
let scoped_object = object.scope(agent, gc.nogc());
let proto = get_prototype_from_constructor(
agent,
constructor.unbind(),
intrinsic_default_proto,
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
object = unsafe { scoped_object.take(agent) }.bind(gc.nogc());
proto
};
let object = object.unbind();
let proto = proto.unbind();
let gc = gc.into_nogc();
let object = object.bind(gc);
let proto = proto.bind(gc);
Ok(ordinary_object_populate_with_intrinsics(
agent, object, proto,
))
}
pub(crate) fn get_prototype_from_constructor<'a>(
agent: &mut Agent,
constructor: Function,
intrinsic_default_proto: ProtoIntrinsics,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<Object<'a>>> {
let mut constructor = constructor.bind(gc.nogc());
let mut function_realm = try_get_function_realm(agent, constructor, gc.nogc());
if let Some(function_realm) = function_realm {
let intrinsic_constructor =
get_intrinsic_constructor(agent, function_realm, intrinsic_default_proto);
if Some(constructor) == intrinsic_constructor {
return Ok(None);
}
}
let key = BUILTIN_STRING_MEMORY.prototype.to_property_key();
let proto = try_get(
agent,
constructor,
key,
PropertyLookupCache::get(agent, key),
gc.nogc(),
);
let proto = match proto {
ControlFlow::Continue(TryGetResult::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetResult::Value(v)) => v,
ControlFlow::Break(TryError::Err(e)) => {
return Err(e.unbind().bind(gc.into_nogc()));
}
_ => {
let scoped_realm = function_realm.map(|r| r.scope(agent, gc.nogc()));
let scoped_constructor = constructor.scope(agent, gc.nogc());
let proto = handle_try_get_result(
agent,
constructor.unbind(),
BUILTIN_STRING_MEMORY.prototype.to_property_key(),
proto.unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let gc = gc.nogc();
constructor = unsafe { scoped_constructor.take(agent) }.bind(gc);
function_realm = scoped_realm.map(|r| unsafe { r.take(agent) }.bind(gc));
proto
}
};
match Object::try_from(proto) {
Err(_) => {
if function_realm.is_none() {
let err = get_function_realm(agent, constructor.unbind(), gc.nogc()).unwrap_err();
return Err(err.unbind());
}
Ok(None)
}
Ok(proto) => {
if let Some(realm) = function_realm {
let default_proto = agent
.get_realm_record_by_id(realm)
.intrinsics()
.get_intrinsic_default_proto(intrinsic_default_proto);
if proto == default_proto {
return Ok(None);
}
}
Ok(Some(proto.unbind().bind(gc.into_nogc())))
}
}
}
fn get_intrinsic_constructor<'a>(
agent: &Agent,
function_realm: Realm<'a>,
intrinsic_default_proto: ProtoIntrinsics,
) -> Option<Function<'a>> {
let intrinsics = agent.get_realm_record_by_id(function_realm).intrinsics();
match intrinsic_default_proto {
ProtoIntrinsics::AggregateError => Some(intrinsics.aggregate_error().into()),
ProtoIntrinsics::Array => Some(intrinsics.array().into()),
ProtoIntrinsics::ArrayIterator => None,
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::ArrayBuffer => Some(intrinsics.array_buffer().into()),
ProtoIntrinsics::AsyncFunction => Some(intrinsics.async_function().into()),
ProtoIntrinsics::AsyncGenerator => None,
ProtoIntrinsics::AsyncGeneratorFunction => {
Some(intrinsics.async_generator_function().into())
}
ProtoIntrinsics::BigInt => Some(intrinsics.big_int().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::BigInt64Array => Some(intrinsics.big_int64_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::BigUint64Array => Some(intrinsics.big_uint64_array().into()),
ProtoIntrinsics::Boolean => Some(intrinsics.boolean().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::DataView => Some(intrinsics.data_view().into()),
#[cfg(feature = "shared-array-buffer")]
ProtoIntrinsics::SharedDataView => Some(intrinsics.data_view().into()),
#[cfg(feature = "date")]
ProtoIntrinsics::Date => Some(intrinsics.date().into()),
ProtoIntrinsics::Error => Some(intrinsics.error().into()),
ProtoIntrinsics::EvalError => Some(intrinsics.eval_error().into()),
ProtoIntrinsics::FinalizationRegistry => Some(intrinsics.finalization_registry().into()),
#[cfg(feature = "proposal-float16array")]
ProtoIntrinsics::Float16Array => Some(intrinsics.float16_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Float32Array => Some(intrinsics.float32_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Float64Array => Some(intrinsics.float64_array().into()),
ProtoIntrinsics::Function => Some(intrinsics.function().into()),
ProtoIntrinsics::Generator => None,
ProtoIntrinsics::GeneratorFunction => Some(intrinsics.generator_function().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int16Array => Some(intrinsics.int16_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int32Array => Some(intrinsics.int32_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Int8Array => Some(intrinsics.int8_array().into()),
ProtoIntrinsics::Iterator => Some(intrinsics.iterator().into()),
ProtoIntrinsics::Map => Some(intrinsics.map().into()),
ProtoIntrinsics::MapIterator => None,
ProtoIntrinsics::Number => Some(intrinsics.number().into()),
ProtoIntrinsics::Object => Some(intrinsics.object().into()),
ProtoIntrinsics::Promise => Some(intrinsics.promise().into()),
ProtoIntrinsics::RangeError => Some(intrinsics.range_error().into()),
ProtoIntrinsics::ReferenceError => Some(intrinsics.reference_error().into()),
#[cfg(feature = "regexp")]
ProtoIntrinsics::RegExp => Some(intrinsics.reg_exp().into()),
#[cfg(feature = "set")]
ProtoIntrinsics::Set => Some(intrinsics.set().into()),
#[cfg(feature = "set")]
ProtoIntrinsics::SetIterator => None,
#[cfg(feature = "shared-array-buffer")]
ProtoIntrinsics::SharedArrayBuffer => Some(intrinsics.shared_array_buffer().into()),
ProtoIntrinsics::String => Some(intrinsics.string().into()),
ProtoIntrinsics::StringIterator => None,
#[cfg(feature = "regexp")]
ProtoIntrinsics::RegExpStringIterator => None,
ProtoIntrinsics::Symbol => Some(intrinsics.symbol().into()),
ProtoIntrinsics::SyntaxError => Some(intrinsics.syntax_error().into()),
ProtoIntrinsics::TypeError => Some(intrinsics.type_error().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint16Array => Some(intrinsics.uint16_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint32Array => Some(intrinsics.uint32_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint8Array => Some(intrinsics.uint8_array().into()),
#[cfg(feature = "array-buffer")]
ProtoIntrinsics::Uint8ClampedArray => Some(intrinsics.uint8_clamped_array().into()),
ProtoIntrinsics::URIError => Some(intrinsics.uri_error().into()),
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakMap => Some(intrinsics.weak_map().into()),
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakRef => Some(intrinsics.weak_ref().into()),
#[cfg(feature = "weak-refs")]
ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into()),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into()),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalDuration => Some(intrinsics.temporal_duration().into()),
#[cfg(feature = "temporal")]
ProtoIntrinsics::TemporalPlainTime => Some(intrinsics.temporal_plain_time().into()),
}
}
pub(crate) fn try_get_prototype_from_constructor<'a>(
agent: &mut Agent,
constructor: Function<'a>,
intrinsic_default_proto: ProtoIntrinsics,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, Option<Object<'a>>> {
let function_realm = try_get_function_realm(agent, constructor, gc);
if let Some(function_realm) = function_realm {
let intrinsic_constructor =
get_intrinsic_constructor(agent, function_realm, intrinsic_default_proto);
if Some(constructor) == intrinsic_constructor {
return TryResult::Continue(None);
}
}
let key = BUILTIN_STRING_MEMORY.prototype.to_property_key();
let proto = try_get_result_into_value(try_get(
agent,
constructor,
key,
PropertyLookupCache::get(agent, key),
gc,
))?;
match Object::try_from(proto) {
Err(_) => {
if function_realm.is_none() {
let err = get_function_realm(agent, constructor.unbind(), gc).unwrap_err();
return err.into();
}
TryResult::Continue(None)
}
Ok(proto) => {
if let Some(realm) = function_realm {
let default_proto = agent
.get_realm_record_by_id(realm)
.intrinsics()
.get_intrinsic_default_proto(intrinsic_default_proto);
if proto == default_proto {
return TryResult::Continue(None);
}
}
TryResult::Continue(Some(proto))
}
}
}
#[inline]
#[expect(dead_code)]
pub(crate) fn set_immutable_prototype(
agent: &mut Agent,
o: Module,
v: Option<Object>,
gc: NoGcScope,
) -> bool {
let current = unwrap_try(o.try_get_prototype_of(agent, gc));
v == current
}
pub(crate) fn try_get_ordinary_object_value<'a>(
agent: &Agent,
binding_object: OrdinaryObject<'a>,
name: PropertyKey<'a>,
) -> Result<Option<Value<'a>>, ()> {
let PropertyStorageRef {
keys,
values,
descriptors,
} = binding_object.get_property_storage(agent);
let index = keys
.iter()
.enumerate()
.find(|(_, k)| **k == name)
.map(|(i, _)| i);
if let Some(index) = index {
let Some(value) = values[index] else {
if descriptors.is_some_and(|d| {
d.get(&(index as u32))
.is_some_and(|d| d.has_setter() && !d.has_getter())
}) {
return Ok(Some(Value::Undefined));
}
return Err(());
};
return Ok(Some(value));
}
let proto = binding_object.internal_prototype(agent);
if let Some(Object::Object(proto)) = proto {
try_get_ordinary_object_value(agent, proto, name)
} else if proto.is_none() {
Ok(None)
} else {
Err(())
}
}