diff --git a/crates/js/src/interp.rs b/crates/js/src/interp.rs index 4d4b3d039..795241829 100644 --- a/crates/js/src/interp.rs +++ b/crates/js/src/interp.rs @@ -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) } diff --git a/crates/js/src/interp_tests.rs b/crates/js/src/interp_tests.rs index 1b2a192a7..c838d1b1b 100644 --- a/crates/js/src/interp_tests.rs +++ b/crates/js/src/interp_tests.rs @@ -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(); diff --git a/crates/js/src/realm.rs b/crates/js/src/realm.rs index 2cfa19231..d13bb0481 100644 --- a/crates/js/src/realm.rs +++ b/crates/js/src/realm.rs @@ -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 }),