JavaScript: JSON.parse releases its text before the reviver runs

The text is rooted and held only while the parse runs. A reviver can use the room the text took.
This commit is contained in:
dotCooCoo
2026-11-08 16:34:49 -08:00
parent fb4c38db5d
commit 5faa5c4d1d
2 changed files with 18 additions and 9 deletions
+10 -9
View File
@@ -162,15 +162,16 @@ fn parse(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Val
let mark = interp.temp_roots.len();
// The text stays rooted for the whole parse, so a collection still counts it.
interp.temp_roots.push(source.clone());
let r = super::global::units_of(interp, std::slice::from_ref(&source)).and_then(|text| {
let v = parse_text(interp, &text)?;
let reviver = arg(args, 1);
if !interp.is_callable(&reviver) {
return Ok(v);
}
interp.temp_roots.push(v.clone());
revive(interp, v, &reviver)
});
let parsed = super::global::units_of(interp, std::slice::from_ref(&source)).and_then(|text| parse_text(interp, &text));
interp.temp_roots.truncate(mark);
drop(source);
let v = parsed?;
let reviver = arg(args, 1);
if !interp.is_callable(&reviver) {
return Ok(v);
}
interp.temp_roots.push(v.clone());
let r = revive(interp, v, &reviver);
interp.temp_roots.truncate(mark);
r
}
+8
View File
@@ -722,6 +722,14 @@ fn json_parse_keeps_its_text_counted_for_the_whole_parse() {
assert_eq!(realm.run(setup), Ok(JsValue::Number(0.0)));
realm.interp().collect_garbage();
assert_eq!(limit(&mut realm, parse), LimitKind::Heap);
// The text is released before the reviver runs, so the reviver can use the room it took.
let setup = "var ws = new Array(65537).join(' '); var o = {toString: function () { return ws + '1'; }}; 0";
let mut measure = small(Limits::default());
assert_eq!(measure.run(setup), Ok(JsValue::Number(0.0)));
let heap_bytes = measure.collect_garbage() + 2 * 65537 + (16 << 10);
let mut realm = small(Limits { heap_bytes, ..Limits::default() });
assert_eq!(realm.run(setup), Ok(JsValue::Number(0.0)));
assert_eq!(realm.run("JSON.parse(o, function () { return new Array(40001).join('x').length; })"), Ok(JsValue::Number(40000.0)));
}
#[test]