From 40283f4a380955ac22639e31bcd6354c5931b75c Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 22:49:15 -0800 Subject: [PATCH] Scripts: loose equality and the JSON space string keep a converted string rooted while it is flattened --- crates/js/src/builtins/json.rs | 3 ++- crates/js/src/interp.rs | 4 +++- crates/js/src/text_tests.rs | 19 ++++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/js/src/builtins/json.rs b/crates/js/src/builtins/json.rs index 364ec9969..d32344946 100644 --- a/crates/js/src/builtins/json.rs +++ b/crates/js/src/builtins/json.rs @@ -474,7 +474,8 @@ fn settings(interp: &mut Interp, replacer: &Value, space: &Value) -> JsResult s.gap = flat_charged(interp, &t)?.iter().take(10).copied().collect(), + // Rooted, so what the string holds is still counted while its copy is reserved. + Value::String(t) => s.gap = interp.rooted(&[Value::String(t.clone())], |i| flat_charged(i, &t))?.iter().take(10).copied().collect(), _ => {} } Ok(s) diff --git a/crates/js/src/interp.rs b/crates/js/src/interp.rs index 19d2eef22..b399ebc23 100644 --- a/crates/js/src/interp.rs +++ b/crates/js/src/interp.rs @@ -2816,7 +2816,9 @@ impl Interp { | (Value::Bool(_), Value::Bool(_)) | (Value::Object(_), Value::Object(_)) => return Ok(strict_eq(&x, &y)), (Value::Number(n), Value::String(s)) | (Value::String(s), Value::Number(n)) => { - return Ok(*n == string_to_number(&flat_charged(self, s)?)); + // Rooted, so what the string holds is still counted while its copy is reserved. + let units = self.rooted(&[x.clone(), y.clone()], |i| flat_charged(i, s))?; + return Ok(*n == string_to_number(&units)); } (Value::Bool(v), _) => x = Value::Number(f64::from(u8::from(*v))), (_, Value::Bool(v)) => y = Value::Number(f64::from(u8::from(*v))), diff --git a/crates/js/src/text_tests.rs b/crates/js/src/text_tests.rs index 3d3455544..6fccc085f 100644 --- a/crates/js/src/text_tests.rs +++ b/crates/js/src/text_tests.rs @@ -361,13 +361,18 @@ fn a_converted_rope_counts_what_it_holds_while_its_copy_is_made() { let build = "var s = 'x'; for (var i = 0; i < 18; i++) s = s + s; function f(c) { return s + c; } \ function mk() { var a = f('a'); a[0]; return a + 'b'; }"; let limits = Limits { heap_bytes: 4 * 262_146, ..Limits::default() }; - let not_stopped: Vec<_> = ["+({ valueOf: mk })", "({})[{ toString: mk }]"] - .into_iter() - .filter(|read| { - let result = Realm::with_options(Options::default(), limits.clone()).run(&format!("{build} {read}; 0")); - !matches!(result, Err(ref e) if e.kind == RunErrorKind::Limit(LimitKind::Heap)) - }) - .collect(); + let not_stopped: Vec<_> = [ + "+({ valueOf: mk })", + "({})[{ toString: mk }]", + "({ valueOf: mk }) == 1", + "JSON.stringify(0, null, (function () { var o = new String(''); o.toString = mk; return o; })())", + ] + .into_iter() + .filter(|read| { + let result = Realm::with_options(Options::default(), limits.clone()).run(&format!("{build} {read}; 0")); + !matches!(result, Err(ref e) if e.kind == RunErrorKind::Limit(LimitKind::Heap)) + }) + .collect(); assert!(not_stopped.is_empty(), "not stopped by the heap limit: {not_stopped:?}"); }