use core::ops::ControlFlow;
use crate::{
ecmascript::{
Agent, ArgumentsList, Array, BUILTIN_STRING_MEMORY, BuiltinConstructorFunction,
ECMAScriptCodeEvaluationState, Environment, ExceptionType, ExecutionContext, Function,
InternalMethods, InternalSlots, IteratorRecord, JsError, JsResult, KeyedGroup, Number,
Object, OrdinaryObject, PrivateName, PropertyDescriptor, PropertyKey, PropertyKeySet,
PropertyLookupCache, ProtoIntrinsics, Realm, SetResult, SmallInteger, String, TryError,
TryGetResult, TryHasResult, TryResult, Value, array_create,
canonicalize_keyed_collection_key, get_iterator, if_abrupt_close_iterator, is_callable,
is_constructor, iterator_close_with_error, iterator_step_value, js_result_into_try,
new_class_field_initializer_environment, require_object_coercible, to_length, to_object,
to_property_key, to_property_key_simple, try_get_result_into_value, try_result_into_js,
try_to_length, try_validate_non_revoked_proxy, unwrap_try, validate_non_revoked_proxy,
},
engine::{
Bindable, GcScope, NoGcScope, Rootable, Scopable, ScopableCollection, Scoped,
ScopedCollection, Vm, instanceof_operator,
},
heap::{ArenaAccess, ElementDescriptor, ObjectEntry, WellKnownSymbols},
};
use super::{
operations_on_iterator_objects::MaybeInvalidIteratorRecord, testing_and_comparison::same_value,
};
#[inline]
pub(crate) fn get<'a, 'b>(
agent: &mut Agent,
o: impl InternalMethods<'b>,
p: PropertyKey,
gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let p = p.bind(gc.nogc());
o.internal_get(agent, p.unbind(), o.into(), gc)
}
#[inline]
pub(crate) fn try_get<'a, 'gc>(
agent: &mut Agent,
o: impl InternalMethods<'a>,
p: PropertyKey,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, TryGetResult<'gc>> {
o.try_get(agent, p, o.into(), cache, gc)
}
pub(crate) fn get_v<'gc>(
agent: &mut Agent,
v: Value,
p: PropertyKey,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let v = v.bind(gc.nogc());
let p = p.bind(gc.nogc());
let o = match v {
Value::Undefined | Value::Null => {
return Err(to_object(agent, v.unbind(), gc.into_nogc()).unwrap_err());
}
Value::Boolean(_) => agent
.current_realm_record()
.intrinsics()
.boolean_prototype()
.into(),
Value::String(_) | Value::SmallString(_) => {
let v = String::try_from(v).unwrap();
if let Some(value) = v.get_property_value(agent, p) {
return Ok(value.unbind());
}
agent
.current_realm_record()
.intrinsics()
.string_prototype()
.into()
}
Value::Symbol(_) => agent
.current_realm_record()
.intrinsics()
.symbol_prototype()
.into(),
Value::Number(_) | Value::Integer(_) | Value::SmallF64(_) => agent
.current_realm_record()
.intrinsics()
.number_prototype()
.into(),
Value::BigInt(_) | Value::SmallBigInt(_) => agent
.current_realm_record()
.intrinsics()
.big_int_prototype()
.into(),
_ => Object::try_from(v).unwrap(),
};
o.unbind().internal_get(agent, p.unbind(), v.unbind(), gc)
}
pub(crate) fn try_get_v<'gc>(
agent: &mut Agent,
v: Value,
p: PropertyKey,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, Value<'gc>> {
let v = v.bind(gc);
let p = p.bind(gc);
let o = match v {
Value::Undefined | Value::Null => {
return to_object(agent, v, gc).unwrap_err().into();
}
Value::Boolean(_) => agent
.current_realm_record()
.intrinsics()
.boolean_prototype()
.into(),
Value::String(_) | Value::SmallString(_) => {
let v = String::try_from(v).unwrap();
if let Some(value) = v.get_property_value(agent, p) {
return TryResult::Continue(value.bind(gc));
}
agent
.current_realm_record()
.intrinsics()
.string_prototype()
.into()
}
Value::Symbol(_) => agent
.current_realm_record()
.intrinsics()
.symbol_prototype()
.into(),
Value::Number(_) | Value::Integer(_) | Value::SmallF64(_) => agent
.current_realm_record()
.intrinsics()
.number_prototype()
.into(),
Value::BigInt(_) | Value::SmallBigInt(_) => agent
.current_realm_record()
.intrinsics()
.big_int_prototype()
.into(),
_ => Object::try_from(v).unwrap(),
};
try_get_result_into_value(o.try_get(agent, p, v, None, gc))
}
pub(crate) fn set<'a>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
v: Value,
throw: bool,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let o = o.bind(gc.nogc());
let p = p.bind(gc.nogc());
let scoped_p = p.scope(agent, gc.nogc());
let success = o
.unbind()
.internal_set(agent, p.unbind(), v, o.unbind().into(), gc.reborrow())
.unbind()?;
let p = unsafe { scoped_p.take(agent) }.bind(gc.nogc());
if !success && throw {
return throw_set_error(agent, p.unbind(), gc.into_nogc()).into();
}
Ok(())
}
pub(crate) fn throw_set_error<'a>(
agent: &mut Agent,
p: PropertyKey,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
agent.throw_exception(
ExceptionType::TypeError,
format!("Could not set property '{}'.", p.as_display(agent)),
gc,
)
}
pub(crate) fn try_set<'gc>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
v: Value,
throw: bool,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
let result = o.try_set(agent, p, v, o.into(), cache, gc)?;
if result.failed() && throw {
return throw_set_error(agent, p, gc).into();
}
result.into()
}
pub(crate) fn try_create_data_property<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
value: Value,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, bool> {
let new_desc = PropertyDescriptor {
value: Some(value.unbind()),
writable: Some(true),
get: None,
set: None,
enumerable: Some(true),
configurable: Some(true),
};
object.try_define_own_property(agent, property_key, new_desc, cache, gc)
}
pub(crate) fn create_data_property<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
value: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, bool> {
let new_desc = PropertyDescriptor::new_data_descriptor(value);
object.internal_define_own_property(agent, property_key, new_desc, gc)
}
pub(crate) fn try_create_data_property_or_throw<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
value: Value,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, ()> {
let success = try_create_data_property(agent, object, property_key, value, cache, gc)?;
if !success {
agent
.throw_exception(
ExceptionType::TypeError,
format!(
"Could not create property '{}'.",
property_key.as_display(agent)
),
gc,
)
.into()
} else {
TryResult::Continue(())
}
}
pub(crate) fn create_data_property_or_throw<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let property_key = property_key.bind(gc.nogc());
let scoped_property_key = property_key.scope(agent, gc.nogc());
let success = create_data_property(agent, object, property_key.unbind(), value, gc.reborrow())
.unbind()?;
if !success {
Err(agent.throw_exception(
ExceptionType::TypeError,
format!(
"Could not create property '{}'.",
scoped_property_key
.get(agent)
.bind(gc.nogc())
.as_display(agent)
),
gc.into_nogc(),
))
} else {
Ok(())
}
}
pub(crate) fn try_define_property_or_throw<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
desc: PropertyDescriptor,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, ()> {
let success = object.try_define_own_property(agent, property_key, desc, cache, gc)?;
if !success {
agent
.throw_exception_with_static_message(
ExceptionType::TypeError,
"Failed to defined property on object",
gc,
)
.into()
} else {
TryResult::Continue(())
}
}
pub(crate) fn define_property_or_throw<'a, 'gc>(
agent: &mut Agent,
object: impl InternalMethods<'a>,
property_key: PropertyKey,
desc: PropertyDescriptor,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let property_key = property_key.bind(gc.nogc());
let scoped_key = property_key.scope(agent, gc.nogc());
let desc = desc.bind(gc.nogc());
let success = object
.internal_define_own_property(agent, property_key.unbind(), desc.unbind(), gc.reborrow())
.unbind()?;
if !success {
let property_key = unsafe { scoped_key.take(agent).bind(gc.nogc()) };
let message = format!(
"Failed to defined property '{}' on object",
property_key.as_display(agent)
);
Err(agent.throw_exception(ExceptionType::TypeError, message, gc.into_nogc()))
} else {
Ok(())
}
}
pub(crate) fn delete_property_or_throw<'a, 'gc>(
agent: &mut Agent,
o: impl InternalMethods<'a>,
p: PropertyKey,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let p = p.bind(gc.nogc());
let success = o
.internal_delete(agent, p.unbind(), gc.reborrow())
.unbind()?;
if !success {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Failed to delete property",
gc.into_nogc(),
))
} else {
Ok(())
}
}
pub(crate) fn try_get_object_method<'a>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, Option<Function<'a>>> {
let func = try_get_result_into_value(o.try_get(agent, p, o.into(), None, gc))?;
js_result_into_try(get_method_internal(agent, func, gc))
}
pub(crate) fn get_method<'a>(
agent: &mut Agent,
v: Value,
p: PropertyKey,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<Function<'a>>> {
let p = p.bind(gc.nogc());
let func = get_v(agent, v, p.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
get_method_internal(agent, func.unbind(), gc.into_nogc())
}
pub(crate) fn get_object_method<'a>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<Function<'a>>> {
let p = p.bind(gc.nogc());
let func = o
.internal_get(agent, p.unbind(), o.into(), gc.reborrow())
.unbind()?;
let gc = gc.into_nogc();
let func = func.bind(gc);
get_method_internal(agent, func, gc)
}
fn get_method_internal<'a>(
agent: &mut Agent,
func: Value,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, Option<Function<'a>>> {
if func.is_undefined() || func.is_null() {
return Ok(None);
}
let func = is_callable(func, gc);
if func.is_none() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Not a callable object",
gc,
));
}
Ok(func)
}
#[inline(always)]
pub(crate) fn try_has_property<'gc>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, TryHasResult<'gc>> {
o.try_has_property(agent, p, cache, gc)
}
#[inline(always)]
pub(crate) fn has_property<'a>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
o.internal_has_property(agent, p, gc)
}
#[inline(always)]
pub(crate) fn try_has_own_property<'gc>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
cache: Option<PropertyLookupCache>,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, bool> {
let desc = o.try_get_own_property(agent, p, cache, gc)?;
TryResult::Continue(desc.is_some())
}
#[inline(always)]
pub(crate) fn has_own_property<'a>(
agent: &mut Agent,
o: Object,
p: PropertyKey,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let desc = o.internal_get_own_property(agent, p, gc)?;
Ok(desc.is_some())
}
pub(crate) fn call<'gc>(
agent: &mut Agent,
f: Value,
v: Value,
arguments_list: Option<ArgumentsList>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let arguments_list = arguments_list.unwrap_or_default();
match is_callable(f, gc.nogc()) {
None => Err(throw_not_callable(agent, gc.into_nogc()).unbind()),
Some(f) => {
let current_stack_size = agent.stack_refs.borrow().len();
let result = f.unbind().internal_call(agent, v, arguments_list, gc);
agent.stack_refs.borrow_mut().truncate(current_stack_size);
result
}
}
}
#[cold]
#[inline(never)]
pub(crate) fn throw_not_callable<'a>(agent: &mut Agent, gc: NoGcScope<'a, '_>) -> JsError<'a> {
agent.throw_exception_with_static_message(ExceptionType::TypeError, "Not a callable object", gc)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IntegrityLevel {
Sealed,
Frozen,
}
pub(crate) trait Level {
const LEVEL: IntegrityLevel;
}
mod integrity {
use super::{IntegrityLevel, Level};
pub(crate) struct Sealed {}
pub(crate) struct Frozen {}
impl Level for Sealed {
const LEVEL: IntegrityLevel = IntegrityLevel::Sealed;
}
impl Level for Frozen {
const LEVEL: IntegrityLevel = IntegrityLevel::Frozen;
}
}
pub(crate) use integrity::*;
pub(crate) fn set_integrity_level<'a, T: Level>(
agent: &mut Agent,
o: Object,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let status = o
.internal_prevent_extensions(agent, gc.reborrow())
.unbind()?;
if !status {
return Ok(false);
}
let keys = o
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let keys = keys.unbind().bind(gc.nogc());
if T::LEVEL == IntegrityLevel::Sealed {
let mut broke = false;
let mut i = 0;
for k in keys.iter() {
if try_result_into_js(try_define_property_or_throw(
agent,
o,
*k,
PropertyDescriptor {
configurable: Some(false),
..Default::default()
},
None,
gc.nogc(),
))
.unbind()?
.is_none()
{
broke = true;
break;
}
i += 1;
}
if !broke {
return Ok(true);
}
let keys = keys[i..]
.iter()
.map(|pk| pk.scope(agent, gc.nogc()))
.collect::<Vec<_>>();
for k in keys {
define_property_or_throw(
agent,
o,
k.get(agent),
PropertyDescriptor {
configurable: Some(false),
..Default::default()
},
gc.reborrow(),
)
.unbind()?;
}
} else {
let mut broke = false;
let mut i = 0;
for &k in keys.iter() {
let current_desc = if let TryResult::Continue(result) =
o.try_get_own_property(agent, k, None, gc.nogc())
{
result
} else {
broke = true;
break;
};
if let Some(current_desc) = current_desc {
let desc = if current_desc.is_accessor_descriptor() {
PropertyDescriptor {
configurable: Some(false),
..Default::default()
}
} else {
PropertyDescriptor {
configurable: Some(false),
writable: Some(false),
..Default::default()
}
};
if try_result_into_js(try_define_property_or_throw(
agent,
o,
k,
desc,
None,
gc.nogc(),
))
.unbind()?
.is_none()
{
broke = true;
break;
};
}
i += 1;
}
if !broke {
return Ok(true);
}
let keys = keys[i..]
.iter()
.map(|pk| pk.scope(agent, gc.nogc()))
.collect::<Vec<_>>();
for k in keys {
let current_desc = o
.internal_get_own_property(agent, k.get(agent), gc.reborrow())
.unbind()?
.bind(gc.nogc());
if let Some(current_desc) = current_desc {
let desc = if current_desc.is_accessor_descriptor() {
PropertyDescriptor {
configurable: Some(false),
..Default::default()
}
} else {
PropertyDescriptor {
configurable: Some(false),
writable: Some(false),
..Default::default()
}
};
define_property_or_throw(agent, o, k.get(agent), desc, gc.reborrow()).unbind()?;
}
}
}
Ok(true)
}
pub(crate) fn test_integrity_level<'a, T: Level>(
agent: &mut Agent,
o: Object,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
if o.internal_is_extensible(agent, gc.reborrow()).unbind()? {
return Ok(false);
}
let keys = o
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let keys = keys.unbind().bind(gc.nogc());
let mut broke = false;
let mut i = 0;
for &k in keys.iter() {
let TryResult::Continue(result) = o.try_get_own_property(agent, k, None, gc.nogc()) else {
broke = true;
break;
};
if let Some(current_desc) = result {
if current_desc.configurable == Some(true) {
return Ok(false);
}
if T::LEVEL == IntegrityLevel::Frozen && current_desc.is_data_descriptor() {
if current_desc.writable == Some(true) {
return Ok(false);
}
}
}
i += 1;
}
if !broke {
return Ok(true);
}
let keys = keys
.iter()
.skip(i)
.map(|pk| pk.scope(agent, gc.nogc()))
.collect::<Vec<_>>();
for k in keys {
if let Some(current_desc) = o
.internal_get_own_property(agent, k.get(agent), gc.reborrow())
.unbind()?
.bind(gc.nogc())
{
if current_desc.configurable == Some(true) {
return Ok(false);
}
if T::LEVEL == IntegrityLevel::Frozen && current_desc.is_data_descriptor() {
if current_desc.writable == Some(true) {
return Ok(false);
}
}
}
}
Ok(true)
}
pub(crate) fn create_array_from_list<'a>(
agent: &mut Agent,
elements: &[Value],
gc: NoGcScope<'a, '_>,
) -> Array<'a> {
let len = elements.len();
let array = array_create(agent, len, len, None, gc).unwrap();
array
.as_mut_slice(agent)
.copy_from_slice(unsafe { core::mem::transmute::<&[Value], &[Option<Value>]>(elements) });
array
}
pub(crate) fn create_array_from_scoped_list<'a>(
agent: &mut Agent,
elements: ScopedCollection<Vec<Value>>,
gc: NoGcScope<'a, '_>,
) -> Array<'a> {
let elements = elements.take(agent).bind(gc);
let len = elements.len();
let array = array_create(agent, len, len, None, gc).unwrap();
let slice = array.as_mut_slice(agent).iter_mut().zip(elements);
{
for (target, el) in slice {
*target = Some(el.unbind());
}
}
array
}
pub(crate) fn length_of_array_like<'a>(
agent: &mut Agent,
obj: Object,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, i64> {
let obj = obj.bind(gc.nogc());
if let Ok(array) = Array::try_from(obj) {
return Ok(array.len(agent) as i64);
}
let property = get(
agent,
obj.unbind(),
PropertyKey::from(BUILTIN_STRING_MEMORY.length),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
to_length(agent, property.unbind(), gc)
}
pub(crate) fn try_length_of_array_like<'a>(
agent: &mut Agent,
obj: Object,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, i64> {
if let Ok(array) = Array::try_from(obj) {
return TryResult::Continue(array.len(agent) as i64);
}
let property = try_get_result_into_value(try_get(
agent,
obj,
PropertyKey::from(BUILTIN_STRING_MEMORY.length),
None,
gc,
))?;
try_to_length(agent, property, gc)
}
pub(crate) fn create_list_from_array_like<'gc>(
agent: &mut Agent,
obj: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Vec<Value<'gc>>> {
match obj {
Value::Array(array) if array.is_simple(agent) => {
let gc = gc.into_nogc();
Ok(array
.as_slice(agent)
.iter()
.map(|el| el.unwrap_or(Value::Undefined).bind(gc))
.collect())
}
_ if obj.is_object() => {
let object = Object::try_from(obj).unwrap();
let len = length_of_array_like(agent, object, gc.reborrow()).unbind()?;
let len = usize::try_from(len).unwrap();
let mut list = Vec::<Value>::with_capacity(len).scope(agent, gc.nogc());
for i in 0..len {
let next = get(
agent,
object,
PropertyKey::Integer(SmallInteger::try_from(i as u64).unwrap()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
list.push(agent, next);
}
let gc = gc.into_nogc();
Ok(list.take(agent).bind(gc))
}
_ => Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Not an object",
gc.into_nogc(),
)),
}
}
pub(crate) fn create_property_key_list_from_array_like<'a, 'b>(
agent: &mut Agent,
obj: Value,
mut gc: GcScope<'a, 'b>,
) -> JsResult<'a, ScopedCollection<'b, Vec<PropertyKey<'static>>>> {
let Ok(object) = Object::try_from(obj) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Not an object",
gc.into_nogc(),
));
};
let object = object.bind(gc.nogc());
let scoped_object = object.scope(agent, gc.nogc());
let len = length_of_array_like(agent, object.unbind(), gc.reborrow()).unbind()?;
let len = usize::try_from(len).unwrap();
let mut list = Vec::<PropertyKey>::with_capacity(len).scope(agent, gc.nogc());
let mut index = 0;
while index < len {
let next = get(
agent,
scoped_object.get(agent).unbind(),
PropertyKey::Integer(SmallInteger::try_from(index as u64).unwrap()).unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
match next {
Value::String(_) | Value::SmallString(_) => {
let string_value = String::try_from(next).unwrap();
let scoped_property_key =
to_property_key_simple(agent, string_value.unbind(), gc.nogc()).unwrap();
list.push(agent, scoped_property_key);
}
Value::Symbol(sym) => list.push(agent, sym.into()),
_ => {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"proxy [[OwnPropertyKeys]] must return an array with only string and symbol elements",
gc.into_nogc(),
));
}
}
index += 1;
}
Ok(list)
}
pub(crate) fn call_function<'gc>(
agent: &mut Agent,
f: Function,
v: Value,
arguments_list: Option<ArgumentsList>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let f = f.bind(gc.nogc());
let arguments_list = arguments_list.unwrap_or_default();
let current_stack_size = agent.stack_refs.borrow().len();
let result = f.unbind().internal_call(agent, v, arguments_list, gc);
agent.stack_refs.borrow_mut().truncate(current_stack_size);
result
}
pub(crate) fn construct<'a>(
agent: &mut Agent,
f: Function,
arguments_list: Option<ArgumentsList>,
new_target: Option<Function>,
gc: GcScope<'a, '_>,
) -> JsResult<'a, Object<'a>> {
let f = f.bind(gc.nogc());
let new_target = new_target.unwrap_or(f);
let arguments_list = arguments_list.unwrap_or_default();
f.unbind()
.internal_construct(agent, arguments_list, new_target.unbind(), gc)
}
pub(crate) fn invoke<'a>(
agent: &mut Agent,
v: Value,
p: PropertyKey,
arguments_list: Option<ArgumentsList>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let v = v.bind(gc.nogc());
let p = p.bind(gc.nogc());
let mut arguments_list = arguments_list.unwrap_or_default();
if let Some(func) = try_result_into_js(try_get_v(agent, v, p, gc.nogc()))
.unbind()?
.bind(gc.nogc())
{
call(agent, func.unbind(), v.unbind(), Some(arguments_list), gc)
} else {
let scoped_v = v.scope(agent, gc.nogc());
let v_unbound = v.unbind();
let p_unbound = p.unbind();
let func = arguments_list
.with_scoped(
agent,
|agent, _, gc| get_v(agent, v_unbound, p_unbound, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
call(
agent,
func.unbind(),
scoped_v.get(agent),
Some(arguments_list),
gc,
)
}
}
pub(crate) fn ordinary_has_instance<'a, 'b>(
agent: &mut Agent,
c: impl TryInto<Function<'b>>,
o: impl Into<Value<'b>>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let Some(c) = is_callable(c, gc.nogc()) else {
return Ok(false);
};
if let Function::BoundFunction(c) = c {
let bc = c.bound_target_function(agent);
return instanceof_operator(agent, o, bc.unbind(), gc);
}
let Ok(o) = Object::try_from(o.into()) else {
return Ok(false);
};
let p = get(
agent,
c.unbind(),
BUILTIN_STRING_MEMORY.prototype.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Ok(p) = Object::try_from(p) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Non-object prototype found",
gc.into_nogc(),
));
};
is_prototype_of_loop(agent, p.unbind(), o.unbind(), gc)
}
pub(crate) fn species_constructor<'a>(
agent: &mut Agent,
o: Object<'a>,
default_constructor: ProtoIntrinsics,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Function<'a>> {
let c = get(
agent,
o,
BUILTIN_STRING_MEMORY.constructor.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if c.is_undefined() {
return Ok(agent
.current_realm_record()
.intrinsics()
.get_intrinsic_default_constructor(default_constructor));
}
let Ok(c) = Object::try_from(c) else {
return Err(throw_constructor_property_not_an_object(
agent,
gc.into_nogc(),
));
};
let s = get(
agent,
c.unbind(),
WellKnownSymbols::Species.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if s.is_undefined() || s.is_null() {
return Ok(agent
.current_realm_record()
.intrinsics()
.get_intrinsic_default_constructor(default_constructor));
}
if let Some(s) = is_constructor(agent, s) {
return Ok(s.unbind());
}
Err(throw_species_constructor_is_not_a_constructor(
agent,
gc.into_nogc(),
))
}
#[cold]
#[inline(never)]
fn throw_constructor_property_not_an_object<'gc>(
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> JsError<'gc> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"constructor property value is not an object",
gc.into_nogc(),
)
}
#[cold]
#[inline(never)]
fn throw_species_constructor_is_not_a_constructor<'gc>(
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> JsError<'gc> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"species constructor is not a constructor",
gc.into_nogc(),
)
}
#[cfg(feature = "array-buffer")]
pub(crate) fn try_species_constructor<'gc>(
agent: &mut Agent,
o: Object<'gc>,
default_constructor: ProtoIntrinsics,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, Option<Function<'gc>>> {
let c = try_get_result_into_value(try_get(
agent,
o,
BUILTIN_STRING_MEMORY.constructor.into(),
None,
gc,
))?;
if c.is_undefined() {
return TryResult::Continue(None);
}
let Ok(c) = Object::try_from(c) else {
return throw_constructor_property_not_an_object(agent, gc).into();
};
let s = try_get_result_into_value(try_get(
agent,
c,
WellKnownSymbols::Species.into(),
None,
gc,
))?;
if s.is_undefined() || s.is_null() {
return TryResult::Continue(None);
}
if let Some(s) = is_constructor(agent, s) {
let default_constructor = agent
.current_realm_record()
.intrinsics()
.get_intrinsic_default_constructor(default_constructor);
return TryResult::Continue(if s == default_constructor {
None
} else {
Some(s)
});
}
throw_species_constructor_is_not_a_constructor(agent, gc).into()
}
pub(crate) fn is_prototype_of_loop<'a>(
agent: &mut Agent,
o: Object,
v: Object,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let mut v = v.bind(gc.nogc());
{
let gc = gc.nogc();
loop {
let proto = v.try_get_prototype_of(agent, gc);
let TryResult::Continue(proto) = proto else {
break;
};
if let Some(proto) = proto {
v = proto;
if o == v {
return Ok(true);
}
} else {
return Ok(false);
}
}
}
let o = o.scope(agent, gc.nogc());
loop {
let proto = v
.unbind()
.internal_get_prototype_of(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
if let Some(proto) = proto {
v = proto;
if o.get(agent) == v {
return Ok(true);
}
} else {
return Ok(false);
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum EnumPropKind {
Value,
KeyValue,
}
pub(crate) trait EnumerablePropertiesKind {
const KIND: EnumPropKind;
}
mod enumerable_properties_kind {
use super::{EnumPropKind, EnumerablePropertiesKind};
pub(crate) struct EnumerateValues;
pub(crate) struct EnumerateKeysAndValues;
impl EnumerablePropertiesKind for EnumerateValues {
const KIND: EnumPropKind = EnumPropKind::Value;
}
impl EnumerablePropertiesKind for EnumerateKeysAndValues {
const KIND: EnumPropKind = EnumPropKind::KeyValue;
}
}
pub(crate) use enumerable_properties_kind::*;
#[cfg(feature = "json")]
pub(crate) fn scoped_enumerable_own_keys<'a, 'b>(
agent: &mut Agent,
o: Scoped<'b, Object<'static>>,
mut gc: GcScope<'a, 'b>,
) -> JsResult<'a, ScopedCollection<'b, Vec<PropertyKey<'static>>>> {
if !matches!(
o.get(agent),
Object::Proxy(_) | Object::Module(_) | Object::EmbedderObject(_)
) {
let gc = gc.into_nogc();
let o = o.get(agent).bind(gc);
let keys = unwrap_try(o.try_own_property_keys(agent, gc))
.into_iter()
.filter_map(|key| {
if !key.is_string() {
return None;
}
let desc = unwrap_try(o.try_get_own_property(agent, key, None, gc));
if desc?.enumerable != Some(true) {
return None;
}
Some(key)
})
.collect::<Vec<PropertyKey>>();
return Ok(keys.scope(agent, gc));
}
let own_string_keys = o
.get(agent)
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc())
.into_iter()
.filter_map(|key| {
if key.is_string() {
Some(key.scope(agent, gc.nogc()))
} else {
None
}
})
.collect::<Vec<_>>();
let results = own_string_keys
.into_iter()
.filter_map(|scoped_key| {
let key = scoped_key.get(agent).bind(gc.nogc());
let desc =
match o
.get(agent)
.internal_get_own_property(agent, key.unbind(), gc.reborrow())
{
Ok(desc) => desc,
Err(err) => return Some(Err(err.unbind())),
};
if desc?.enumerable != Some(true) {
let _ = unsafe { scoped_key.take(agent) };
return None;
}
Some(Ok(scoped_key))
})
.collect::<JsResult<Vec<_>>>()?;
let gc = gc.into_nogc();
let results = results
.into_iter()
.map(|p| unsafe { p.take(agent).bind(gc) })
.collect::<Vec<_>>();
Ok(results.scope(agent, gc))
}
pub(crate) fn enumerable_own_properties<'gc, Kind: EnumerablePropertiesKind>(
agent: &mut Agent,
o: Object,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Vec<Value<'gc>>> {
let mut o = o.bind(gc.nogc());
let mut scoped_o = None;
let mut own_keys =
if let TryResult::Continue(own_keys) = o.try_own_property_keys(agent, gc.nogc()) {
own_keys
} else {
scoped_o = Some(o.scope(agent, gc.nogc()));
let result = o
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
o = scoped_o.as_ref().unwrap().get(agent).bind(gc.nogc());
result
};
let mut results: Vec<Value> = Vec::with_capacity(own_keys.len());
let mut broke = false;
let mut i = 0;
for &key in own_keys.iter() {
if let PropertyKey::Symbol(_) = key {
i += 1;
continue;
}
let TryResult::Continue(desc) = o.try_get_own_property(agent, key, None, gc.nogc()) else {
broke = true;
break;
};
let Some(desc) = desc else {
i += 1;
continue;
};
if desc.enumerable != Some(true) {
i += 1;
continue;
}
let value = if let Some(value) = desc.value
&& !matches!(o, Object::Proxy(_))
{
value
} else {
match try_get(agent, o, key, None, gc.nogc()) {
ControlFlow::Continue(TryGetResult::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetResult::Value(value)) => value,
_ => {
broke = true;
break;
}
}
};
if Kind::KIND == EnumPropKind::Value {
results.push(value);
} else {
debug_assert_eq!(Kind::KIND, EnumPropKind::KeyValue);
let key_value = match key {
PropertyKey::Symbol(_) | PropertyKey::PrivateName(_) => {
unreachable!();
}
PropertyKey::Integer(int) => {
let int = int.into_i64();
String::from_string(agent, int.to_string(), gc.nogc())
}
PropertyKey::SmallString(str) => str.into(),
PropertyKey::String(str) => str.into(),
};
let entry = create_array_from_list(agent, &[key_value.into(), value], gc.nogc());
results.push(entry.into());
}
i += 1;
}
if broke {
let _ = own_keys.drain(..i);
let scoped_o = scoped_o.unwrap_or_else(|| o.scope(agent, gc.nogc()));
enumerable_own_properties_slow::<Kind>(
agent,
scoped_o,
own_keys.unbind(),
results.unbind(),
gc,
)
} else {
Ok(results.unbind().bind(gc.into_nogc()))
}
}
fn enumerable_own_properties_slow<'gc, Kind: EnumerablePropertiesKind>(
agent: &mut Agent,
o: Scoped<Object>,
own_keys: Vec<PropertyKey>,
results: Vec<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Vec<Value<'gc>>> {
let own_keys = own_keys.scope(agent, gc.nogc());
let mut results = results.scope(agent, gc.nogc());
for key in own_keys.iter(agent) {
let local_key = key.get(gc.nogc());
if local_key.is_symbol() {
continue;
}
let desc = o
.get(agent)
.internal_get_own_property(agent, local_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let Some(desc) = desc else {
continue;
};
if desc.enumerable != Some(true) {
continue;
}
let value = get(
agent,
o.get(agent),
key.get(gc.nogc()).unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if Kind::KIND == EnumPropKind::Value {
results.push(agent, value);
} else {
debug_assert_eq!(Kind::KIND, EnumPropKind::KeyValue);
let key_value =
String::try_from(key.get(gc.nogc()).convert_to_value(agent, gc.nogc())).unwrap();
let entry = create_array_from_list(
agent,
&[key_value.unbind().into(), value.unbind()],
gc.nogc(),
);
results.push(agent, entry.into());
}
}
Ok(results.take(agent))
}
pub(crate) fn enumerable_own_keys<'gc>(
agent: &mut Agent,
o: Object,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Vec<PropertyKey<'gc>>> {
if let Object::Object(o) = o {
return Ok(ordinary_enumerable_own_keys(agent, o, gc.into_nogc()));
}
let mut o = o.bind(gc.nogc());
let mut scoped_o = None;
let mut own_keys =
if let TryResult::Continue(own_keys) = o.try_own_property_keys(agent, gc.nogc()) {
own_keys
} else {
scoped_o = Some(o.scope(agent, gc.nogc()));
let result = o
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
o = scoped_o.as_ref().unwrap().get(agent).bind(gc.nogc());
result
};
let mut results: Vec<PropertyKey> = Vec::with_capacity(own_keys.len());
let mut broke = false;
let mut i = 0;
for &key in own_keys.iter() {
if let PropertyKey::Symbol(_) = key {
i += 1;
continue;
}
let TryResult::Continue(desc) = o.try_get_own_property(agent, key, None, gc.nogc()) else {
broke = true;
break;
};
let Some(desc) = desc else {
i += 1;
continue;
};
if desc.enumerable != Some(true) {
i += 1;
continue;
}
results.push(key);
i += 1;
}
if broke {
let _ = own_keys.drain(..i);
let scoped_o = scoped_o.unwrap_or_else(|| o.scope(agent, gc.nogc()));
enumerable_own_keys_slow(agent, scoped_o, own_keys.unbind(), results.unbind(), gc)
} else {
Ok(results.unbind().bind(gc.into_nogc()))
}
}
fn ordinary_enumerable_own_keys<'gc>(
agent: &mut Agent,
o: OrdinaryObject,
gc: NoGcScope<'gc, '_>,
) -> Vec<PropertyKey<'gc>> {
let props = o.get_property_storage(agent);
let mut integer_keys = vec![];
let mut result_keys = Vec::with_capacity(props.len());
for (index, key) in props.keys.iter().enumerate() {
match key {
PropertyKey::Integer(integer_key) => {
let enumerable = props
.descriptors
.and_then(|d| d.get(&(index as u32)))
.is_none_or(|desc| desc.is_enumerable());
if !enumerable {
continue;
}
let key_value = integer_key.into_i64();
if (0..u32::MAX as i64).contains(&key_value) {
integer_keys.push(key_value as u32);
} else {
result_keys.push(key.bind(gc));
}
}
PropertyKey::Symbol(_) | PropertyKey::PrivateName(_) => {
continue;
}
_ => {
let enumerable = props
.descriptors
.and_then(|d| d.get(&(index as u32)))
.is_none_or(|desc| desc.is_enumerable());
if !enumerable {
continue;
}
result_keys.push(key.bind(gc))
}
}
}
if !integer_keys.is_empty() {
integer_keys.sort();
result_keys.splice(0..0, integer_keys.into_iter().map(|key| key.into()));
}
result_keys
}
fn enumerable_own_keys_slow<'gc>(
agent: &mut Agent,
o: Scoped<Object>,
own_keys: Vec<PropertyKey>,
results: Vec<PropertyKey>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Vec<PropertyKey<'gc>>> {
let own_keys = own_keys.scope(agent, gc.nogc());
let mut results = results.scope(agent, gc.nogc());
for key in own_keys.iter(agent) {
let local_key = key.get(gc.nogc());
if local_key.is_symbol() {
continue;
}
let desc = o
.get(agent)
.internal_get_own_property(agent, local_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let Some(desc) = desc else {
continue;
};
if desc.enumerable != Some(true) {
continue;
}
results.push(agent, key.get(gc.nogc()));
}
Ok(results.take(agent))
}
pub(crate) fn get_function_realm<'a, 'gc>(
agent: &mut Agent,
obj: impl Into<Object<'a>>,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Realm<'gc>> {
let obj = obj.into().bind(gc);
match obj {
Object::BuiltinFunction(f) => Ok(f.realm(agent)),
Object::ECMAScriptFunction(f) => Ok(f.realm(agent)),
Object::BoundFunction(f) => {
get_function_realm(agent, f.bound_target_function(agent), gc)
}
Object::Proxy(obj) => {
let obj = validate_non_revoked_proxy(agent, obj, gc)?;
let proxy_target = obj.target;
get_function_realm(agent, proxy_target, gc)
}
_ => Ok(agent.current_realm_id_internal()),
}
}
pub(crate) fn try_get_function_realm<'a, 'gc>(
agent: &Agent,
obj: impl Into<Object<'a>>,
gc: NoGcScope<'gc, '_>,
) -> Option<Realm<'gc>> {
let obj = obj.into().bind(gc);
match obj {
Object::BuiltinFunction(f) => Some(f.realm(agent)),
Object::ECMAScriptFunction(f) => Some(f.realm(agent)),
Object::BoundFunction(f) => {
try_get_function_realm(agent, f.bound_target_function(agent), gc)
}
Object::Proxy(obj) => {
let obj = try_validate_non_revoked_proxy(agent, obj, gc)?;
let proxy_target = obj.target;
try_get_function_realm(agent, proxy_target, gc)
}
_ => Some(agent.current_realm_id_internal()),
}
}
pub(crate) fn copy_data_properties<'a>(
agent: &mut Agent,
target: OrdinaryObject,
source: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let mut target = target.bind(gc.nogc());
if source.is_undefined() || source.is_null() {
return Ok(());
}
let mut from = to_object(agent, source, gc.nogc()).unwrap();
let mut scoped_target = None;
let mut scoped_from = None;
if let Object::Object(from) = from {
if from.try_copy_from_object(agent, from) {
return Ok(());
}
}
let mut keys = if let TryResult::Continue(keys) = from.try_own_property_keys(agent, gc.nogc()) {
keys
} else {
scoped_target = Some(target.scope(agent, gc.nogc()));
scoped_from = Some(from.scope(agent, gc.nogc()));
let keys = from
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.as_ref().unwrap().get(agent).bind(gc.nogc());
from = scoped_from.as_ref().unwrap().get(agent).bind(gc.nogc());
keys
};
let mut broke = false;
let mut i = 0;
for &next_key in keys.iter() {
let TryResult::Continue(desc) = from.try_get_own_property(agent, next_key, None, gc.nogc())
else {
broke = true;
break;
};
if let Some(desc) = desc
&& desc.enumerable.unwrap()
{
let prop_value = match try_get(agent, from, next_key, None, gc.nogc()) {
ControlFlow::Continue(TryGetResult::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetResult::Value(value)) => value,
_ => {
broke = true;
break;
}
};
assert!(
try_create_data_property(agent, target, next_key, prop_value, None, gc.nogc())
.is_continue()
);
}
i += 1;
}
if broke {
let _ = keys.drain(..i);
let target = scoped_target.unwrap_or_else(|| target.scope(agent, gc.nogc()));
let from = scoped_from.unwrap_or_else(|| from.scope(agent, gc.nogc()));
copy_data_properties_slow(agent, target, from, keys.unbind(), gc)
} else {
Ok(())
}
}
fn copy_data_properties_slow<'a>(
agent: &mut Agent,
target: Scoped<OrdinaryObject>,
from: Scoped<Object>,
keys: Vec<PropertyKey>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let keys = keys.scope(agent, gc.nogc());
for next_key in keys.iter(agent) {
if let Some(dest) = from
.get(agent)
.internal_get_own_property(agent, next_key.get(gc.nogc()).unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
&& dest.enumerable.unwrap()
{
let prop_value = get(
agent,
from.get(agent),
next_key.get(gc.nogc()).unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
unwrap_try(try_create_data_property(
agent,
target.get(agent),
next_key.get(gc.nogc()).unbind(),
prop_value,
None,
gc.nogc(),
));
}
}
Ok(())
}
pub(crate) fn try_copy_data_properties_into_object<'gc, 'b>(
agent: &mut Agent,
source: impl Into<Object<'b>>,
excluded_items: &PropertyKeySet,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, OrdinaryObject<'gc>> {
let from = source.into();
let mut entries = Vec::new();
for next_key in from.try_own_property_keys(agent, gc)? {
if excluded_items.contains(agent, next_key) {
continue;
}
if let Some(dest) = from.try_get_own_property(agent, next_key, None, gc)?
&& dest.enumerable.unwrap()
{
let prop_value = if let Some(prop_value) = dest.value {
prop_value
} else {
match try_get(agent, from, next_key, None, gc) {
ControlFlow::Continue(TryGetResult::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetResult::Value(value)) => value,
_ => {
return TryError::GcError.into();
}
}
};
entries.push(ObjectEntry::new_data_entry(next_key, prop_value));
}
}
let obj = OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.into(),
),
&entries,
);
if let Ok(obj) = obj {
TryResult::Continue(obj)
} else {
TryError::GcError.into()
}
}
pub(crate) fn copy_data_properties_into_object<'a, 'b>(
agent: &mut Agent,
source: impl Into<Object<'b>>,
excluded_items: ScopedCollection<PropertyKeySet>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, OrdinaryObject<'a>> {
let from = source.into().bind(gc.nogc());
let scoped_from = from.scope(agent, gc.nogc());
let mut entries = Vec::new();
let mut keys = from
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let from = scoped_from.get(agent).bind(gc.nogc());
let mut broke = false;
let mut i = 0;
for next_key in keys.iter() {
if excluded_items.contains(agent, *next_key) {
i += 1;
continue;
}
let next_key = *next_key;
let TryResult::Continue(desc) = from.try_get_own_property(agent, next_key, None, gc.nogc())
else {
broke = true;
break;
};
if let Some(desc) = desc
&& desc.enumerable.unwrap()
{
let prop_value = match try_get(agent, from, next_key, None, gc.nogc()) {
ControlFlow::Continue(TryGetResult::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetResult::Value(value)) => value,
_ => {
broke = true;
break;
}
};
entries.push(ObjectEntry::new_data_entry(next_key, prop_value));
}
i += 1;
}
let object = match OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.into(),
),
&entries,
) {
Ok(obj) => obj,
Err(err) => return Err(agent.throw_allocation_exception(err, gc.into_nogc())),
};
if broke {
let _ = keys.drain(..i);
copy_data_properties_into_object_slow(
agent,
scoped_from,
excluded_items,
keys.unbind(),
object.unbind(),
gc,
)
} else {
let _ = excluded_items.take(agent);
Ok(object.unbind())
}
}
fn copy_data_properties_into_object_slow<'a>(
agent: &mut Agent,
from: Scoped<Object>,
excluded_items: ScopedCollection<PropertyKeySet>,
keys: Vec<PropertyKey>,
object: OrdinaryObject,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, OrdinaryObject<'a>> {
let keys = keys.scope(agent, gc.nogc());
let object = object.scope(agent, gc.nogc());
for next_key in keys.iter(agent) {
let local_next_key = next_key.get(gc.nogc());
if excluded_items.contains(agent, local_next_key) {
continue;
}
if let Some(desc) = from
.get(agent)
.internal_get_own_property(agent, local_next_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
&& desc.enumerable.unwrap()
{
let prop_value = get(
agent,
from.get(agent),
next_key.get(gc.nogc()).unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
unwrap_try(try_create_data_property_or_throw(
agent,
object.get(agent),
next_key.get(gc.nogc()),
prop_value,
None,
gc.nogc(),
));
}
}
let _ = excluded_items.take(agent);
Ok(object.get(agent).bind(gc.into_nogc()))
}
pub(crate) fn throw_no_proxy_private_names<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
agent.throw_exception_with_static_message(
ExceptionType::Error,
"can't access private field or method: proxy private fields are not supported yet",
gc,
)
}
pub(crate) fn throw_no_private_name_error<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"can't access private field or method: object is not the right class",
gc,
)
}
fn throw_no_private_name_getter_error<'a>(agent: &mut Agent, gc: NoGcScope<'a, '_>) -> JsError<'a> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"can't access private field or method: field was defined without a getter",
gc,
)
}
pub(crate) fn private_element_find<'a>(
agent: &'a Agent,
o: Object,
p: PrivateName,
) -> Option<(
Option<Value<'static>>,
Option<&'a ElementDescriptor<'static>>,
)> {
o.get_backing_object(agent)
.and_then(|o| o.property_storage().private_element_find(agent, p))
}
pub(crate) fn private_get<'a>(
agent: &mut Agent,
o: Object,
p: PrivateName,
gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let o = o.bind(gc.nogc());
if o.is_proxy() {
return Err(throw_no_proxy_private_names(agent, gc.into_nogc()));
}
match private_element_find(agent, o, p) {
Some((Some(value), descriptor)) => {
assert!(
descriptor.is_none()
|| descriptor.is_some_and(|d| d.is_data_descriptor()
&& !d.is_writable().unwrap()
&& !d.is_enumerable()
&& !d.is_configurable())
);
Ok(value.bind(gc.into_nogc()))
}
Some((None, Some(descriptor))) => {
assert!(descriptor.is_accessor_descriptor());
let Some(getter) = descriptor.getter_function(gc.nogc()) else {
return Err(throw_no_private_name_getter_error(agent, gc.into_nogc()));
};
call_function(agent, getter.unbind(), o.unbind().into(), None, gc)
}
_ => Err(throw_no_private_name_error(agent, gc.into_nogc())),
}
}
pub(crate) fn try_private_get<'a>(
agent: &mut Agent,
o: Object,
p: PrivateName,
gc: NoGcScope<'a, '_>,
) -> TryResult<'a, TryGetResult<'a>> {
let o = o.bind(gc);
if o.is_proxy() {
return throw_no_proxy_private_names(agent, gc).into();
}
match private_element_find(agent, o, p) {
Some((Some(value), descriptor)) => {
assert!(
descriptor.is_none()
|| descriptor.is_some_and(|d| d.is_data_descriptor()
&& !d.is_writable().unwrap()
&& !d.is_enumerable())
);
TryGetResult::Value(value.bind(gc)).into()
}
Some((None, Some(descriptor))) => {
assert!(descriptor.is_accessor_descriptor());
if let Some(getter) = descriptor.getter_function(gc) {
TryGetResult::Get(getter).into()
} else {
throw_no_private_name_getter_error(agent, gc).into()
}
}
_ => throw_no_private_name_error(agent, gc).into(),
}
}
pub(crate) fn private_set<'a>(
agent: &mut Agent,
o: Object,
p: PrivateName,
value: Value,
gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let o = o.bind(gc.nogc());
let value = value.bind(gc.nogc());
if o.is_proxy() {
return Err(throw_no_proxy_private_names(agent, gc.into_nogc()));
}
match o
.get_backing_object(agent)
.and_then(|o| o.property_storage().private_element_find_mut(agent, p))
{
Some((Some(entry_value), descriptor)) => {
if let Some(d) = descriptor {
assert!(d.is_data_descriptor() && !d.is_writable().unwrap() && !d.is_enumerable());
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"cannot assign to private method",
gc.into_nogc(),
))
} else {
*entry_value = value.unbind();
Ok(())
}
}
Some((None, Some(descriptor))) => {
assert!(descriptor.is_accessor_descriptor());
let Some(setter) = descriptor.setter_function(gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"setting getter-only private field",
gc.into_nogc(),
));
};
call_function(
agent,
setter.unbind(),
o.unbind().into(),
Some(ArgumentsList::from_mut_value(&mut value.unbind())),
gc,
)?;
Ok(())
}
_ => {
Err(throw_no_private_name_error(agent, gc.into_nogc()))
}
}
}
pub(crate) fn try_private_set<'gc>(
agent: &mut Agent,
o: Object,
p: PrivateName,
value: Value,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, SetResult<'gc>> {
if o.is_proxy() {
return throw_no_proxy_private_names(agent, gc).into();
}
match o
.get_backing_object(agent)
.and_then(|o| o.property_storage().private_element_find_mut(agent, p))
{
Some((Some(entry_value), descriptor)) => {
if let Some(d) = descriptor {
assert!(d.is_data_descriptor() && !d.is_writable().unwrap() && !d.is_enumerable());
agent
.throw_exception_with_static_message(
ExceptionType::TypeError,
"cannot assign to private method",
gc,
)
.into()
} else {
*entry_value = value.unbind();
SetResult::Done.into()
}
}
Some((None, Some(descriptor))) => {
assert!(descriptor.is_accessor_descriptor());
let Some(setter) = descriptor.setter_function(gc) else {
return agent
.throw_exception_with_static_message(
ExceptionType::TypeError,
"setting getter-only private field",
gc,
)
.into();
};
SetResult::Set(setter).into()
}
_ => {
throw_no_private_name_error(agent, gc).into()
}
}
}
pub(crate) fn initialize_instance_elements<'a>(
agent: &mut Agent,
o: Object,
constructor: BuiltinConstructorFunction,
gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
if o.is_proxy() {
return Err(throw_no_proxy_private_names(agent, gc.into_nogc()));
}
let o = o.bind(gc.nogc());
let constructor = constructor.bind(gc.nogc());
let constructor_data = constructor.get(agent);
if let Some(bytecode) = constructor_data.compiled_initializer_bytecode {
let f = constructor.into();
let outer_env = constructor_data.environment;
let outer_priv_env = constructor_data.private_environment;
let source_code = constructor_data.source_code;
let decl_env = new_class_field_initializer_environment(agent, f, o, outer_env, gc.nogc());
agent.push_execution_context(ExecutionContext {
ecmascript_code: Some(ECMAScriptCodeEvaluationState {
lexical_environment: Environment::Function(decl_env.unbind()),
variable_environment: Environment::Function(decl_env.unbind()),
private_environment: outer_priv_env.unbind(),
is_strict_mode: true,
source_code: source_code.unbind(),
}),
function: Some(f.unbind()),
realm: constructor.get(agent).realm.unbind(),
script_or_module: None,
});
let bytecode = bytecode.scope(agent, gc.nogc());
let result = Vm::execute(agent, bytecode, None, gc).into_js_result();
agent.pop_execution_context();
result?;
}
Ok(())
}
pub(crate) fn add_value_to_keyed_group<K: 'static + Rootable + Copy + Into<Value<'static>>>(
agent: &mut Agent,
groups: &mut ScopedCollection<Box<KeyedGroup>>,
key: K,
value: Value,
) {
if core::any::TypeId::of::<K>() == core::any::TypeId::of::<PropertyKey>() {
let key = unsafe { core::mem::transmute_copy::<K, PropertyKey>(&key) };
groups.add_property_keyed_value(agent, key, value);
} else if core::any::TypeId::of::<K>() == core::any::TypeId::of::<Value>() {
let key = unsafe { core::mem::transmute_copy::<K, Value>(&key) };
groups.add_collection_keyed_value(agent, key, value);
} else {
unreachable!()
}
}
pub(crate) fn group_by_property<'gc>(
agent: &mut Agent,
items: Value,
callback_fn: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Box<KeyedGroup<'gc>>> {
let items = items.bind(gc.nogc());
let callback_fn = callback_fn.bind(gc.nogc());
require_object_coercible(agent, items, gc.nogc())
.unbind()?
.bind(gc.nogc());
let Some(callback_fn) = is_callable(callback_fn, gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Callback is not callable",
gc.into_nogc(),
));
};
let callback_fn = callback_fn.scope(agent, gc.nogc());
let mut groups = KeyedGroup::new(gc.nogc()).scope(agent, gc.nogc());
let MaybeInvalidIteratorRecord {
iterator,
next_method: Some(next_method),
} = get_iterator(agent, items.unbind(), false, gc.reborrow())
.unbind()?
.bind(gc.nogc())
else {
return Err(throw_not_callable(agent, gc.into_nogc()));
};
let iterator = iterator.scope(agent, gc.nogc());
let next_method = next_method.scope(agent, gc.nogc());
let mut k = 0;
loop {
if k >= u32::MAX as usize {
let error = agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Maximum array size of 2**53-1 exceeded",
gc.nogc(),
);
return Err(iterator_close_with_error(
agent,
iterator.get(agent),
error.unbind(),
gc,
));
}
let next = iterator_step_value(
agent,
IteratorRecord {
iterator: iterator.get(agent),
next_method: next_method.get(agent),
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Some(next) = next else {
return Ok(groups.take(agent));
};
let value = next.unbind().bind(gc.nogc());
let scoped_value = value.scope(agent, gc.nogc());
let fk = Number::try_from(k).unwrap().into();
let key = call_function(
agent,
callback_fn.get(agent),
Value::Undefined,
Some(ArgumentsList::from_mut_slice(&mut [value.unbind(), fk])),
gc.reborrow(),
);
let iterator_record = IteratorRecord {
iterator: iterator.get(agent),
next_method: next_method.get(agent),
};
let key = if_abrupt_close_iterator!(agent, key, iterator_record, gc);
let key = to_property_key(agent, key.unbind(), gc.reborrow()).unbind();
let iterator_record = IteratorRecord {
iterator: iterator.get(agent),
next_method: next_method.get(agent),
};
let key = if_abrupt_close_iterator!(agent, key, iterator_record, gc);
let value = unsafe { scoped_value.take(agent) };
add_value_to_keyed_group(agent, &mut groups, key.unbind(), value);
k += 1;
}
}
pub(crate) fn group_by_collection<'gc>(
agent: &mut Agent,
items: Value,
callback_fn: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Box<KeyedGroup<'gc>>> {
let items = items.bind(gc.nogc());
let callback_fn = callback_fn.bind(gc.nogc());
require_object_coercible(agent, items, gc.nogc())
.unbind()?
.bind(gc.nogc());
let Some(callback_fn) = is_callable(callback_fn, gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Callback is not callable",
gc.into_nogc(),
));
};
let callback_fn = callback_fn.scope(agent, gc.nogc());
let mut groups = KeyedGroup::new(gc.nogc()).scope(agent, gc.nogc());
let MaybeInvalidIteratorRecord {
iterator,
next_method: Some(next_method),
} = get_iterator(agent, items.unbind(), false, gc.reborrow())
.unbind()?
.bind(gc.nogc())
else {
return Err(throw_not_callable(agent, gc.into_nogc()));
};
let iterator = iterator.scope(agent, gc.nogc());
let next_method = next_method.scope(agent, gc.nogc());
let mut k = 0;
loop {
if k >= u32::MAX as usize {
let error = agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Maximum array size of 2**53-1 exceeded",
gc.nogc(),
);
return Err(iterator_close_with_error(
agent,
iterator.get(agent),
error.unbind(),
gc,
));
}
let next = iterator_step_value(
agent,
IteratorRecord {
iterator: iterator.get(agent),
next_method: next_method.get(agent),
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Some(next) = next else {
return Ok(groups.take(agent));
};
let value = next.unbind().bind(gc.nogc());
let scoped_value = value.scope(agent, gc.nogc());
let fk = Number::try_from(k).unwrap().into();
let key = call_function(
agent,
callback_fn.get(agent),
Value::Undefined,
Some(ArgumentsList::from_mut_slice(&mut [value.unbind(), fk])),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
let iterator_record = IteratorRecord {
iterator: iterator.get(agent),
next_method: next_method.get(agent),
};
let key = if_abrupt_close_iterator!(agent, key, iterator_record, gc);
let key = canonicalize_keyed_collection_key(agent, key);
let value = unsafe { scoped_value.take(agent) };
add_value_to_keyed_group(agent, &mut groups, key.unbind(), value);
k += 1;
}
}
pub(crate) fn setter_that_ignores_prototype_properties<'a>(
agent: &mut Agent,
this_value: Value,
home: Object,
p: PropertyKey,
v: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let this_value = this_value.bind(gc.nogc());
let home = home.bind(gc.nogc());
let mut p = p.bind(gc.nogc());
let mut v = v.bind(gc.nogc());
let Ok(mut this_value) = Object::try_from(this_value) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"this value is not an object",
gc.into_nogc(),
));
};
if same_value(agent, this_value, home) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Could not create property",
gc.into_nogc(),
));
}
let desc = if let TryResult::Continue(desc) =
this_value.try_get_own_property(agent, p, None, gc.nogc())
{
desc
} else {
let scoped_this_value = this_value.scope(agent, gc.nogc());
let scoped_p = p.scope(agent, gc.nogc());
let scoped_v = v.scope(agent, gc.nogc());
let desc = this_value
.unbind()
.internal_get_own_property(agent, p.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
unsafe {
v = scoped_v.take(agent).bind(gc.nogc());
p = scoped_p.take(agent).bind(gc.nogc());
this_value = scoped_this_value.take(agent).bind(gc.nogc());
}
desc
};
if desc.is_none() {
create_data_property_or_throw(agent, this_value.unbind(), p.unbind(), v.unbind(), gc)
} else {
set(agent, this_value.unbind(), p.unbind(), v.unbind(), true, gc)
}
}