mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 07:42:08 +00:00
JSON: parse counts a string it has read and a flattened text toward the heap limit
A string value the parser has read is held only by the parser until it is stored. The property reservation can start a collection, which dropped the string from the heap total. The value now stays rooted across that reservation, for array elements and for object members. `JSON.parse` also charges the buffer it flattens a concatenated text into, through the helper `parseInt` and `parseFloat` use.
This commit is contained in:
@@ -35,7 +35,7 @@ fn trim_start(s: &[u16]) -> &[u16] {
|
||||
|
||||
/// ToString of the first argument, as code units, with each one taking a unit of fuel. A
|
||||
/// concatenation is flattened into a new buffer, which is charged to the heap.
|
||||
fn units_of(interp: &mut Interp, args: &[Value]) -> JsResult<std::rc::Rc<[u16]>> {
|
||||
pub(super) fn units_of(interp: &mut Interp, args: &[Value]) -> JsResult<std::rc::Rc<[u16]>> {
|
||||
let s = interp.to_str(&arg(args, 0))?;
|
||||
for _ in 0..s.len() {
|
||||
interp.use_fuel()?;
|
||||
|
||||
@@ -158,10 +158,7 @@ fn syntax(interp: &mut Interp, why: &str, at: usize) -> crate::interp::Abrupt {
|
||||
|
||||
/// `JSON.parse` (section 15.12.2).
|
||||
fn parse(interp: &mut Interp, _: Value, args: &[Value], _: bool) -> JsResult<Value> {
|
||||
let text = interp.to_str(&arg(args, 0))?.flat();
|
||||
for _ in 0..text.len() {
|
||||
interp.use_fuel()?;
|
||||
}
|
||||
let text = super::global::units_of(interp, args)?;
|
||||
let mark = interp.temp_roots.len();
|
||||
let r = parse_text(interp, &text).and_then(|v| {
|
||||
let reviver = arg(args, 1);
|
||||
@@ -273,7 +270,7 @@ fn parse_text(interp: &mut Interp, text: &[u16]) -> JsResult<Value> {
|
||||
Open::Array { obj, next } => {
|
||||
let (obj, i) = (*obj, *next);
|
||||
*next = next.saturating_add(1);
|
||||
interp.reserve_heap(ELEMENT_BYTES)?;
|
||||
interp.rooted(std::slice::from_ref(&v), |i| i.reserve_heap(ELEMENT_BYTES))?;
|
||||
interp.define(obj, key_of(f64::from(i)), Prop::Data { value: v, flags: OPEN })?;
|
||||
p.skip_space();
|
||||
if p.eat(b',') {
|
||||
@@ -288,7 +285,7 @@ fn parse_text(interp: &mut Interp, text: &[u16]) -> JsResult<Value> {
|
||||
Open::Object { obj, name } => {
|
||||
let obj = *obj;
|
||||
let Some(k) = name.take() else { return Err(syntax(interp, "a member has no name", p.pos)) };
|
||||
interp.reserve_heap(ELEMENT_BYTES)?;
|
||||
interp.rooted(std::slice::from_ref(&v), |i| i.reserve_heap(ELEMENT_BYTES))?;
|
||||
interp.define(obj, k, Prop::Data { value: v, flags: OPEN })?;
|
||||
p.skip_space();
|
||||
if p.eat(b',') {
|
||||
|
||||
@@ -659,6 +659,40 @@ fn json_stringify_unwraps_number_string_and_boolean_objects() {
|
||||
assert_eq!(string("Object.getPrototypeOf(Object('')).toString = function () { return 'q'; }; JSON.stringify(Object('abc'))"), "\"q\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_parse_counts_a_string_while_it_reserves_the_property_for_it() {
|
||||
// The limits around the parsed value's size include one where the reservation for the string's
|
||||
// property is what starts a collection. The text is a concatenation, so the parse also flattens
|
||||
// it. Whenever the parse fits, the heap total counts everything that is live.
|
||||
let setup = |text: &str| format!("var s = new Array(4097).join('x'); var text = {text}; 0");
|
||||
for text in ["'[\"' + s + '\"]'", "'{\"k\":\"' + s + '\"}'"] {
|
||||
let setup = setup(text);
|
||||
let parse = "var a = JSON.parse(text); 0";
|
||||
let mut measure = small(Limits::default());
|
||||
assert_eq!(measure.run(&setup), Ok(JsValue::Number(0.0)));
|
||||
measure.interp().collect_garbage();
|
||||
assert_eq!(measure.run(parse), Ok(JsValue::Number(0.0)));
|
||||
let full = measure.interp().heap.bytes;
|
||||
let (mut fits, mut over) = (0, 0);
|
||||
for heap_bytes in (full - 8192..=full + 256).step_by(4) {
|
||||
let mut realm = small(Limits { heap_bytes, ..Limits::default() });
|
||||
assert_eq!(realm.run(&setup), Ok(JsValue::Number(0.0)));
|
||||
realm.interp().collect_garbage();
|
||||
match realm.run(parse) {
|
||||
Ok(_) => {
|
||||
let counted = realm.interp().heap.bytes;
|
||||
let live = realm.collect_garbage();
|
||||
assert!(counted >= live, "{text}: {counted} bytes counted with {live} live under a limit of {heap_bytes}");
|
||||
fits += 1;
|
||||
}
|
||||
Err(RunError { kind: RunErrorKind::Limit(LimitKind::Heap), .. }) => over += 1,
|
||||
Err(e) => panic!("{text}: {e:?}"),
|
||||
}
|
||||
}
|
||||
assert!(fits > 0 && over > 0, "{text}: {fits} limits fit and {over} did not");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_json_nesting_needs_no_native_stack() {
|
||||
let handle = std::thread::Builder::new()
|
||||
|
||||
Reference in New Issue
Block a user