Browse Source

Implement runtime limits for loops (#2857)

pull/2905/head
Haled Odat 2 years ago committed by GitHub
parent
commit
802d796d51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      boa_cli/src/debug/limits.rs
  2. 7
      boa_cli/src/debug/mod.rs
  3. 21
      boa_engine/src/builtins/array/tests.rs
  4. 48
      boa_engine/src/builtins/bigint/tests.rs
  5. 4
      boa_engine/src/builtins/date/tests.rs
  6. 11
      boa_engine/src/builtins/function/tests.rs
  7. 4
      boa_engine/src/builtins/json/tests.rs
  8. 4
      boa_engine/src/builtins/map/tests.rs
  9. 13
      boa_engine/src/builtins/number/tests.rs
  10. 40
      boa_engine/src/builtins/object/tests.rs
  11. 32
      boa_engine/src/builtins/regexp/tests.rs
  12. 4
      boa_engine/src/builtins/set/tests.rs
  13. 28
      boa_engine/src/builtins/string/tests.rs
  14. 2
      boa_engine/src/bytecompiler/statement/loop.rs
  15. 31
      boa_engine/src/context/mod.rs
  16. 8
      boa_engine/src/environments/tests.rs
  17. 101
      boa_engine/src/error.rs
  18. 4
      boa_engine/src/lib.rs
  19. 4
      boa_engine/src/object/tests.rs
  20. 14
      boa_engine/src/realm.rs
  21. 10
      boa_engine/src/tests/control_flow/loops.rs
  22. 8
      boa_engine/src/tests/control_flow/mod.rs
  23. 16
      boa_engine/src/tests/function.rs
  24. 16
      boa_engine/src/tests/mod.rs
  25. 60
      boa_engine/src/tests/operators.rs
  26. 4
      boa_engine/src/tests/spread.rs
  27. 4
      boa_engine/src/value/tests.rs
  28. 21
      boa_engine/src/vm/call_frame/env_stack.rs
  29. 1
      boa_engine/src/vm/flowgraph/mod.rs
  30. 24
      boa_engine/src/vm/mod.rs
  31. 16
      boa_engine/src/vm/opcode/call/mod.rs
  32. 65
      boa_engine/src/vm/opcode/iteration/loop_ops.rs
  33. 8
      boa_engine/src/vm/opcode/new/mod.rs
  34. 61
      boa_engine/src/vm/runtime_limits.rs
  35. 39
      boa_engine/src/vm/tests.rs
  36. 47
      boa_examples/src/bin/runtime_limits.rs
  37. 19
      docs/boa_object.md

35
boa_cli/src/debug/limits.rs

@ -0,0 +1,35 @@
use boa_engine::{
object::{FunctionObjectBuilder, ObjectInitializer},
property::Attribute,
Context, JsArgs, JsObject, JsResult, JsValue, NativeFunction,
};
fn get_loop(_: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
let max = context.runtime_limits().loop_iteration_limit();
Ok(JsValue::from(max))
}
fn set_loop(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
let value = args.get_or_undefined(0).to_length(context)?;
context.runtime_limits_mut().set_loop_iteration_limit(value);
Ok(JsValue::undefined())
}
pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
let get_loop = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_loop))
.name("get loop")
.length(0)
.build();
let set_loop = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_loop))
.name("set loop")
.length(1)
.build();
ObjectInitializer::new(context)
.accessor(
"loop",
Some(get_loop),
Some(set_loop),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.build()
}

7
boa_cli/src/debug/mod.rs

