use core::ops::ControlFlow;
use oxc_span::Span;
use crate::{
ecmascript::{
Agent, ArgumentsList, Array, BUILTIN_STRING_MEMORY, BigInt, BuiltinConstructorArgs,
ConstructorStatus, Environment, ExceptionType, Function, FunctionAstRef, InternalMethods,
InternalSlots, JsResult, Number, Numeric, Object, OrdinaryFunctionCreateParams,
OrdinaryObject, Primitive, PrivateMethod, Promise, PropertyDescriptor, PropertyKey,
PropertyKeySet, PropertyLookupCache, ProtoIntrinsics, Reference, ScriptOrModule,
SetFunctionNamePrefix, SetResult, String, TryError, TryGetValueContinue, TryHasResult,
TryResult, Value, array_create, call, call_function, call_proxy_set, construct,
copy_data_properties, copy_data_properties_into_object, create_builtin_constructor,
create_data_property_or_throw, create_unmapped_arguments_object, define_property_or_throw,
evaluate_import_call, get_this_environment, get_this_value, get_value, has_property,
is_constructor, is_less_than, is_loosely_equal, is_private_reference,
is_property_reference, is_strictly_equal, is_super_reference, is_unresolvable_reference,
iterator_complete, iterator_value, make_constructor, make_method,
new_class_static_element_environment, new_declarative_environment, new_private_environment,
ordinary_function_create, ordinary_object_create_with_intrinsics, perform_eval,
private_element_find, put_value, resolve_binding, resolve_private_identifier,
resolve_this_binding, set, set_function_name, throw_no_proxy_private_names,
throw_read_undefined_or_null_error, to_boolean, to_number, to_number_primitive, to_numeric,
to_numeric_primitive, to_object, to_property_key, to_property_key_complex,
to_property_key_primitive, to_property_key_simple, to_string, to_string_primitive,
try_copy_data_properties_into_object, try_create_data_property,
try_define_property_or_throw, try_get_value, try_has_property,
try_initialize_referenced_binding, try_put_value, try_resolve_binding, try_result_into_js,
try_result_into_option_js, unwrap_try,
},
engine::{
ActiveIterator, Bindable, GcScope, NoGcScope, Scopable, ScopableCollection, Scoped,
bytecode::{
Executable, FunctionExpression, Instruction, NamedEvaluationParameter,
executable::ArrowFunctionExpression,
instructions::Instr,
iterator::{ObjectPropertiesIteratorRecord, VmIteratorRecord},
},
throw_iterator_returned_non_object,
},
heap::{ArenaAccessMut, ObjectEntry},
};
use super::{
ExceptionHandler, Vm, apply_string_or_numeric_addition,
apply_string_or_numeric_binary_operator, bigint_binary_operator,
binding_methods::{execute_simple_array_binding, execute_simple_object_binding},
concat_string_from_slice, instanceof_operator, number_binary_operator, set_class_name,
throw_error_in_target_not_object, typeof_operator, verify_is_object, with_vm_gc,
};
pub(super) fn execute_await_promise_resolve<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let value = vm.result.take().unwrap();
let promise = with_vm_gc(
agent,
vm,
|agent, gc| Promise::resolve(agent, value, gc),
gc,
)?;
vm.result = Some(promise.unbind().into());
Ok(())
}
pub(super) fn execute_array_create<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = array_create(agent, 0, instr.get_first_index(), None, gc)?;
vm.result = Some(result.unbind().into());
Ok(())
}
pub(super) fn execute_array_push<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let value = vm.result.take().unwrap().bind(gc.nogc());
let array = vm.stack.last().unwrap().bind(gc.nogc());
let Ok(array) = Array::try_from(array) else {
unreachable!();
};
let len = array.len(agent);
let key = PropertyKey::Integer(len.into());
let array = array.unbind();
let value = value.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| create_data_property_or_throw(agent, array, key, value, gc),
gc,
)?;
Ok(())
}
pub(super) fn execute_array_elision<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let array = vm.stack.last().unwrap().bind(gc.nogc());
let Ok(array) = Array::try_from(array) else {
unreachable!();
};
let length = array.len(agent) + 1;
let array = array.unbind().into();
with_vm_gc(
agent,
vm,
|agent, gc| {
set(
agent,
array,
BUILTIN_STRING_MEMORY.length.into(),
length.into(),
true,
gc,
)
},
gc,
)?;
Ok(())
}
pub(super) fn execute_bitwise_not<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let old_value = Numeric::try_from(vm.result.take().unwrap())
.unwrap()
.bind(gc);
if let Ok(old_value) = Number::try_from(old_value) {
vm.result = Some(Number::bitwise_not(agent, old_value).unbind().into());
} else {
let Ok(old_value) = BigInt::try_from(old_value) else {
unreachable!();
};
vm.result = Some(BigInt::bitwise_not(agent, old_value).unbind().into());
}
Ok(())
}
#[inline(never)]
#[cold]
pub(super) fn execute_debug(agent: &Agent, vm: &Vm) {
if agent.options.print_internals {
eprintln!("Debug: {vm:#?}");
}
}
#[inline(always)]
pub(super) fn execute_resolve_binding<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc());
let reference = if let TryResult::Continue(reference) =
try_resolve_binding(agent, identifier, None, gc.nogc())
{
reference
} else {
let identifier = identifier.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| resolve_binding(agent, identifier, None, None, gc),
gc,
)?
};
vm.reference = Some(reference.unbind());
Ok(())
}
pub(super) fn execute_resolve_binding_with_cache<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc());
let cache = executable.fetch_cache(agent, instr.get_second_index(), gc.nogc());
let reference = if let TryResult::Continue(reference) =
try_resolve_binding(agent, identifier, Some(cache), gc.nogc())
{
reference
} else {
execute_resolve_binding_with_cache_cold(agent, vm, identifier.unbind(), cache.unbind(), gc)
.unbind()?
};
vm.reference = Some(reference.unbind());
Ok(())
}
#[cold]
fn execute_resolve_binding_with_cache_cold<'gc>(
agent: &mut Agent,
vm: &mut Vm,
identifier: String,
cache: PropertyLookupCache,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Reference<'gc>> {
let identifier = identifier.unbind();
let cache = cache.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| resolve_binding(agent, identifier, Some(cache), None, gc),
gc,
)
}
pub(super) fn execute_resolve_this_binding<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let this = resolve_this_binding(agent, gc)?.unbind();
vm.result = Some(this);
Ok(())
}
#[inline(always)]
pub(super) fn execute_load_constant(
agent: &Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let constant = executable.fetch_constant(agent, instr.get_first_index(), gc);
vm.stack.push(constant.unbind());
}
#[inline(always)]
pub(super) fn execute_store_constant(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let constant = executable.fetch_constant(agent, instr.get_first_index(), gc);
vm.result = Some(constant.unbind());
}
pub(super) fn execute_unary_minus(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let old_value = vm.result.unwrap().bind(gc);
let result: Value = if let Ok(old_value) = Number::try_from(old_value) {
Number::unary_minus(agent, old_value).into()
}
else {
let old_value = BigInt::try_from(old_value).unwrap();
BigInt::unary_minus(agent, old_value).into()
};
vm.result = Some(result.unbind());
}
pub(super) fn execute_to_number<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let arg0 = vm.result.unwrap().bind(gc.nogc());
let result = if let Ok(arg0) = Primitive::try_from(arg0) {
to_number_primitive(agent, arg0.unbind(), gc.into_nogc())
} else {
let arg0 = arg0.unbind();
with_vm_gc(agent, vm, |agent, gc| to_number(agent, arg0, gc), gc)
};
vm.result = Some(result?.unbind().into());
Ok(())
}
#[inline(always)]
pub(super) fn execute_to_numeric<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let arg0 = vm.result.unwrap().bind(gc.nogc());
let result = if let Ok(arg0) = Primitive::try_from(arg0) {
to_numeric_primitive(agent, arg0.unbind(), gc.into_nogc())
} else {
execute_to_numeric_cold(agent, vm, arg0.unbind(), gc)
};
vm.result = Some(result?.unbind().into());
Ok(())
}
#[inline(never)]
#[cold]
fn execute_to_numeric_cold<'gc>(
agent: &mut Agent,
vm: &mut Vm,
arg0: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Numeric<'gc>> {
with_vm_gc(agent, vm, |agent, gc| to_numeric(agent, arg0, gc), gc)
}
pub(super) fn execute_to_object<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
vm.result = Some(to_object(agent, vm.result.unwrap(), gc)?.unbind().into());
Ok(())
}
pub(super) fn execute_apply_addition_binary_operator<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lval), Number::try_from(rval)) {
Number::add(agent, lnum, rnum).into()
} else if let (Ok(lstr), Ok(rstr)) = (String::try_from(lval), String::try_from(rval)) {
String::concat(agent, [lstr, rstr], gc.into_nogc()).into()
} else if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lval), BigInt::try_from(rval)) {
BigInt::add(agent, lnum, rnum).into()
} else {
with_vm_gc(
agent,
vm,
|agent, gc| apply_string_or_numeric_addition(agent, lval, rval, gc),
gc,
)?
};
vm.result = Some(result.unbind());
Ok(())
}
pub(super) fn execute_apply_binary_operator<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instruction: Instruction,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lval), Number::try_from(rval)) {
number_binary_operator(agent, instruction, lnum, rnum, gc.into_nogc())?.into()
} else if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lval), BigInt::try_from(rval)) {
bigint_binary_operator(agent, instruction, lnum, rnum, gc.into_nogc())?.into()
} else {
with_vm_gc(
agent,
vm,
|agent, gc| apply_string_or_numeric_binary_operator(agent, lval, instruction, rval, gc),
gc,
)?
};
vm.result = Some(result.unbind());
Ok(())
}
pub(super) fn execute_object_define_property<'gc>(
agent: &mut Agent,
vm: &mut Vm,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let key = vm.stack.pop().unwrap();
let key = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, key, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let key = key.unbind().bind(gc.nogc());
let value = vm.result.take().unwrap().bind(gc.nogc());
let object = vm.stack.last().unwrap().bind(gc.nogc());
let object = Object::try_from(object).unwrap();
create_data_property_or_throw(agent, object.unbind(), key.unbind(), value.unbind(), gc)?;
Ok(())
}
pub(super) fn execute_object_define_method<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let FunctionExpression { expression, .. } =
executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let function_expression = expression.get();
let enumerable = instr.get_second_bool();
let prop_key = vm.stack.pop().unwrap();
let prop_key = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, prop_key, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let object = Object::try_from(*vm.stack.last().unwrap())
.unwrap()
.bind(gc.nogc());
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: false,
env,
private_env,
};
let closure = ordinary_function_create(agent, params, gc.nogc());
make_method(agent, closure, object);
set_function_name(agent, closure, prop_key, None, gc.nogc());
let desc = PropertyDescriptor {
value: Some(closure.unbind().into()),
writable: Some(true),
enumerable: Some(enumerable),
configurable: Some(true),
..Default::default()
};
let object = object.unbind();
let prop_key = prop_key.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc),
gc,
)?;
Ok(())
}
pub(super) fn execute_object_define_getter<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let FunctionExpression { expression, .. } =
executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let function_expression = expression.get();
let enumerable = instr.get_second_bool();
let prop_key = vm.stack.pop().unwrap();
let prop_key = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, prop_key, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: false,
env,
private_env,
};
let closure = ordinary_function_create(agent, params, gc.nogc());
let object = Object::try_from(*vm.stack.last().unwrap())
.unwrap()
.bind(gc.nogc());
make_method(agent, closure, object);
set_function_name(
agent,
closure,
prop_key,
Some(SetFunctionNamePrefix::Get),
gc.nogc(),
);
let desc = PropertyDescriptor {
get: Some(Some(closure.unbind().into())),
enumerable: Some(enumerable),
configurable: Some(true),
..Default::default()
};
let object = object.unbind();
let prop_key = prop_key.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc),
gc,
)?;
Ok(())
}
pub(super) fn execute_object_define_setter<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let FunctionExpression { expression, .. } =
executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let function_expression = expression.get();
let enumerable = instr.get_second_bool();
let prop_key = vm.stack.pop().unwrap();
let prop_key = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, prop_key, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: false,
env,
private_env,
};
let closure = ordinary_function_create(agent, params, gc.nogc());
let object = Object::try_from(*vm.stack.last().unwrap())
.unwrap()
.bind(gc.nogc());
make_method(agent, closure, object);
set_function_name(
agent,
closure,
prop_key,
Some(SetFunctionNamePrefix::Set),
gc.nogc(),
);
let desc = PropertyDescriptor {
set: Some(Some(closure.unbind().into())),
enumerable: Some(enumerable),
configurable: Some(true),
..Default::default()
};
let object = object.unbind();
let prop_key = prop_key.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc),
gc,
)?;
Ok(())
}
pub(super) fn execute_object_set_prototype<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let prop_value = vm.result.take().unwrap().bind(gc.nogc());
let object = Object::try_from(*vm.stack.last().unwrap())
.unwrap()
.bind(gc.nogc());
let prop_value = if prop_value.is_null() {
None
} else if let Ok(prop_value) = Object::try_from(prop_value) {
Some(prop_value.unbind())
} else {
return Ok(());
};
let object = object.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| object.internal_set_prototype_of(agent, prop_value, gc),
gc,
)?;
Ok(())
}
#[inline(always)]
pub(super) fn execute_put_value<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
cache: bool,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let value = vm.result.take().unwrap().bind(gc.nogc());
let mut reference = vm.reference.take().unwrap().bind(gc.nogc());
let cache = if cache {
Some(executable.fetch_cache(agent, instr.get_first_index(), gc.nogc()))
} else {
None
};
let result = try_put_value(agent, &mut reference, value, cache, gc.nogc());
match result {
ControlFlow::Continue(SetResult::Done)
| ControlFlow::Continue(SetResult::Unwritable)
| ControlFlow::Continue(SetResult::Accessor) => {}
ControlFlow::Break(TryError::Err(err)) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
_ => handle_set_value_break(
agent,
vm,
&reference.unbind(),
result.unbind(),
value.unbind(),
gc,
)?,
};
Ok(())
}
#[inline(always)]
pub(super) fn execute_get_value<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
cache: bool,
keep_reference: bool,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let reference = if keep_reference {
handle_keep_reference(agent, vm, gc.reborrow())
.unbind()?
.bind(gc.nogc())
} else {
vm.reference.take().unwrap()
};
let cache = if cache {
Some(executable.fetch_cache(agent, instr.get_first_index(), gc.nogc()))
} else {
None
};
let result = try_get_value(agent, &reference, cache, gc.nogc());
let result = match result {
ControlFlow::Continue(TryGetValueContinue::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetValueContinue::Value(value)) => value,
ControlFlow::Break(TryError::Err(err)) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
_ => handle_get_value_break(agent, vm, &reference.unbind(), result.unbind(), gc)?,
};
vm.result = Some(result.unbind());
Ok(())
}
#[cold]
fn handle_keep_reference<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Reference<'gc>> {
let reference = vm.reference.as_mut().unwrap();
if is_property_reference(reference) && !reference.is_static_property_reference() {
if let Ok(referenced_name) =
Primitive::try_from(reference.referenced_name_value().bind(gc.nogc()))
{
let referenced_name = to_property_key_primitive(agent, referenced_name, gc.nogc());
reference.set_referenced_name_to_property_key(referenced_name);
Ok(reference.clone().bind(gc.into_nogc()))
} else {
mutate_reference_property_key(agent, vm, gc)
}
} else {
Ok(reference.clone().bind(gc.into_nogc()))
}
}
pub(super) fn execute_typeof<'gc>(
agent: &mut Agent,
vm: &mut Vm,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let val = if let Some(reference) = vm.reference.take() {
if is_unresolvable_reference(&reference) {
Value::Undefined
} else {
let result = try_get_value(agent, &reference, None, gc.nogc());
match result {
ControlFlow::Continue(TryGetValueContinue::Unset) => Value::Undefined,
ControlFlow::Continue(TryGetValueContinue::Value(value)) => value,
ControlFlow::Break(TryError::Err(err)) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
_ => with_vm_gc(
agent,
vm,
|agent, gc| get_value(agent, &reference, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc()),
}
}
} else {
vm.result.unwrap().bind(gc.nogc())
};
vm.result = Some(typeof_operator(agent, val, gc.nogc()).into());
Ok(())
}
pub(super) fn execute_object_create(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let object = ordinary_object_create_with_intrinsics(agent, ProtoIntrinsics::Object, None, gc);
vm.stack.push(object.unbind().into());
}
pub(super) fn execute_object_create_with_shape(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let shape = executable.fetch_object_shape(agent, instr.get_first_index(), gc);
let len = shape.len(agent);
let first_property_index = vm.stack.len() - len as usize;
let obj = OrdinaryObject::create_object_with_shape_and_data_properties(
agent,
shape,
&vm.stack[first_property_index..],
);
vm.stack.truncate(first_property_index);
vm.result = Some(obj.unbind().into());
}
pub(super) fn execute_copy_data_properties<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let source = vm.result.take().unwrap();
let Value::Object(target) = *vm.stack.last().unwrap() else {
unreachable!()
};
with_vm_gc(
agent,
vm,
|agent, gc| copy_data_properties(agent, target, source, gc),
gc,
)?;
Ok(())
}
pub(super) fn execute_copy_data_properties_into_object<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let from = Object::try_from(vm.result.unwrap())
.unwrap()
.bind(gc.nogc());
let num_excluded_items = instr.get_first_index();
let mut excluded_items = PropertyKeySet::with_capacity(num_excluded_items, gc.nogc());
assert!(vm.reference.is_none());
for _ in 0..num_excluded_items {
let reference = vm.reference_stack.pop().unwrap();
debug_assert_eq!(reference.base_value(), from.into());
debug_assert!(!is_super_reference(&reference));
excluded_items.insert(agent, reference.referenced_name_property_key());
}
if let TryResult::Continue(result) =
try_copy_data_properties_into_object(agent, from, &excluded_items, gc.nogc())
{
vm.result = Some(result.unbind().into());
} else {
let from = from.unbind();
let excluded_items = excluded_items.scope(agent, gc.nogc());
let result = with_vm_gc(
agent,
vm,
|agent, gc| copy_data_properties_into_object(agent, from, excluded_items, gc),
gc,
)?;
vm.result = Some(result.unbind().into());
}
Ok(())
}
pub(super) fn execute_instantiate_arrow_function_expression<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let ArrowFunctionExpression {
expression,
identifier,
} = executable.fetch_arrow_function_expression(agent, instr.get_first_index());
let function_expression = expression.get();
let identifier = *identifier;
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: true,
env,
private_env,
};
let mut function = ordinary_function_create(agent, params, gc.nogc());
let name = if let Some(parameter) = &identifier {
let pk_result = match parameter {
NamedEvaluationParameter::Result => {
let value = vm.result.take().unwrap().bind(gc.nogc());
if let Some(pk) = to_property_key_simple(agent, value, gc.nogc()) {
Ok(pk)
} else {
Err(value)
}
}
NamedEvaluationParameter::Stack => {
let value = vm.stack.last().unwrap().bind(gc.nogc());
if let Some(pk) = to_property_key_simple(agent, value, gc.nogc()) {
Ok(pk)
} else {
Err(value)
}
}
};
match pk_result {
Ok(pk) => pk.bind(gc.nogc()),
Err(pk_value) => {
let scoped_function = function.scope(agent, gc.nogc());
let pk_value = pk_value.unbind();
let pk = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key_complex(agent, pk_value, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
function = unsafe { scoped_function.take(agent).bind(gc.nogc()) };
pk
}
}
} else {
let pk: PropertyKey = String::EMPTY_STRING.into();
pk.bind(gc.nogc())
};
set_function_name(agent, function, name, None, gc.nogc());
vm.result = Some(function.unbind().into());
Ok(())
}
pub(super) fn execute_instantiate_ordinary_function_expression<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let FunctionExpression {
expression,
identifier,
..
} = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let function_expression = expression.get();
let identifier = *identifier;
let (name, env, initialize_binding) = if let Some(parameter) = identifier {
debug_assert!(function_expression.id.is_none());
let pk = match parameter {
NamedEvaluationParameter::Result => vm.result.take().unwrap(),
NamedEvaluationParameter::Stack => *vm.stack.last().unwrap(),
};
let name = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, pk, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let env = agent.current_lexical_environment(gc.nogc());
let initialize_binding = false;
(name, env, initialize_binding)
} else if let Some(binding_identifier) = &function_expression.id
&& function_expression.is_expression()
{
assert!(identifier.is_none());
let name = String::from_str(agent, &binding_identifier.name, gc.nogc());
let outer_env = agent.current_lexical_environment(gc.nogc());
let func_env = new_declarative_environment(agent, Some(outer_env), gc.nogc());
func_env.create_immutable_binding(agent, name, false);
let initialize_binding = true;
(
name.into(),
Environment::Declarative(func_env),
initialize_binding,
)
} else if let Some(binding_identifier) = &function_expression.id
&& function_expression.is_declaration()
{
let name = String::from_str(agent, &binding_identifier.name, gc.nogc());
let initialize_binding = false;
(
name.into(),
agent.current_lexical_environment(gc.nogc()),
initialize_binding,
)
} else {
(
String::EMPTY_STRING.into(),
agent.current_lexical_environment(gc.nogc()),
false,
)
};
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: false,
env,
private_env,
};
let function = ordinary_function_create(agent, params, gc.nogc());
let FunctionExpression {
compiled_bytecode, ..
} = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
if let Some(compiled_bytecode) = compiled_bytecode {
function.get_mut(agent).compiled_bytecode = Some(compiled_bytecode.unbind());
}
set_function_name(agent, function, name, None, gc.nogc());
if !function_expression.r#async && !function_expression.generator {
make_constructor(agent, function, None, None, gc.nogc());
}
if function_expression.generator {
let prototype = ordinary_object_create_with_intrinsics(
agent,
ProtoIntrinsics::Object,
Some(if function_expression.r#async {
agent
.current_realm_record()
.intrinsics()
.async_generator_prototype()
.into()
} else {
agent
.current_realm_record()
.intrinsics()
.generator_prototype()
.into()
}),
gc.nogc(),
);
unwrap_try(try_define_property_or_throw(
agent,
function,
BUILTIN_STRING_MEMORY.prototype.to_property_key(),
PropertyDescriptor {
value: Some(prototype.unbind().into()),
writable: Some(true),
get: None,
set: None,
enumerable: Some(false),
configurable: Some(false),
},
None,
gc.nogc(),
));
}
if initialize_binding {
let name = match name {
PropertyKey::SmallString(data) => data.into(),
PropertyKey::String(data) => data.unbind().into(),
_ => unreachable!(),
};
unwrap_try(env.try_initialize_binding(agent, name, None, function.into(), gc.nogc()));
}
vm.result = Some(function.unbind().into());
Ok(())
}
pub(super) fn execute_class_define_constructor<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let name = vm.stack.pop().unwrap();
let class_name = set_class_name(agent, vm, name, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let FunctionExpression {
expression,
compiled_bytecode,
..
} = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let function_expression = expression.get();
let compiled_bytecode = *compiled_bytecode;
let has_constructor_parent = instr.get_second_bool();
let function_prototype = if has_constructor_parent {
Some(Object::try_from(vm.stack.pop().unwrap()).unwrap())
} else {
None
};
let proto = OrdinaryObject::try_from(*vm.stack.last().unwrap()).unwrap();
let is_null_derived_class = !has_constructor_parent
&& unwrap_try(proto.try_get_prototype_of(agent, gc.nogc())).is_none();
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let params = OrdinaryFunctionCreateParams {
function_prototype,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::ClassConstructor(function_expression),
lexical_this: false,
env,
private_env,
};
let function = ordinary_function_create(agent, params, gc.nogc());
if let Some(compiled_bytecode) = compiled_bytecode {
function.get_mut(agent).compiled_bytecode = Some(compiled_bytecode.unbind());
}
set_function_name(agent, function, class_name.into(), None, gc.nogc());
make_constructor(agent, function, Some(false), Some(proto), gc.nogc());
function.get_mut(agent).ecmascript_function.home_object = Some(proto.into());
function
.get_mut(agent)
.ecmascript_function
.constructor_status = if has_constructor_parent || is_null_derived_class {
ConstructorStatus::DerivedClass
} else {
ConstructorStatus::BaseClass
};
unwrap_try(proto.try_define_own_property(
agent,
BUILTIN_STRING_MEMORY.constructor.into(),
PropertyDescriptor {
value: Some(function.unbind().into()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
..Default::default()
},
None,
gc.nogc(),
));
vm.result = Some(function.unbind().into());
Ok(())
}
pub(super) fn execute_class_define_default_constructor<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let name = vm.stack.pop().unwrap();
let class_name = set_class_name(agent, vm, name, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let class_initializer_bytecode_index = instr.get_first_index();
let (compiled_initializer_bytecode, has_constructor_parent) = executable
.fetch_class_initializer_bytecode(agent, class_initializer_bytecode_index, gc.nogc());
let function_prototype = if has_constructor_parent {
Some(Object::try_from(vm.stack.pop().unwrap()).unwrap())
} else {
Some(
agent
.current_realm_record()
.intrinsics()
.function_prototype()
.into(),
)
};
let proto = Object::try_from(*vm.stack.last().unwrap()).unwrap();
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc());
let source_code = agent.current_source_code(gc.nogc());
let function = create_builtin_constructor(
agent,
BuiltinConstructorArgs {
class_name,
is_derived: has_constructor_parent,
prototype: function_prototype,
prototype_property: proto,
compiled_initializer_bytecode,
env,
private_env,
source_code,
source_text: Span::new(0, 0),
},
gc.nogc(),
);
unwrap_try(proto.try_define_own_property(
agent,
BUILTIN_STRING_MEMORY.constructor.into(),
PropertyDescriptor {
value: Some(function.unbind().into()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
..Default::default()
},
None,
gc.nogc(),
));
vm.result = Some(function.unbind().into());
Ok(())
}
pub(super) fn execute_class_define_private_method<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let description = String::try_from(vm.result.take().unwrap().bind(gc.nogc())).unwrap();
let FunctionExpression { expression, .. } =
executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc());
let bits = instr.get_second_index() as u8;
let is_static = bits & 0b100 == 0b100;
let is_setter = bits & 0b10 == 0b10;
let is_getter = bits & 0b1 == 0b1;
assert!(vm.stack.len() >= 2);
let object = if is_static {
vm.stack[vm.stack.len() - 1]
} else {
vm.stack[vm.stack.len() - 2]
};
let object = Object::try_from(object).unwrap().bind(gc.nogc());
let function_expression = expression.get();
let env = agent.current_lexical_environment(gc.nogc());
let private_env = agent.current_private_environment(gc.nogc()).unwrap();
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text: function_expression.span,
ast: FunctionAstRef::from(function_expression),
lexical_this: false,
env,
private_env: Some(private_env),
};
let closure = ordinary_function_create(agent, params, gc.nogc());
make_method(agent, closure, object);
let function_name = format!("#{}", description.to_string_lossy_(agent));
let function_name = String::from_string(agent, function_name, gc.nogc());
set_function_name(
agent,
closure,
function_name.into(),
if is_getter {
Some(SetFunctionNamePrefix::Get)
} else if is_setter {
Some(SetFunctionNamePrefix::Set)
} else {
None
},
gc.nogc(),
);
if is_static {
let desc = PropertyDescriptor {
value: if !is_getter && !is_setter {
Some(closure.unbind().into())
} else {
None
},
writable: if !is_getter && !is_setter {
Some(false)
} else {
None
},
get: if is_getter {
Some(Some(closure.unbind().into()))
} else {
None
},
set: if is_setter {
Some(Some(closure.unbind().into()))
} else {
None
},
enumerable: Some(false),
configurable: Some(true),
};
let private_name = private_env.add_static_private_method(agent, description);
let object = object.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| define_property_or_throw(agent, object, private_name.into(), desc, gc),
gc,
)?;
} else {
let private_method = if is_getter {
PrivateMethod::Getter(closure)
} else if is_setter {
PrivateMethod::Setter(closure)
} else {
PrivateMethod::Method(closure)
};
private_env.add_instance_private_method(agent, description, private_method);
}
Ok(())
}
pub(super) fn execute_class_define_private_property<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let description = executable.fetch_identifier(agent, instr.get_first_index(), gc);
let is_static = instr.get_second_bool();
let private_env = agent
.current_private_environment(gc)
.expect("Attempted to define private property with no PrivateEnvironment");
if is_static {
let private_name = private_env.add_static_private_field(agent, description);
let object = vm.stack.last().unwrap().bind(gc);
let object = Object::try_from(object).unwrap();
if let Err(err) = object
.get_or_create_backing_object(agent)
.bind(gc)
.property_storage()
.add_private_field_slot(agent, private_name)
{
return Err(agent.throw_allocation_exception(err, gc));
};
} else {
private_env.add_instance_private_field(agent, description);
}
Ok(())
}
pub(super) fn execute_class_initialize_private_elements<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let target = Object::try_from(vm.stack.last().unwrap().bind(gc)).unwrap();
target
.get_or_create_backing_object(agent)
.property_storage()
.initialize_private_elements(agent, gc)?;
Ok(())
}
pub(super) fn execute_class_initialize_private_value<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let target = Object::try_from(vm.stack.last().unwrap().bind(gc)).unwrap();
let value = vm.result.take().unwrap().bind(gc);
if target.is_proxy() {
return Err(throw_no_proxy_private_names(agent, gc));
}
let offset = instr.get_first_index();
let private_env = agent
.current_private_environment(gc)
.expect("Attempted to define private property with no PrivateEnvironment");
let private_name = unsafe { private_env.get_private_name(agent, offset) };
assert!(
target
.get_or_create_backing_object(agent)
.property_storage()
.set_private_field_value(agent, private_name, offset, value)
);
Ok(())
}
pub(super) fn execute_direct_eval_call<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let func = with_vm_gc(
agent,
vm,
|agent, mut gc| {
let func_ref =
resolve_binding(agent, BUILTIN_STRING_MEMORY.eval, None, None, gc.reborrow())
.unbind()?
.bind(gc.nogc());
get_value(agent, &func_ref.unbind(), gc)
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let args = vm.get_call_args(instr, gc.nogc());
let result = if func == agent.current_realm_record().intrinsics().eval().into() {
if args.is_empty() {
Value::Undefined
} else {
let eval_arg = args[0];
let strict_caller = agent.is_evaluating_strict_code();
let eval_arg = eval_arg.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| perform_eval(agent, eval_arg, true, strict_caller, gc),
gc,
)?
}
} else {
let func = func.unbind();
let mut args = args.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| {
call(
agent,
func,
Value::Undefined,
Some(ArgumentsList::from_mut_slice(args.as_mut_slice())),
gc,
)
},
gc,
)?
};
vm.result = Some(result.unbind());
Ok(())
}
pub(super) fn execute_evaluate_call<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let reference = vm.reference.take();
let this_value = if let Some(reference) = reference {
if is_property_reference(&reference) {
get_this_value(&reference).bind(gc.nogc())
} else {
let ref_env = reference.base_env();
ref_env
.with_base_object(agent)
.map_or(Value::Undefined, |object| object.into())
.bind(gc.nogc())
}
} else {
Value::Undefined
};
let mut args = vm.get_call_args(instr, gc.nogc()).unbind();
let func = vm.stack.pop().unwrap().unbind();
let this_value = this_value.unbind();
let result = with_vm_gc(
agent,
vm,
|agent, gc| {
call(
agent,
func,
this_value,
Some(ArgumentsList::from_mut_slice(args.as_mut_slice())),
gc,
)
},
gc,
);
vm.result = Some(result?.unbind());
Ok(())
}
pub(super) fn execute_evaluate_new<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let args = vm.get_call_args(instr, gc.nogc());
let constructor = vm.stack.pop().unwrap().bind(gc.nogc());
let Some(constructor) = is_constructor(agent, constructor) else {
let constructor_string = {
let constructor = constructor.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| constructor.string_repr(agent, gc),
gc.reborrow(),
)
};
let error_message = format!(
"'{}' is not a constructor.",
constructor_string.to_string_lossy_(agent)
);
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc.into_nogc()));
};
let constructor = constructor.unbind();
let mut args = args.unbind();
let result = with_vm_gc(
agent,
vm,
|agent, gc| {
construct(
agent,
constructor,
Some(ArgumentsList::from_mut_slice(args.as_mut_slice())),
None,
gc,
)
},
gc,
)?;
vm.result = Some(result.unbind().into());
Ok(())
}
pub(super) fn execute_evaluate_super<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let Environment::Function(this_env) = get_this_environment(agent, gc.nogc()) else {
unreachable!();
};
let (new_target, func) = {
let new_target = this_env.get_new_target(agent);
let function_object = this_env.get_function_object(agent);
(
Function::try_from(new_target.unwrap())
.unwrap()
.bind(gc.nogc()),
unwrap_try(function_object.try_get_prototype_of(agent, gc.nogc())),
)
};
let arg_list = vm.get_call_args(instr, gc.nogc());
let Some(func) = func.and_then(|func| is_constructor(agent, func)) else {
let constructor = func.map_or(Value::Null, |f| f.unbind().into());
let error_message = with_vm_gc(
agent,
vm,
|agent, gc| {
format!(
"'{}' is not a constructor.",
constructor.string_repr(agent, gc).to_string_lossy_(agent)
)
},
gc.reborrow(),
);
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc.into_nogc()));
};
let result = {
let func = func.unbind();
let mut arg_list = arg_list.unbind();
let new_target = new_target.unbind();
let result = with_vm_gc(
agent,
vm,
|agent, gc| {
construct(
agent,
func,
Some(ArgumentsList::from_mut_slice(arg_list.as_mut_slice())),
Some(new_target),
gc,
)
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
result.unbind().bind(gc.nogc())
};
let Environment::Function(this_er) = get_this_environment(agent, gc.nogc()) else {
unreachable!();
};
this_er
.bind_this_value(agent, result.into(), gc.nogc())
.unbind()?
.bind(gc.nogc());
let Function::ECMAScriptFunction(_f) = this_er.get_function_object(agent) else {
unreachable!();
};
vm.result = Some(result.unbind().into());
Ok(())
}
#[inline(always)]
pub(super) fn execute_evaluate_property_access_with_expression_key(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope,
) {
let property_name_value = vm.result.take().unwrap().bind(gc);
let base_value = vm.stack.pop().unwrap().bind(gc);
let strict = agent.is_evaluating_strict_code();
vm.reference = Some(
Reference::new_property_expression_reference(base_value, property_name_value, strict)
.unbind(),
);
}
#[inline(always)]
pub(super) fn execute_evaluate_property_access_with_identifier_key(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let property_key = executable.fetch_property_key(agent, instr.get_first_index(), gc);
let base_value = vm.result.take().unwrap().bind(gc);
let strict = agent.is_evaluating_strict_code();
vm.reference =
Some(Reference::new_property_reference(base_value, property_key, strict).unbind());
}
pub(super) fn execute_make_private_reference(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let private_identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc);
let base_value = vm.result.take().unwrap().bind(gc);
let private_env = agent
.current_private_environment(gc)
.expect("Attempted to make private reference in non-class environment");
let private_name = resolve_private_identifier(agent, private_env, private_identifier);
vm.reference
.replace(Reference::new_private_reference(base_value, private_name).unbind());
}
pub(super) fn execute_make_super_property_reference_with_expression_key<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let env = get_this_environment(agent, gc);
let actual_this = env.get_this_binding(agent, gc).unbind()?.bind(gc);
let property_name_value = vm.result.take().unwrap().bind(gc);
let strict = agent.is_evaluating_strict_code();
debug_assert!(env.has_super_binding(agent));
let Environment::Function(env) = env else {
unreachable!()
};
let base_value = env.get_super_base(agent, gc);
vm.reference = Some(
Reference::new_super_expression_reference(
base_value,
property_name_value,
actual_this,
strict,
)
.unbind(),
);
Ok(())
}
pub(super) fn execute_make_super_property_reference_with_identifier_key<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let env = get_this_environment(agent, gc);
let actual_this = env.get_this_binding(agent, gc).unbind()?.bind(gc);
let property_key = executable.fetch_property_key(agent, instr.get_first_index(), gc);
let strict = agent.is_evaluating_strict_code();
assert!(env.has_super_binding(agent));
let Environment::Function(env) = env else {
unreachable!()
};
let base_value = env.get_super_base(agent, gc);
vm.reference = Some(
Reference::new_super_reference(
base_value,
property_key,
actual_this,
strict,
)
.unbind(),
);
Ok(())
}
#[inline(always)]
pub(super) fn execute_jump(agent: &Agent, vm: &mut Vm, instr: Instr) {
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("Jumping to {ip}");
}
vm.ip = ip;
}
#[inline(always)]
pub(super) fn execute_jump_if_not(agent: &Agent, vm: &mut Vm, instr: Instr) {
let result = vm.result.take().unwrap();
let ip = instr.get_jump_slot();
if !to_boolean(agent, result) {
if agent.options.print_internals {
eprintln!("Comparison failed, jumping to {ip}");
}
vm.ip = ip;
}
}
#[inline(always)]
pub(super) fn execute_jump_if_true(agent: &Agent, vm: &mut Vm, instr: Instr) {
let result = vm.result.take().unwrap();
let Value::Boolean(result) = result else {
unreachable!()
};
if result {
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("Comparison succeeded, jumping to {ip}");
}
vm.ip = ip;
}
}
pub(super) fn execute_increment(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let lhs = vm.result.take().unwrap().bind(gc);
let old_value = Numeric::try_from(lhs).unwrap();
let new_value: Value = if let Ok(old_value) = Number::try_from(old_value) {
Number::add(agent, old_value, 1.into()).into()
} else {
let old_value = BigInt::try_from(old_value).unwrap();
BigInt::add(agent, old_value, 1.into()).into()
};
vm.result = Some(new_value.unbind());
}
pub(super) fn execute_decrement(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let lhs = vm.result.take().unwrap().bind(gc);
let old_value = Numeric::try_from(lhs).unwrap();
let new_value: Value = if let Ok(old_value) = Number::try_from(old_value) {
Number::subtract(agent, old_value, 1.into()).into()
} else {
let old_value = BigInt::try_from(old_value).unwrap();
BigInt::subtract(agent, old_value, 1.into()).into()
};
vm.result = Some(new_value.unbind());
}
pub(super) fn execute_less_than<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| is_less_than::<true>(agent, lval, rval, gc),
gc,
)?;
let result = result == Some(true);
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_less_than_equals<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| is_less_than::<false>(agent, rval, lval, gc),
gc,
)?;
let result = result == Some(false);
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_greater_than<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| is_less_than::<false>(agent, rval, lval, gc),
gc,
)?;
let result = result == Some(true);
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_greater_than_equals<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| is_less_than::<true>(agent, lval, rval, gc),
gc,
)?;
let result = result == Some(false);
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_has_property<'gc>(
agent: &mut Agent,
vm: &mut Vm,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap().bind(gc.nogc());
let rval = vm.result.take().unwrap().bind(gc.nogc());
let Ok(rval) = Object::try_from(rval) else {
return Err(throw_error_in_target_not_object(
agent,
rval.unbind(),
gc.into_nogc(),
));
};
let property_key = if let Ok(lval) = Primitive::try_from(lval) {
to_property_key_primitive(agent, lval, gc.nogc())
} else {
let scoped_rval = rval.scope(agent, gc.nogc());
let lval = lval.unbind();
let result = with_vm_gc(
agent,
vm,
|agent, mut gc| {
let property_key = to_property_key(agent, lval, gc.reborrow())
.unbind()?
.bind(gc.nogc());
has_property(
agent,
unsafe { scoped_rval.take(agent) },
property_key.unbind(),
gc,
)
},
gc.reborrow(),
)
.unbind()?;
vm.result = Some(result.into());
return Ok(());
};
let result = match try_has_property(agent, rval, property_key, None, gc.nogc()) {
ControlFlow::Continue(c) => match c {
TryHasResult::Unset => false,
TryHasResult::Offset(_, _) | TryHasResult::Custom(_, _) => true,
TryHasResult::Proxy(proxy) => {
let proxy = proxy.unbind();
let property_key = property_key.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| proxy.internal_has_property(agent, property_key, gc),
gc,
)?
}
},
ControlFlow::Break(TryError::Err(err)) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
ControlFlow::Break(TryError::GcError) => {
let rval = rval.unbind();
let property_key = property_key.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| has_property(agent, rval.unbind(), property_key.unbind(), gc),
gc,
)?
}
};
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_has_private_element<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let Some(reference) = vm.reference.take() else {
unreachable!()
};
let (r_val, private_name) = reference.into_private_reference_data();
if let Ok(r_val) = Object::try_from(r_val) {
let result = private_element_find(agent, r_val, private_name).is_some();
vm.result = Some(result.into());
Ok(())
} else {
Err(throw_error_in_target_not_object(agent, r_val, gc))
}
}
#[inline(always)]
pub(super) fn execute_is_strictly_equal(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let lval = vm.stack.pop().unwrap().bind(gc);
let rval = vm.result.take().unwrap().bind(gc);
let result = is_strictly_equal(agent, lval, rval);
vm.result = Some(result.into());
}
pub(super) fn execute_is_loosely_equal<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| is_loosely_equal(agent, lval, rval, gc),
gc,
)?;
vm.result = Some(result.into());
Ok(())
}
#[inline(always)]
pub(super) fn execute_is_constructor(agent: &Agent, vm: &mut Vm) {
let val = vm.result.take().unwrap();
let result = if let Ok(val) = Function::try_from(val) {
val.is_constructor(agent)
} else {
false
};
vm.result = Some(result.into());
}
#[inline(always)]
pub(super) fn execute_logical_not(agent: &Agent, vm: &mut Vm) {
let old_value = to_boolean(agent, vm.result.take().unwrap());
vm.result = Some((!old_value).into());
}
#[inline(always)]
pub(super) fn execute_initialize_referenced_binding(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let v = vm.reference.take().unwrap();
let w = vm.result.take().unwrap();
unwrap_try(try_initialize_referenced_binding(agent, v, w, gc));
}
pub(super) fn execute_initialize_variable_environment<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) {
let num_variables = instr.get_first_index();
let strict = instr.get_second_bool();
let env = agent.current_lexical_environment(gc);
let var_env = new_declarative_environment(agent, Some(env), gc);
agent.set_current_variable_environment(var_env.into());
for _ in 0..num_variables {
let n = String::try_from(vm.stack.pop().unwrap()).unwrap();
let initial_value = vm.stack.pop().unwrap();
var_env.create_mutable_binding(agent, n, false);
var_env.initialize_binding(agent, n, initial_value);
}
let lex_env = if !strict {
new_declarative_environment(agent, Some(Environment::Declarative(var_env)), gc)
} else {
var_env
};
agent.set_current_lexical_environment(lex_env.into());
}
pub(super) fn execute_enter_declarative_environment(agent: &mut Agent, gc: NoGcScope) {
let outer_env = agent.current_lexical_environment(gc);
let new_env = new_declarative_environment(agent, Some(outer_env), gc);
agent.set_current_lexical_environment(new_env.into());
}
pub(super) fn execute_enter_class_static_element_environment(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope,
) {
let class_constructor = Function::try_from(*vm.stack.last().unwrap())
.unwrap()
.bind(gc);
let local_env = new_class_static_element_environment(agent, class_constructor, gc);
let local_env = Environment::Function(local_env);
agent.set_current_lexical_environment(local_env);
agent.set_current_variable_environment(local_env);
}
pub(super) fn execute_enter_private_environment(agent: &mut Agent, instr: Instr, gc: NoGcScope) {
let outer_env = agent.current_private_environment(gc);
let new_env = new_private_environment(agent, outer_env, instr.get_first_index(), gc);
agent.set_current_private_environment(new_env.into());
}
pub(super) fn execute_exit_declarative_environment(agent: &mut Agent, gc: NoGcScope) {
let old_env = agent
.current_lexical_environment(gc)
.get_outer_env(agent)
.unwrap();
agent.set_current_lexical_environment(old_env);
}
pub(super) fn execute_exit_variable_environment(agent: &mut Agent, gc: NoGcScope) {
let old_env = agent
.current_variable_environment(gc)
.get_outer_env(agent)
.unwrap();
agent.set_current_variable_environment(old_env);
}
pub(super) fn execute_exit_private_environment(agent: &mut Agent, gc: NoGcScope) {
let old_env = agent
.current_private_environment(gc)
.unwrap()
.get_outer_env(agent);
agent.set_current_private_environment(old_env);
}
#[inline(always)]
pub(super) fn execute_create_mutable_binding(
agent: &mut Agent,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let lex_env = agent.current_lexical_environment(gc);
let name = executable.fetch_identifier(agent, instr.get_first_index(), gc);
unwrap_try(lex_env.try_create_mutable_binding(agent, name.unbind(), false, None, gc));
}
#[inline(always)]
pub(super) fn execute_create_immutable_binding(
agent: &mut Agent,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope,
) {
let lex_env = agent.current_lexical_environment(gc);
let name = executable.fetch_identifier(agent, instr.get_first_index(), gc);
lex_env
.create_immutable_binding(agent, name, true, gc)
.unwrap();
}
pub(super) fn execute_throw_error<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let exception_type_immediate = instr.get_first_arg();
let message = String::try_from(vm.result.take().unwrap()).unwrap();
let exception_type = ExceptionType::try_from(exception_type_immediate).unwrap();
Err(agent.throw_exception_with_message(exception_type, message, gc))
}
pub(super) fn execute_push_exception_jump_target(
agent: &Agent,
vm: &mut Vm,
instr: Instr,
gc: NoGcScope,
) {
vm.exception_handler_stack
.push(ExceptionHandler::CatchBlock {
ip: instr.get_jump_slot() as u32,
lexical_environment: agent.current_lexical_environment(gc).unbind(),
});
}
pub(super) fn execute_instanceof_operator<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lval = vm.stack.pop().unwrap();
let rval = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| instanceof_operator(agent, lval, rval, gc),
gc,
)?;
vm.result = Some(result.into());
Ok(())
}
pub(super) fn execute_begin_simple_array_binding_pattern<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lexical = instr.get_second_bool();
let env = if lexical {
Some(
agent
.current_lexical_environment(gc.nogc())
.scope(agent, gc.nogc()),
)
} else {
None
};
execute_simple_array_binding(agent, vm, executable, env, gc).unbind()?;
Ok(())
}
pub(super) fn execute_begin_simple_object_binding_pattern<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let lexical = instr.get_first_bool();
let env = if lexical {
Some(
agent
.current_lexical_environment(gc.nogc())
.scope(agent, gc.nogc()),
)
} else {
None
};
let object = to_object(agent, vm.result.take().unwrap(), gc.nogc())
.unbind()?
.bind(gc.nogc());
execute_simple_object_binding(agent, vm, executable, object.unbind(), env, gc)
}
#[inline(never)]
pub(super) fn execute_binding_pattern() -> ! {
unreachable!("BeginArrayBindingPattern should take care of stepping over these");
}
pub(super) fn execute_string_concat<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let argument_count = instr.get_first_index();
let first_arg_index = vm.stack.len() - argument_count;
let mut length = 0;
let all_easy = vm.stack[first_arg_index..]
.iter()
.all(|ele| ele.is_primitive() && !ele.is_symbol());
let string = if all_easy {
let gc = gc.nogc();
let args = &mut vm.stack[first_arg_index..];
for arg in args.iter_mut() {
let string: String<'_> =
to_string_primitive(agent, Primitive::try_from(*arg).unwrap(), gc).unwrap();
length += string.len_(agent);
*arg = string.unbind().into();
}
let args = &*args;
let args = unsafe { std::mem::transmute::<&[Value<'_>], &[String<'_>]>(args) };
concat_string_from_slice(agent, args, length, gc)
} else {
let mut args = vm
.stack
.split_off(first_arg_index)
.iter_mut()
.map(|v| v.scope(agent, gc.nogc()))
.collect::<Vec<_>>();
with_vm_gc::<JsResult<()>>(
agent,
vm,
|agent, mut gc| {
for ele in args.iter_mut() {
let maybe_string = ele.get(agent).bind(gc.nogc());
if maybe_string.is_string() {
continue;
}
let string = to_string(agent, maybe_string.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
length += string.len_(agent);
let string: Value = string.into();
unsafe { ele.replace(agent, string.unbind()) };
}
Ok(())
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let gc = gc.nogc();
let args = args
.into_iter()
.map(|v| String::try_from(v.get(agent)).unwrap().bind(gc))
.collect::<Vec<_>>();
concat_string_from_slice(agent, &args, length, gc)
};
vm.stack.truncate(first_arg_index);
vm.result = Some(string.unbind().into());
Ok(())
}
pub(super) fn execute_enumerate_object_properties(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let object = to_object(agent, vm.result.take().unwrap(), gc).unwrap();
vm.iterator_stack.push(
VmIteratorRecord::ObjectProperties(Box::new(ObjectPropertiesIteratorRecord::new(object)))
.unbind(),
);
}
pub(super) fn execute_get_iterator_sync<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let expr_value = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| VmIteratorRecord::from_value(agent, expr_value, gc),
gc,
)?;
vm.iterator_stack.push(result.unbind());
Ok(())
}
pub(super) fn execute_get_iterator_async<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let expr_value = vm.result.take().unwrap();
let result = with_vm_gc(
agent,
vm,
|agent, gc| VmIteratorRecord::async_from_value(agent, expr_value, gc),
gc,
)?;
vm.iterator_stack.push(result.unbind());
Ok(())
}
pub(super) fn execute_iterator_step_value<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc),
gc,
)?;
vm.result = result.unbind();
if result.is_none() {
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("Iterator finished, jumping to {ip}");
}
vm.ip = ip;
}
Ok(())
}
pub(super) fn execute_iterator_step_value_or_undefined<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = if let Some(r) = try_result_into_option_js(
vm.get_active_iterator_mut()
.try_step_value(agent, gc.nogc()),
) {
r.unbind().bind(gc.into_nogc())
} else {
with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc),
gc,
)
};
if result.map_or(true, |r| r.is_none()) {
*vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator;
}
vm.result = Some(result?.unwrap_or(Value::Undefined).unbind());
Ok(())
}
pub(super) fn execute_iterator_call_next_method<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm.result.take();
vm.result = Some(
with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).call_next(agent, result, gc),
gc,
)?
.unbind(),
);
Ok(())
}
pub(super) fn execute_iterator_complete<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm
.result
.expect("No iterator result object")
.bind(gc.nogc());
let Ok(result) = Object::try_from(result) else {
return Err(throw_iterator_returned_non_object(agent, gc.into_nogc()));
};
let done = {
let result = result.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| iterator_complete(agent, result, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
if done {
let result = unsafe {
Object::try_from(vm.result.unwrap_unchecked())
.unwrap_unchecked()
.bind(gc.nogc())
};
let result = {
let result = result.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| iterator_value(agent, result, gc),
gc,
)?
};
vm.result = Some(result.unbind());
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("IteratorComplete returned true, jumping to {ip}");
}
vm.ip = ip;
}
Ok(())
}
pub(super) fn execute_iterator_value<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm
.result
.expect("No iterator result object")
.bind(gc.nogc());
let result = Object::try_from(result).expect("Iterator returned a non-object result");
let result = {
let result = result.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| iterator_value(agent, result, gc),
gc,
)?
};
vm.result = Some(result.unbind());
Ok(())
}
pub(super) fn execute_iterator_throw<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm.result.take().expect("IteratorThrow with no error");
let result = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).throw(agent, result, gc),
gc,
)?;
if let Some(result) = result {
vm.result = Some(result.unbind());
} else {
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("No iterator throw method found, jumping to {ip}");
}
vm.ip = ip;
}
Ok(())
}
pub(super) fn execute_iterator_return<'gc>(
agent: &mut Agent,
vm: &mut Vm,
instr: Instr,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm.result.expect("IteratorReturn with no received value");
let value = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, Some(result), gc),
gc,
)?;
if let Some(value) = value {
vm.result = Some(value.unbind());
} else {
let ip = instr.get_jump_slot();
if agent.options.print_internals {
eprintln!("No iterator return method found, jumping to {ip}");
}
vm.ip = ip;
}
Ok(())
}
pub(super) fn execute_iterator_rest_into_array<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let capacity = vm
.get_active_iterator()
.remaining_length_estimate(agent)
.unwrap_or(0);
let array = array_create(agent, 0, capacity, None, gc.nogc())
.unbind()?
.scope(agent, gc.nogc());
let result = with_vm_gc::<JsResult<()>>(
agent,
vm,
|agent, mut gc| {
let mut idx: u32 = 0;
while let Some(value) = ActiveIterator::new(agent, gc.nogc())
.step_value(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc())
{
let key = PropertyKey::Integer(idx.into());
unwrap_try(try_create_data_property(
agent,
array.get(agent),
key,
value.unbind(),
None,
gc.nogc(),
));
idx += 1;
}
Ok(())
},
gc,
);
*vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator;
result?;
vm.result = Some(unsafe { array.take(agent).into() });
Ok(())
}
pub(super) fn execute_iterator_close<'gc>(
agent: &mut Agent,
vm: &mut Vm,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
if !vm
.get_active_iterator()
.requires_return_call(agent, gc.nogc())
{
return Ok(());
}
let result = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(result) = result {
verify_is_object(agent, result.unbind(), gc.into_nogc())?;
}
Ok(())
}
pub(super) fn execute_async_iterator_close<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, bool> {
if !vm
.get_active_iterator()
.requires_return_call(agent, gc.nogc())
{
vm.ip += 4;
return Ok(false);
}
let result = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc),
gc,
)?;
if let Some(result) = result {
let result = vm.result.replace(result.unbind());
vm.stack.push(result.unwrap_or(Value::Undefined));
return Ok(true);
} else {
vm.ip += 4;
}
Ok(false)
}
pub(super) fn execute_iterator_close_with_error(agent: &mut Agent, vm: &mut Vm, gc: GcScope) {
if vm
.get_active_iterator()
.requires_return_call(agent, gc.nogc())
{
let _ = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc),
gc,
);
}
}
pub(super) fn execute_async_iterator_close_with_error(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope,
) -> bool {
if vm
.get_active_iterator()
.requires_return_call(agent, gc.nogc())
{
let inner_result_value = with_vm_gc(
agent,
vm,
|agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc),
gc,
);
if let Ok(Some(value)) = inner_result_value {
vm.stack.push(vm.result.take().unwrap());
vm.result = Some(value.unbind());
vm.exception_handler_stack
.push(ExceptionHandler::IgnoreErrorAndNextInstruction);
return true;
}
}
vm.ip += 2;
false
}
pub(super) fn execute_create_unmapped_arguments_object<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let Some(VmIteratorRecord::SliceIterator(slice)) = vm.iterator_stack.last() else {
unreachable!()
};
match create_unmapped_arguments_object(agent, slice, gc) {
Ok(o) => {
vm.result = Some(o.unbind().into());
Ok(())
}
Err(err) => Err(agent.throw_allocation_exception(err, gc)),
}
}
pub(super) fn execute_get_new_target(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let env_rec = get_this_environment(agent, gc);
let Environment::Function(env_rec) = env_rec else {
unreachable!()
};
vm.result = Some(
env_rec
.get_new_target(agent)
.map_or(Value::Undefined, |v| v.into())
.unbind(),
);
}
pub(super) fn execute_import_call(agent: &mut Agent, vm: &mut Vm, gc: GcScope) {
let specifier = vm.stack.pop().unwrap().bind(gc.nogc());
let options = vm.result.take().bind(gc.nogc());
vm.result = {
let specifier = specifier.unbind();
let options = options.unbind();
Some(
with_vm_gc(
agent,
vm,
|agent, gc| evaluate_import_call(agent, specifier, options, gc),
gc,
)
.unbind()
.into(),
)
};
}
pub(super) fn execute_import_meta(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) {
let module = agent.get_active_script_or_module(gc);
let Some(ScriptOrModule::SourceTextModule(module)) = module else {
unreachable!()
};
let import_meta = module.get_import_meta(agent);
let import_meta = match import_meta {
None => {
let import_meta_values = agent
.host_hooks
.get_import_meta_properties(agent, module, gc);
let import_meta = OrdinaryObject::create_object(
agent,
None,
&import_meta_values
.into_iter()
.map(|(key, value)| ObjectEntry::new_data_entry(key, value))
.collect::<Box<[ObjectEntry]>>(),
)
.expect("Should perform GC here");
agent
.host_hooks
.finalize_import_meta(agent, import_meta, module, gc);
module.set_import_meta(agent, import_meta);
import_meta
}
Some(import_meta) => {
import_meta
}
};
vm.result = Some(import_meta.unbind().into());
}
pub(super) fn execute_delete<'a>(
agent: &mut Agent,
vm: &mut Vm,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let reference = vm.reference.take().unwrap().bind(gc.nogc());
if is_unresolvable_reference(&reference) {
debug_assert!(!reference.strict());
vm.result = Some(true.into());
} else if is_property_reference(&reference) {
debug_assert!(!is_private_reference(&reference));
if is_super_reference(&reference) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::ReferenceError,
"Invalid delete involving 'super'.",
gc.into_nogc(),
));
}
let base = reference.base_value();
let mut base_obj = to_object(agent, base, gc.nogc()).unbind()?.bind(gc.nogc());
let strict = reference.strict();
let referenced_name = if !reference.is_static_property_reference() {
let referenced_name = reference.referenced_name_value();
if let Some(referenced_name) = to_property_key_simple(agent, referenced_name, gc.nogc())
{
referenced_name
} else {
let referenced_name = referenced_name.unbind();
let scoped_base_obj = base_obj.scope(agent, gc.nogc());
let referenced_name = with_vm_gc(
agent,
vm,
|agent, gc| to_property_key_complex(agent, referenced_name, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
base_obj = unsafe { scoped_base_obj.take(agent) }.bind(gc.nogc());
referenced_name
}
} else {
reference.referenced_name_property_key()
};
let delete_status = if let TryResult::Continue(delete_status) =
base_obj.try_delete(agent, referenced_name, gc.nogc())
{
delete_status
} else {
let base_obj = base_obj.unbind();
let referenced_name = referenced_name.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| base_obj.internal_delete(agent, referenced_name, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
if !delete_status && strict {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Cannot delete property",
gc.into_nogc(),
));
}
vm.result = Some(delete_status.into());
} else {
let base = reference.base_env();
let referenced_name = reference.referenced_name_string();
let result = if let Some(result) =
try_result_into_js(base.try_delete_binding(agent, referenced_name, gc.nogc()))
.unbind()?
{
result
} else {
let referenced_name = referenced_name.unbind();
let base = base.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| base.delete_binding(agent, referenced_name, gc),
gc,
)?
};
vm.result = Some(result.into());
}
Ok(())
}
pub(super) fn execute_verify_is_object<'gc>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let result = vm.result.unwrap();
if !result.is_object() {
let message = executable.fetch_identifier(agent, instr.get_first_index(), gc);
return Err(agent.throw_exception_with_message(ExceptionType::TypeError, message, gc));
}
Ok(())
}
#[inline(never)]
#[cold]
fn handle_set_value_break<'gc>(
agent: &mut Agent,
vm: &mut Vm,
reference: &Reference,
result: TryResult<SetResult>,
mut value: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
match result {
ControlFlow::Continue(SetResult::Done)
| ControlFlow::Continue(SetResult::Unwritable)
| ControlFlow::Continue(SetResult::Accessor) => Ok(()),
ControlFlow::Continue(SetResult::Proxy(proxy)) => {
let p = reference.referenced_name_property_key();
let receiver = reference.this_value(agent);
let strict = reference.strict();
with_vm_gc(
agent,
vm,
|agent, gc| call_proxy_set(agent, proxy, p, value, receiver, strict, gc),
gc,
)
}
ControlFlow::Continue(SetResult::Set(setter)) => {
let receiver = reference.this_value(agent);
with_vm_gc(
agent,
vm,
|agent, gc| {
call_function(
agent,
setter,
receiver,
Some(ArgumentsList::from_mut_value(&mut value)),
gc,
)
},
gc,
)
.map(|_| ())
}
ControlFlow::Break(TryError::Err(err)) => Err(err.unbind().bind(gc.into_nogc())),
ControlFlow::Break(TryError::GcError) => with_vm_gc(
agent,
vm,
|agent, gc| put_value(agent, reference, value, gc),
gc,
),
}
}
#[inline(never)]
#[cold]
fn mutate_reference_property_key<'gc>(
agent: &mut Agent,
vm: &mut Vm,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Reference<'gc>> {
let reference = vm.reference.as_ref().unwrap();
let referenced_name = reference.referenced_name_value().bind(gc.nogc());
let referenced_name =
if let Some(referenced_name) = to_property_key_simple(agent, referenced_name, gc.nogc()) {
referenced_name
} else {
let base = reference.base_value().bind(gc.nogc());
if base.is_undefined() || base.is_null() {
return Err(throw_read_undefined_or_null_error(
agent,
referenced_name.unbind(),
base.unbind(),
gc.into_nogc(),
));
}
let referenced_name = referenced_name.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| to_property_key_complex(agent, referenced_name, gc),
gc,
)?
};
let reference = vm.reference.as_mut().unwrap();
reference.set_referenced_name_to_property_key(referenced_name);
Ok(reference.clone())
}
#[inline(never)]
#[cold]
fn handle_get_value_break<'a>(
agent: &mut Agent,
vm: &mut Vm,
reference: &Reference,
result: ControlFlow<TryError, TryGetValueContinue>,
gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let result = result.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| match result {
ControlFlow::Continue(TryGetValueContinue::Get { getter, receiver }) => {
call_function(agent, getter, receiver, None, gc)
}
ControlFlow::Continue(TryGetValueContinue::Proxy {
proxy,
receiver,
property_key,
}) => proxy.internal_get(agent, property_key, receiver, gc),
ControlFlow::Break(TryError::Err(err)) => Err(err.bind(gc.into_nogc())),
ControlFlow::Break(TryError::GcError) => get_value(agent, reference, gc),
_ => unreachable!(),
},
gc,
)
}