//! The interpreter: runs bytecode on an explicit stack of frames. //! //! A call to a function written in JavaScript pushes a frame and goes on in the same loop, so //! JavaScript recursion uses the heap, not the Rust stack. The loop runs again inside a Rust call //! only where the language needs a function's result in the middle of an operation (a getter or //! setter, `valueOf` and `toString` during a conversion, indirect `eval`); those nested runs are //! counted against [`crate::Limits::reentry`]. //! //! Before each instruction the interpreter takes one unit of fuel, checks the interrupt flag and, //! when the heap has grown past its threshold, collects garbage. Running out of fuel, the heap //! limit, the frame limit, the re-entry limit and the interrupt flag each end the run with an //! [`Abrupt::Limit`] that unwinds every frame without running catch or finally blocks. //! //! Collection runs only between instructions. An operation that may run JavaScript in the middle //! keeps its operands on the operand stack until it is done, so everything it holds is reachable //! from the roots when a nested run collects. use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use crate::ast::BinaryOp; use crate::bytecode::{Code, CodeKind, Op, Source}; use crate::compiler::{CompileFail, compile_program}; use crate::heap::{ ArgMap, Binding, CONFIGURABLE, Class, ENUMERABLE, Env, EnvId, ForIn, Func, HIDDEN, Heap, Key, NativeKind, OPEN, ObjId, Object, PROP_BYTES, Prop, PropMap, ROPE_BYTES, Roots, WRITABLE, key, string_bytes, }; use crate::value::{Str, Value, array_index, index_key, number_to_str, string_to_number, to_int32, to_uint32}; use crate::{LimitKind, Limits, Options}; /// How an operation ended other than normally. #[derive(Debug)] pub(crate) enum Abrupt { /// A JavaScript exception: `try` and `catch` can handle it. Throw(Value), /// A limit was reached; nothing handles it. Limit(LimitKind), /// The interpreter found itself in a state it never makes (a bug); nothing handles it. Internal(String), } pub(crate) type JsResult = Result; fn internal(message: &str) -> JsResult { Err(Abrupt::Internal(message.into())) } /// The kinds of error object the engine throws. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum ErrorType { Error, Eval, Range, Reference, Syntax, Type, Uri, } /// The objects the interpreter itself refers to. #[derive(Clone, Copy, Debug)] pub(crate) struct Intrinsics { pub(crate) object_proto: ObjId, pub(crate) function_proto: ObjId, pub(crate) array_proto: ObjId, pub(crate) string_proto: ObjId, pub(crate) number_proto: ObjId, pub(crate) boolean_proto: ObjId, pub(crate) regexp_proto: ObjId, pub(crate) error_protos: [ObjId; 7], pub(crate) eval: ObjId, pub(crate) throw_type_error: ObjId, } impl Intrinsics { fn placeholder() -> Self { let o = ObjId(0); Self { object_proto: o, function_proto: o, array_proto: o, string_proto: o, number_proto: o, boolean_proto: o, regexp_proto: o, error_protos: [o; 7], eval: o, throw_type_error: o, } } pub(crate) fn error_proto(&self, t: ErrorType) -> ObjId { let i = match t { ErrorType::Error => 0, ErrorType::Eval => 1, ErrorType::Range => 2, ErrorType::Reference => 3, ErrorType::Syntax => 4, ErrorType::Type => 5, ErrorType::Uri => 6, }; self.error_protos.get(i).copied().unwrap_or(self.object_proto) } fn all(&self) -> Vec { let mut v = vec![ self.object_proto, self.function_proto, self.array_proto, self.string_proto, self.number_proto, self.boolean_proto, self.regexp_proto, self.eval, self.throw_type_error, ]; v.extend(self.error_protos); v } } pub(crate) struct Frame { pub(crate) code: Rc, pub(crate) pc: usize, /// Where this frame's operands start on the operand stack. base: usize, env: EnvId, var_env: EnvId, /// `with` and catch environments entered and not yet left. env_depth: u32, this: Value, callee: Option, /// The object `new` made, returned unless the function returns an object. construct: Option, completion: Value, /// Returning from this frame returns from the Rust call that pushed it. boundary: bool, } /// Where the exception being thrown was raised: the code and the instruction. type ThrowSite = (Rc, usize); pub(crate) struct Interp { pub(crate) heap: Heap, pub(crate) stack: Vec, pub(crate) frames: Vec, /// Values native code holds while it may run JavaScript. pub(crate) temp_roots: Vec, pub(crate) global: ObjId, pub(crate) global_env: EnvId, pub(crate) intrinsics: Intrinsics, pub(crate) limits: Limits, pub(crate) options: Options, pub(crate) fuel: u64, pub(crate) interrupt: Arc, pub(crate) reentry: usize, gc_threshold: usize, /// UTF-16 code units of source parsed in this run, for [`Options::max_source_units`]. pub(crate) source_units: usize, pub(crate) throw_site: Option, /// The last exception a run did not catch. pub(crate) last_thrown: Value, } /// The least heap size a collection waits for. const MIN_GC_BYTES: usize = 1 << 20; /// The most arguments `Function.prototype.apply` passes. pub(crate) const MAX_APPLY_ARGS: usize = 1 << 16; /// The estimated bytes of a name's entries in the list and the set `for-in` builds. const KEY_ENTRY_BYTES: usize = 32; /// The estimated bytes of the index names `0` to `len - 1` while `for-in` lists them. fn index_keys_bytes(len: usize) -> usize { let mut total = 0usize; let (mut lo, mut hi, mut digits) = (0usize, 10usize, 1usize); while lo < len { let count = hi.min(len) - lo; total = total.saturating_add(count.saturating_mul(string_bytes(digits) + KEY_ENTRY_BYTES)); lo = hi; hi = hi.saturating_mul(10); digits += 1; } total } /// A call to a function written in JavaScript, ready to enter. struct Entry { callee: ObjId, code: Rc, env: EnvId, this: Value, args: Vec, } /// What a call resolved to. enum Prepared { Closure(Entry), Done(Value), } /// A variable found in the environment chain. enum Resolved { Decl(EnvId), Obj { object: ObjId, with_this: bool }, Unresolved, } /// A reference base as an operand: a declarative environment's index as a number, a binding /// object as itself, and an unresolvable reference as `undefined`. The environment stays /// reachable through the frame's environment chain while the operand is on the stack. fn resolved_to_value(r: &Resolved) -> Value { match r { Resolved::Decl(e) => Value::Number(f64::from(e.0)), Resolved::Obj { object, .. } => Value::Object(*object), Resolved::Unresolved => Value::Undefined, } } fn value_to_resolved(v: &Value) -> JsResult { match v { #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] Value::Number(n) if *n >= 0.0 && *n <= f64::from(u32::MAX) && n.fract() == 0.0 => Ok(Resolved::Decl(EnvId(*n as u32))), Value::Object(object) => Ok(Resolved::Obj { object: *object, with_this: false }), Value::Undefined => Ok(Resolved::Unresolved), _ => internal("not a reference base"), } } #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum Hint { Default, Number, String, } fn is(k: &[u16], s: &str) -> bool { k.len() == s.len() && k.iter().zip(s.bytes()).all(|(&a, b)| a == u16::from(b)) } impl Interp { pub(crate) fn new(options: Options, limits: Limits) -> Self { let mut heap = Heap::default(); let global = heap.alloc_object(Object::new(Class::Ordinary, None)); let global_env = heap.alloc_env(Env::Obj { object: global, with_this: false, outer: None }); let mut interp = Self { heap, stack: Vec::new(), frames: Vec::new(), temp_roots: Vec::new(), global, global_env, intrinsics: Intrinsics::placeholder(), fuel: limits.fuel, limits, options, interrupt: Arc::new(AtomicBool::new(false)), reentry: 0, gc_threshold: MIN_GC_BYTES, source_units: 0, throw_site: None, last_thrown: Value::Undefined, }; crate::builtins::install(&mut interp); interp.gc_threshold = interp.next_threshold(); interp } fn next_threshold(&self) -> usize { (self.heap.bytes.saturating_mul(2)).max(MIN_GC_BYTES).min(self.limits.heap_bytes) } // ---- errors ---- pub(crate) fn error_object(&mut self, t: ErrorType, message: &str) -> ObjId { let proto = self.intrinsics.error_proto(t); let mut o = Object::new(Class::Error, Some(proto)); if !message.is_empty() { o.props.insert(key("message"), Prop::Data { value: Value::str(message), flags: HIDDEN }); } self.heap.alloc_object(o) } pub(crate) fn throw(&mut self, t: ErrorType, message: &str) -> Abrupt { Abrupt::Throw(Value::Object(self.error_object(t, message))) } pub(crate) fn type_error(&mut self, message: &str) -> Abrupt { self.throw(ErrorType::Type, message) } // ---- the operand stack ---- fn push(&mut self, v: Value) -> JsResult<()> { self.stack.push(v); Ok(()) } fn frame(&self) -> JsResult<&Frame> { self.frames.last().ok_or_else(|| Abrupt::Internal("no frame".into())) } fn frame_mut(&mut self) -> JsResult<&mut Frame> { self.frames.last_mut().ok_or_else(|| Abrupt::Internal("no frame".into())) } fn base(&self) -> usize { self.frames.last().map_or(0, |f| f.base) } fn pop(&mut self) -> JsResult { if self.stack.len() <= self.base() { return internal("operand stack underflow"); } self.stack.pop().ok_or_else(|| Abrupt::Internal("operand stack underflow".into())) } /// The value `n` places below the top (0 is the top). fn peek(&self, n: usize) -> JsResult { let len = self.stack.len(); match len.checked_sub(n + 1).filter(|&i| i >= self.base()) { Some(i) => self.stack.get(i).cloned().ok_or_else(|| Abrupt::Internal("operand stack underflow".into())), None => internal("operand stack underflow"), } } fn drop_n(&mut self, n: usize) -> JsResult<()> { let len = self.stack.len(); match len.checked_sub(n).filter(|&i| i >= self.base()) { Some(i) => { self.stack.truncate(i); Ok(()) } None => internal("operand stack underflow"), } } /// Replaces the top `n` values with `v`. fn replace(&mut self, n: usize, v: Value) -> JsResult<()> { self.drop_n(n)?; self.push(v) } // ---- running ---- /// Takes a unit of fuel and checks the interrupt flag. pub(crate) fn use_fuel(&mut self) -> JsResult<()> { if self.fuel == 0 { return Err(Abrupt::Limit(LimitKind::Fuel)); } self.fuel -= 1; if self.interrupt.load(Ordering::Relaxed) { return Err(Abrupt::Limit(LimitKind::Interrupted)); } Ok(()) } fn tick(&mut self) -> JsResult<()> { self.use_fuel()?; if self.heap.bytes > self.gc_threshold { self.collect_garbage(); if self.heap.bytes > self.limits.heap_bytes { return Err(Abrupt::Limit(LimitKind::Heap)); } self.gc_threshold = self.next_threshold(); } Ok(()) } /// Ends the run with the heap limit when `bytes` more would not fit in it after a /// collection. Everything the caller still needs must be reachable from the roots. fn reserve_heap(&mut self, bytes: usize) -> JsResult<()> { if self.heap.bytes.saturating_add(bytes) <= self.limits.heap_bytes { return Ok(()); } self.collect_garbage(); self.gc_threshold = self.next_threshold(); if self.heap.bytes.saturating_add(bytes) > self.limits.heap_bytes { return Err(Abrupt::Limit(LimitKind::Heap)); } Ok(()) } pub(crate) fn collect_garbage(&mut self) { let mut roots = Roots::default(); roots.values.extend(self.stack.iter()); roots.values.extend(self.temp_roots.iter()); roots.values.push(&self.last_thrown); for f in &self.frames { roots.values.push(&f.this); roots.values.push(&f.completion); roots.objects.extend(f.callee.iter().chain(f.construct.iter())); roots.envs.push(f.env); roots.envs.push(f.var_env); roots.codes.push(&f.code); } roots.objects.push(self.global); roots.objects.extend(self.intrinsics.all()); roots.envs.push(self.global_env); self.heap.collect(&roots); } /// Runs frames until the boundary frame on top returns, and returns its value. pub(crate) fn execute(&mut self) -> JsResult { loop { match self.step() { Ok(Some(v)) => return Ok(v), Ok(None) => {} Err(Abrupt::Throw(v)) => self.unwind(v)?, Err(other) => { self.note_site(); self.abort_to_boundary(); return Err(other); } } } } fn step(&mut self) -> JsResult> { self.tick()?; let frame = self.frame_mut()?; let Some(&op) = frame.code.ops.get(frame.pc) else { return internal("ran past the end of the code") }; frame.pc += 1; match op { Op::Return => { let v = self.pop()?; self.return_value(v) } Op::ReturnCompletion => { let v = self.frame()?.completion.clone(); self.return_value(v) } _ => self.exec(op).map(|()| None), } } fn note_site(&mut self) { if self.throw_site.is_none() && let Some(f) = self.frames.last() { self.throw_site = Some((f.code.clone(), f.pc.saturating_sub(1))); } } /// Finds the handler for an exception: in the top frame, or in the frames below it up to the /// boundary frame. Returns the exception when no handler takes it before the boundary frame. fn unwind(&mut self, v: Value) -> JsResult<()> { self.note_site(); loop { let Some(frame) = self.frames.last_mut() else { return Err(Abrupt::Throw(v)) }; if let Some(h) = frame.code.handler(frame.pc.saturating_sub(1)) { let target_len = frame.base + h.depth as usize; while frame.env_depth > h.env_depth { let outer = self.heap.env(frame.env).and_then(Env::outer); let Some(outer) = outer else { return internal("environment chain ended early") }; frame.env = outer; frame.env_depth -= 1; } frame.pc = h.target as usize; if self.stack.len() < target_len { return internal("handler stack deeper than the stack"); } self.stack.truncate(target_len); self.stack.push(v); self.throw_site = None; return Ok(()); } let Some(frame) = self.frames.pop() else { return Err(Abrupt::Throw(v)) }; self.stack.truncate(frame.base); if frame.boundary { return Err(Abrupt::Throw(v)); } } } fn abort_to_boundary(&mut self) { while let Some(frame) = self.frames.pop() { self.stack.truncate(frame.base); if frame.boundary { break; } } } fn return_value(&mut self, v: Value) -> JsResult> { let Some(frame) = self.frames.pop() else { return internal("return without a frame") }; self.stack.truncate(frame.base); let v = match frame.construct { Some(o) if !matches!(v, Value::Object(_)) => Value::Object(o), _ => v, }; if frame.boundary { return Ok(Some(v)); } self.push(v)?; Ok(None) } fn jump_to(&mut self, target: u32) -> JsResult<()> { self.frame_mut()?.pc = target as usize; Ok(()) } fn name(&self, i: u32) -> JsResult { self.frame()?.code.names.get(i as usize).cloned().ok_or_else(|| Abrupt::Internal("name out of range".into())) } fn strict(&self) -> bool { self.frames.last().is_some_and(|f| f.code.strict) } fn exec(&mut self, op: Op) -> JsResult<()> { match op { Op::Undefined => self.push(Value::Undefined), Op::Null => self.push(Value::Null), Op::True => self.push(Value::Bool(true)), Op::False => self.push(Value::Bool(false)), Op::Number(i) => { let n = self.frame()?.code.numbers.get(i as usize).copied(); self.push(Value::Number(n.unwrap_or(f64::NAN))) } Op::String(i) => { let s = self.frame()?.code.strings.get(i as usize).cloned(); self.push(s.map_or(Value::Undefined, Value::String)) } Op::This => { let this = self.frame()?.this.clone(); self.push(this) } Op::Pop => self.pop().map(drop), Op::PopUnder(n) => self.op_pop_under(n), Op::Dup => self.op_dup(1), Op::Dup2 => self.op_dup(2), Op::Swap => self.op_swap(), Op::Rot3 => self.op_rot3(), Op::GetVar(n) => self.op_get_var(n, false), Op::GetVarThis(n) => self.op_get_var(n, true), Op::SetVar(n) => self.op_set_var(n), Op::RefVar(n) => self.op_ref_var(n), Op::GetRef(n) => self.op_get_ref(n), Op::PutRef(n) => self.op_put_ref(n), Op::TypeofVar(n) => self.op_typeof_var(n), Op::DeleteVar(n) => self.op_delete_var(n), Op::IncVar { name, prefix, dec } => self.op_inc_var(name, prefix, dec), Op::GetMember(n) => self.op_get_member(n, false), Op::GetMemberThis(n) => self.op_get_member(n, true), Op::SetMember(n) => self.op_set_member(n), Op::DeleteMember(n) => self.op_delete_member(n), Op::IncMember { name, prefix, dec } => self.op_inc_member(name, prefix, dec), Op::ToKey => self.op_to_key(), Op::CheckObject => self.op_check_object(), Op::GetIndex => self.op_get_index(false), Op::GetIndexThis => self.op_get_index(true), Op::SetIndex => self.op_set_index(), Op::DeleteIndex => self.op_delete_index(), Op::IncIndex { prefix, dec } => self.op_inc_index(prefix, dec), Op::Call(n) => self.op_call(n, false), Op::CallEval(n) => self.op_call(n, true), Op::New(n) => self.op_new(n), Op::Binary(b) => self.op_binary(b), Op::Neg | Op::Plus | Op::BitNot | Op::Not | Op::Typeof | Op::ToNumber => self.op_unary(op), Op::Jump(t) => self.jump_to(t), Op::JumpIfFalse(t) | Op::JumpIfTrue(t) => self.op_branch(t, matches!(op, Op::JumpIfTrue(_)), false), Op::JumpIfFalseKeep(t) | Op::JumpIfTrueKeep(t) => self.op_branch(t, matches!(op, Op::JumpIfTrueKeep(_)), true), Op::Throw => Err(Abrupt::Throw(self.pop()?)), Op::ThrowReferenceError(i) => self.op_throw_reference_error(i), Op::EndFinally(t) => self.op_end_finally(t), Op::NewObject => self.op_new_object(), Op::NewArray => self.op_new_array(), Op::ArrayPush => self.op_array_push(true), Op::ArrayHole => self.op_array_push(false), Op::InitProp(n) => self.op_init_prop(n), Op::InitGetter(n) => self.op_init_accessor(n, true), Op::InitSetter(n) => self.op_init_accessor(n, false), Op::Closure(i) => self.op_closure(i), Op::RegExp(i) => self.op_regexp(i), Op::PushWith => self.op_push_with(), Op::PushCatch(n) => self.op_push_catch(n), Op::PopEnv => self.op_pop_env(), Op::ForInStart => self.op_for_in_start(), Op::ForInNext(t) => self.op_for_in_next(t), Op::SetCompletion => { let v = self.pop()?; self.frame_mut()?.completion = v; Ok(()) } Op::Return | Op::ReturnCompletion => internal("return reached the dispatcher"), } } // ---- stack operations ---- fn op_pop_under(&mut self, n: u32) -> JsResult<()> { let top = self.pop()?; self.drop_n(n as usize)?; self.push(top) } fn op_dup(&mut self, n: usize) -> JsResult<()> { for _ in 0..n { let v = self.peek(n - 1)?; self.push(v)?; } Ok(()) } fn op_swap(&mut self) -> JsResult<()> { let b = self.pop()?; let a = self.pop()?; self.push(b)?; self.push(a) } fn op_rot3(&mut self) -> JsResult<()> { let c = self.pop()?; let b = self.pop()?; let a = self.pop()?; self.push(b)?; self.push(c)?; self.push(a) } fn op_branch(&mut self, target: u32, when: bool, keep: bool) -> JsResult<()> { let v = self.peek(0)?; let jump = v.truthy() == when; if !(keep && jump) { self.pop()?; } if jump { self.jump_to(target) } else { Ok(()) } } fn op_throw_reference_error(&mut self, i: u32) -> JsResult<()> { let message = self.frame()?.code.strings.get(i as usize).map(Str::to_rust_string).unwrap_or_default(); Err(self.throw(ErrorType::Reference, &message)) } fn op_end_finally(&mut self, table: u32) -> JsResult<()> { let Value::Number(kind) = self.pop()? else { return internal("finally kind is not a number") }; if kind == 0.0 { return self.pop().map(drop); } if kind == 1.0 { return Err(Abrupt::Throw(self.pop()?)); } let k = (kind - 2.0) as usize; let target = self.frame()?.code.finally_tables.get(table as usize).and_then(|t| t.get(k)).copied(); match target { Some(t) => self.jump_to(t), None => internal("finally exit out of range"), } } // ---- variables ---- fn resolve(&self, name: &[u16]) -> JsResult { let mut cur = Some(self.frame()?.env); while let Some(e) = cur { match self.heap.env(e) { Some(Env::Decl { bindings, outer }) => { if bindings.contains(name) { return Ok(Resolved::Decl(e)); } cur = *outer; } Some(Env::Obj { object, with_this, outer }) => { if self.has_property(*object, name)? { return Ok(Resolved::Obj { object: *object, with_this: *with_this }); } cur = *outer; } None => return internal("environment out of range"), } } Ok(Resolved::Unresolved) } fn binding_value(&self, env: EnvId, name: &[u16]) -> JsResult { match self.heap.env(env) { Some(Env::Decl { bindings, .. }) => Ok(bindings.get(name).map_or(Value::Undefined, |b| b.value.clone())), _ => internal("not a declarative environment"), } } fn not_defined(&mut self, name: &[u16]) -> Abrupt { let message = format!("{} is not defined", String::from_utf16_lossy(name)); self.throw(ErrorType::Reference, &message) } fn get_var(&mut self, name: &Key) -> JsResult<(Value, Value)> { let r = self.resolve(name)?; self.get_resolved(r, name) } /// GetValue of a reference whose base was resolved earlier (section 8.7.1). fn get_resolved(&mut self, r: Resolved, name: &Key) -> JsResult<(Value, Value)> { match r { Resolved::Decl(e) => Ok((self.binding_value(e, name)?, Value::Undefined)), Resolved::Obj { object, with_this } => { let v = self.get_from(object, name, &Value::Object(object))?; Ok((v, if with_this { Value::Object(object) } else { Value::Undefined })) } Resolved::Unresolved => Err(self.not_defined(name)), } } fn set_var(&mut self, name: &Key, v: Value) -> JsResult<()> { let r = self.resolve(name)?; self.set_resolved(r, name, v) } /// PutValue of a reference whose base was resolved earlier (section 8.7.2). fn set_resolved(&mut self, r: Resolved, name: &Key, v: Value) -> JsResult<()> { let strict = self.strict(); match r { Resolved::Decl(e) => { let Some(Env::Decl { bindings, .. }) = self.heap.env_mut(e) else { return internal("not a declarative environment") }; match bindings.get_mut(name) { Some(b) if b.mutable => { b.value = v; Ok(()) } _ if strict => Err(self.type_error("assignment to a constant binding")), _ => Ok(()), } } Resolved::Obj { object, .. } => self.put(object, name.clone(), v, strict), Resolved::Unresolved if strict => Err(self.not_defined(name)), Resolved::Unresolved => { let global = self.global; self.put(global, name.clone(), v, false) } } } fn op_get_var(&mut self, n: u32, with_this: bool) -> JsResult<()> { let name = self.name(n)?; let (v, this) = self.get_var(&name)?; self.push(v)?; if with_this { self.push(this) } else { Ok(()) } } fn op_set_var(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = self.peek(0)?; self.set_var(&name, v) } fn op_typeof_var(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = match self.resolve(&name)? { Resolved::Unresolved => Value::Undefined, _ => self.get_var(&name)?.0, }; let t = self.type_of(&v); self.push(Value::str(t)) } fn op_delete_var(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let result = match self.resolve(&name)? { Resolved::Unresolved => true, Resolved::Decl(e) => match self.heap.env_mut(e) { Some(Env::Decl { bindings, .. }) => { if bindings.get(&name).is_some_and(|b| b.deletable) { bindings.remove(&name); true } else { false } } _ => return internal("not a declarative environment"), }, Resolved::Obj { object, .. } => self.delete(object, &name, false)?, }; self.push(Value::Bool(result)) } fn op_inc_var(&mut self, n: u32, prefix: bool, dec: bool) -> JsResult<()> { let name = self.name(n)?; let r = self.resolve(&name)?; let base = resolved_to_value(&r); let (v, _) = self.get_resolved(r, &name)?; // The base stays on the stack, where the collector sees it, while ToNumber runs script. self.push(base)?; let old = self.to_number(&v)?; let base = self.pop()?; let new = if dec { old - 1.0 } else { old + 1.0 }; self.set_resolved(value_to_resolved(&base)?, &name, Value::Number(new))?; self.push(Value::Number(if prefix { new } else { old })) } /// Resolves a variable before the right-hand side of an assignment runs, and pushes the /// reference's base. fn op_ref_var(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let r = self.resolve(&name)?; self.push(resolved_to_value(&r)) } /// Replaces the reference base on top of the stack with the variable's value. fn op_get_ref(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let base = self.peek(0)?; let (v, _) = self.get_resolved(value_to_resolved(&base)?, &name)?; self.pop()?; self.push(v) } /// Stores the value on top of the stack through the reference base under it, and leaves the /// value. fn op_put_ref(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = self.peek(0)?; let base = self.peek(1)?; self.set_resolved(value_to_resolved(&base)?, &name, v)?; let v = self.pop()?; self.pop()?; self.push(v) } // ---- properties ---- fn object(&self, id: ObjId) -> JsResult<&Object> { self.heap.object(id).ok_or_else(|| Abrupt::Internal("object out of range".into())) } fn object_mut(&mut self, id: ObjId) -> JsResult<&mut Object> { self.heap.object_mut(id).ok_or_else(|| Abrupt::Internal("object out of range".into())) } /// [[GetOwnProperty]] (section 8.12.1, with the special cases of arrays, strings and /// arguments objects). pub(crate) fn get_own_property(&self, id: ObjId, k: &[u16]) -> JsResult> { let o = self.object(id)?; match &o.class { Class::Array { length, length_writable } if is(k, "length") => { return Ok(Some(Prop::Data { value: Value::Number(f64::from(*length)), flags: if *length_writable { WRITABLE } else { 0 } })); } Class::String(s) => { if is(k, "length") { return Ok(Some(Prop::Data { value: Value::Number(s.len() as f64), flags: 0 })); } if let Some(i) = array_index(k) && (i as usize) < s.len() { let unit = s.flat().get(i as usize).copied().unwrap_or(0); return Ok(Some(Prop::Data { value: Value::String(Str::from_units(vec![unit])), flags: ENUMERABLE })); } } Class::Arguments(map) => { if let Some(name) = array_index(k).and_then(|i| map.names.get(i as usize)).and_then(Option::as_ref) && let Some(Prop::Data { flags, .. }) = o.props.get(k) { let flags = *flags; return Ok(Some(Prop::Data { value: self.binding_value(map.env, name)?, flags })); } } _ => {} } Ok(o.props.get(k).cloned()) } /// [[GetProperty]]: the property found on the object or its prototype chain. pub(crate) fn find_property(&self, id: ObjId, k: &[u16]) -> JsResult> { let mut cur = Some(id); let mut steps = 0usize; while let Some(o) = cur { if let Some(p) = self.get_own_property(o, k)? { return Ok(Some(p)); } cur = self.object(o)?.proto; steps += 1; if steps > 1_000_000 { return internal("prototype chain too long"); } } Ok(None) } pub(crate) fn has_property(&self, id: ObjId, k: &[u16]) -> JsResult { Ok(self.find_property(id, k)?.is_some()) } /// [[Get]] starting at `start`, with `receiver` as `this` for a getter. pub(crate) fn get_from(&mut self, start: ObjId, k: &[u16], receiver: &Value) -> JsResult { match self.find_property(start, k)? { None | Some(Prop::Accessor { get: None, .. }) => Ok(Value::Undefined), Some(Prop::Data { value, .. }) => Ok(value), Some(Prop::Accessor { get: Some(g), .. }) => self.call(&Value::Object(g), receiver, &[]), } } /// The prototype a primitive's properties are looked up on. fn primitive_proto(&mut self, v: &Value, k: &[u16]) -> JsResult { match v { Value::Object(o) => Ok(*o), Value::String(_) => Ok(self.intrinsics.string_proto), Value::Number(_) => Ok(self.intrinsics.number_proto), Value::Bool(_) => Ok(self.intrinsics.boolean_proto), Value::Undefined | Value::Null => { let message = format!( "cannot read property '{}' of {}", String::from_utf16_lossy(k), if matches!(v, Value::Null) { "null" } else { "undefined" } ); Err(self.type_error(&message)) } } } /// `base[k]` for any value (GetValue, section 8.7.1). pub(crate) fn get_value(&mut self, base: &Value, k: &[u16]) -> JsResult { if let Value::String(s) = base { if is(k, "length") { return Ok(Value::Number(s.len() as f64)); } if let Some(i) = array_index(k) && (i as usize) < s.len() { let unit = s.flat().get(i as usize).copied().unwrap_or(0); return Ok(Value::String(Str::from_units(vec![unit]))); } } let start = self.primitive_proto(base, k)?; self.get_from(start, k, base) } /// Adds or replaces an own property directly, keeping the heap estimate. pub(crate) fn define(&mut self, id: ObjId, k: Key, prop: Prop) -> JsResult<()> { let o = self.object_mut(id)?; let new = !o.props.contains(&k); if let Class::Array { length, .. } = &mut o.class && let Some(i) = array_index(&k) && i >= *length { *length = i + 1; } o.props.insert(k, prop); if new { self.heap.bytes += PROP_BYTES; } Ok(()) } fn reject(&mut self, throw: bool, k: &[u16], why: &str) -> JsResult<()> { if throw { let message = format!("cannot assign to property '{}': {why}", String::from_utf16_lossy(k)); return Err(self.type_error(&message)); } Ok(()) } /// [[Put]] (section 8.12.5). pub(crate) fn put(&mut self, id: ObjId, k: Key, v: Value, throw: bool) -> JsResult<()> { match self.get_own_property(id, &k)? { Some(Prop::Data { flags, .. }) => { if flags & WRITABLE == 0 { return self.reject(throw, &k, "it is read-only"); } return self.write_own(id, k, v, throw); } Some(Prop::Accessor { set: Some(s), .. }) => return self.call(&Value::Object(s), &Value::Object(id), &[v]).map(drop), Some(Prop::Accessor { set: None, .. }) => return self.reject(throw, &k, "it has only a getter"), None => {} } let proto = self.object(id)?.proto; match proto.map(|p| self.find_property(p, &k)).transpose()?.flatten() { Some(Prop::Accessor { set: Some(s), .. }) => return self.call(&Value::Object(s), &Value::Object(id), &[v]).map(drop), Some(Prop::Accessor { set: None, .. }) => return self.reject(throw, &k, "it has only a getter"), Some(Prop::Data { flags, .. }) if flags & WRITABLE == 0 => return self.reject(throw, &k, "it is read-only"), _ => {} } let o = self.object(id)?; if !o.extensible { return self.reject(throw, &k, "the object is not extensible"); } if let Class::Array { length, length_writable: false } = o.class && array_index(&k).is_some_and(|i| i >= length) { return self.reject(throw, &k, "the array's length is read-only"); } self.define(id, k, Prop::Data { value: v, flags: OPEN }) } /// Sets the value of an existing writable own data property. fn write_own(&mut self, id: ObjId, k: Key, v: Value, throw: bool) -> JsResult<()> { let o = self.object(id)?; match &o.class { Class::Array { .. } if is(&k, "length") => return self.set_array_length(id, &v, throw), Class::Arguments(map) => { if let Some(name) = array_index(&k).and_then(|i| map.names.get(i as usize)).and_then(Option::as_ref) { let (env, name) = (map.env, name.clone()); if let Some(Env::Decl { bindings, .. }) = self.heap.env_mut(env) && let Some(b) = bindings.get_mut(&name) { b.value = v.clone(); } } } _ => {} } match self.object_mut(id)?.props.get_mut(&k) { Some(Prop::Data { value, .. }) => { *value = v; Ok(()) } _ => internal("own data property vanished"), } } /// Assigning to an array's `length` (section 15.4.5.1). fn set_array_length(&mut self, id: ObjId, v: &Value, throw: bool) -> JsResult<()> { let n = self.to_number(v)?; let new = to_uint32(n); if f64::from(new) != n { return Err(self.throw(ErrorType::Range, "invalid array length")); } let o = self.object_mut(id)?; let Class::Array { length, length_writable } = &mut o.class else { return internal("not an array") }; if !*length_writable { return self.reject(throw, &key("length"), "it is read-only"); } let old = *length; *length = new; if new < old { let doomed: Vec = o.props.keys().into_iter().filter(|k| array_index(k).is_some_and(|i| i >= new)).collect(); for k in doomed { o.props.remove(&k); } } Ok(()) } /// [[Delete]] (section 8.12.7). pub(crate) fn delete(&mut self, id: ObjId, k: &[u16], throw: bool) -> JsResult { match self.get_own_property(id, k)? { None => Ok(true), Some(p) if p.configurable() => { let o = self.object_mut(id)?; o.props.remove(k); if let Class::Arguments(map) = &mut o.class && let Some(slot) = array_index(k).and_then(|i| map.names.get_mut(i as usize)) { *slot = None; } Ok(true) } Some(_) if throw => { let message = format!("cannot delete property '{}'", String::from_utf16_lossy(k)); Err(self.type_error(&message)) } Some(_) => Ok(false), } } /// PutValue on a property reference (section 8.7.2), whose base may be a primitive. fn put_value(&mut self, base: &Value, k: Key, v: Value) -> JsResult<()> { let strict = self.strict(); match base { Value::Object(o) => self.put(*o, k, v, strict), Value::Undefined | Value::Null => { let message = format!( "cannot set property '{}' of {}", String::from_utf16_lossy(&k), if matches!(base, Value::Null) { "null" } else { "undefined" } ); Err(self.type_error(&message)) } _ => { let proto = self.primitive_proto(base, &k)?; let own = matches!(base, Value::String(s) if is(&k, "length") || array_index(&k).is_some_and(|i| (i as usize) < s.len())); if !own && let Some(Prop::Accessor { set: Some(s), .. }) = self.find_property(proto, &k)? { return self.call(&Value::Object(s), base, &[v]).map(drop); } self.reject(strict, &k, "the value is a primitive") } } } fn op_get_member(&mut self, n: u32, with_this: bool) -> JsResult<()> { let name = self.name(n)?; let base = self.peek(0)?; let v = self.get_value(&base, &name)?; self.replace(1, v)?; if with_this { self.push(base) } else { Ok(()) } } fn op_set_member(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = self.peek(0)?; let base = self.peek(1)?; self.put_value(&base, name, v.clone())?; self.replace(2, v) } fn check_coercible(&mut self, base: &Value, k: &[u16]) -> JsResult<()> { if base.is_nullish() { self.primitive_proto(base, k)?; } Ok(()) } fn op_check_object(&mut self) -> JsResult<()> { let base = self.peek(0)?; self.check_coercible(&base, &[]) } fn op_to_key(&mut self) -> JsResult<()> { let k = self.peek(0)?; let base = self.peek(1)?; self.check_coercible(&base, &[])?; let name = self.to_key(&k)?; self.replace(1, Value::String(Str::from_rc(name))) } fn op_get_index(&mut self, with_this: bool) -> JsResult<()> { let k = self.peek(0)?; let base = self.peek(1)?; self.check_coercible(&base, &[])?; let name = self.to_key(&k)?; let v = self.get_value(&base, &name)?; self.replace(2, v)?; if with_this { self.push(base) } else { Ok(()) } } fn op_set_index(&mut self) -> JsResult<()> { let v = self.peek(0)?; let k = self.peek(1)?; let base = self.peek(2)?; let name = self.to_key(&k)?; self.put_value(&base, name, v.clone())?; self.replace(3, v) } fn op_delete_member(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let base = self.peek(0)?; let o = self.to_object(&base)?; let strict = self.strict(); let r = self.delete(o, &name, strict)?; self.replace(1, Value::Bool(r)) } fn op_delete_index(&mut self) -> JsResult<()> { let k = self.peek(0)?; let base = self.peek(1)?; self.check_coercible(&base, &[])?; let name = self.to_key(&k)?; let o = self.to_object(&base)?; let strict = self.strict(); let r = self.delete(o, &name, strict)?; self.replace(2, Value::Bool(r)) } fn inc_property(&mut self, base: &Value, name: Key, prefix: bool, dec: bool) -> JsResult { let v = self.get_value(base, &name)?; let old = self.to_number(&v)?; let new = if dec { old - 1.0 } else { old + 1.0 }; self.put_value(base, name, Value::Number(new))?; Ok(Value::Number(if prefix { new } else { old })) } fn op_inc_member(&mut self, n: u32, prefix: bool, dec: bool) -> JsResult<()> { let name = self.name(n)?; let base = self.peek(0)?; let r = self.inc_property(&base, name, prefix, dec)?; self.replace(1, r) } fn op_inc_index(&mut self, prefix: bool, dec: bool) -> JsResult<()> { let k = self.peek(0)?; let base = self.peek(1)?; self.check_coercible(&base, &[])?; let name = self.to_key(&k)?; let r = self.inc_property(&base, name, prefix, dec)?; self.replace(2, r) } // ---- object literals and functions ---- fn op_new_object(&mut self) -> JsResult<()> { let o = self.new_object(); self.push(Value::Object(o)) } pub(crate) fn new_object(&mut self) -> ObjId { let proto = self.intrinsics.object_proto; self.heap.alloc_object(Object::new(Class::Ordinary, Some(proto))) } fn op_new_array(&mut self) -> JsResult<()> { let proto = self.intrinsics.array_proto; let a = self.heap.alloc_object(Object::new(Class::Array { length: 0, length_writable: true }, Some(proto))); self.push(Value::Object(a)) } fn op_array_push(&mut self, with_value: bool) -> JsResult<()> { let v = if with_value { Some(self.pop()?) } else { None }; let Value::Object(a) = self.peek(0)? else { return internal("array literal is not an object") }; let o = self.object_mut(a)?; let Class::Array { length, .. } = &mut o.class else { return internal("array literal is not an array") }; let i = *length; if i == u32::MAX - 1 { return Err(self.throw(ErrorType::Range, "array literal too long")); } *length = i + 1; match v { Some(v) => self.define(a, index_key(i), Prop::Data { value: v, flags: OPEN }), None => Ok(()), } } fn op_init_prop(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = self.pop()?; let Value::Object(o) = self.peek(0)? else { return internal("object literal is not an object") }; self.define(o, name, Prop::Data { value: v, flags: OPEN }) } fn op_init_accessor(&mut self, n: u32, getter: bool) -> JsResult<()> { let name = self.name(n)?; let Value::Object(f) = self.pop()? else { return internal("accessor is not a function") }; let Value::Object(o) = self.peek(0)? else { return internal("object literal is not an object") }; let (mut get, mut set) = match self.object(o)?.props.get(&name) { Some(Prop::Accessor { get, set, .. }) => (*get, *set), _ => (None, None), }; if getter { get = Some(f); } else { set = Some(f); } self.define(o, name, Prop::Accessor { get, set, flags: ENUMERABLE | CONFIGURABLE }) } fn op_closure(&mut self, i: u32) -> JsResult<()> { let frame = self.frame()?; let env = frame.env; let Some(code) = frame.code.functions.get(i as usize).cloned() else { return internal("function out of range") }; let f = self.make_closure(code, env)?; self.push(Value::Object(f)) } /// A function object for `code` closing over `env` (section 13.2). pub(crate) fn make_closure(&mut self, code: Rc, env: EnvId) -> JsResult { let env = match &code.self_name { Some(_) => self.heap.alloc_env(Env::Decl { bindings: PropMap::default(), outer: Some(env) }), None => env, }; let proto = self.intrinsics.function_proto; let params = code.params.len(); let strict = code.strict; let self_name = code.self_name.clone(); let f = self.heap.alloc_object(Object::new(Class::Function(Func::Closure { code, env }), Some(proto))); self.define(f, key("length"), Prop::Data { value: Value::Number(params as f64), flags: 0 })?; let p = self.new_object(); self.define(p, key("constructor"), Prop::Data { value: Value::Object(f), flags: HIDDEN })?; self.define(f, key("prototype"), Prop::Data { value: Value::Object(p), flags: WRITABLE })?; if strict { let thrower = Some(self.intrinsics.throw_type_error); for name in ["caller", "arguments"] { self.define(f, key(name), Prop::Accessor { get: thrower, set: thrower, flags: 0 })?; } } if let Some(name) = self_name { self.env_insert(env, name, Binding { value: Value::Object(f), mutable: false, deletable: false })?; } Ok(f) } fn env_insert(&mut self, env: EnvId, name: Key, b: Binding) -> JsResult<()> { let Some(Env::Decl { bindings, .. }) = self.heap.env_mut(env) else { return internal("not a declarative environment") }; let new = !bindings.contains(&name); bindings.insert(name, b); if new { self.heap.bytes += PROP_BYTES; } Ok(()) } fn op_regexp(&mut self, i: u32) -> JsResult<()> { let Some((source, flags)) = self.frame()?.code.regexps.get(i as usize).cloned() else { return internal("regular expression out of range") }; let proto = self.intrinsics.regexp_proto; let r = self.heap.alloc_object(Object::new(Class::RegExp, Some(proto))); let flag_units = flags.flat(); let has = |c: u8| flag_units.contains(&u16::from(c)); let (g, i_, m) = (has(b'g'), has(b'i'), has(b'm')); self.define(r, key("source"), Prop::Data { value: Value::String(source), flags: 0 })?; self.define(r, key("global"), Prop::Data { value: Value::Bool(g), flags: 0 })?; self.define(r, key("ignoreCase"), Prop::Data { value: Value::Bool(i_), flags: 0 })?; self.define(r, key("multiline"), Prop::Data { value: Value::Bool(m), flags: 0 })?; self.define(r, key("lastIndex"), Prop::Data { value: Value::Number(0.0), flags: WRITABLE })?; self.push(Value::Object(r)) } // ---- environments ---- fn op_push_with(&mut self) -> JsResult<()> { let v = self.peek(0)?; let object = self.to_object(&v)?; self.pop()?; let outer = self.frame()?.env; let env = self.heap.alloc_env(Env::Obj { object, with_this: true, outer: Some(outer) }); let frame = self.frame_mut()?; frame.env = env; frame.env_depth += 1; Ok(()) } fn op_push_catch(&mut self, n: u32) -> JsResult<()> { let name = self.name(n)?; let v = self.pop()?; let outer = self.frame()?.env; let mut bindings = PropMap::default(); bindings.insert(name, Binding { value: v, mutable: true, deletable: false }); let env = self.heap.alloc_env(Env::Decl { bindings, outer: Some(outer) }); self.heap.bytes += PROP_BYTES; let frame = self.frame_mut()?; frame.env = env; frame.env_depth += 1; Ok(()) } fn op_pop_env(&mut self) -> JsResult<()> { let env = self.frame()?.env; let Some(outer) = self.heap.env(env).and_then(Env::outer) else { return internal("left the outermost environment") }; let frame = self.frame_mut()?; if frame.env_depth == 0 { return internal("left more environments than were entered"); } frame.env = outer; frame.env_depth -= 1; Ok(()) } // ---- for-in ---- /// The names `for-in` visits (section 12.6.4): the enumerable properties of the object and /// its prototypes, own properties in the order they were added, each name once, and none /// that an object nearer the start of the chain has (enumerable or not). /// /// For a String object, the heap budget for all its index names is checked before any is /// built, and each name takes a unit of fuel. fn enumerable_keys(&mut self, id: ObjId) -> JsResult> { let mut seen = std::collections::HashSet::new(); let mut out = Vec::new(); let mut cur = Some(id); while let Some(o) = cur { let obj = self.object(o)?; let string_len = if let Class::String(s) = &obj.class { Some(s.len()) } else { None }; let is_array = matches!(obj.class, Class::Array { .. }); if let Some(len) = string_len { self.reserve_heap(index_keys_bytes(len))?; for i in 0..len { self.use_fuel()?; let k = index_key(u32::try_from(i).unwrap_or(u32::MAX)); if seen.insert(k.clone()) { out.push(k); } } seen.insert(key("length")); } if is_array { seen.insert(key("length")); } let obj = self.object(o)?; for (k, p) in obj.props.iter() { if seen.insert(k.clone()) && p.enumerable() { out.push(k.clone()); } } cur = obj.proto; } Ok(out) } fn op_for_in_start(&mut self) -> JsResult<()> { let v = self.peek(0)?; let (keys, object) = if v.is_nullish() { (Vec::new(), None) } else { let o = self.to_object(&v)?; // A wrapper made by ToObject is not on the stack; a collection while the names are // listed must not free it. let saved = self.temp_roots.len(); self.temp_roots.push(Value::Object(o)); let keys = self.enumerable_keys(o); self.temp_roots.truncate(saved); (keys?, Some(o)) }; self.heap.bytes += keys.iter().map(|k| string_bytes(k.len())).sum::(); let iter = self.heap.alloc_object(Object::new(Class::ForIn(Box::new(ForIn { keys, pos: 0, object })), None)); self.replace(1, Value::Object(iter)) } fn op_for_in_next(&mut self, target: u32) -> JsResult<()> { let Value::Object(iter) = self.peek(0)? else { return internal("for-in iterator is not an object") }; loop { let Class::ForIn(state) = &mut self.object_mut(iter)?.class else { return internal("not a for-in iterator") }; let Some(k) = state.keys.get(state.pos).cloned() else { return self.jump_to(target) }; state.pos += 1; let object = state.object; if let Some(o) = object && self.has_property(o, &k)? { return self.push(Value::String(Str::from_rc(k))); } } } // ---- calls ---- pub(crate) fn is_callable(&self, v: &Value) -> bool { v.as_object().and_then(|o| self.heap.object(o)).is_some_and(|o| matches!(o.class, Class::Function(_))) } fn func_of(&self, v: &Value) -> Option { match &self.heap.object(v.as_object()?)?.class { Class::Function(f) => Some(f.clone()), _ => None, } } /// Resolves `call` and `apply` and runs native and host functions; a function written in /// JavaScript comes back to be entered by the caller. fn prepare_call(&mut self, mut f: Value, mut this: Value, mut args: Vec) -> JsResult { loop { let Some(func) = self.func_of(&f) else { return Err(self.type_error("the value is not a function")) }; match func { Func::Closure { code, env } => { let callee = f.as_object().ok_or_else(|| Abrupt::Internal("callee is not an object".into()))?; return Ok(Prepared::Closure(Entry { callee, code, env, this, args })); } Func::Native { kind: NativeKind::Call, .. } => { self.use_fuel()?; f = this; this = if args.is_empty() { Value::Undefined } else { args.remove(0) }; } Func::Native { kind: NativeKind::Apply, .. } => { self.use_fuel()?; if !self.is_callable(&this) { return Err(self.type_error("apply was called on a value that is not a function")); } let list = args.get(1).cloned().unwrap_or(Value::Undefined); f = this; this = args.first().cloned().unwrap_or(Value::Undefined); args = self.list_from_array_like(&list)?; } Func::Native { f: native, .. } => return Ok(Prepared::Done(native(self, this, &args, false)?)), Func::Host { f: host, .. } => { let js_args: Vec = args.iter().map(|v| self.to_js_value(v)).collect(); return match host(&js_args) { Ok(v) => Ok(Prepared::Done(self.from_js_value(v))), Err(message) => Err(self.throw(ErrorType::Error, &message)), }; } } } } /// The elements of an array-like `apply` argument (section 15.3.4.3). fn list_from_array_like(&mut self, list: &Value) -> JsResult> { let Value::Object(o) = list else { if list.is_nullish() { return Ok(Vec::new()); } return Err(self.type_error("the argument list is not an object")); }; let len = self.get_from(*o, &key("length"), list)?; let n = to_uint32(self.to_number(&len)?) as usize; if n > MAX_APPLY_ARGS { return Err(self.throw(ErrorType::Range, "too many arguments")); } let saved = self.temp_roots.len(); let mut out = Vec::with_capacity(n); for i in 0..n { match self.get_from(*o, &index_key(u32::try_from(i).unwrap_or(u32::MAX)), list) { Ok(v) => { self.temp_roots.push(v.clone()); out.push(v); } Err(e) => { self.temp_roots.truncate(saved); return Err(e); } } } self.temp_roots.truncate(saved); Ok(out) } fn op_call(&mut self, argc: u32, eval: bool) -> JsResult<()> { let n = argc as usize; let start = self.stack.len().checked_sub(n + 2).filter(|&s| s >= self.base()).ok_or_else(|| Abrupt::Internal("call below the stack".into()))?; let f = self.stack.get(start).cloned().unwrap_or(Value::Undefined); let this = self.stack.get(start + 1).cloned().unwrap_or(Value::Undefined); let args: Vec = self.stack.get(start + 2..).map(<[Value]>::to_vec).unwrap_or_default(); if eval && f.as_object() == Some(self.intrinsics.eval) { return self.direct_eval(start, args); } match self.prepare_call(f, this, args)? { Prepared::Done(v) => { self.stack.truncate(start); self.push(v) } Prepared::Closure(entry) => { self.stack.truncate(start); self.enter_function(entry, None, false) } } } fn op_new(&mut self, argc: u32) -> JsResult<()> { let n = argc as usize; let start = self.stack.len().checked_sub(n + 1).filter(|&s| s >= self.base()).ok_or_else(|| Abrupt::Internal("new below the stack".into()))?; let f = self.stack.get(start).cloned().unwrap_or(Value::Undefined); let args: Vec = self.stack.get(start + 1..).map(<[Value]>::to_vec).unwrap_or_default(); match self.func_of(&f) { Some(Func::Closure { code, env }) => { let Some(callee) = f.as_object() else { return internal("callee is not an object") }; let proto = match self.get_from(callee, &key("prototype"), &f)? { Value::Object(p) => p, _ => self.intrinsics.object_proto, }; let obj = self.heap.alloc_object(Object::new(Class::Ordinary, Some(proto))); self.stack.truncate(start); self.enter_function(Entry { callee, code, env, this: Value::Object(obj), args }, Some(obj), false) } Some(Func::Native { f: native, constructor: true, .. }) => { let v = native(self, Value::Undefined, &args, true)?; self.stack.truncate(start); self.push(v) } _ => Err(self.type_error("the value is not a constructor")), } } /// Pushes a frame for a call to a function written in JavaScript, its operands already off /// the stack. fn enter_function(&mut self, entry: Entry, construct: Option, boundary: bool) -> JsResult<()> { let Entry { callee, code, env, this, args } = entry; if self.frames.len() >= self.limits.frames { return Err(Abrupt::Limit(LimitKind::Frames)); } let this = if code.strict { this } else { match this { Value::Undefined | Value::Null => Value::Object(self.global), Value::Object(_) => this, prim => Value::Object(self.to_object(&prim)?), } }; let fenv = self.instantiate_function(&code, env, &args, callee)?; let base = self.stack.len(); self.frames.push(Frame { code, pc: 0, base, env: fenv, var_env: fenv, env_depth: 0, this, callee: Some(callee), construct, completion: Value::Undefined, boundary, }); Ok(()) } /// Declaration binding instantiation for function code (section 10.5). fn instantiate_function(&mut self, code: &Rc, outer: EnvId, args: &[Value], callee: ObjId) -> JsResult { let mut bindings = PropMap::default(); for (i, p) in code.params.iter().enumerate() { bindings.insert(p.clone(), Binding { value: args.get(i).cloned().unwrap_or(Value::Undefined), mutable: true, deletable: false }); } let count = bindings.len(); let env = self.heap.alloc_env(Env::Decl { bindings, outer: Some(outer) }); self.heap.bytes += PROP_BYTES * count; for (name, fi) in &code.fun_decls { let Some(fcode) = code.functions.get(*fi as usize).cloned() else { return internal("function out of range") }; let f = self.make_closure(fcode, env)?; self.env_insert(env, name.clone(), Binding { value: Value::Object(f), mutable: true, deletable: false })?; } let has = |interp: &Self, name: &[u16]| matches!(interp.heap.env(env), Some(Env::Decl { bindings, .. }) if bindings.contains(name)); if code.uses_arguments && !has(self, &key("arguments")) { let a = self.create_arguments(code, env, args, callee)?; self.env_insert(env, key("arguments"), Binding { value: Value::Object(a), mutable: !code.strict, deletable: false })?; } for v in &code.vars { if !has(self, v) { self.env_insert(env, v.clone(), Binding { value: Value::Undefined, mutable: true, deletable: false })?; } } Ok(env) } /// The `arguments` object (section 10.6). fn create_arguments(&mut self, code: &Code, env: EnvId, args: &[Value], callee: ObjId) -> JsResult { let mut names = vec![None; args.len()]; if !code.strict { let mut mapped = std::collections::HashSet::new(); for (i, p) in code.params.iter().enumerate().take(args.len()).rev() { if mapped.insert(p.clone()) && let Some(slot) = names.get_mut(i) { *slot = Some(p.clone()); } } } let proto = self.intrinsics.object_proto; let a = self.heap.alloc_object(Object::new(Class::Arguments(Box::new(ArgMap { env, names })), Some(proto))); for (i, v) in args.iter().enumerate() { self.define(a, index_key(u32::try_from(i).unwrap_or(u32::MAX)), Prop::Data { value: v.clone(), flags: OPEN })?; } self.define(a, key("length"), Prop::Data { value: Value::Number(args.len() as f64), flags: HIDDEN })?; if code.strict { let thrower = Some(self.intrinsics.throw_type_error); for name in ["callee", "caller"] { self.define(a, key(name), Prop::Accessor { get: thrower, set: thrower, flags: 0 })?; } } else { self.define(a, key("callee"), Prop::Data { value: Value::Object(callee), flags: HIDDEN })?; } Ok(a) } /// Calls `f` from Rust and returns its result, running the loop again for a function written /// in JavaScript. pub(crate) fn call(&mut self, f: &Value, this: &Value, args: &[Value]) -> JsResult { if self.reentry >= self.limits.reentry { return Err(Abrupt::Limit(LimitKind::Reentry)); } let saved = self.stack.len(); self.stack.push(f.clone()); self.stack.push(this.clone()); self.stack.extend(args.iter().cloned()); self.reentry += 1; let r = self.call_rooted(f.clone(), this.clone(), args.to_vec(), saved); self.reentry -= 1; self.stack.truncate(saved); r } fn call_rooted(&mut self, f: Value, this: Value, args: Vec, saved: usize) -> JsResult { match self.prepare_call(f, this, args)? { Prepared::Done(v) => Ok(v), Prepared::Closure(entry) => { self.stack.truncate(saved); self.enter_function(entry, None, true)?; self.execute() } } } // ---- eval and scripts ---- /// The options for source parsed next in this run: [`Options::max_source_units`] is what /// the run may still parse. fn remaining_source_options(&self) -> Options { Options { max_source_units: self.options.max_source_units.saturating_sub(self.source_units), ..self.options.clone() } } /// Checks that `units` more code units of source fit in what this run may still parse, /// before the source is flattened or encoded. pub(crate) fn check_source_units(&self, units: usize) -> Result<(), CompileError> { crate::check_length(units, &self.remaining_source_options()).map_err(CompileError::Syntax) } /// Encodes `text` as UTF-16 for parsing, after checking that it fits in what this run may /// still parse. pub(crate) fn encode_source(&self, text: &str) -> Result, CompileError> { crate::to_utf16(text, &self.remaining_source_options()).map_err(CompileError::Syntax) } /// Parses and compiles source for eval code or a script, counting it against the source /// limit. pub(crate) fn compile_source(&mut self, units: &[u16], strict: bool, kind: CodeKind) -> Result, CompileError> { self.source_units = self.source_units.saturating_add(units.len()); let remaining = self.options.max_source_units.saturating_sub(self.source_units.saturating_sub(units.len())); let options = Options { strict, max_source_units: remaining, ..self.options.clone() }; let program = crate::parse_utf16(units, &options).map_err(CompileError::Syntax)?; let source = Rc::new(Source { lines: program.lines.clone(), text: units.into() }); compile_program(&program, &source, kind).map_err(|e| match e { CompileFail::Syntax(s) => CompileError::Syntax(s), CompileFail::Internal(m) => CompileError::Internal(m), }) } /// Turns a compile failure inside a running script into what it throws. pub(crate) fn compile_error_to_abrupt(&mut self, e: CompileError) -> Abrupt { match e { CompileError::Syntax(s) => self.throw(ErrorType::Syntax, &s.message), CompileError::Internal(m) => Abrupt::Internal(m), } } fn direct_eval(&mut self, start: usize, args: Vec) -> JsResult<()> { let x = args.into_iter().next().unwrap_or(Value::Undefined); let Value::String(s) = &x else { self.stack.truncate(start); return self.push(x); }; let strict = self.strict(); self.check_source_units(s.len()).map_err(|e| self.compile_error_to_abrupt(e))?; let units = s.flat(); let code = self.compile_source(&units, strict, CodeKind::Eval).map_err(|e| self.compile_error_to_abrupt(e))?; let frame = self.frame()?; let (caller_env, caller_var_env, this) = (frame.env, frame.var_env, frame.this.clone()); let (env, var_env) = if code.strict { let e = self.heap.alloc_env(Env::Decl { bindings: PropMap::default(), outer: Some(caller_env) }); (e, e) } else { (caller_env, caller_var_env) }; self.stack.truncate(start); self.instantiate_global(&code, var_env, true)?; self.push_code_frame(code, env, var_env, this, false) } /// Indirect eval and scripts: runs `code` in the global environment and returns its /// completion value. pub(crate) fn run_global(&mut self, code: Rc, is_eval: bool) -> JsResult { if self.reentry >= self.limits.reentry { return Err(Abrupt::Limit(LimitKind::Reentry)); } let (env, global) = (self.global_env, self.global); let (env, var_env) = if code.strict && is_eval { let e = self.heap.alloc_env(Env::Decl { bindings: PropMap::default(), outer: Some(env) }); (e, e) } else { (env, env) }; self.instantiate_global(&code, var_env, is_eval)?; self.push_code_frame(code, env, var_env, Value::Object(global), true)?; self.reentry += 1; let r = self.execute(); self.reentry -= 1; r } fn push_code_frame(&mut self, code: Rc, env: EnvId, var_env: EnvId, this: Value, boundary: bool) -> JsResult<()> { if self.frames.len() >= self.limits.frames { return Err(Abrupt::Limit(LimitKind::Frames)); } let base = self.stack.len(); self.frames.push(Frame { code, pc: 0, base, env, var_env, env_depth: 0, this, callee: None, construct: None, completion: Value::Undefined, boundary, }); Ok(()) } /// Declaration binding instantiation for global and eval code (section 10.5). fn instantiate_global(&mut self, code: &Rc, var_env: EnvId, is_eval: bool) -> JsResult<()> { let configurable = if is_eval { CONFIGURABLE } else { 0 }; for (name, fi) in &code.fun_decls { let Some(fcode) = code.functions.get(*fi as usize).cloned() else { return internal("function out of range") }; let f = self.make_closure(fcode, var_env)?; self.declare(var_env, name, Some(Value::Object(f)), configurable)?; } for v in &code.vars { self.declare(var_env, v, None, configurable)?; } Ok(()) } /// Declares `name` in a variable environment, with `value` for a function declaration. fn declare(&mut self, env: EnvId, name: &Key, value: Option, configurable: u8) -> JsResult<()> { match self.heap.env(env) { Some(Env::Decl { bindings, .. }) => { let exists = bindings.contains(name); match value { Some(v) => self.env_insert(env, name.clone(), Binding { value: v, mutable: true, deletable: configurable != 0 }), None if !exists => { self.env_insert(env, name.clone(), Binding { value: Value::Undefined, mutable: true, deletable: configurable != 0 }) } None => Ok(()), } } Some(Env::Obj { object, .. }) => { let object = *object; let declared = self.has_property(object, name)?; let flags = WRITABLE | ENUMERABLE | configurable; match value { None if declared => Ok(()), None => self.define(object, name.clone(), Prop::Data { value: Value::Undefined, flags }), Some(v) => { let existing = self.find_property(object, name)?; match existing { None => self.define(object, name.clone(), Prop::Data { value: v, flags }), Some(p) if p.configurable() => self.define(object, name.clone(), Prop::Data { value: v, flags }), Some(Prop::Data { flags: f, .. }) if f & (WRITABLE | ENUMERABLE) == WRITABLE | ENUMERABLE => { self.put(object, name.clone(), v, true) } Some(_) => { let message = format!("cannot declare function {}", String::from_utf16_lossy(name)); Err(self.type_error(&message)) } } } } } None => internal("environment out of range"), } } // ---- conversions ---- pub(crate) fn type_of(&self, v: &Value) -> &'static str { match v { Value::Undefined => "undefined", Value::Null => "object", Value::Bool(_) => "boolean", Value::Number(_) => "number", Value::String(_) => "string", Value::Object(_) if self.is_callable(v) => "function", Value::Object(_) => "object", } } /// ToPrimitive (section 9.1) through [[DefaultValue]] (section 8.12.8). #[allow(clippy::wrong_self_convention)] pub(crate) fn to_primitive(&mut self, v: &Value, hint: Hint) -> JsResult { let Value::Object(o) = v else { return Ok(v.clone()) }; let order = if hint == Hint::String { ["toString", "valueOf"] } else { ["valueOf", "toString"] }; for name in order { let f = self.get_from(*o, &key(name), v)?; if self.is_callable(&f) { let r = self.call(&f, v, &[])?; if !matches!(r, Value::Object(_)) { return Ok(r); } } } Err(self.type_error("cannot convert the object to a primitive value")) } #[allow(clippy::wrong_self_convention)] pub(crate) fn to_number(&mut self, v: &Value) -> JsResult { Ok(match v { Value::Undefined => f64::NAN, Value::Null => 0.0, Value::Bool(b) => f64::from(u8::from(*b)), Value::Number(n) => *n, Value::String(s) => string_to_number(&s.flat()), Value::Object(_) => { let p = self.to_primitive(v, Hint::Number)?; return self.to_number(&p); } }) } /// ToString applied to a primitive. pub(crate) fn primitive_to_str(&mut self, v: &Value) -> Str { match v { Value::Undefined => Str::from("undefined"), Value::Null => Str::from("null"), Value::Bool(b) => Str::from(if *b { "true" } else { "false" }), Value::Number(n) => { let s = number_to_str(*n); self.heap.bytes += string_bytes(s.len()); s } Value::String(s) => s.clone(), Value::Object(_) => Str::from("[object]"), } } #[allow(clippy::wrong_self_convention)] pub(crate) fn to_str(&mut self, v: &Value) -> JsResult { let p = self.to_primitive(v, Hint::String)?; Ok(self.primitive_to_str(&p)) } /// A property name from a value (ToString, with a short way for array indices). #[allow(clippy::wrong_self_convention)] pub(crate) fn to_key(&mut self, v: &Value) -> JsResult { match v { Value::String(s) => { let rope = s.is_rope(); let flat = s.flat(); if rope { self.heap.bytes += string_bytes(flat.len()); } Ok(flat) } Value::Number(n) if *n >= 0.0 && *n < 4_294_967_295.0 && n.fract() == 0.0 => Ok(index_key(*n as u32)), _ => Ok(self.to_str(v)?.flat()), } } /// ToObject (section 9.9). #[allow(clippy::wrong_self_convention)] pub(crate) fn to_object(&mut self, v: &Value) -> JsResult { let (class, proto) = match v { Value::Object(o) => return Ok(*o), Value::Undefined | Value::Null => return Err(self.type_error("cannot convert undefined or null to an object")), Value::Bool(_) => (Class::Boolean, self.intrinsics.boolean_proto), Value::Number(_) => (Class::Number, self.intrinsics.number_proto), Value::String(s) => (Class::String(s.clone()), self.intrinsics.string_proto), }; Ok(self.heap.alloc_object(Object::new(class, Some(proto)))) } /// Concatenates two strings, ending the run with the heap limit when the result could not /// be flattened within it. pub(crate) fn concat(&mut self, a: &Str, b: &Str) -> JsResult { let len = a.len() + b.len(); if len > self.limits.heap_bytes / 4 { return Err(Abrupt::Limit(LimitKind::Heap)); } self.heap.bytes += ROPE_BYTES; Ok(Str::concat(a, b)) } // ---- operators ---- fn op_unary(&mut self, op: Op) -> JsResult<()> { let v = self.peek(0)?; let r = match op { Op::Neg => Value::Number(-self.to_number(&v)?), Op::Plus | Op::ToNumber => Value::Number(self.to_number(&v)?), Op::BitNot => Value::Number(f64::from(!to_int32(self.to_number(&v)?))), Op::Not => Value::Bool(!v.truthy()), Op::Typeof => Value::str(self.type_of(&v)), _ => return internal("not a unary operator"), }; self.replace(1, r) } fn op_binary(&mut self, op: BinaryOp) -> JsResult<()> { let b = self.peek(0)?; let a = self.peek(1)?; let r = self.binary(op, &a, &b)?; self.replace(2, r) } pub(crate) fn binary(&mut self, op: BinaryOp, a: &Value, b: &Value) -> JsResult { let num = |interp: &mut Self, f: fn(f64, f64) -> f64| -> JsResult { let x = interp.to_number(a)?; let y = interp.to_number(b)?; Ok(Value::Number(f(x, y))) }; let int = |interp: &mut Self, f: fn(i32, i32) -> i32| -> JsResult { let x = to_int32(interp.to_number(a)?); let y = to_int32(interp.to_number(b)?); Ok(Value::Number(f64::from(f(x, y)))) }; match op { BinaryOp::Add => self.add(a, b), BinaryOp::Sub => num(self, |x, y| x - y), BinaryOp::Mul => num(self, |x, y| x * y), BinaryOp::Div => num(self, |x, y| x / y), BinaryOp::Mod => num(self, |x, y| x % y), BinaryOp::Shl => int(self, |x, y| x.wrapping_shl((y & 31) as u32)), BinaryOp::Shr => int(self, |x, y| x >> (y & 31)), BinaryOp::UShr => { let x = to_uint32(self.to_number(a)?); let y = to_uint32(self.to_number(b)?); Ok(Value::Number(f64::from(x >> (y & 31)))) } BinaryOp::BitAnd => int(self, |x, y| x & y), BinaryOp::BitOr => int(self, |x, y| x | y), BinaryOp::BitXor => int(self, |x, y| x ^ y), BinaryOp::Lt => Ok(Value::Bool(self.less_than(a, b, true)? == Some(true))), BinaryOp::Gt => Ok(Value::Bool(self.less_than(b, a, false)? == Some(true))), BinaryOp::LtEq => Ok(Value::Bool(self.less_than(b, a, false)? == Some(false))), BinaryOp::GtEq => Ok(Value::Bool(self.less_than(a, b, true)? == Some(false))), BinaryOp::Eq => Ok(Value::Bool(self.loose_eq(a, b)?)), BinaryOp::NotEq => Ok(Value::Bool(!self.loose_eq(a, b)?)), BinaryOp::StrictEq => Ok(Value::Bool(strict_eq(a, b))), BinaryOp::StrictNotEq => Ok(Value::Bool(!strict_eq(a, b))), BinaryOp::Instanceof => self.instance_of(a, b).map(Value::Bool), BinaryOp::In => { let Value::Object(o) = b else { return Err(self.type_error("the right side of 'in' is not an object")) }; let k = self.to_key(a)?; self.has_property(*o, &k).map(Value::Bool) } } } /// The addition operator (section 11.6.1). fn add(&mut self, a: &Value, b: &Value) -> JsResult { if let (Value::Number(x), Value::Number(y)) = (a, b) { return Ok(Value::Number(x + y)); } let pa = self.to_primitive(a, Hint::Default)?; let pb = self.to_primitive(b, Hint::Default)?; if matches!(pa, Value::String(_)) || matches!(pb, Value::String(_)) { let sa = self.primitive_to_str(&pa); let sb = self.primitive_to_str(&pb); return self.concat(&sa, &sb).map(Value::String); } Ok(Value::Number(self.to_number(&pa)? + self.to_number(&pb)?)) } /// The abstract relational comparison `x < y` (section 11.8.5): `None` when either side is /// NaN. fn less_than(&mut self, x: &Value, y: &Value, left_first: bool) -> JsResult> { let (px, py) = if left_first { let px = self.to_primitive(x, Hint::Number)?; (px, self.to_primitive(y, Hint::Number)?) } else { let py = self.to_primitive(y, Hint::Number)?; (self.to_primitive(x, Hint::Number)?, py) }; if let (Value::String(a), Value::String(b)) = (&px, &py) { return Ok(Some(*a.flat() < *b.flat())); } let nx = self.to_number(&px)?; let ny = self.to_number(&py)?; if nx.is_nan() || ny.is_nan() { return Ok(None); } Ok(Some(nx < ny)) } /// The abstract equality comparison (section 11.9.3). fn loose_eq(&mut self, a: &Value, b: &Value) -> JsResult { let (mut x, mut y) = (a.clone(), b.clone()); loop { match (&x, &y) { (Value::Undefined | Value::Null, Value::Undefined | Value::Null) => return Ok(true), (Value::Number(_), Value::Number(_)) | (Value::String(_), Value::String(_)) | (Value::Bool(_), Value::Bool(_)) | (Value::Object(_), Value::Object(_)) => return Ok(strict_eq(&x, &y)), (Value::Number(n), Value::String(s)) | (Value::String(s), Value::Number(n)) => return Ok(*n == string_to_number(&s.flat())), (Value::Bool(v), _) => x = Value::Number(f64::from(u8::from(*v))), (_, Value::Bool(v)) => y = Value::Number(f64::from(u8::from(*v))), (Value::Number(_) | Value::String(_), Value::Object(_)) => y = self.to_primitive(&y, Hint::Default)?, (Value::Object(_), Value::Number(_) | Value::String(_)) => x = self.to_primitive(&x, Hint::Default)?, _ => return Ok(false), } } } /// `instanceof` (sections 11.8.6 and 15.3.5.3). fn instance_of(&mut self, v: &Value, f: &Value) -> JsResult { if !self.is_callable(f) { return Err(self.type_error("the right side of 'instanceof' is not a function")); } let Some(fid) = f.as_object() else { return Ok(false) }; let Value::Object(mut o) = v.clone() else { return Ok(false) }; let Value::Object(proto) = self.get_from(fid, &key("prototype"), f)? else { return Err(self.type_error("the function's prototype is not an object")); }; let mut steps = 0usize; while let Some(p) = self.object(o)?.proto { if p == proto { return Ok(true); } o = p; steps += 1; if steps > 1_000_000 { return internal("prototype chain too long"); } } Ok(false) } // ---- host values ---- pub(crate) fn to_js_value(&self, v: &Value) -> crate::JsValue { match v { Value::Undefined => crate::JsValue::Undefined, Value::Null => crate::JsValue::Null, Value::Bool(b) => crate::JsValue::Bool(*b), Value::Number(n) => crate::JsValue::Number(*n), Value::String(s) => crate::JsValue::String(s.to_rust_string()), Value::Object(o) => crate::JsValue::Object(self.heap.object(*o).map_or("Object", |o| o.class.name()).to_string()), } } #[allow(clippy::wrong_self_convention)] pub(crate) fn from_js_value(&mut self, v: crate::JsValue) -> Value { match v { crate::JsValue::Undefined => Value::Undefined, crate::JsValue::Null => Value::Null, crate::JsValue::Bool(b) => Value::Bool(b), crate::JsValue::Number(n) => Value::Number(n), crate::JsValue::String(s) => Value::str(&s), crate::JsValue::Object(_) => Value::Object(self.new_object()), } } /// A description of a thrown value that runs no JavaScript: `Name: message` for an error, /// the text of a primitive. pub(crate) fn describe(&mut self, v: &Value) -> String { let Value::Object(o) = v else { return self.primitive_to_str(v).to_rust_string() }; let data = |interp: &Self, k: &str| match interp.find_property(*o, &key(k)) { Ok(Some(Prop::Data { value: Value::String(s), .. })) => Some(s.to_rust_string()), _ => None, }; match (data(self, "name"), data(self, "message")) { (Some(n), Some(m)) if !m.is_empty() => format!("{n}: {m}"), (Some(n), _) => n, (None, Some(m)) => m, (None, None) => format!("[object {}]", self.heap.object(*o).map_or("Object", |o| o.class.name())), } } } /// A compile failure, before it becomes an exception or a run error. pub(crate) enum CompileError { Syntax(crate::SyntaxError), Internal(String), } /// The strict equality comparison (section 11.9.6). pub(crate) fn strict_eq(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true, (Value::Number(x), Value::Number(y)) => x == y, (Value::String(x), Value::String(y)) => x == y, (Value::Bool(x), Value::Bool(y)) => x == y, (Value::Object(x), Value::Object(y)) => x == y, _ => false, } }