@ -5,6 +5,7 @@ use boa_engine::{object::ObjectInitializer, property::Attribute, Context, JsObje
mod function;
mod gc;
mod limits;
mod object;
mod optimizer;
mod realm;
@ -17,6 +18,7 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject {
let optimizer_module = optimizer::create_object(context);
let gc_module = gc::create_object(context);
let realm_module = realm::create_object(context);
let limits_module = limits::create_object(context);
ObjectInitializer::new(context)
.property(
@ -49,6 +51,11 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject {
realm_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
"limits",
limits_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build()
}

21
boa_engine/src/builtins/array/tests.rs

@ -1,8 +1,5 @@
use super::Array;
use crate::{
builtins::{error::ErrorKind, Number},
run_test_actions, Context, JsValue, TestAction,
};
use crate::{builtins::Number, run_test_actions, Context, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -191,7 +188,7 @@ fn flat_map_not_callable() {
var array = [1,2,3];
array.flatMap("not a function");
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"flatMap mapper function is not callable",
)]);
}
@ -639,7 +636,7 @@ fn reduce() {
// Empty array
TestAction::assert_native_error(
"[].reduce((acc, x) => acc + x);",
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduce: called on an empty array and with no initial value",
),
// Array with no defined elements
@ -650,7 +647,7 @@ fn reduce() {
delete deleteArr[1];
deleteArr.reduce((acc, x) => acc + x);
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduce: called on an empty array and with no initial value",
),
// No callback
@ -659,7 +656,7 @@ fn reduce() {
var someArr = [0, 1];
someArr.reduce('');
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduce: callback function is not callable",
),
]);
@ -720,7 +717,7 @@ fn reduce_right() {
// Empty array
TestAction::assert_native_error(
"[].reduceRight((acc, x) => acc + x);",
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduceRight: called on an empty array and with no initial value",
),
// Array with no defined elements
@ -731,7 +728,7 @@ fn reduce_right() {
delete deleteArr[1];
deleteArr.reduceRight((acc, x) => acc + x);
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduceRight: called on an empty array and with no initial value",
),
// No callback
@ -740,7 +737,7 @@ fn reduce_right() {
var otherArr = [0, 1];
otherArr.reduceRight("");
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Array.prototype.reduceRight: callback function is not callable",
),
]);
@ -865,7 +862,7 @@ fn array_spread_arrays() {
fn array_spread_non_iterable() {
run_test_actions([TestAction::assert_native_error(
"const array2 = [...5];",
ErrorKind::Type,
JsNativeErrorKind::Type,
"value with type `number` is not iterable",
)]);
}

48
boa_engine/src/builtins/bigint/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsBigInt, TestAction};
use crate::{run_test_actions, JsBigInt, JsNativeErrorKind, TestAction};
#[test]
fn equality() {
@ -62,17 +62,17 @@ fn bigint_function_throws() {
run_test_actions([
TestAction::assert_native_error(
"BigInt(0.1)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"cannot convert 0.1 to a BigInt",
),
TestAction::assert_native_error(
"BigInt(null)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"cannot convert null to a BigInt",
),
TestAction::assert_native_error(
"BigInt(undefined)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"cannot convert undefined to a BigInt",
),
]);
@ -108,29 +108,37 @@ fn operations() {
),
TestAction::assert_eq("15000n / 50n", JsBigInt::from(300)),
TestAction::assert_eq("15001n / 50n", JsBigInt::from(300)),
TestAction::assert_native_error("1n/0n", ErrorKind::Range, "BigInt division by zero"),
TestAction::assert_native_error(
"1n/0n",
JsNativeErrorKind::Range,
"BigInt division by zero",
),
TestAction::assert_eq("15007n % 10n", JsBigInt::from(7)),
TestAction::assert_native_error("1n % 0n", ErrorKind::Range, "BigInt division by zero"),
TestAction::assert_native_error(
"1n % 0n",
JsNativeErrorKind::Range,
"BigInt division by zero",
),
TestAction::assert_eq(
"100n ** 10n",
JsBigInt::from_string("100000000000000000000").unwrap(),
),
TestAction::assert_native_error(
"10n ** (-10n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"BigInt negative exponent",
),
TestAction::assert_eq("8n << 2n", JsBigInt::from(32)),
TestAction::assert_native_error(
"1000n << 1000000000000000n",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Maximum BigInt size exceeded",
),
TestAction::assert_eq("8n >> 2n", JsBigInt::from(2)),
// TODO: this should return 0n instead of throwing
TestAction::assert_native_error(
"1000n >> 1000000000000000n",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Maximum BigInt size exceeded",
),
]);
@ -151,17 +159,17 @@ fn to_string_invalid_radix() {
run_test_actions([
TestAction::assert_native_error(
"10n.toString(null)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"radix must be an integer at least 2 and no greater than 36",
),
TestAction::assert_native_error(
"10n.toString(-1)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"radix must be an integer at least 2 and no greater than 36",
),
TestAction::assert_native_error(
"10n.toString(37)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"radix must be an integer at least 2 and no greater than 36",
),
]);
@ -219,22 +227,22 @@ fn as_int_n_errors() {
run_test_actions([
TestAction::assert_native_error(
"BigInt.asIntN(-1, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asIntN(-2.5, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asIntN(9007199254740992, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asIntN(0n, 0n)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"argument must not be a bigint",
),
]);
@ -292,22 +300,22 @@ fn as_uint_n_errors() {
run_test_actions([
TestAction::assert_native_error(
"BigInt.asUintN(-1, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asUintN(-2.5, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asUintN(9007199254740992, 0n)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"Index must be between 0 and 2^53 - 1",
),
TestAction::assert_native_error(
"BigInt.asUintN(0n, 0n)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"argument must not be a bigint",
),
]);

4
boa_engine/src/builtins/date/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
use indoc::indoc;
@ -47,7 +47,7 @@ fn timestamp_from_utc(
fn date_this_time_value() {
run_test_actions([TestAction::assert_native_error(
"({toString: Date.prototype.toString}).toString()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"'this' is not a Date",
)]);
}

11
boa_engine/src/builtins/function/tests.rs

@ -1,11 +1,10 @@
use crate::{
builtins::error::ErrorKind,
error::JsNativeError,
js_string,
native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor},
run_test_actions, JsValue, TestAction,
run_test_actions, JsNativeErrorKind, JsValue, TestAction,
};
use indoc::indoc;
@ -60,7 +59,7 @@ fn function_prototype() {
),
TestAction::assert_native_error(
"new Function.prototype()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"not a constructor",
),
]);
@ -81,7 +80,7 @@ fn function_prototype_call_throw() {
let call = Function.prototype.call;
call(call)
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"undefined is not a function",
)]);
}
@ -181,12 +180,12 @@ fn function_constructor_early_errors_super() {
run_test_actions([
TestAction::assert_native_error(
"Function('super()')()",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"invalid `super` call",
),
TestAction::assert_native_error(
"Function('super.a')()",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"invalid `super` reference",
),
]);

4
boa_engine/src/builtins/json/tests.rs

@ -1,6 +1,6 @@
use indoc::indoc;
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
#[test]
fn json_sanity() {
@ -307,7 +307,7 @@ fn json_fields_should_be_enumerable() {
fn json_parse_with_no_args_throws_syntax_error() {
run_test_actions([TestAction::assert_native_error(
"JSON.parse();",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"expected value at line 1 column 1",
)]);
}

4
boa_engine/src/builtins/map/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -242,7 +242,7 @@ fn recursive_display() {
fn not_a_function() {
run_test_actions([TestAction::assert_native_error(
"let map = Map()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"calling a builtin Map constructor without new is forbidden",
)]);
}

13
boa_engine/src/builtins/number/tests.rs

@ -1,10 +1,7 @@
#![allow(clippy::float_cmp)]
use crate::{
builtins::{error::ErrorKind, Number},
run_test_actions,
value::AbstractRelation,
TestAction,
builtins::Number, run_test_actions, value::AbstractRelation, JsNativeErrorKind, TestAction,
};
#[test]
@ -81,10 +78,10 @@ fn to_precision() {
"(1/3).toPrecision(60)",
"0.333333333333333314829616256247390992939472198486328125000000",
),
TestAction::assert_native_error("(1).toPrecision(101)", ErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(0)", ErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(-2000)", ErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision('%')", ErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(101)", JsNativeErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(0)", JsNativeErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision(-2000)", JsNativeErrorKind::Range, ERROR),
TestAction::assert_native_error("(1).toPrecision('%')", JsNativeErrorKind::Range, ERROR),
]);
}

40
boa_engine/src/builtins/object/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -15,7 +15,7 @@ fn object_create_with_regular_object() {
fn object_create_with_undefined() {
run_test_actions([TestAction::assert_native_error(
"Object.create()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"Object prototype may only be an Object or null: undefined",
)]);
}
@ -24,7 +24,7 @@ fn object_create_with_undefined() {
fn object_create_with_number() {
run_test_actions([TestAction::assert_native_error(
"Object.create(5)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"Object prototype may only be an Object or null: 5",
)]);
}
@ -238,11 +238,19 @@ fn object_get_own_property_names_invalid_args() {
const ERROR: &str = "cannot convert 'null' or 'undefined' to object";
run_test_actions([
TestAction::assert_native_error("Object.getOwnPropertyNames()", ErrorKind::Type, ERROR),
TestAction::assert_native_error("Object.getOwnPropertyNames(null)", ErrorKind::Type, ERROR),
TestAction::assert_native_error(
"Object.getOwnPropertyNames()",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error(
"Object.getOwnPropertyNames(null)",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error(
"Object.getOwnPropertyNames(undefined)",
ErrorKind::Type,
JsNativeErrorKind::Type,
ERROR,
),
]);
@ -307,15 +315,19 @@ fn object_get_own_property_symbols_invalid_args() {
const ERROR: &str = "cannot convert 'null' or 'undefined' to object";
run_test_actions([
TestAction::assert_native_error("Object.getOwnPropertySymbols()", ErrorKind::Type, ERROR),
TestAction::assert_native_error(
"Object.getOwnPropertySymbols()",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error(
"Object.getOwnPropertySymbols(null)",
ErrorKind::Type,
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error(
"Object.getOwnPropertySymbols(undefined)",
ErrorKind::Type,
JsNativeErrorKind::Type,
ERROR,
),
]);
@ -382,9 +394,13 @@ fn object_from_entries_invalid_args() {
const ERROR: &str = "cannot convert null or undefined to Object";
run_test_actions([
TestAction::assert_native_error("Object.fromEntries()", ErrorKind::Type, ERROR),
TestAction::assert_native_error("Object.fromEntries(null)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("Object.fromEntries(undefined)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("Object.fromEntries()", JsNativeErrorKind::Type, ERROR),
TestAction::assert_native_error("Object.fromEntries(null)", JsNativeErrorKind::Type, ERROR),
TestAction::assert_native_error(
"Object.fromEntries(undefined)",
JsNativeErrorKind::Type,
ERROR,
),
]);
}

32
boa_engine/src/builtins/regexp/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, object::JsObject, run_test_actions, JsValue, TestAction};
use crate::{object::JsObject, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -120,12 +120,12 @@ fn no_panic_on_parse_fail() {
run_test_actions([
TestAction::assert_native_error(
r"var re = /]/u;",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid regular expression literal: Unbalanced bracket at line 1, col 10",
),
TestAction::assert_native_error(
r"var re = /a{/u;",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid regular expression literal: Invalid quantifier at line 1, col 10",
),
]);
@ -182,13 +182,25 @@ fn search() {
4,
),
// this-val-non-obj
TestAction::assert_native_error("search.value.call()", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(undefined)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(null)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(true)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call('string')", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(Symbol.search)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(86)", ErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call()", JsNativeErrorKind::Type, ERROR),
TestAction::assert_native_error(
"search.value.call(undefined)",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error("search.value.call(null)", JsNativeErrorKind::Type, ERROR),
TestAction::assert_native_error("search.value.call(true)", JsNativeErrorKind::Type, ERROR),
TestAction::assert_native_error(
"search.value.call('string')",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error(
"search.value.call(Symbol.search)",
JsNativeErrorKind::Type,
ERROR,
),
TestAction::assert_native_error("search.value.call(86)", JsNativeErrorKind::Type, ERROR),
// u-lastindex-advance
TestAction::assert_eq(r"/\udf06/u[Symbol.search]('\ud834\udf06')", -1),
TestAction::assert_eq("/a/[Symbol.search](\"a\")", 0),

4
boa_engine/src/builtins/set/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
use indoc::indoc;
#[test]
@ -170,7 +170,7 @@ fn recursive_display() {
fn not_a_function() {
run_test_actions([TestAction::assert_native_error(
"Set()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"calling a builtin Set constructor without new is forbidden",
)]);
}

28
boa_engine/src/builtins/string/tests.rs

@ -1,6 +1,6 @@
use indoc::indoc;
use crate::{builtins::error::ErrorKind, js_string, run_test_actions, JsValue, TestAction};
use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction};
#[test]
fn length() {
@ -104,7 +104,7 @@ fn repeat() {
fn repeat_throws_when_count_is_negative() {
run_test_actions([TestAction::assert_native_error(
"'x'.repeat(-1)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"repeat count must be a positive finite number \
that doesn't overflow the maximum string length (2^32 - 1)",
)]);
@ -114,7 +114,7 @@ fn repeat_throws_when_count_is_negative() {
fn repeat_throws_when_count_is_infinity() {
run_test_actions([TestAction::assert_native_error(
"'x'.repeat(Infinity)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"repeat count must be a positive finite number \
that doesn't overflow the maximum string length (2^32 - 1)",
)]);
@ -124,7 +124,7 @@ fn repeat_throws_when_count_is_infinity() {
fn repeat_throws_when_count_overflows_max_length() {
run_test_actions([TestAction::assert_native_error(
"'x'.repeat(2 ** 64)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"repeat count must be a positive finite number \
that doesn't overflow the maximum string length (2^32 - 1)",
)]);
@ -247,7 +247,7 @@ fn starts_with() {
fn starts_with_with_regex_arg() {
run_test_actions([TestAction::assert_native_error(
"'Saturday night'.startsWith(/Saturday/)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"First argument to String.prototype.startsWith must not be a regular expression",
)]);
}
@ -273,7 +273,7 @@ fn ends_with() {
fn ends_with_with_regex_arg() {
run_test_actions([TestAction::assert_native_error(
"'Saturday night'.endsWith(/night/)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"First argument to String.prototype.endsWith must not be a regular expression",
)]);
}
@ -299,7 +299,7 @@ fn includes() {
fn includes_with_regex_arg() {
run_test_actions([TestAction::assert_native_error(
"'Saturday night'.includes(/day/)",
ErrorKind::Type,
JsNativeErrorKind::Type,
"First argument to String.prototype.includes must not be a regular expression",
)]);
}
@ -589,7 +589,7 @@ fn split_with_symbol_split_method() {
sep[Symbol.split] = 10;
'hello'.split(sep, 10);
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"value returned for property of object is not a function",
),
]);
@ -880,32 +880,32 @@ fn from_code_point() {
TestAction::assert_eq("String.fromCodePoint(9731, 9733, 9842, 0x4F60)", "☃★♲你"),
TestAction::assert_native_error(
"String.fromCodePoint('_')",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `NaN` is not an integer",
),
TestAction::assert_native_error(
"String.fromCodePoint(Infinity)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `inf` is not an integer",
),
TestAction::assert_native_error(
"String.fromCodePoint(-1)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `-1` outside of Unicode range",
),
TestAction::assert_native_error(
"String.fromCodePoint(3.14)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `3.14` is not an integer",
),
TestAction::assert_native_error(
"String.fromCodePoint(3e-2)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `0.03` is not an integer",
),
TestAction::assert_native_error(
"String.fromCodePoint(NaN)",
ErrorKind::Range,
JsNativeErrorKind::Range,
"codepoint `NaN` is not an integer",
),
]);

2
boa_engine/src/bytecompiler/statement/loop.rs

@ -56,6 +56,7 @@ impl ByteCompiler<'_, '_> {
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);
@ -345,6 +346,7 @@ impl ByteCompiler<'_, '_> {
let start_address = self.next_opcode_location();
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.push_loop_control_info(label, start_address);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);

31
boa_engine/src/context/mod.rs

@ -40,6 +40,8 @@ use boa_interner::{Interner, Sym};
use boa_parser::{Error as ParseError, Parser};
use boa_profiler::Profiler;
use crate::vm::RuntimeLimits;
/// ECMAScript context. It is the primary way to interact with the runtime.
///
/// `Context`s constructed in a thread share the same runtime, therefore it
@ -548,30 +550,36 @@ impl<'host> Context<'host> {
/// Set the value of trace on the context
#[cfg(feature = "trace")]
#[inline]
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
/// Get optimizer options.
#[inline]
pub const fn optimizer_options(&self) -> OptimizerOptions {
self.optimizer_options
}
/// Enable or disable optimizations
#[inline]
pub fn set_optimizer_options(&mut self, optimizer_options: OptimizerOptions) {
self.optimizer_options = optimizer_options;
}
/// Changes the strictness mode of the context.
#[inline]
pub fn strict(&mut self, strict: bool) {
self.strict = strict;
}
/// Enqueues a [`NativeJob`] on the [`JobQueue`].
#[inline]
pub fn enqueue_job(&mut self, job: NativeJob) {
self.job_queue().enqueue_promise_job(job, self);
}
/// Runs all the jobs in the job queue.
#[inline]
pub fn run_jobs(&mut self) {
self.job_queue().run_jobs(self);
self.clear_kept_objects();
@ -585,16 +593,19 @@ impl<'host> Context<'host> {
/// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
/// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
/// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
#[inline]
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
/// Retrieves the current stack trace of the context.
#[inline]
pub fn stack_trace(&mut self) -> impl Iterator<Item = &CallFrame> {
self.vm.frames.iter().rev()
}
/// Replaces the currently active realm with `realm`, and returns the old realm.
#[inline]
pub fn enter_realm(&mut self, realm: Realm) -> Realm {
self.vm
.environments
@ -607,14 +618,34 @@ impl<'host> Context<'host> {
}
/// Gets the host hooks.
#[inline]
pub fn host_hooks(&self) -> MaybeShared<'host, dyn HostHooks> {
self.host_hooks.clone()
}
/// Gets the job queue.
#[inline]
pub fn job_queue(&self) -> MaybeShared<'host, dyn JobQueue> {
self.job_queue.clone()
}
/// Get the [`RuntimeLimits`].
#[inline]
pub const fn runtime_limits(&self) -> RuntimeLimits {
self.vm.runtime_limits
}
/// Set the [`RuntimeLimits`].
#[inline]
pub fn set_runtime_limits(&mut self, runtime_limits: RuntimeLimits) {
self.vm.runtime_limits = runtime_limits;
}
/// Get a mutable reference to the [`RuntimeLimits`].
#[inline]
pub fn runtime_limits_mut(&mut self) -> &mut RuntimeLimits {
&mut self.vm.runtime_limits
}
}
// ==== Private API ====

8
boa_engine/src/environments/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
use indoc::indoc;
#[test]
@ -10,7 +10,7 @@ fn let_is_block_scoped() {
}
bar;
"#},
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"bar is not defined",
)]);
}
@ -24,7 +24,7 @@ fn const_is_block_scoped() {
}
bar;
"#},
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"bar is not defined",
)]);
}
@ -51,7 +51,7 @@ fn functions_use_declaration_scope() {
foo();
}
"#},
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"bar is not defined",
)]);
}

101
boa_engine/src/error.rs

@ -44,7 +44,7 @@ use thiserror::Error;
/// let kind = &native_error.as_native().unwrap().kind;
/// assert!(matches!(kind, JsNativeErrorKind::Type));
/// ```
#[derive(Debug, Clone, Trace, Finalize)]
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
pub struct JsError {
inner: Repr,
}
@ -59,7 +59,7 @@ pub struct JsError {
/// This should never be used outside of this module. If that's not the case,
/// you should add methods to either `JsError` or `JsNativeError` to
/// represent that special use case.
#[derive(Debug, Clone, Trace, Finalize)]
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
enum Repr {
Native(JsNativeError),
Opaque(JsValue),
@ -417,7 +417,7 @@ impl std::fmt::Display for JsError {
///
/// assert_eq!(native_error.message(), "cannot decode uri");
/// ```
#[derive(Clone, Trace, Finalize, Error)]
#[derive(Clone, Trace, Finalize, Error, PartialEq, Eq)]
#[error("{kind}: {message}")]
pub struct JsNativeError {
/// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.)
@ -468,10 +468,17 @@ impl JsNativeError {
/// ));
/// ```
#[must_use]
#[inline]
pub fn aggregate(errors: Vec<JsError>) -> Self {
Self::new(JsNativeErrorKind::Aggregate(errors), Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Aggregate`].
#[inline]
pub const fn is_aggregate(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Aggregate(_))
}
/// Creates a new `JsNativeError` of kind `Error`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -483,10 +490,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Error));
/// ```
#[must_use]
#[inline]
pub fn error() -> Self {
Self::new(JsNativeErrorKind::Error, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Error`].
#[inline]
pub const fn is_error(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Error)
}
/// Creates a new `JsNativeError` of kind `EvalError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -498,10 +512,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Eval));
/// ```
#[must_use]
#[inline]
pub fn eval() -> Self {
Self::new(JsNativeErrorKind::Eval, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Eval`].
#[inline]
pub const fn is_eval(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Eval)
}
/// Creates a new `JsNativeError` of kind `RangeError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -513,10 +534,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Range));
/// ```
#[must_use]
#[inline]
pub fn range() -> Self {
Self::new(JsNativeErrorKind::Range, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Range`].
#[inline]
pub const fn is_range(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Range)
}
/// Creates a new `JsNativeError` of kind `ReferenceError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -528,10 +556,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Reference));
/// ```
#[must_use]
#[inline]
pub fn reference() -> Self {
Self::new(JsNativeErrorKind::Reference, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Reference`].
#[inline]
pub const fn is_reference(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Reference)
}
/// Creates a new `JsNativeError` of kind `SyntaxError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -543,10 +578,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Syntax));
/// ```
#[must_use]
#[inline]
pub fn syntax() -> Self {
Self::new(JsNativeErrorKind::Syntax, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Syntax`].
#[inline]
pub const fn is_syntax(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Syntax)
}
/// Creates a new `JsNativeError` of kind `TypeError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -558,10 +600,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Type));
/// ```
#[must_use]
#[inline]
pub fn typ() -> Self {
Self::new(JsNativeErrorKind::Type, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Type`].
#[inline]
pub const fn is_type(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Type)
}
/// Creates a new `JsNativeError` of kind `UriError`, with empty `message` and undefined `cause`.
///
/// # Examples
@ -573,10 +622,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Uri));
/// ```
#[must_use]
#[inline]
pub fn uri() -> Self {
Self::new(JsNativeErrorKind::Uri, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::Uri`].
#[inline]
pub const fn is_uri(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Uri)
}
/// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This
/// is only used in a fuzzing context.
#[cfg(feature = "fuzz")]
@ -589,6 +645,26 @@ impl JsNativeError {
)
}
/// Check if it's a [`JsNativeErrorKind::NoInstructionsRemain`].
#[inline]
#[cfg(feature = "fuzz")]
pub const fn is_no_instructions_remain(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::NoInstructionsRemain)
}
/// Creates a new `JsNativeError` that indicates that the context exceeded the runtime limits.
#[must_use]
#[inline]
pub fn runtime_limit() -> Self {
Self::new(JsNativeErrorKind::RuntimeLimit, Box::default(), None)
}
/// Check if it's a [`JsNativeErrorKind::RuntimeLimit`].
#[inline]
pub const fn is_runtime_limit(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::RuntimeLimit)
}
/// Sets the message of this error.
///
/// # Examples
@ -600,6 +676,7 @@ impl JsNativeError {
/// assert_eq!(error.message(), "number too large");
/// ```
#[must_use]
#[inline]
pub fn with_message<S>(mut self, message: S) -> Self
where
S: Into<Box<str>>,
@ -620,6 +697,7 @@ impl JsNativeError {
/// assert!(error.cause().unwrap().as_native().is_some());
/// ```
#[must_use]
#[inline]
pub fn with_cause<V>(mut self, cause: V) -> Self
where
V: Into<JsError>,
@ -644,6 +722,7 @@ impl JsNativeError {
/// assert_eq!(error.message(), "number too large");
/// ```
#[must_use]
#[inline]
pub const fn message(&self) -> &str {
&self.message
}
@ -665,6 +744,7 @@ impl JsNativeError {
/// assert!(error.cause().unwrap().as_native().is_some());
/// ```
#[must_use]
#[inline]
pub fn cause(&self) -> Option<&JsError> {
self.cause.as_deref()
}
@ -683,6 +763,11 @@ impl JsNativeError {
/// assert!(error_obj.borrow().is_error());
/// assert_eq!(error_obj.get("message", context).unwrap(), "error!".into())
/// ```
///
/// # Panics
///
/// If converting a [`JsNativeErrorKind::RuntimeLimit`] to an opaque object.
#[inline]
pub fn to_opaque(&self, context: &mut Context<'_>) -> JsObject {
let Self {
kind,
@ -717,6 +802,9 @@ impl JsNativeError {
"The NoInstructionsRemain native error cannot be converted to an opaque type."
)
}
JsNativeErrorKind::RuntimeLimit => {
panic!("The RuntimeLimit native error cannot be converted to an opaque type.")
}
};
let o = JsObject::from_proto_and_data_with_shared_shape(
@ -776,7 +864,7 @@ impl From<boa_parser::Error> for JsNativeError {
///
/// [spec]: https://tc39.es/ecma262/#sec-error-objects
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
#[derive(Debug, Clone, Trace, Finalize)]
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
#[non_exhaustive]
pub enum JsNativeErrorKind {
/// A collection of errors wrapped in a single error.
@ -855,10 +943,14 @@ pub enum JsNativeErrorKind {
/// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
/// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
Uri,
/// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS
/// error variant.
#[cfg(feature = "fuzz")]
NoInstructionsRemain,
/// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant.
RuntimeLimit,
}
impl PartialEq<ErrorKind> for JsNativeErrorKind {
@ -888,6 +980,7 @@ impl std::fmt::Display for JsNativeErrorKind {
Self::Syntax => "SyntaxError",
Self::Type => "TypeError",
Self::Uri => "UriError",
Self::RuntimeLimit => "RuntimeLimit",
#[cfg(feature = "fuzz")]
Self::NoInstructionsRemain => "NoInstructionsRemain",
}

4
boa_engine/src/lib.rs

@ -257,7 +257,7 @@ enum Inner {
},
AssertNativeError {
source: Cow<'static, str>,
kind: builtins::error::ErrorKind,
kind: JsNativeErrorKind,
message: &'static str,
},
AssertContext {
@ -328,7 +328,7 @@ impl TestAction {
/// Asserts that evaluating `source` throws a native error of `kind` and `message`.
fn assert_native_error(
source: impl Into<Cow<'static, str>>,
kind: builtins::error::ErrorKind,
kind: JsNativeErrorKind,
message: &'static str,
) -> Self {
Self(Inner::AssertNativeError {

4
boa_engine/src/object/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
use indoc::indoc;
#[test]
@ -9,7 +9,7 @@ fn ordinary_has_instance_nonobject_prototype() {
C.prototype = 1
String instanceof C
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"function has non-object prototype in instanceof check",
)]);
}

14
boa_engine/src/realm.rs

@ -24,6 +24,14 @@ pub struct Realm {
inner: Gc<Inner>,
}
impl Eq for Realm {}
impl PartialEq for Realm {
fn eq(&self, other: &Self) -> bool {
Gc::ptr_eq(&self.inner, &other.inner)
}
}
impl fmt::Debug for Realm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Realm")
@ -35,12 +43,6 @@ impl fmt::Debug for Realm {
}
}
impl PartialEq for Realm {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(&*self.inner, &*other.inner)
}
}
#[derive(Trace, Finalize)]
struct Inner {
intrinsics: Intrinsics,

10
boa_engine/src/tests/control_flow/loops.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
use indoc::indoc;
#[test]
@ -125,7 +125,7 @@ fn for_loop_iteration_variable_does_not_leak() {
for (let i = 0;false;) {}
i
"#},
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"i is not defined",
)]);
}
@ -138,7 +138,7 @@ fn test_invalid_break_target() {
break nonexistent;
}
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"undefined break target: nonexistent at line 1, col 1",
)]);
}
@ -625,7 +625,7 @@ fn for_of_loop_let() {
}
"#}),
TestAction::assert_eq("result", 3),
TestAction::assert_native_error("i", ErrorKind::Reference, "i is not defined"),
TestAction::assert_native_error("i", JsNativeErrorKind::Reference, "i is not defined"),
]);
}
@ -639,7 +639,7 @@ fn for_of_loop_const() {
}
"#}),
TestAction::assert_eq("result", 3),
TestAction::assert_native_error("i", ErrorKind::Reference, "i is not defined"),
TestAction::assert_native_error("i", JsNativeErrorKind::Reference, "i is not defined"),
]);
}

8
boa_engine/src/tests/control_flow/mod.rs

@ -1,13 +1,13 @@
use indoc::indoc;
mod loops;
use crate::{builtins::error::ErrorKind, run_test_actions, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, TestAction};
#[test]
fn test_invalid_break() {
run_test_actions([TestAction::assert_native_error(
"break;",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"illegal break statement at line 1, col 1",
)]);
}
@ -20,7 +20,7 @@ fn test_invalid_continue_target() {
continue nonexistent;
}
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"undefined continue target: nonexistent at line 1, col 1",
)]);
}
@ -29,7 +29,7 @@ fn test_invalid_continue_target() {
fn test_invalid_continue() {
run_test_actions([TestAction::assert_native_error(
"continue;",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"illegal continue statement at line 1, col 1",
)]);
}

16
boa_engine/src/tests/function.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -87,7 +87,7 @@ fn should_set_this_value() {
fn should_type_error_when_new_is_not_constructor() {
run_test_actions([TestAction::assert_native_error(
"new ''()",
ErrorKind::Type,
JsNativeErrorKind::Type,
"not a constructor",
)]);
}
@ -125,9 +125,13 @@ fn not_a_function() {
let a = {};
let b = true;
"#}),
TestAction::assert_native_error("a()", ErrorKind::Type, "not a callable function"),
TestAction::assert_native_error("a.a()", ErrorKind::Type, "not a callable function"),
TestAction::assert_native_error("b()", ErrorKind::Type, "not a callable function"),
TestAction::assert_native_error("a()", JsNativeErrorKind::Type, "not a callable function"),
TestAction::assert_native_error(
"a.a()",
JsNativeErrorKind::Type,
"not a callable function",
),
TestAction::assert_native_error("b()", JsNativeErrorKind::Type, "not a callable function"),
]);
}
@ -140,7 +144,7 @@ fn strict_mode_dup_func_parameters() {
'use strict';
function f(a, b, b) {}
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Duplicate parameter name not allowed in this context at line 2, col 12",
)]);
}

16
boa_engine/src/tests/mod.rs

@ -7,7 +7,7 @@ mod operators;
mod promise;
mod spread;
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
#[test]
fn length_correct_value_on_string_literal() {
@ -40,7 +40,7 @@ fn empty_var_decl_undefined() {
fn identifier_on_global_object_undefined() {
run_test_actions([TestAction::assert_native_error(
"bar;",
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"bar is not defined",
)]);
}
@ -372,7 +372,7 @@ fn undefined_constant() {
fn identifier_op() {
run_test_actions([TestAction::assert_native_error(
"break = 1",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
r#"expected token 'identifier', got '=' in identifier parsing at line 1, col 7"#,
)]);
}
@ -386,7 +386,7 @@ fn strict_mode_octal() {
'use strict';
var n = 023;
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"implicit octal literals are not allowed in strict mode at line 2, col 9",
)]);
}
@ -404,7 +404,7 @@ fn strict_mode_with() {
}
}
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"with statement not allowed in strict mode at line 3, col 5",
)]);
}
@ -429,7 +429,11 @@ fn strict_mode_reserved_name() {
];
run_test_actions(cases.into_iter().map(|(case, msg)| {
TestAction::assert_native_error(format!("'use strict'; {case}"), ErrorKind::Syntax, msg)
TestAction::assert_native_error(
format!("'use strict'; {case}"),
JsNativeErrorKind::Syntax,
msg,
)
}));
}

60
boa_engine/src/tests/operators.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -143,22 +143,22 @@ fn invalid_unary_access() {
run_test_actions([
TestAction::assert_native_error(
"++[]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 1",
),
TestAction::assert_native_error(
"[]++",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 3",
),
TestAction::assert_native_error(
"--[]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 1",
),
TestAction::assert_native_error(
"[]--",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 3",
),
]);
@ -170,22 +170,22 @@ fn unary_operations_on_this() {
run_test_actions([
TestAction::assert_native_error(
"++this",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 1",
),
TestAction::assert_native_error(
"--this",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 1",
),
TestAction::assert_native_error(
"this++",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 5",
),
TestAction::assert_native_error(
"this--",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 5",
),
]);
@ -305,7 +305,7 @@ fn assignment_to_non_assignable() {
.map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 3",
)
}),
@ -330,7 +330,7 @@ fn assignment_to_non_assignable_ctd() {
.map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 13",
)
}),
@ -344,7 +344,7 @@ fn multicharacter_assignment_to_non_assignable() {
run_test_actions(["3 **= 5", "3 <<= 5", "3 >>= 5"].into_iter().map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 3",
)
}));
@ -358,7 +358,7 @@ fn multicharacter_assignment_to_non_assignable_ctd() {
.map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 13",
)
}),
@ -373,7 +373,7 @@ fn multicharacter_bitwise_assignment_to_non_assignable() {
.map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 3",
)
}),
@ -393,7 +393,7 @@ fn multicharacter_bitwise_assignment_to_non_assignable_ctd() {
.map(|src| {
TestAction::assert_native_error(
src,
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 13",
)
}),
@ -405,22 +405,22 @@ fn assign_to_array_decl() {
run_test_actions([
TestAction::assert_native_error(
"[1] = [2]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 5",
),
TestAction::assert_native_error(
"[3, 5] = [7, 8]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 8",
),
TestAction::assert_native_error(
"[6, 8] = [2]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 8",
),
TestAction::assert_native_error(
"[6] = [2, 9]",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"Invalid left-hand side in assignment at line 1, col 5",
),
]);
@ -430,7 +430,7 @@ fn assign_to_array_decl() {
fn assign_to_object_decl() {
run_test_actions([TestAction::assert_native_error(
"{a: 3} = {a: 5};",
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"unexpected token '=', primary expression at line 1, col 8",
)]);
}
@ -439,7 +439,7 @@ fn assign_to_object_decl() {
fn assignmentoperator_lhs_not_defined() {
run_test_actions([TestAction::assert_native_error(
"a += 5",
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"a is not defined",
)]);
}
@ -448,7 +448,7 @@ fn assignmentoperator_lhs_not_defined() {
fn assignmentoperator_rhs_throws_error() {
run_test_actions([TestAction::assert_native_error(
"let a; a += b",
ErrorKind::Reference,
JsNativeErrorKind::Reference,
"b is not defined",
)]);
}
@ -457,7 +457,7 @@ fn assignmentoperator_rhs_throws_error() {
fn instanceofoperator_rhs_not_object() {
run_test_actions([TestAction::assert_native_error(
"let s = new String(); s instanceof 1",
ErrorKind::Type,
JsNativeErrorKind::Type,
"right-hand side of 'instanceof' should be an object, got `number`",
)]);
}
@ -466,7 +466,7 @@ fn instanceofoperator_rhs_not_object() {
fn instanceofoperator_rhs_not_callable() {
run_test_actions([TestAction::assert_native_error(
"let s = new String(); s instanceof {}",
ErrorKind::Type,
JsNativeErrorKind::Type,
"right-hand side of 'instanceof' is not callable",
)]);
}
@ -504,7 +504,7 @@ fn delete_variable_in_strict() {
let x = 10;
delete x;
"#},
ErrorKind::Syntax,
JsNativeErrorKind::Syntax,
"cannot delete variables in strict mode at line 3, col 1",
)]);
}
@ -513,7 +513,7 @@ fn delete_variable_in_strict() {
fn delete_non_configurable() {
run_test_actions([TestAction::assert_native_error(
"'use strict'; delete Boolean.prototype",
ErrorKind::Type,
JsNativeErrorKind::Type,
"Cannot delete property",
)]);
}
@ -528,7 +528,7 @@ fn delete_non_configurable_in_function() {
}
t()
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Cannot delete property",
)]);
}
@ -557,7 +557,7 @@ fn delete_in_function_global_strict() {
}
a();
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Cannot delete property",
)]);
}
@ -591,7 +591,7 @@ fn delete_in_strict_function_returned() {
}
a()();
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"Cannot delete property",
)]);
}
@ -633,7 +633,7 @@ mod in_operator {
fn should_type_error_when_rhs_not_object() {
run_test_actions([TestAction::assert_native_error(
"'fail' in undefined",
ErrorKind::Type,
JsNativeErrorKind::Type,
"right-hand side of 'in' should be an object, got `undefined`",
)]);
}

4
boa_engine/src/tests/spread.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -97,7 +97,7 @@ fn spread_getters_in_object() {
var a = { x: 42 };
var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } };
"#},
ErrorKind::Error,
JsNativeErrorKind::Error,
"not thrown yet",
)]);
}

