mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
JavaScript: a strict realm runs scripts as strict mode code, and apply unroots its arguments when one throws
Realm::run compiles the script with the realm's Options::strict instead of always as sloppy code, so a realm made with strict: true rejects with statements and calls functions with an undefined this. Indirect eval and Function keep their own strictness (ES5.1 10.4.2, 15.3.2.1). Function.prototype.apply drops the temporary GC roots it holds for the argument values when an index getter throws, as it already did when reading succeeds. Before, a caught throw left them rooted until the run ended, so a loop of such calls could reach the heap limit.
This commit is contained in:
+12
-5
@@ -1428,14 +1428,21 @@ impl Interp {
|
||||
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 {
|
||||
let v = self.get_from(*o, &index_key(u32::try_from(i).unwrap_or(u32::MAX)), list)?;
|
||||
self.temp_roots.push(v.clone());
|
||||
out.push(v);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
let keep = self.temp_roots.len() - n;
|
||||
self.temp_roots.truncate(keep);
|
||||
self.temp_roots.truncate(saved);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
|
||||
@@ -405,6 +405,28 @@ fn bind_last_thrown_binds_undefined_after_a_run_that_did_not_throw() {
|
||||
assert_eq!(realm.run("caught"), Ok(JsValue::Undefined), "a run that reached a limit threw nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_strict_realm_runs_scripts_as_strict_mode_code() {
|
||||
let mut realm = Realm::with_options(Options { strict: true, ..Options::default() }, Limits::default());
|
||||
assert_eq!(realm.run("with ({}) {}").map_err(|e| e.kind), Err(RunErrorKind::Syntax));
|
||||
assert_eq!(realm.run("(function () { return this; })()"), Ok(JsValue::Undefined));
|
||||
assert_eq!(realm.run("(function () { return typeof this; })()"), Ok(JsValue::String("undefined".into())));
|
||||
let mut realm = Realm::new();
|
||||
assert_eq!(realm.run("with ({}) {} 1"), Ok(JsValue::Number(1.0)));
|
||||
assert_eq!(realm.run("(function () { return typeof this; })()"), Ok(JsValue::String("object".into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_apply_that_throws_while_reading_its_arguments_keeps_nothing_alive() {
|
||||
// Each call reads a fresh array at index 0 before index 1 throws; the arrays must not stay
|
||||
// reachable after the call ends.
|
||||
let mut realm = small(Limits { heap_bytes: 2 << 20, ..Limits::default() });
|
||||
let src = "function f() {} var caught = 0; \
|
||||
var list = {length: 2, get 0() { var a = []; for (var k = 0; k < 100; k++) a[k] = k; return a; }, get 1() { throw 'stop'; }}; \
|
||||
for (var i = 0; i < 5000; i++) { try { f.apply(null, list); } catch (e) { caught++; } } caught";
|
||||
assert_eq!(realm.run(src), Ok(JsValue::Number(5000.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wasm_builds_have_smaller_default_limits() {
|
||||
let limits = Limits::default();
|
||||
|
||||
@@ -174,7 +174,8 @@ impl Realm {
|
||||
interp.source_units = 0;
|
||||
interp.throw_site = None;
|
||||
interp.last_thrown = Value::Undefined;
|
||||
let code = match interp.encode_source(source).and_then(|units| interp.compile_source(&units, false, CodeKind::Global)) {
|
||||
let strict = interp.options.strict;
|
||||
let code = match interp.encode_source(source).and_then(|units| interp.compile_source(&units, strict, CodeKind::Global)) {
|
||||
Ok(code) => code,
|
||||
Err(CompileError::Syntax(e)) => return Err(e.into()),
|
||||
Err(CompileError::Internal(m)) => return Err(RunError { kind: RunErrorKind::Internal, message: m, line: 0, column: 0 }),
|
||||
|
||||
Reference in New Issue
Block a user