Files
vectorcraft/crates/js/src/builtins.rs
T
dotCooCoo d151f3f5d0 JavaScript: source that eval and Function compile takes fuel before it is parsed
Direct and indirect eval, Function and host evaluators parsed and compiled
their source before taking any fuel or checking the heap, so a script with a
small fuel limit could make the interpreter parse megabytes of source in a
handful of instructions. Interp::prepare_source now takes one unit of fuel for
each UTF-16 code unit and checks that the source text fits in the heap before
the source is flattened, parsed or compiled. A host evaluator's string takes
the fuel for a third of its bytes before it is encoded, and the rest after.

Limits::fuel and the maxFuel row in docs/mcp.md say so.
2026-11-11 19:52:06 -08:00

621 lines
27 KiB
Rust

//! The built-in objects (ECMA-262 5.1 section 15): `Object` with `Object.prototype` and the
//! ES5 statics, `Function` with `Function.prototype.call`, `apply`, `bind` and `toString`,
//! `Array`, the error constructors, `Math`, `JSON`, the global functions and constants, Annex
//! B's `escape` and `unescape`, and the prototypes primitive values look properties up on, with
//! `Boolean.prototype`'s `toString` and `valueOf`. `String`, `Number`, `RegExp` and `Date` are in
//! their own modules.
//!
//! Every loop in a built-in takes a unit of fuel for each step, and every string, array, object
//! or property it makes is checked against the heap limit before it is made, so no built-in can
//! run past [`crate::Limits`].
mod array;
pub(crate) mod extensions;
mod global;
mod json;
mod math;
mod object;
use std::rc::Rc;
use crate::bytecode::{CodeKind, Source};
use crate::compiler::{CompileFail, compile_constructed_function};
use crate::heap::{
BOUND_ARG_BYTES, Class, Func, HIDDEN, Key, NativeFn, NativeKind, OBJECT_BYTES, OPEN, ObjId, Object, PROP_BYTES, Prop, key, string_bytes,
};
use crate::interp::{Abrupt, Desc, ErrorType, Interp, JsResult};
use crate::value::{Str, Value, index_key, number_to_str, to_uint32};
fn alloc(interp: &mut Interp, class: Class, proto: Option<ObjId>) -> ObjId {
interp.heap.alloc_object(Object::new(class, proto))
}
/// A built-in function object.
pub(crate) fn native(interp: &mut Interp, name: &'static str, f: NativeFn, length: u32, kind: NativeKind, constructor: bool) -> JsResult<ObjId> {
let proto = interp.intrinsics.function_proto;
let o = alloc(interp, Class::Function(Func::Native { f, kind, constructor, name }), Some(proto));
interp.define(o, key("length"), Prop::Data { value: Value::Number(f64::from(length)), flags: 0 })?;
Ok(o)
}
pub(crate) fn method(interp: &mut Interp, on: ObjId, name: &'static str, f: NativeFn, length: u32) -> JsResult<()> {
let m = native(interp, name, f, length, NativeKind::Plain, false)?;
interp.define(on, key(name), Prop::Data { value: Value::Object(m), flags: HIDDEN })
}
pub(crate) fn data(interp: &mut Interp, on: ObjId, name: &str, value: Value, flags: u8) -> JsResult<()> {
interp.define(on, key(name), Prop::Data { value, flags })
}
/// Links a constructor and its prototype and makes the constructor a global.
pub(crate) fn constructor(interp: &mut Interp, name: &'static str, ctor: ObjId, proto: ObjId) -> JsResult<()> {
data(interp, ctor, "prototype", Value::Object(proto), 0)?;
data(interp, proto, "constructor", Value::Object(ctor), HIDDEN)?;
let global = interp.global;
data(interp, global, name, Value::Object(ctor), HIDDEN)
}
/// Creates the built-ins and the global object's properties. The caller installs them before it
/// sets the realm's limits, so no limit stops the install. If it fails, the failure is kept in
/// `interp.install_error` and every run of the realm ends with it.
pub(crate) fn install(interp: &mut Interp) {
if let Err(e) = try_install(interp) {
let reason = match e {
Abrupt::Throw(v) => interp.describe(&v),
Abrupt::Limit(kind) => format!("limit reached: {kind:?}"),
Abrupt::Internal(message) => message,
};
interp.install_error = Some(format!("the built-ins could not be installed: {reason}"));
}
}
fn try_install(interp: &mut Interp) -> JsResult<()> {
let object_proto = alloc(interp, Class::Ordinary, None);
interp.intrinsics.object_proto = object_proto;
let function_proto = alloc(
interp,
Class::Function(Func::Native { f: function_proto_call, kind: NativeKind::Plain, constructor: false, name: "" }),
Some(object_proto),
);
interp.intrinsics.function_proto = function_proto;
data(interp, function_proto, "length", Value::Number(0.0), 0)?;
let global = interp.global;
if let Some(g) = interp.heap.object_mut(global) {
g.proto = Some(object_proto);
}
interp.intrinsics.array_proto = alloc(interp, Class::Array { length: 0, length_writable: true }, Some(object_proto));
interp.intrinsics.string_proto = alloc(interp, Class::String(Str::from("")), Some(object_proto));
interp.intrinsics.number_proto = alloc(interp, Class::Number(0.0), Some(object_proto));
let boolean_proto = alloc(interp, Class::Boolean(false), Some(object_proto));
interp.intrinsics.boolean_proto = boolean_proto;
interp.intrinsics.regexp_proto = alloc(interp, Class::Ordinary, Some(object_proto));
let thrower = native(interp, "", throw_type_error, 0, NativeKind::Plain, false)?;
if let Some(t) = interp.heap.object_mut(thrower) {
t.extensible = false;
}
interp.intrinsics.throw_type_error = thrower;
data(interp, global, "NaN", Value::Number(f64::NAN), 0)?;
data(interp, global, "Infinity", Value::Number(f64::INFINITY), 0)?;
data(interp, global, "undefined", Value::Undefined, 0)?;
let eval = native(interp, "eval", eval, 1, NativeKind::Eval, false)?;
interp.intrinsics.eval = eval;
data(interp, global, "eval", Value::Object(eval), HIDDEN)?;
let object = native(interp, "Object", object_ctor, 1, NativeKind::Plain, true)?;
constructor(interp, "Object", object, object_proto)?;
method(interp, object_proto, "toString", object_to_string, 0)?;
method(interp, object_proto, "toLocaleString", object_to_locale_string, 0)?;
method(interp, object_proto, "valueOf", object_value_of, 0)?;
method(interp, object_proto, "hasOwnProperty", has_own_property, 1)?;
method(interp, object_proto, "isPrototypeOf", is_prototype_of, 1)?;
method(interp, object_proto, "propertyIsEnumerable", property_is_enumerable, 1)?;
let function = native(interp, "Function", function_ctor, 1, NativeKind::Plain, true)?;
constructor(interp, "Function", function, function_proto)?;
let call = native(interp, "call", unreachable_native, 1, NativeKind::Call, false)?;
data(interp, function_proto, "call", Value::Object(call), HIDDEN)?;
let apply = native(interp, "apply", unreachable_native, 2, NativeKind::Apply, false)?;
data(interp, function_proto, "apply", Value::Object(apply), HIDDEN)?;
method(interp, function_proto, "bind", function_bind, 1)?;
method(interp, function_proto, "toString", function_to_string, 0)?;
method(interp, boolean_proto, "toString", boolean_to_string, 0)?;
method(interp, boolean_proto, "valueOf", boolean_value_of, 0)?;
install_errors(interp, object_proto)?;
object::install(interp, object)?;
array::install(interp)?;
math::install(interp)?;
json::install(interp)?;
global::install(interp)?;
crate::string_builtins::install(interp)?;
crate::number_builtins::install(interp)?;
crate::regexp_builtins::install(interp)?;
crate::date_builtins::install(interp)?;
if interp.options.e4x {
crate::xml_builtins::install(interp)?;
}
Ok(())
}
// ---- helpers for the built-ins ----
/// ToInteger (section 9.4).
fn to_integer(interp: &mut Interp, v: &Value) -> JsResult<f64> {
let n = interp.to_number(v)?;
Ok(if n.is_nan() { 0.0 } else { n.trunc() })
}
/// The property name of the index `n`, which may be past the largest array index.
fn key_of(n: f64) -> Key {
if (0.0..4_294_967_295.0).contains(&n) && n.fract() == 0.0 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
return index_key(n as u32);
}
number_to_str(n).flat()
}
/// ToUint32 of an object's `length`.
fn length_of(interp: &mut Interp, o: ObjId) -> JsResult<u32> {
let len = interp.get_from(o, &key("length"), &Value::Object(o))?;
Ok(to_uint32(interp.to_number(&len)?))
}
fn get_at(interp: &mut Interp, o: ObjId, k: f64) -> JsResult<Value> {
interp.get_from(o, &key_of(k), &Value::Object(o))
}
fn has_at(interp: &mut Interp, o: ObjId, k: f64) -> JsResult<bool> {
interp.has_property(o, &key_of(k))
}
/// The estimated bytes a new element adds.
const ELEMENT_BYTES: usize = PROP_BYTES + 32 + 2 * 10;
/// `o[k] = v` as the array methods assign: a failure throws. When `o` has no own property `k`, `v`
/// stays rooted while heap is reserved for the new element, since the caller may hold it nowhere
/// else; overwriting an own property reserves nothing.
pub(crate) fn put_at(interp: &mut Interp, o: ObjId, k: f64, v: Value) -> JsResult<()> {
let k = key_of(k);
if interp.heap.object(o).is_some_and(|obj| obj.props.contains(&k)) {
return interp.put(o, k, v, true);
}
interp.rooted(std::slice::from_ref(&v), |i| {
i.reserve_heap(ELEMENT_BYTES)?;
i.put(o, k.clone(), v.clone(), true)
})
}
fn delete_at(interp: &mut Interp, o: ObjId, k: f64) -> JsResult<()> {
interp.delete(o, &key_of(k), true).map(drop)
}
/// Defines `a[k]` as a writable, enumerable and configurable data property, as the methods that
/// build a new array do (DefineOwnProperty with `throw` false). `v` stays rooted while heap is
/// reserved for it.
fn create_at(interp: &mut Interp, a: ObjId, k: f64, v: Value) -> JsResult<()> {
interp.rooted(std::slice::from_ref(&v), |i| {
i.reserve_heap(ELEMENT_BYTES)?;
i.define_own_property(a, key_of(k), &Desc::data(v.clone(), OPEN), false).map(drop)
})
}
/// A new array with the given length and no elements.
fn new_array(interp: &mut Interp, length: u32) -> JsResult<ObjId> {
interp.reserve_heap(OBJECT_BYTES)?;
let proto = interp.intrinsics.array_proto;
Ok(interp.heap.alloc_object(Object::new(Class::Array { length, length_writable: true }, Some(proto))))
}
/// A new string from code units, checked against the heap limit and charged to the heap.
fn new_string(interp: &mut Interp, units: Vec<u16>) -> JsResult<Value> {
let bytes = string_bytes(units.len());
interp.reserve_heap(bytes)?;
interp.heap.bytes = interp.heap.bytes.saturating_add(bytes);
Ok(Value::String(Str::from_units(units)))
}
/// Code units a built-in may write into a string for one unit of fuel.
const UNITS_PER_FUEL: usize = 16;
/// A string the built-ins build piece by piece: each piece takes fuel for its length, is checked
/// against the longest string the heap limit allows, and has its bytes reserved before it is
/// added. The buffer's bytes count toward the heap until `finish` or until the native call that
/// made the builder returns. Everything the caller still needs must be reachable from the roots.
pub(crate) struct Builder {
units: Vec<u16>,
/// Code units already reserved and charged to the heap.
charged: usize,
/// Code units written since fuel was last taken, fewer than `UNITS_PER_FUEL`.
unpaid: usize,
}
impl Builder {
pub(crate) fn new() -> Self {
Builder { units: Vec::new(), charged: 0, unpaid: 0 }
}
/// Checks that `more` code units fit, reserving heap for them, and takes fuel for writing them.
fn make_room(&mut self, interp: &mut Interp, more: usize) -> JsResult<()> {
let want = self.units.len().saturating_add(more);
if want > interp.limits.heap_bytes / 4 {
return Err(Abrupt::Limit(crate::LimitKind::Heap));
}
self.unpaid += more;
let fuel = (self.unpaid / UNITS_PER_FUEL) as u64;
self.unpaid %= UNITS_PER_FUEL;
if fuel > 0 {
if interp.fuel < fuel {
interp.fuel = 0;
return Err(Abrupt::Limit(crate::LimitKind::Fuel));
}
// The last unit goes through `use_fuel`, which also checks for an interrupt.
interp.fuel -= fuel - 1;
interp.use_fuel()?;
}
if want > self.charged {
let grow = (want - self.charged).max(self.charged / 2).max(256).min((interp.limits.heap_bytes / 4).saturating_sub(self.charged));
interp.reserve_heap(2 * grow)?;
interp.heap.bytes = interp.heap.bytes.saturating_add(2 * grow);
interp.held_bytes = interp.held_bytes.saturating_add(2 * grow);
self.charged += grow;
}
Ok(())
}
pub(crate) fn push_units(&mut self, interp: &mut Interp, units: &[u16]) -> JsResult<()> {
self.make_room(interp, units.len())?;
self.units.extend_from_slice(units);
Ok(())
}
/// Appends `units` `count` times. Nothing is written for an empty `units`, however large
/// `count` is.
pub(crate) fn push_repeated(&mut self, interp: &mut Interp, units: &[u16], count: u64) -> JsResult<()> {
if units.is_empty() || count == 0 {
return Ok(());
}
let more = usize::try_from(count).unwrap_or(usize::MAX).saturating_mul(units.len());
self.make_room(interp, more)?;
for _ in 0..count {
self.units.extend_from_slice(units);
}
Ok(())
}
pub(crate) fn push_str(&mut self, interp: &mut Interp, s: &str) -> JsResult<()> {
let units: Vec<u16> = s.encode_utf16().collect();
self.push_units(interp, &units)
}
/// The string built, whose bytes were charged as it grew. From here on the heap counts them
/// as the string's.
pub(crate) fn finish(self, interp: &mut Interp) -> Value {
interp.held_bytes = interp.held_bytes.saturating_sub(2 * self.charged);
Value::String(Str::from_units(self.units))
}
}
fn require_callable(interp: &mut Interp, v: &Value, what: &str) -> JsResult<()> {
if interp.is_callable(v) {
return Ok(());
}
Err(interp.type_error(&format!("{what} is not a function")))
}
fn install_errors(interp: &mut Interp, object_proto: ObjId) -> JsResult<()> {
let kinds: [(&'static str, NativeFn); 7] = [
("Error", error_ctor),
("EvalError", eval_error_ctor),
("RangeError", range_error_ctor),
("ReferenceError", reference_error_ctor),
("SyntaxError", syntax_error_ctor),
("TypeError", type_error_ctor),
("URIError", uri_error_ctor),
];
let mut error_proto = object_proto;
for (i, (name, f)) in kinds.into_iter().enumerate() {
// Error.prototype and each NativeError prototype are Error objects (sections 15.11.4 and
// 15.11.7.7).
let proto = alloc(interp, Class::Error, Some(if i == 0 { object_proto } else { error_proto }));
if i == 0 {
error_proto = proto;
method(interp, proto, "toString", error_to_string, 0)?;
}
if let Some(slot) = interp.intrinsics.error_protos.get_mut(i) {
*slot = proto;
}
data(interp, proto, "name", Value::str(name), HIDDEN)?;
data(interp, proto, "message", Value::str(""), HIDDEN)?;
let ctor = native(interp, name, f, 1, NativeKind::Plain, true)?;
constructor(interp, name, ctor, proto)?;
}
Ok(())
}
pub(crate) fn arg(args: &[Value], i: usize) -> Value {
args.get(i).cloned().unwrap_or(Value::Undefined)
}
fn unreachable_native(_: &mut Interp, _: Value, _: &[Value], _: bool) -> JsResult<Value> {
Err(Abrupt::Internal("call or apply reached its native body".into()))
}
fn throw_type_error(interp: &mut Interp, _: Value, _: &[Value], _: bool) -> JsResult<Value> {
Err(interp.type_error("'caller', 'callee' and 'arguments' cannot be used on strict mode functions"))
}
fn function_proto_call(_: &mut Interp, _: Value, _: &[Value], _: bool) -> JsResult<Value> {
Ok(Value::Undefined)
}
// ---- Object ----
/// `Object(value)` and `new Object(value)` (sections 15.2.1 and 15.2.2).
fn object_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
let v = arg(args, 0);
if v.is_nullish() {
return Ok(Value::Object(interp.new_object()));
}
interp.to_object(&v).map(Value::Object)
}
/// `Object.prototype.toString` (section 15.2.4.2).
fn object_to_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
// With `$.localize` on, a localization object gives its text for the current locale.
if interp.localization.enabled()
&& let Value::Object(id) = this
&& let Some(text) = extensions::localized_text(interp, id)?
{
return Ok(text);
}
let class = match &this {
Value::Undefined => "Undefined",
Value::Null => "Null",
_ => {
let o = interp.to_object(&this)?;
interp.heap.object(o).map_or("Object", |o| o.class.name())
}
};
Ok(Value::str(&format!("[object {class}]")))
}
/// The Boolean a `Boolean.prototype` method was called on (sections 15.6.4.2 and 15.6.4.3): a
/// primitive, or the primitive value of a Boolean object.
fn this_boolean(interp: &mut Interp, this: &Value, name: &str) -> JsResult<bool> {
let b = match this {
Value::Bool(b) => Some(*b),
Value::Object(o) => match interp.heap.object(*o).map(|x| &x.class) {
Some(Class::Boolean(b)) => Some(*b),
_ => None,
},
_ => None,
};
b.ok_or_else(|| interp.type_error(&format!("Boolean.prototype.{name} was called on a value that is not a Boolean")))
}
/// `Boolean.prototype.toString` (section 15.6.4.2).
fn boolean_to_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
let b = this_boolean(interp, &this, "toString")?;
Ok(Value::str(if b { "true" } else { "false" }))
}
/// `Boolean.prototype.valueOf` (section 15.6.4.3).
fn boolean_value_of(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
this_boolean(interp, &this, "valueOf").map(Value::Bool)
}
/// `Object.prototype.toLocaleString` (section 15.2.4.3).
fn object_to_locale_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
let o = interp.to_object(&this)?;
let receiver = Value::Object(o);
let f = interp.get_from(o, &key("toString"), &receiver)?;
if !interp.is_callable(&f) {
return Err(interp.type_error("toString is not a function"));
}
interp.call(&f, &receiver, &[])
}
fn object_value_of(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
interp.to_object(&this).map(Value::Object)
}
fn has_own_property(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let k = interp.to_key(&arg(args, 0))?;
let o = interp.to_object(&this)?;
Ok(Value::Bool(interp.get_own_property(o, &k)?.is_some()))
}
fn is_prototype_of(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let Value::Object(mut v) = arg(args, 0) else { return Ok(Value::Bool(false)) };
let o = interp.to_object(&this)?;
while let Some(p) = interp.heap.object(v).and_then(|v| v.proto) {
if p == o {
return Ok(Value::Bool(true));
}
v = p;
interp.use_fuel()?;
}
Ok(Value::Bool(false))
}
fn property_is_enumerable(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let k = interp.to_key(&arg(args, 0))?;
let o = interp.to_object(&this)?;
Ok(Value::Bool(interp.get_own_property(o, &k)?.is_some_and(|p| p.enumerable())))
}
// ---- Function ----
/// `Function(p1, …, body)` and `new Function(…)` (section 15.3.2.1): the source is read as
/// `(function anonymous(p1,…\n) {\nbody\n})` and must be exactly that one function.
fn function_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
const HEAD: &str = "(function anonymous(";
const MID: &str = "\n) {\n";
const TAIL: &str = "\n})";
let mut parts = Vec::with_capacity(args.len());
for a in args {
parts.push(interp.to_str(a)?);
}
let body = parts.pop().unwrap_or_else(|| Str::from(""));
// The length is checked against the source limit before any argument is flattened or copied.
let total = parts
.iter()
.map(Str::len)
.chain([parts.len().saturating_sub(1), HEAD.len() + MID.len() + TAIL.len(), body.len()])
.fold(0usize, usize::saturating_add);
interp.check_source_units(total).map_err(|e| interp.compile_error_to_abrupt(e))?;
interp.prepare_source(total)?;
let mut units = Vec::with_capacity(total);
units.extend(HEAD.encode_utf16());
for (i, p) in parts.iter().enumerate() {
if i > 0 {
units.push(u16::from(b','));
}
units.extend_from_slice(&p.flat());
}
units.extend(MID.encode_utf16());
units.extend_from_slice(&body.flat());
units.extend(TAIL.encode_utf16());
interp.source_units = interp.source_units.saturating_add(units.len());
let remaining = interp.options.max_source_units.saturating_sub(interp.source_units.saturating_sub(units.len()));
let options = crate::Options { strict: false, max_source_units: remaining, ..interp.options.clone() };
let program = match crate::parse_utf16(&units, &options) {
Ok(p) => p,
Err(e) => return Err(interp.throw(ErrorType::Syntax, &e.message)),
};
let source = Rc::new(Source { lines: program.lines.clone(), text: units.into(), tag: interp.source_tag, strict_writes: interp.writes_mode() });
let code = match compile_constructed_function(&program, &source) {
Ok(Some(code)) => code,
Ok(None) => return Err(interp.throw(ErrorType::Syntax, "the parameters or body do not form one function")),
Err(CompileFail::Syntax(e)) => return Err(interp.throw(ErrorType::Syntax, &e.message)),
Err(CompileFail::Internal(m)) => return Err(Abrupt::Internal(m)),
};
interp.charge_code(&code, 0)?;
let env = interp.global_env;
interp.make_closure(code, env).map(Value::Object)
}
/// `Function.prototype.toString` (section 15.3.4.2): a function's source text, or a stand-in
/// for a built-in.
fn function_to_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
let func = this.as_object().and_then(|o| interp.heap.object(o)).and_then(|o| match &o.class {
Class::Function(f) => Some(f.clone()),
_ => None,
});
let text = match func {
Some(Func::Closure { code, .. }) => {
let (start, end) = (code.span.0 as usize, code.span.1 as usize);
let units = code.source.text.get(start..end).unwrap_or_default();
let bytes = string_bytes(units.len());
interp.reserve_heap(bytes)?;
interp.heap.bytes = interp.heap.bytes.saturating_add(bytes);
return Ok(Value::String(Str::from_units(units.to_vec())));
}
Some(Func::Native { name, .. }) => format!("function {name}() {{ [native code] }}"),
Some(Func::Host { name, .. } | Func::Data { name, .. } | Func::Eval { name, .. }) => format!("function {name}() {{ [native code] }}"),
Some(Func::Bound { .. }) => "function () { [native code] }".to_string(),
None => return Err(interp.type_error("Function.prototype.toString was called on a value that is not a function")),
};
Ok(Value::str(&text))
}
/// `Function.prototype.bind` (section 15.3.4.5).
fn function_bind(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
require_callable(interp, &this, "the value bind was called on")?;
let Some(target) = this.as_object() else { return Err(interp.type_error("the value bind was called on is not a function")) };
let bound_this = arg(args, 0);
let bound_args: Rc<[Value]> = args.get(1..).unwrap_or_default().into();
// A host function's `length` is an ordinary property a script can set, so it is converted
// to an integer first, as later editions specify.
let target_length = match interp.get_from(target, &key("length"), &this)? {
Value::Number(n) if n.is_nan() => 0.0,
Value::Number(n) => n.trunc(),
_ => 0.0,
};
// Adding 0 turns a negative zero into 0.
let length = (target_length - bound_args.len() as f64).max(0.0) + 0.0;
interp.reserve_heap(OBJECT_BYTES + 4 * PROP_BYTES + BOUND_ARG_BYTES * bound_args.len())?;
let proto = interp.intrinsics.function_proto;
let class = Class::Function(Func::Bound { target, this: bound_this, args: bound_args.clone() });
let f = interp.heap.alloc_object(Object::new(class, Some(proto)));
interp.heap.bytes = interp.heap.bytes.saturating_add(BOUND_ARG_BYTES * bound_args.len());
interp.define(f, key("length"), Prop::Data { value: Value::Number(length), flags: 0 })?;
let thrower = Some(interp.intrinsics.throw_type_error);
for name in ["caller", "arguments"] {
interp.define(f, key(name), Prop::Accessor { get: thrower, set: thrower, flags: 0 })?;
}
Ok(Value::Object(f))
}
/// The global `eval` called indirectly (section 15.1.2.1.1): global code in the global
/// environment.
fn eval(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
let x = arg(args, 0);
let Value::String(s) = &x else { return Ok(x) };
interp.check_source_units(s.len()).map_err(|e| interp.compile_error_to_abrupt(e))?;
interp.prepare_source(s.len())?;
let units = s.flat();
let code = interp.compile_source(&units, false, CodeKind::Eval).map_err(|e| interp.compile_error_to_abrupt(e))?;
interp.run_global(code, true)
}
// ---- errors ----
fn make_error(interp: &mut Interp, t: ErrorType, args: &[Value]) -> JsResult<Value> {
let message = arg(args, 0);
let text = if matches!(message, Value::Undefined) { None } else { Some(interp.to_str(&message)?) };
let o = interp.error_object(t, "");
if let Some(text) = text {
interp.define(o, key("message"), Prop::Data { value: Value::String(text), flags: HIDDEN })?;
}
Ok(Value::Object(o))
}
fn error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Error, args)
}
fn eval_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Eval, args)
}
fn range_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Range, args)
}
fn reference_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Reference, args)
}
fn syntax_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Syntax, args)
}
fn type_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Type, args)
}
fn uri_error_ctor(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
make_error(interp, ErrorType::Uri, args)
}
/// `Error.prototype.toString` (section 15.11.4.4).
fn error_to_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
let Value::Object(o) = this else { return Err(interp.type_error("Error.prototype.toString was called on a value that is not an object")) };
let name = interp.get_from(o, &key("name"), &this)?;
let name = if matches!(name, Value::Undefined) { Str::from("Error") } else { interp.to_str(&name)? };
let message = interp.get_from(o, &key("message"), &this)?;
let message = if matches!(message, Value::Undefined) { Str::from("") } else { interp.to_str(&message)? };
if name.len() == 0 {
return Ok(Value::String(message));
}
if message.len() == 0 {
return Ok(Value::String(name));
}
let sep = Str::from(": ");
let head = interp.concat(&name, &sep)?;
interp.concat(&head, &message).map(Value::String)
}