4
boa_engine/src/value/tests.rs

@ -700,7 +700,7 @@ fn to_bigint() {
/// Relevant mitigation for these are in `JsObject::ordinary_to_primitive` and
/// `JsObject::to_json`
mod cyclic_conversions {
use crate::builtins::error::ErrorKind;
use crate::JsNativeErrorKind;
use super::*;
@ -712,7 +712,7 @@ mod cyclic_conversions {
a[0] = a;
JSON.stringify(a)
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"cyclic object value",
)]);
}

21
boa_engine/src/vm/call_frame/env_stack.rs

@ -3,7 +3,10 @@
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum EnvEntryKind {
Global,
Loop,
Loop {
/// This is used to keep track of how many iterations a loop has done.
iteration_count: u64,
},
Try,
Catch,
Finally,
@ -49,8 +52,9 @@ impl EnvStackEntry {
}
/// Returns calling `EnvStackEntry` with `kind` field of `Loop`.
pub(crate) const fn with_loop_flag(mut self) -> Self {
self.kind = EnvEntryKind::Loop;
/// And the loop iteration set to zero.
pub(crate) const fn with_loop_flag(mut self, iteration_count: u64) -> Self {
self.kind = EnvEntryKind::Loop { iteration_count };
self
}
@ -95,8 +99,15 @@ impl EnvStackEntry {
}
/// Returns true if an `EnvStackEntry` is a loop
pub(crate) fn is_loop_env(&self) -> bool {
self.kind == EnvEntryKind::Loop
pub(crate) const fn is_loop_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Loop { .. })
}
pub(crate) const fn as_loop_iteration_count(self) -> Option<u64> {
if let EnvEntryKind::Loop { iteration_count } = self.kind {
return Some(iteration_count);
}
None
}
/// Returns true if an `EnvStackEntry` is a try block

1
boa_engine/src/vm/flowgraph/mod.rs

@ -108,7 +108,6 @@ impl CodeBlock {
EdgeStyle::Line,
);
}
Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::JumpIfNotUndefined

