mod binding_methods;
mod execute_instructions;
use execute_instructions::*;
use std::{hint::unreachable_unchecked, ptr::NonNull};
use wtf8::Wtf8Buf;
use crate::{
ecmascript::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, BigInt, Environment, ExceptionType, JsError,
JsResult, Number, Object, Primitive, Promise, Reference, ScopedArgumentsList, String,
Value, call_function, get_method, is_callable, ordinary_has_instance, to_boolean,
to_numeric, to_numeric_primitive, to_primitive, to_property_key, to_string_primitive,
try_get_object_method, try_result_into_option_js,
},
engine::{
Bindable, GcScope, NoGcScope, Scopable, Scoped, bindable_handle,
bytecode::{
Executable, IndexType, Instruction, InstructionIter, instructions::Instr,
iterator::VmIteratorRecord,
},
},
heap::{CompactionLists, HeapMarkAndSweep, WellKnownSymbols, WorkQueues},
};
#[derive(Debug)]
pub(crate) enum ExecutionResult<'a> {
Return(Value<'a>),
Throw(JsError<'a>),
Await {
vm: SuspendedVm,
promise: Promise<'a>,
},
Yield {
vm: SuspendedVm,
yielded_value: Value<'a>,
},
}
impl<'a> ExecutionResult<'a> {
pub(crate) fn into_js_result(self) -> JsResult<'a, Value<'a>> {
match self {
ExecutionResult::Return(value) => Ok(value),
ExecutionResult::Throw(err) => Err(err.unbind()),
_ => panic!("Unexpected yield or await"),
}
}
}
bindable_handle!(ExecutionResult);
#[must_use]
enum ContinuationKind {
Normal,
Return,
Yield,
Await,
}
#[derive(Debug)]
enum ExceptionHandler<'a> {
CatchBlock {
ip: u32,
lexical_environment: Environment<'a>,
},
IgnoreErrorAndNextInstruction,
}
#[derive(Debug, Default)]
pub(crate) struct Vm {
ip: usize,
stack: Vec<Value<'static>>,
reference_stack: Vec<Reference<'static>>,
iterator_stack: Vec<VmIteratorRecord<'static>>,
exception_handler_stack: Vec<ExceptionHandler<'static>>,
result: Option<Value<'static>>,
reference: Option<Reference<'static>>,
}
#[derive(Debug)]
pub(crate) struct SuspendedVm {
ip: usize,
stack: Box<[Value<'static>]>,
reference_stack: Box<[Reference<'static>]>,
iterator_stack: Box<[VmIteratorRecord<'static>]>,
exception_jump_target_stack: Box<[ExceptionHandler<'static>]>,
}
impl SuspendedVm {
pub(crate) fn resume<'gc>(
self,
agent: &mut Agent,
executable: Scoped<Executable>,
value: Value,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
if agent.options.print_internals {
eprintln!("Resuming function with value\n");
}
let vm = Vm::from_suspended(self);
vm.resume(agent, executable, value, gc)
}
pub(crate) fn resume_throw<'gc>(
self,
agent: &mut Agent,
executable: Scoped<Executable>,
err: Value,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
if agent.options.print_internals {
eprintln!("Resuming function with error\n");
}
if self.exception_jump_target_stack.is_empty() {
let err = JsError::new(err.unbind());
return ExecutionResult::Throw(err);
}
let vm = Vm::from_suspended(self);
vm.resume_throw(agent, executable, err, gc)
}
pub(crate) fn resume_return<'gc>(
mut self,
agent: &mut Agent,
executable: Scoped<Executable>,
result: Value,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
if agent.options.print_internals {
eprintln!("Resuming function with return\n");
}
let next_instruction = executable.get_instruction(agent, &mut self.ip);
assert_eq!(next_instruction.map(|i| i.kind), Some(Instruction::Jump));
let peek_next_instruction = executable.get_instructions(agent).get(self.ip).copied();
if peek_next_instruction == Some(Instruction::Return.as_u8()) {
return ExecutionResult::Return(result.bind(gc.into_nogc()));
}
let vm = Vm::from_suspended(self);
vm.resume(agent, executable, result, gc)
}
}
impl Vm {
fn new() -> Self {
Self {
ip: 0,
stack: Vec::with_capacity(32),
reference_stack: Vec::new(),
iterator_stack: Vec::new(),
exception_handler_stack: Vec::new(),
result: None,
reference: None,
}
}
fn suspend(self) -> SuspendedVm {
SuspendedVm {
ip: self.ip,
stack: self.stack.into_boxed_slice(),
reference_stack: self.reference_stack.into_boxed_slice(),
iterator_stack: self.iterator_stack.into_boxed_slice(),
exception_jump_target_stack: self.exception_handler_stack.into_boxed_slice(),
}
}
fn from_suspended(suspended: SuspendedVm) -> Self {
Self {
ip: suspended.ip,
stack: suspended.stack.into_vec(),
reference_stack: suspended.reference_stack.into_vec(),
iterator_stack: suspended.iterator_stack.into_vec(),
exception_handler_stack: suspended.exception_jump_target_stack.into_vec(),
result: None,
reference: None,
}
}
pub(crate) fn execute<'gc>(
agent: &mut Agent,
executable: Scoped<Executable>,
arguments: Option<&mut [Value<'static>]>,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
let mut vm = Vm::new();
if let Some(arguments) = arguments {
ArgumentsList::from_mut_slice(arguments).with_scoped(
agent,
|agent, arguments, gc| {
let arguments = unsafe {
core::mem::transmute::<ScopedArgumentsList, ScopedArgumentsList<'static>>(
arguments,
)
};
vm.iterator_stack
.push(VmIteratorRecord::SliceIterator(arguments));
if agent.options.print_internals {
vm.print_internals(agent, executable.clone(), gc.nogc());
}
vm.inner_execute(agent, executable, gc)
},
gc,
)
} else {
if agent.options.print_internals {
vm.print_internals(agent, executable.clone(), gc.nogc());
}
vm.inner_execute(agent, executable, gc)
}
}
fn print_internals(&self, agent: &mut Agent, executable: Scoped<Executable>, gc: NoGcScope) {
eprintln!();
eprintln!("=== Executing Executable ===");
eprintln!("Constants: {:?}", executable.get_constants(agent, gc));
eprintln!();
eprintln!("Instructions:");
let iter = InstructionIter::new(executable.get_instructions(agent));
for (ip, instr) in iter {
instr.debug_print(agent, ip, executable.clone(), gc);
}
eprintln!();
}
fn resume<'gc>(
mut self,
agent: &mut Agent,
executable: Scoped<Executable>,
value: Value,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
self.result = Some(value.unbind());
self.inner_execute(agent, executable, gc)
}
fn resume_throw<'gc>(
mut self,
agent: &mut Agent,
executable: Scoped<Executable>,
err: Value,
gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
let err = err.bind(gc.nogc());
let err = JsError::new(err.unbind());
if !self.handle_error(agent, err) {
if agent.options.print_internals {
eprintln!("Exiting function with error\n");
}
return ExecutionResult::Throw(err);
}
self.inner_execute(agent, executable, gc)
}
fn inner_execute<'gc>(
mut self,
agent: &mut Agent,
executable: Scoped<Executable>,
mut gc: GcScope<'gc, '_>,
) -> ExecutionResult<'gc> {
let stack_depth = agent.stack_refs.borrow().len();
let instructions = executable.get_instructions(agent);
while let Some(instr) = Instr::consume_instruction(instructions, &mut self.ip) {
if agent.check_gc() {
self.trigger_gc(agent, gc.reborrow());
}
if agent.options.print_internals {
Self::print_executing(instr.kind);
}
let result = Self::execute_instruction(
agent,
&mut self,
executable.clone(),
instr,
gc.reborrow(),
);
match result {
Ok(ContinuationKind::Normal) => {}
_ => unsafe {
if let Some(r) = self.handle_execute_instruction_abnormal_result(agent, result)
{
return r.unbind().bind(gc.into_nogc());
}
},
}
agent.stack_refs.borrow_mut().truncate(stack_depth);
}
ExecutionResult::Return(Value::Undefined)
}
#[inline(never)]
#[cold]
unsafe fn handle_execute_instruction_abnormal_result<'a>(
&mut self,
agent: &mut Agent,
result: JsResult<'a, ContinuationKind>,
) -> Option<ExecutionResult<'a>> {
match result {
Ok(ContinuationKind::Normal) => unsafe { unreachable_unchecked() },
Ok(ContinuationKind::Return) => {
if agent.options.print_internals {
Self::print_exiting();
}
let result = self.result.unwrap_or(Value::Undefined);
Some(ExecutionResult::Return(result))
}
Ok(ContinuationKind::Yield) => {
let yielded_value = self.result.take().unwrap();
if agent.options.print_internals {
Self::print_yielding(yielded_value);
}
Some(ExecutionResult::Yield {
vm: core::mem::take(self).suspend(),
yielded_value,
})
}
Ok(ContinuationKind::Await) => {
if agent.options.print_internals {
Self::print_awaiting();
}
let Value::Promise(promise) = self.result.take().unwrap() else {
unreachable!()
};
Some(ExecutionResult::Await {
vm: core::mem::take(self).suspend(),
promise,
})
}
Err(err) => {
if !self.handle_error(agent, err) {
if agent.options.print_internals {
Self::print_exiting_with_error();
}
Some(ExecutionResult::Throw(err.unbind()))
} else {
None
}
}
}
}
#[inline(never)]
#[cold]
fn trigger_gc(&mut self, agent: &mut Agent, gc: GcScope) {
with_vm_gc(agent, self, |agent, gc| agent.gc(gc), gc);
}
#[inline(never)]
#[cold]
fn print_executing(instruction: Instruction) {
eprintln!("Executing: {instruction:?}");
}
#[inline(never)]
#[cold]
fn print_exiting() {
eprintln!("Exiting function with result\n");
}
#[inline(never)]
#[cold]
fn print_exiting_with_error() {
eprintln!("Exiting function with error\n");
}
#[inline(never)]
#[cold]
fn print_awaiting() {
eprintln!("Awaiting value in function\n");
}
#[inline(never)]
#[cold]
fn print_yielding(yielded_value: Value) {
eprintln!("Yielding value from function {yielded_value:?}\n");
}
#[inline(never)]
#[cold]
#[must_use]
fn handle_error(&mut self, agent: &mut Agent, err: JsError) -> bool {
if let Some(handler) = self.exception_handler_stack.pop() {
match handler {
ExceptionHandler::CatchBlock {
ip,
lexical_environment,
} => {
if agent.options.print_internals {
eprintln!("Error: {:?}", err.value());
eprintln!("Jumping to catch block in {ip}\n");
}
self.ip = ip as usize;
agent.set_current_lexical_environment(lexical_environment);
self.result = Some(err.value().unbind());
}
ExceptionHandler::IgnoreErrorAndNextInstruction => {
if agent.options.print_internals {
eprintln!("Ignoring throw error and skipping to {}\n", self.ip + 1);
}
self.ip += 1;
}
}
true
} else {
false
}
}
fn execute_instruction<'a>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'a, '_>,
) -> JsResult<'a, ContinuationKind> {
match instr.kind {
Instruction::Load => {
vm.execute_load();
}
Instruction::LoadCopy => {
vm.execute_load_copy();
}
Instruction::PutValueToIndex => {
vm.execute_load_to_index(instr.get_first_index());
}
Instruction::Store => {
vm.execute_store();
}
Instruction::GetValueFromIndex => {
vm.execute_store_from_index(instr.get_first_index());
}
Instruction::StoreConstant => {
execute_store_constant(agent, vm, executable, instr, gc.into_nogc());
}
Instruction::PopStack => {
vm.execute_pop_stack();
}
Instruction::Jump => execute_jump(agent, vm, instr),
Instruction::JumpIfNot => execute_jump_if_not(agent, vm, instr),
Instruction::ResolveBinding => {
execute_resolve_binding(agent, vm, executable, instr, gc)?;
}
Instruction::GetValue
| Instruction::GetValueWithCache
| Instruction::GetValueKeepReference
| Instruction::GetValueWithCacheKeepReference => {
let cache = matches!(
instr.kind,
Instruction::GetValueWithCache | Instruction::GetValueWithCacheKeepReference
);
let keep_reference = matches!(
instr.kind,
Instruction::GetValueKeepReference
| Instruction::GetValueWithCacheKeepReference
);
execute_get_value(agent, vm, executable, instr, cache, keep_reference, gc)?
}
Instruction::PutValue | Instruction::PutValueWithCache => {
let cache = matches!(instr.kind, Instruction::PutValueWithCache);
execute_put_value(agent, vm, executable, instr, cache, gc)?
}
Instruction::ToNumeric => execute_to_numeric(agent, vm, gc)?,
Instruction::CreateImmutableBinding => {
execute_create_immutable_binding(agent, executable, instr, gc.into_nogc())
}
Instruction::CreateMutableBinding => {
execute_create_mutable_binding(agent, executable, instr, gc.into_nogc())
}
Instruction::InitializeReferencedBinding => {
execute_initialize_referenced_binding(agent, vm, gc.into_nogc())
}
Instruction::PushReference => vm.execute_push_reference(),
Instruction::PopReference => vm.execute_pop_reference(),
Instruction::EvaluatePropertyAccessWithIdentifierKey => {
execute_evaluate_property_access_with_identifier_key(
agent,
vm,
executable,
instr,
gc.into_nogc(),
)
}
Instruction::EvaluatePropertyAccessWithExpressionKey => {
execute_evaluate_property_access_with_expression_key(agent, vm, gc.into_nogc())
}
_ => return Self::execute_cold_instruction(agent, vm, executable, instr, gc),
}
Ok(ContinuationKind::Normal)
}
#[inline(never)]
fn execute_cold_instruction<'a>(
agent: &mut Agent,
vm: &mut Vm,
executable: Scoped<Executable>,
instr: Instr,
gc: GcScope<'a, '_>,
) -> JsResult<'a, ContinuationKind> {
let _: () = match instr.kind {
Instruction::Load
| Instruction::LoadCopy
| Instruction::PutValueToIndex
| Instruction::Store
| Instruction::StoreConstant
| Instruction::GetValueFromIndex
| Instruction::PopStack
| Instruction::Jump
| Instruction::JumpIfNot
| Instruction::ResolveBinding
| Instruction::GetValue
| Instruction::GetValueWithCache
| Instruction::GetValueKeepReference
| Instruction::GetValueWithCacheKeepReference
| Instruction::PutValue
| Instruction::PutValueWithCache
| Instruction::ToNumeric
| Instruction::CreateImmutableBinding
| Instruction::CreateMutableBinding
| Instruction::InitializeReferencedBinding
| Instruction::PushReference
| Instruction::PopReference
| Instruction::EvaluatePropertyAccessWithIdentifierKey
| Instruction::EvaluatePropertyAccessWithExpressionKey => {
unreachable!("hot instruction not handled before execute_cold_instruction")
}
Instruction::Return => return Ok(ContinuationKind::Return),
Instruction::Await => {
execute_await_promise_resolve(agent, vm, gc)?;
return Ok(ContinuationKind::Await);
}
Instruction::Yield => return Ok(ContinuationKind::Yield),
Instruction::IsStrictlyEqual => execute_is_strictly_equal(agent, vm, gc.into_nogc()),
Instruction::IsNullOrUndefined => vm.execute_is_null_or_undefined(),
Instruction::IsNull => vm.execute_is_null(),
Instruction::IsUndefined => vm.execute_is_undefined(),
Instruction::IsObject => vm.execute_is_object(),
Instruction::IsConstructor => execute_is_constructor(agent, vm),
Instruction::JumpIfTrue => execute_jump_if_true(agent, vm, instr),
Instruction::LoadReplace => vm.execute_load_replace(),
Instruction::LoadConstant => {
execute_load_constant(agent, vm, executable, instr, gc.into_nogc())
}
Instruction::LoadStoreSwap => vm.execute_load_store_swap(),
Instruction::UpdateEmpty => vm.execute_update_empty(),
Instruction::Swap => vm.execute_swap(),
Instruction::Empty => vm.execute_empty(),
Instruction::LogicalNot => execute_logical_not(agent, vm),
Instruction::ApplyAdditionBinaryOperator => {
execute_apply_addition_binary_operator(agent, vm, gc)?
}
Instruction::ApplySubtractionBinaryOperator
| Instruction::ApplyMultiplicationBinaryOperator
| Instruction::ApplyDivisionBinaryOperator
| Instruction::ApplyRemainderBinaryOperator
| Instruction::ApplyExponentialBinaryOperator
| Instruction::ApplyShiftLeftBinaryOperator
| Instruction::ApplyShiftRightBinaryOperator
| Instruction::ApplyShiftRightZeroFillBinaryOperator
| Instruction::ApplyBitwiseORBinaryOperator
| Instruction::ApplyBitwiseXORBinaryOperator
| Instruction::ApplyBitwiseAndBinaryOperator => {
execute_apply_binary_operator(agent, vm, instr.kind, gc)?
}
Instruction::ArrayCreate => execute_array_create(agent, vm, instr, gc.into_nogc())?,
Instruction::ArrayPush => execute_array_push(agent, vm, gc)?,
Instruction::ArrayElision => execute_array_elision(agent, vm, gc)?,
Instruction::BitwiseNot => execute_bitwise_not(agent, vm, gc.into_nogc())?,
Instruction::CreateUnmappedArgumentsObject => {
execute_create_unmapped_arguments_object(agent, vm, gc.into_nogc())?
}
Instruction::CopyDataProperties => execute_copy_data_properties(agent, vm, gc)?,
Instruction::CopyDataPropertiesIntoObject => {
execute_copy_data_properties_into_object(agent, vm, instr, gc)?
}
Instruction::Delete => execute_delete(agent, vm, gc)?,
Instruction::DirectEvalCall => execute_direct_eval_call(agent, vm, instr, gc)?,
Instruction::EvaluateCall => execute_evaluate_call(agent, vm, instr, gc)?,
Instruction::EvaluateNew => execute_evaluate_new(agent, vm, instr, gc)?,
Instruction::EvaluateSuper => execute_evaluate_super(agent, vm, instr, gc)?,
Instruction::MakePrivateReference => {
execute_make_private_reference(agent, vm, executable, instr, gc.into_nogc())
}
Instruction::MakeSuperPropertyReferenceWithExpressionKey => {
execute_make_super_property_reference_with_expression_key(
agent,
vm,
gc.into_nogc(),
)?
}
Instruction::MakeSuperPropertyReferenceWithIdentifierKey => {
execute_make_super_property_reference_with_identifier_key(
agent,
vm,
executable,
instr,
gc.into_nogc(),
)?
}
Instruction::GreaterThan => execute_greater_than(agent, vm, gc)?,
Instruction::GreaterThanEquals => execute_greater_than_equals(agent, vm, gc)?,
Instruction::HasProperty => execute_has_property(agent, vm, gc)?,
Instruction::HasPrivateElement => {
execute_has_private_element(agent, vm, gc.into_nogc())?
}
Instruction::Increment => execute_increment(agent, vm, gc.into_nogc()),
Instruction::Decrement => execute_decrement(agent, vm, gc.into_nogc()),
Instruction::InstanceofOperator => execute_instanceof_operator(agent, vm, gc)?,
Instruction::InstantiateArrowFunctionExpression => {
execute_instantiate_arrow_function_expression(agent, vm, executable, instr, gc)?
}
Instruction::InstantiateOrdinaryFunctionExpression => {
execute_instantiate_ordinary_function_expression(agent, vm, executable, instr, gc)?
}
Instruction::ClassDefineConstructor => {
execute_class_define_constructor(agent, vm, executable, instr, gc)?
}
Instruction::ClassDefineDefaultConstructor => {
execute_class_define_default_constructor(agent, vm, executable, instr, gc)?
}
Instruction::ClassDefinePrivateMethod => {
execute_class_define_private_method(agent, vm, executable, instr, gc)?
}
Instruction::ClassDefinePrivateProperty => {
execute_class_define_private_property(agent, vm, executable, instr, gc.into_nogc())?
}
Instruction::ClassInitializePrivateElements => {
execute_class_initialize_private_elements(agent, vm, gc.into_nogc())?
}
Instruction::ClassInitializePrivateValue => {
execute_class_initialize_private_value(agent, vm, instr, gc.into_nogc())?
}
Instruction::LessThan => execute_less_than(agent, vm, gc)?,
Instruction::LessThanEquals => execute_less_than_equals(agent, vm, gc)?,
Instruction::IsLooselyEqual => execute_is_loosely_equal(agent, vm, gc)?,
Instruction::ObjectCreate => execute_object_create(agent, vm, gc.into_nogc()),
Instruction::ObjectCreateWithShape => {
execute_object_create_with_shape(agent, vm, executable, instr, gc.into_nogc())
}
Instruction::ObjectDefineProperty => execute_object_define_property(agent, vm, gc)?,
Instruction::ObjectDefineMethod => {
execute_object_define_method(agent, vm, executable, instr, gc)?
}
Instruction::ObjectDefineGetter => {
execute_object_define_getter(agent, vm, executable, instr, gc)?
}
Instruction::ObjectDefineSetter => {
execute_object_define_setter(agent, vm, executable, instr, gc)?
}
Instruction::ObjectSetPrototype => execute_object_set_prototype(agent, vm, gc)?,
Instruction::PopExceptionJumpTarget => vm.execute_pop_exception_jump_target(),
Instruction::PushExceptionJumpTarget => {
execute_push_exception_jump_target(agent, vm, instr, gc.into_nogc())
}
Instruction::TruncateStack => vm.stack.truncate(instr.get_first_arg() as usize),
Instruction::ResolveBindingWithCache => {
execute_resolve_binding_with_cache(agent, vm, executable, instr, gc)?
}
Instruction::ResolveThisBinding => {
execute_resolve_this_binding(agent, vm, gc.into_nogc())?
}
Instruction::StoreCopy => vm.execute_store_copy(),
Instruction::StringConcat => execute_string_concat(agent, vm, instr, gc)?,
Instruction::Throw => vm.execute_throw(gc.into_nogc())?,
Instruction::ThrowError => execute_throw_error(agent, vm, instr, gc.into_nogc())?,
Instruction::ToNumber => execute_to_number(agent, vm, gc)?,
Instruction::ToObject => execute_to_object(agent, vm, gc.into_nogc())?,
Instruction::Typeof => execute_typeof(agent, vm, gc)?,
Instruction::UnaryMinus => execute_unary_minus(agent, vm, gc.into_nogc()),
Instruction::InitializeVariableEnvironment => {
execute_initialize_variable_environment(agent, vm, instr, gc.into_nogc())
}
Instruction::EnterDeclarativeEnvironment => {
execute_enter_declarative_environment(agent, gc.into_nogc())
}
Instruction::EnterClassStaticElementEnvironment => {
execute_enter_class_static_element_environment(agent, vm, gc.into_nogc())
}
Instruction::EnterPrivateEnvironment => {
execute_enter_private_environment(agent, instr, gc.into_nogc())
}
Instruction::ExitDeclarativeEnvironment => {
execute_exit_declarative_environment(agent, gc.into_nogc())
}
Instruction::ExitVariableEnvironment => {
execute_exit_variable_environment(agent, gc.into_nogc())
}
Instruction::ExitPrivateEnvironment => {
execute_exit_private_environment(agent, gc.into_nogc())
}
Instruction::BeginSimpleObjectBindingPattern => {
execute_begin_simple_object_binding_pattern(agent, vm, executable, instr, gc)?
}
Instruction::BeginSimpleArrayBindingPattern => {
execute_begin_simple_array_binding_pattern(agent, vm, executable, instr, gc)?
}
Instruction::BindingPatternBind
| Instruction::BindingPatternBindNamed
| Instruction::BindingPatternBindToIndex
| Instruction::BindingPatternBindRest
| Instruction::BindingPatternBindRestToIndex
| Instruction::BindingPatternSkip
| Instruction::BindingPatternGetValue
| Instruction::BindingPatternGetValueNamed
| Instruction::BindingPatternGetRestValue
| Instruction::FinishBindingPattern => execute_binding_pattern(),
Instruction::EnumerateObjectProperties => {
execute_enumerate_object_properties(agent, vm, gc.into_nogc())
}
Instruction::GetIteratorSync => execute_get_iterator_sync(agent, vm, gc)?,
Instruction::GetIteratorAsync => execute_get_iterator_async(agent, vm, gc)?,
Instruction::IteratorStepValue => execute_iterator_step_value(agent, vm, instr, gc)?,
Instruction::IteratorStepValueOrUndefined => {
execute_iterator_step_value_or_undefined(agent, vm, gc)?
}
Instruction::IteratorCallNextMethod => {
execute_iterator_call_next_method(agent, vm, gc)?
}
Instruction::IteratorComplete => execute_iterator_complete(agent, vm, instr, gc)?,
Instruction::IteratorValue => execute_iterator_value(agent, vm, gc)?,
Instruction::IteratorThrow => execute_iterator_throw(agent, vm, instr, gc)?,
Instruction::IteratorReturn => execute_iterator_return(agent, vm, instr, gc)?,
Instruction::IteratorRestIntoArray => execute_iterator_rest_into_array(agent, vm, gc)?,
Instruction::IteratorClose => execute_iterator_close(agent, vm, gc)?,
Instruction::AsyncIteratorClose => {
if execute_async_iterator_close(agent, vm, gc)? {
return Ok(ContinuationKind::Await);
}
}
Instruction::IteratorCloseWithError => execute_iterator_close_with_error(agent, vm, gc),
Instruction::AsyncIteratorCloseWithError => {
if execute_async_iterator_close_with_error(agent, vm, gc) {
return Ok(ContinuationKind::Await);
}
}
Instruction::IteratorPop => {
let _ = vm.pop_iterator(gc.into_nogc());
}
Instruction::GetNewTarget => execute_get_new_target(agent, vm, gc.into_nogc()),
Instruction::ImportCall => execute_import_call(agent, vm, gc),
Instruction::ImportMeta => execute_import_meta(agent, vm, gc.into_nogc()),
Instruction::VerifyIsObject => {
execute_verify_is_object(agent, vm, executable, instr, gc.into_nogc())?
}
Instruction::Debug => execute_debug(agent, vm),
};
Ok(ContinuationKind::Normal)
}
fn get_call_args<'gc>(&mut self, instr: Instr, _gc: NoGcScope<'gc, '_>) -> Vec<Value<'gc>> {
let instr_arg0 = instr.get_first_arg();
if instr_arg0 != IndexType::MAX {
let arg_count = instr_arg0 as usize;
debug_assert!(self.stack.len() >= arg_count);
self.stack.split_off(self.stack.len() - arg_count)
} else {
let Value::Integer(integer) = self.result.take().unwrap() else {
panic!("Expected the number of function arguments to be an integer")
};
let arg_count = usize::try_from(integer.into_i64()).unwrap();
debug_assert!(self.stack.len() > arg_count);
let args = self.stack.split_off(self.stack.len() - arg_count);
let integer_copy = self.stack.pop().unwrap();
debug_assert_eq!(Value::Integer(integer), integer_copy);
args
}
}
pub(super) fn pop_iterator<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> VmIteratorRecord<'gc> {
self.iterator_stack
.pop()
.expect("Iterator stack is empty")
.bind(gc)
}
pub(super) fn get_active_iterator(&self) -> &VmIteratorRecord<'static> {
self.iterator_stack.last().expect("Iterator stack is empty")
}
pub(super) fn get_active_iterator_mut(&mut self) -> &mut VmIteratorRecord<'static> {
self.iterator_stack
.last_mut()
.expect("Iterator stack is empty")
}
#[inline(always)]
fn execute_is_null_or_undefined(&mut self) {
let val = self.result.take().unwrap();
let result = val.is_null() || val.is_undefined();
self.result = Some(result.into());
}
#[inline(always)]
fn execute_load(&mut self) {
self.stack.push(self.result.take().unwrap());
}
#[inline(always)]
fn execute_load_copy(&mut self) {
self.stack.push(self.result.unwrap());
}
#[inline(always)]
fn execute_load_to_index(&mut self, index: usize) {
self.stack[index] = self.result.take().unwrap();
}
#[inline(always)]
fn execute_load_store_swap(&mut self) {
let temp = self
.result
.take()
.expect("Expected result value to not be empty");
self.result = Some(self.stack.pop().expect("Trying to pop from empty stack"));
self.stack.push(temp);
}
#[inline(always)]
fn execute_load_replace(&mut self) {
if let Some(result) = self.result.take() {
let temp = self
.stack
.last_mut()
.expect("Trying to replace top of empty stack");
*temp = result;
}
}
#[inline(always)]
fn execute_update_empty(&mut self) {
let temp = self.stack.pop().expect("Trying to pop from empty stack");
if self.result.is_none() {
self.result = Some(temp);
}
}
#[inline(always)]
fn execute_store(&mut self) {
self.result = Some(self.stack.pop().expect("Trying to pop from empty stack"));
}
#[inline(always)]
fn execute_store_from_index(&mut self, index: usize) {
self.result = Some(self.stack[index]);
}
#[inline(always)]
fn execute_store_copy(&mut self) {
self.result = Some(*self.stack.last().expect("Trying to get from empty stack"));
}
#[inline(always)]
fn execute_pop_stack(&mut self) {
let _ = self.stack.pop().expect("Trying to pop from empty stack");
}
#[inline(always)]
fn execute_push_reference(&mut self) {
self.reference_stack.push(self.reference.take().unwrap());
}
#[inline(always)]
fn execute_pop_reference(&mut self) {
self.reference = Some(self.reference_stack.pop().unwrap());
}
#[inline(always)]
fn execute_swap(&mut self) {
let a = self.stack.pop().unwrap();
let b = self.stack.pop().unwrap();
self.stack.push(a);
self.stack.push(b);
}
#[inline(always)]
fn execute_empty(&mut self) {
self.result = None;
}
#[inline(always)]
fn execute_is_undefined(&mut self) {
let val = self.result.take().unwrap();
let result = val.is_undefined();
self.result = Some(result.into());
}
#[inline(always)]
fn execute_is_null(&mut self) {
let val = self.result.take().unwrap();
let result = val.is_null();
self.result = Some(result.into());
}
#[inline(always)]
fn execute_is_object(&mut self) {
let val = self.result.take().unwrap();
let result = val.is_object();
self.result = Some(result.into());
}
#[inline(always)]
fn execute_pop_exception_jump_target(&mut self) {
self.exception_handler_stack.pop().unwrap();
}
#[inline(always)]
fn execute_throw<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> JsResult<'gc, ()> {
let result = self.result.take().unwrap();
Err(JsError::new(result).bind(gc))
}
}
fn concat_string_from_slice<'gc>(
agent: &mut Agent,
slice: &[String],
string_length: usize,
gc: NoGcScope<'gc, '_>,
) -> String<'gc> {
let mut result_string = Wtf8Buf::with_capacity(string_length);
for string in slice.iter() {
result_string.push_wtf8(string.as_wtf8_(agent));
}
String::from_wtf8_buf(agent, result_string, gc)
}
fn apply_string_or_numeric_binary_operator<'gc>(
agent: &mut Agent,
lval: Value,
op_text: Instruction,
rval: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let lval = lval.bind(gc.nogc());
let rval = rval.bind(gc.nogc());
let rval = rval.scope(agent, gc.nogc());
let lnum = to_numeric(agent, lval.unbind(), gc.reborrow())
.unbind()?
.scope(agent, gc.nogc());
let rnum = to_numeric(agent, unsafe { rval.take(agent) }, gc.reborrow()).unbind()?;
let gc = gc.into_nogc();
let rnum = rnum.bind(gc);
let lnum = unsafe { lnum.take(agent) }.bind(gc);
if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lnum), BigInt::try_from(rnum)) {
bigint_binary_operator(agent, op_text, lnum, rnum, gc).map(|v| v.into())
} else if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lnum), Number::try_from(rnum)) {
number_binary_operator(agent, op_text, lnum, rnum, gc).map(|v| v.into())
} else {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"The left and right-hand sides do not have the same type.",
gc,
))
}
}
fn apply_string_or_numeric_addition<'gc>(
agent: &mut Agent,
lval: Value,
rval: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let lval = lval.bind(gc.nogc());
let rval = rval.bind(gc.nogc());
let (lprim, rprim, gc) = match (Primitive::try_from(lval), Primitive::try_from(rval)) {
(Ok(lprim), Ok(rprim)) => {
let lprim = lprim.unbind();
let rprim = rprim.unbind();
let gc = gc.into_nogc();
(lprim.bind(gc), rprim.bind(gc), gc)
}
(Ok(lprim), Err(_)) => {
let lprim = lprim.scope(agent, gc.nogc());
let rprim = to_primitive(agent, rval.unbind(), None, gc.reborrow()).unbind()?;
let gc = gc.into_nogc();
let lprim = unsafe { lprim.take(agent) };
(lprim.bind(gc), rprim.bind(gc), gc)
}
(Err(_), Ok(rprim)) => {
let rprim = rprim.scope(agent, gc.nogc());
let lprim = to_primitive(agent, lval.unbind(), None, gc.reborrow()).unbind()?;
let gc = gc.into_nogc();
let rprim = unsafe { rprim.take(agent) };
(lprim.bind(gc), rprim.bind(gc), gc)
}
(Err(_), Err(_)) => {
let rval = rval.scope(agent, gc.nogc());
let lprim = to_primitive(agent, lval.unbind(), None, gc.reborrow())
.unbind()?
.scope(agent, gc.nogc());
let rprim =
to_primitive(agent, unsafe { rval.take(agent) }, None, gc.reborrow()).unbind()?;
let gc = gc.into_nogc();
let lprim = unsafe { lprim.take(agent) };
(lprim.bind(gc), rprim.bind(gc), gc)
}
};
match (String::try_from(lprim), String::try_from(rprim)) {
(Ok(lstr), Ok(rstr)) => {
return Ok(String::concat(agent, [lstr, rstr], gc).into());
}
(Ok(lstr), Err(_)) => {
let lstr = lstr.scope(agent, gc);
let rstr = to_string_primitive(agent, rprim, gc)?;
return Ok(String::concat(agent, [lstr.get(agent).bind(gc), rstr], gc).into());
}
(Err(_), Ok(rstr)) => {
let rstr = rstr.scope(agent, gc);
let lstr = to_string_primitive(agent, lprim, gc)?;
return Ok(String::concat(agent, [lstr, rstr.get(agent).bind(gc)], gc).into());
}
(Err(_), Err(_)) => {}
}
let lnum = to_numeric_primitive(agent, lprim, gc)?;
let rnum = to_numeric_primitive(agent, rprim, gc)?;
if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lnum), BigInt::try_from(rnum)) {
Ok(BigInt::add(agent, lnum, rnum).into())
} else if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lnum), Number::try_from(rnum)) {
Ok(Number::add(agent, lnum, rnum).into())
} else {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"The left and right-hand sides do not have the same type.",
gc,
))
}
}
fn bigint_binary_operator<'a>(
agent: &mut Agent,
op_text: Instruction,
lnum: BigInt<'a>,
rnum: BigInt<'a>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, BigInt<'a>> {
match op_text {
Instruction::ApplyExponentialBinaryOperator => BigInt::exponentiate(agent, lnum, rnum, gc),
Instruction::ApplyDivisionBinaryOperator => BigInt::divide(agent, lnum, rnum, gc),
Instruction::ApplyRemainderBinaryOperator => BigInt::remainder(agent, lnum, rnum, gc),
Instruction::ApplyShiftRightZeroFillBinaryOperator => {
BigInt::unsigned_right_shift(agent, lnum, rnum, gc)
}
Instruction::ApplyShiftLeftBinaryOperator => BigInt::left_shift(agent, lnum, rnum, gc),
Instruction::ApplyShiftRightBinaryOperator => {
BigInt::signed_right_shift(agent, lnum, rnum, gc)
}
Instruction::ApplySubtractionBinaryOperator => Ok(BigInt::subtract(agent, lnum, rnum)),
Instruction::ApplyMultiplicationBinaryOperator => Ok(BigInt::multiply(agent, lnum, rnum)),
Instruction::ApplyBitwiseORBinaryOperator => Ok(BigInt::bitwise_or(agent, lnum, rnum)),
Instruction::ApplyBitwiseXORBinaryOperator => Ok(BigInt::bitwise_xor(agent, lnum, rnum)),
Instruction::ApplyBitwiseAndBinaryOperator => Ok(BigInt::bitwise_and(agent, lnum, rnum)),
_ => unreachable!(),
}
}
fn number_binary_operator<'a>(
agent: &mut Agent,
op_text: Instruction,
lnum: Number<'a>,
rnum: Number<'a>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, Number<'a>> {
Ok(match op_text {
Instruction::ApplyExponentialBinaryOperator => Number::exponentiate(agent, lnum, rnum),
Instruction::ApplyMultiplicationBinaryOperator => Number::multiply(agent, lnum, rnum, gc),
Instruction::ApplyDivisionBinaryOperator => Number::divide(agent, lnum, rnum, gc),
Instruction::ApplyRemainderBinaryOperator => Number::remainder(agent, lnum, rnum, gc),
Instruction::ApplySubtractionBinaryOperator => Number::subtract(agent, lnum, rnum),
Instruction::ApplyShiftLeftBinaryOperator => Number::left_shift(agent, lnum, rnum),
Instruction::ApplyShiftRightBinaryOperator => Number::signed_right_shift(agent, lnum, rnum),
Instruction::ApplyShiftRightZeroFillBinaryOperator => {
Number::unsigned_right_shift(agent, lnum, rnum)
}
Instruction::ApplyBitwiseORBinaryOperator => Number::bitwise_or(agent, lnum, rnum).into(),
Instruction::ApplyBitwiseXORBinaryOperator => Number::bitwise_xor(agent, lnum, rnum).into(),
Instruction::ApplyBitwiseAndBinaryOperator => Number::bitwise_and(agent, lnum, rnum).into(),
_ => unreachable!(),
})
}
#[inline]
pub(crate) fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> String<'static> {
match val {
Value::Undefined => BUILTIN_STRING_MEMORY.undefined,
Value::Boolean(_) => BUILTIN_STRING_MEMORY.boolean,
Value::String(_) |
Value::SmallString(_) => BUILTIN_STRING_MEMORY.string,
Value::Symbol(_) => BUILTIN_STRING_MEMORY.symbol,
Value::Number(_) |
Value::Integer(_) |
Value::SmallF64(_) => BUILTIN_STRING_MEMORY.number,
Value::BigInt(_) |
Value::SmallBigInt(_) => BUILTIN_STRING_MEMORY.bigint,
Value::Null |
Value::Object(_) |
Value::Array(_) |
Value::Error(_) |
Value::PrimitiveObject(_) |
Value::Arguments(_) |
Value::FinalizationRegistry(_) |
Value::Map(_) |
Value::Promise(_) |
Value::AsyncGenerator(_) |
Value::ArrayIterator(_) |
Value::MapIterator(_) |
Value::StringIterator(_) |
Value::Generator(_) |
Value::Module(_) |
Value::EmbedderObject(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "regexp")]
Value::RegExp(_) | Value::RegExpStringIterator(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "weak-refs")]
Value::WeakMap(_) |
Value::WeakRef(_) |
Value::WeakSet(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "set")]
Value::Set(_) |
Value::SetIterator(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "array-buffer")]
Value::ArrayBuffer(_) |
Value::Int8Array(_) |
Value::Uint8Array(_) |
Value::Uint8ClampedArray(_) |
Value::Int16Array(_) |
Value::Uint16Array(_) |
Value::Int32Array(_) |
Value::Uint32Array(_) |
Value::BigInt64Array(_) |
Value::BigUint64Array(_) |
Value::Float32Array(_) |
Value::Float64Array(_) |
Value::DataView(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "proposal-float16array")]
Value::Float16Array(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "shared-array-buffer")]
Value::SharedArrayBuffer(_) |
Value::SharedInt8Array(_) |
Value::SharedUint8Array(_) |
Value::SharedUint8ClampedArray(_) |
Value::SharedInt16Array(_) |
Value::SharedUint16Array(_) |
Value::SharedInt32Array(_) |
Value::SharedUint32Array(_) |
Value::SharedBigInt64Array(_) |
Value::SharedBigUint64Array(_) |
Value::SharedFloat32Array(_) |
Value::SharedFloat64Array(_) |
Value::SharedDataView(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))]
Value::SharedFloat16Array(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "date")]
Value::Date(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "temporal")]
Value::Instant(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "temporal")]
Value::Duration(_) => BUILTIN_STRING_MEMORY.object,
#[cfg(feature = "temporal")]
Value::PlainTime(_) => BUILTIN_STRING_MEMORY.object,
Value::BoundFunction(_) | Value::BuiltinFunction(_) | Value::ECMAScriptFunction(_) |
Value::BuiltinConstructorFunction(_) |
Value::BuiltinPromiseResolvingFunction(_) |
Value::BuiltinPromiseFinallyFunction(_) |
Value::BuiltinProxyRevokerFunction => BUILTIN_STRING_MEMORY.function,
Value::Proxy(proxy) => {
if proxy.is_callable(agent, gc) {
BUILTIN_STRING_MEMORY.function
} else {
BUILTIN_STRING_MEMORY.object
}
},
}
}
pub(crate) fn instanceof_operator<'a, 'b>(
agent: &mut Agent,
value: impl Into<Value<'b>>,
target: impl Into<Value<'b>>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let mut value = value.into().bind(gc.nogc());
let target = target.into().bind(gc.nogc());
let Ok(mut target) = Object::try_from(target) else {
let error_message = format!(
"Invalid instanceof target {}.",
target
.unbind()
.string_repr(agent, gc.reborrow())
.to_string_lossy_(agent)
);
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc.into_nogc()));
};
let inst_of_handler = if let Some(handler) = try_result_into_option_js(try_get_object_method(
agent,
target,
WellKnownSymbols::HasInstance.into(),
gc.nogc(),
)) {
handler.unbind()?.bind(gc.nogc())
} else {
let scoped_value = value.scope(agent, gc.nogc());
let scoped_target = target.scope(agent, gc.nogc());
let inst_of_handler = get_method(
agent,
target.unbind().into(),
WellKnownSymbols::HasInstance.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
value = unsafe { scoped_value.take(agent) }.bind(gc.nogc());
target = unsafe { scoped_target.take(agent) }.bind(gc.nogc());
inst_of_handler
};
if let Some(inst_of_handler) = inst_of_handler {
let result = call_function(
agent,
inst_of_handler.unbind(),
target.unbind().into(),
Some(ArgumentsList::from_mut_slice(&mut [value.unbind()])),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
Ok(to_boolean(agent, result))
} else {
let Some(target) = is_callable(target, gc.nogc()) else {
let error_message = format!(
"Invalid instanceof target {} is not a function.",
Value::from(target)
.unbind()
.string_repr(agent, gc.reborrow())
.to_string_lossy_(agent)
);
return Err(agent.throw_exception(
ExceptionType::TypeError,
error_message,
gc.into_nogc(),
));
};
Ok(ordinary_has_instance(
agent,
target.unbind(),
value.unbind(),
gc,
)?)
}
}
#[inline(always)]
fn with_vm_gc<'a, 'b, R: 'a>(
agent: &mut Agent,
vm: &mut Vm,
work: impl FnOnce(&mut Agent, GcScope<'a, 'b>) -> R,
gc: GcScope<'a, 'b>,
) -> R {
let vm = NonNull::from(vm);
agent.vm_stack.push(vm);
let result = work(agent, gc);
let return_vm = agent.vm_stack.pop().unwrap();
assert_eq!(vm, return_vm, "VM Stack was misused");
result
}
impl HeapMarkAndSweep for ExceptionHandler<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
match self {
ExceptionHandler::CatchBlock {
ip: _,
lexical_environment,
} => {
lexical_environment.mark_values(queues);
}
ExceptionHandler::IgnoreErrorAndNextInstruction => {}
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
match self {
ExceptionHandler::CatchBlock {
ip: _,
lexical_environment,
} => {
lexical_environment.sweep_values(compactions);
}
ExceptionHandler::IgnoreErrorAndNextInstruction => {}
}
}
}
impl HeapMarkAndSweep for Vm {
fn mark_values(&self, queues: &mut WorkQueues) {
let Vm {
ip: _,
stack,
reference_stack,
iterator_stack,
exception_handler_stack: exception_jump_target_stack,
result,
reference,
} = self;
stack.as_slice().mark_values(queues);
reference_stack.as_slice().mark_values(queues);
iterator_stack.as_slice().mark_values(queues);
exception_jump_target_stack.as_slice().mark_values(queues);
result.mark_values(queues);
reference.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Vm {
ip: _,
stack,
reference_stack,
iterator_stack,
exception_handler_stack: exception_jump_target_stack,
result,
reference,
} = self;
stack.as_mut_slice().sweep_values(compactions);
reference_stack.as_mut_slice().sweep_values(compactions);
iterator_stack.as_mut_slice().sweep_values(compactions);
exception_jump_target_stack
.as_mut_slice()
.sweep_values(compactions);
result.sweep_values(compactions);
reference.sweep_values(compactions);
}
}
impl HeapMarkAndSweep for SuspendedVm {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
ip: _,
stack,
reference_stack,
iterator_stack,
exception_jump_target_stack,
} = self;
stack.mark_values(queues);
reference_stack.mark_values(queues);
iterator_stack.mark_values(queues);
exception_jump_target_stack.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
ip: _,
stack,
reference_stack,
iterator_stack,
exception_jump_target_stack,
} = self;
stack.sweep_values(compactions);
reference_stack.sweep_values(compactions);
iterator_stack.sweep_values(compactions);
exception_jump_target_stack.sweep_values(compactions);
}
}
fn set_class_name<'a>(
agent: &mut Agent,
vm: &mut Vm,
name: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, String<'a>> {
if let Ok(name) = String::try_from(name) {
Ok(name.bind(gc.into_nogc()))
} else if let Value::Symbol(name) = name {
Ok(name
.unbind()
.get_symbol_function_name(agent, gc.into_nogc()))
} else {
let prop_key = {
let name = name.unbind();
with_vm_gc(
agent,
vm,
|agent, gc| to_property_key(agent, name, gc),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
let name = prop_key.convert_to_value(agent, gc.nogc());
set_class_name(agent, vm, name.unbind().into(), gc)
}
}
fn verify_is_object<'a>(
agent: &mut Agent,
value: Value,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
if !value.is_object() {
let message =
String::from_static_str(agent, "iterator.return() returned a non-object value", gc);
Err(agent.throw_exception_with_message(ExceptionType::TypeError, message.unbind(), gc))
} else {
Ok(())
}
}
fn throw_error_in_target_not_object<'a>(
agent: &mut Agent,
value: Value,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
let error_message = format!(
"right-hand side of 'in' should be an object, got {}.",
typeof_operator(agent, value, gc).to_string_lossy_(agent)
);
agent.throw_exception(ExceptionType::TypeError, error_message, gc)
}