24
boa_engine/src/vm/mod.rs

@ -4,14 +4,15 @@
//! This module will provide an instruction set for the AST to use, various traits,
//! plus an interpreter to execute those instructions
#[cfg(feature = "fuzz")]
use crate::JsNativeError;
use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
environments::{DeclarativeEnvironment, DeclarativeEnvironmentStack},
vm::code_block::Readable,
Context, JsError, JsObject, JsResult, JsValue,
};
#[cfg(feature = "fuzz")]
use crate::{JsNativeError, JsNativeErrorKind};
use boa_gc::Gc;
use boa_profiler::Profiler;
use std::{convert::TryInto, mem::size_of};
@ -26,9 +27,12 @@ mod code_block;
mod completion_record;
mod opcode;
mod runtime_limits;
#[cfg(feature = "flowgraph")]
pub mod flowgraph;
pub use runtime_limits::RuntimeLimits;
pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
pub(crate) use {
@ -52,7 +56,7 @@ pub struct Vm {
pub(crate) environments: DeclarativeEnvironmentStack,
#[cfg(feature = "trace")]
pub(crate) trace: bool,
pub(crate) stack_size_limit: usize,
pub(crate) runtime_limits: RuntimeLimits,
pub(crate) active_function: Option<JsObject>,
}
@ -66,7 +70,7 @@ impl Vm {
err: None,
#[cfg(feature = "trace")]
trace: false,
stack_size_limit: 1024,
runtime_limits: RuntimeLimits::default(),
active_function: None,
}
}
@ -268,14 +272,22 @@ impl Context<'_> {
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if matches!(native_error.kind, JsNativeErrorKind::NoInstructionsRemain)
{
if native_error.is_no_instructions_remain() {
self.vm.err = Some(err);
break CompletionType::Throw;
}
}
}
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if native_error.is_runtime_limit() {
self.vm.err = Some(err);
break CompletionType::Throw;
}
}
self.vm.err = Some(err);
// If this frame has not evaluated the throw as an AbruptCompletion, then evaluate it

16
boa_engine/src/vm/opcode/call/mod.rs

@ -17,8 +17,8 @@ impl Operation for CallEval {
const INSTRUCTION: &'static str = "INST - CallEval";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
@ -77,8 +77,8 @@ impl Operation for CallEvalSpread {
const INSTRUCTION: &'static str = "INST - CallEvalSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
@ -143,8 +143,8 @@ impl Operation for Call {
const INSTRUCTION: &'static str = "INST - Call";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
@ -182,8 +182,8 @@ impl Operation for CallSpread {
const INSTRUCTION: &'static str = "INST - CallSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}

65
boa_engine/src/vm/opcode/iteration/loop_ops.rs

@ -1,3 +1,4 @@
use crate::JsNativeError;
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType},
Context, JsResult,
@ -18,15 +19,29 @@ impl Operation for LoopStart {
let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, exit).with_loop_flag());
// Create and push loop evironment entry.
let entry = EnvStackEntry::new(start, exit).with_loop_flag(1);
context.vm.frame_mut().env_stack.push(entry);
Ok(CompletionType::Normal)
}
}
/// This is a helper function used to clean the loop environment created by the
/// [`LoopStart`] and [`LoopContinue`] opcodes.
fn cleanup_loop_environment(context: &mut Context<'_>) {
let mut envs_to_pop = 0_usize;
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.is_loop_env() {
break;
}
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
}
/// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue`.
///
/// Operation:
@ -42,6 +57,8 @@ impl Operation for LoopContinue {
let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
let mut iteration_count = 0;
// 1. Clean up the previous environment.
if let Some(entry) = context
.vm
@ -57,15 +74,28 @@ impl Operation for LoopContinue {
.saturating_sub(entry.env_num());
context.vm.environments.truncate(env_truncation_len);
context.vm.frame_mut().env_stack.pop();
// Pop loop environment and get it's iteration count.
let previous_entry = context.vm.frame_mut().env_stack.pop();
if let Some(previous_iteration_count) =
previous_entry.and_then(EnvStackEntry::as_loop_iteration_count)
{
iteration_count = previous_iteration_count.wrapping_add(1);
let max = context.vm.runtime_limits.loop_iteration_limit();
if previous_iteration_count > max {
cleanup_loop_environment(context);
return Err(JsNativeError::runtime_limit()
.with_message(format!("max loop iteration limit {max} exceeded"))
.into());
}
}
}
// 2. Push a new clean EnvStack.
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, exit).with_loop_flag());
let entry = EnvStackEntry::new(start, exit).with_loop_flag(iteration_count);
context.vm.frame_mut().env_stack.push(entry);
Ok(CompletionType::Normal)
}
@ -83,18 +113,7 @@ impl Operation for LoopEnd {
const INSTRUCTION: &'static str = "INST - LoopEnd";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut envs_to_pop = 0_usize;
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.is_loop_env() {
break;
}
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
cleanup_loop_environment(context);
Ok(CompletionType::Normal)
}
}

8
boa_engine/src/vm/opcode/new/mod.rs

@ -16,8 +16,8 @@ impl Operation for New {
const INSTRUCTION: &'static str = "INST - New";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
@ -55,8 +55,8 @@ impl Operation for NewSpread {
const INSTRUCTION: &'static str = "INST - NewSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.stack_size_limit <= context.vm.stack.len() {
return Err(JsNativeError::range()
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}

61
boa_engine/src/vm/runtime_limits.rs

@ -0,0 +1,61 @@
/// Represents the limits of different runtime operations.
#[derive(Debug, Clone, Copy)]
pub struct RuntimeLimits {
/// Max stack size before an error is thrown.
stack_size_limit: usize,
/// Max loop iterations before an error is thrown.
loop_iteration_limit: u64,
}
impl Default for RuntimeLimits {
#[inline]
fn default() -> Self {
Self {
loop_iteration_limit: u64::MAX,
stack_size_limit: 1024,
}
}
}
impl RuntimeLimits {
/// Return the loop iteration limit.
///
/// If the limit is exceeded in a loop it will throw and errror.
///
/// The limit value [`u64::MAX`] means that there is no limit.
#[inline]
#[must_use]
pub const fn loop_iteration_limit(&self) -> u64 {
self.loop_iteration_limit
}
/// Set the loop iteration limit.
///
/// If the limit is exceeded in a loop it will throw and errror.
///
/// Setting the limit to [`u64::MAX`] means that there is no limit.
#[inline]
pub fn set_loop_iteration_limit(&mut self, value: u64) {
self.loop_iteration_limit = value;
}
/// Disable loop iteration limit.
#[inline]
pub fn disable_loop_iteration_limit(&mut self) {
self.loop_iteration_limit = u64::MAX;
}
/// Get max stack size.
#[inline]
#[must_use]
pub const fn stack_size_limit(&self) -> usize {
self.stack_size_limit
}
/// Set max stack size before an error is thrown.
#[inline]
pub fn set_stack_size_limit(&mut self, value: usize) {
self.stack_size_limit = value;
}
}

39
boa_engine/src/vm/tests.rs

@ -1,4 +1,4 @@
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};
use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction};
use indoc::indoc;
#[test]
@ -190,7 +190,7 @@ fn super_call_constructor_null() {
}
new A();
"#},
ErrorKind::Type,
JsNativeErrorKind::Type,
"super constructor object must be constructor",
)]);
}
@ -237,3 +237,38 @@ fn order_of_execution_in_assigment_with_comma_expressions() {
"1234",
)]);
}
#[test]
fn loop_runtime_limit() {
run_test_actions([
TestAction::assert_eq(
indoc! {r#"
for (let i = 0; i < 20; ++i) { }
"#},
JsValue::undefined(),
),
TestAction::inspect_context(|context| {
context.runtime_limits_mut().set_loop_iteration_limit(10);
}),
TestAction::assert_native_error(
indoc! {r#"
for (let i = 0; i < 20; ++i) { }
"#},
JsNativeErrorKind::RuntimeLimit,
"max loop iteration limit 10 exceeded",
),
TestAction::assert_eq(
indoc! {r#"
for (let i = 0; i < 10; ++i) { }
"#},
JsValue::undefined(),
),
TestAction::assert_native_error(
indoc! {r#"
while (1) { }
"#},
JsNativeErrorKind::RuntimeLimit,
"max loop iteration limit 10 exceeded",
),
]);
}

47
boa_examples/src/bin/runtime_limits.rs

@ -0,0 +1,47 @@
use boa_engine::{Context, Source};
fn main() {
// Create the JavaScript context.
let mut context = Context::default();
// Set the context's runtime limit on loops to 10 iterations.
context.runtime_limits_mut().set_loop_iteration_limit(10);
// The code below iterates 5 times, so no error is thrown.
let result = context.eval_script(Source::from_bytes(
r"
for (let i = 0; i < 5; ++i) { }
",
));
result.expect("no error should be thrown");
// Here we exceed the limit by 1 iteration and a `RuntimeLimit` error is thrown.
//
// This error cannot be caught in JavaScript it propagates to rust caller.
let result = context.eval_script(Source::from_bytes(
r"
try {
for (let i = 0; i < 11; ++i) { }
} catch (e) {
}
",
));
result.expect_err("should have throw an error");
// Preventing an infinity loops
let result = context.eval_script(Source::from_bytes(
r"
while (true) { }
",
));
result.expect_err("should have throw an error");
// The limit applies to all types of loops.
let result = context.eval_script(Source::from_bytes(
r"
for (let e of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { }
",
));
result.expect_err("should have throw an error");
}

19
docs/boa_object.md

@ -173,7 +173,7 @@ $boa.optimizer.constantFolding // true
### Getter & Setter `$boa.optimizer.statistics`
This is and accessor property on the module, its getter returns `true` if enabled or `false` otherwise.
This is an accessor property on the module, its getter returns `true` if enabled or `false` otherwise.
Its setter can be used to enable/disable optimization statistics, which are printed to `stdout`.
```JavaScript
@ -190,7 +190,7 @@ Optimizer {
## Module `$boa.realm`
This modules contains realm utilities to test cross-realm behaviour.
This module contains realm utilities to test cross-realm behaviour.
### `$boa.realm.create`
@ -240,3 +240,18 @@ $boa.shape.same(o1, o2) // true
o2.y = 200
$boa.shape.same(o1, o2) // false
```
## Module `$boa.limits`
This module contains utilities for changing runtime limits.
### Getter & Setter `$boa.limits.loop`
This is an accessor property on the module, its getter returns the loop iteration limit before an error is thrown.
Its setter can be used to set the loop iteration limit.
```javascript
$boa.limits.loop = 10;
while (true) {} // RuntimeLimit: max loop iteration limit 10 exceeded
```

Loading…
Cancel
Save