use super::run_tests::run_on_core;
fn run_deep(source: String) -> String {
std::thread::Builder::new()
.stack_size(256 << 20)
.spawn(move || {
crate::engine::compiler::stack_guard::declare_thread_stack(256 << 20);
run_on_core(&source)
})
.unwrap()
.join()
.unwrap()
}
const CALL_BOUND: usize = if cfg!(feature = "gc-stress") {
2_000
} else {
65_533
};
const TEMPLATE_BOUND: usize = if cfg!(feature = "gc-stress") {
2_000
} else {
65_532
};
fn digits(count: usize) -> String {
(0..count)
.map(|index| (index % 10).to_string())
.collect::<Vec<_>>()
.join(",")
}
fn prefix(count: usize) -> String {
let mut text = digits(count);
text.push(',');
text
}
#[test]
fn calls_past_the_window_pass_every_argument() {
let mut source = String::from(
"function f() { var s = 0; for (var i = 0; i < arguments.length; i++) \
s += arguments[i]; return arguments.length + ':' + s; }\n\
var o = { m: f };\n\
function C() { this.v = f.apply(null, arguments); }\n",
);
let mut expected = String::new();
for (count, sum) in [(254, 1131), (255, 1135), (256, 1140), (1000, 4500)] {
let arguments = digits(count);
source.push_str(&format!(
"print(f({arguments}), o.m({arguments}), new C({arguments}).v);\n"
));
expected.push_str(&format!("{count}:{sum} {count}:{sum} {count}:{sum}\n"));
}
assert_eq!(run_deep(source), expected);
}
#[test]
fn a_call_at_the_front_end_bound_runs() {
let last = CALL_BOUND - 1;
let source = format!(
"var n = 0, s = 0; function f() {{ n = arguments.length; s = arguments[{last}]; }}\n\
f({});\nprint(n, s);",
digits(CALL_BOUND)
);
assert_eq!(run_deep(source), format!("{CALL_BOUND} {}\n", last % 10));
}
#[test]
fn templates_past_the_window_concatenate_every_substitution() {
let short = "${1}".repeat(300);
let long = "${2}".repeat(TEMPLATE_BOUND);
let last = TEMPLATE_BOUND - 1;
let source =
format!("print(`{short}`.length);\nvar s = `{long}`;\nprint(s.length, s[{last}]);");
assert_eq!(run_deep(source), format!("300\n{TEMPLATE_BOUND} 2\n"));
}
#[test]
fn nesting_past_the_window_evaluates_inside_out() {
let object = format!("{}1{}", "{a:".repeat(300), "}".repeat(300));
let array = format!("{}1{}", "[0,".repeat(300), "]".repeat(300));
let call = format!("{}7{}", "id(0,".repeat(300), ")".repeat(300));
let power = format!("2**{}1", "1**".repeat(999));
let source = format!(
"function id(a, b) {{ return b; }}\n\
print(JSON.stringify({object}).length, JSON.stringify({array}).length, {call}, {power});"
);
assert_eq!(run_deep(source), "1801 1201 7 2\n");
}
#[test]
fn deep_expressions_keep_records_and_catches_in_slots() {
let deep = prefix(300);
let source = format!(
"var log = [];\n\
function it(k) {{ var i = 0; return {{ [Symbol.iterator]() {{ return this; }}, \
next() {{ log.push('n' + k); return {{ value: i++, done: i > 3 }}; }}, \
return() {{ log.push('r' + k); return {{}}; }} }}; }}\n\
function g() {{\n\
var a, b;\n\
var r = [{deep} ([a, b] = it(1), a + b), (() => {{ try {{ throw 7; }} catch (e) {{ return e; }} }})(), \
[...'xyz'].length, ({{ ...{{ p: 1 }} }}).p, typeof class {{ static m() {{}} }}, ({{}})?.x?.y];\n\
var o = {{ set t(v) {{ throw 'thrown'; }} }};\n\
try {{ var q = [{deep} ([o.t] = it(2))]; }} catch (e) {{ log.push(e); }}\n\
return r.length + ':' + r.slice(-6) + ':' + log;\n\
}}\n\
print(g());"
);
assert_eq!(
run_deep(source),
"306:1,7,3,1,function,:n1,n1,r1,n2,r2,thrown\n"
);
}
#[test]
fn suspensions_past_the_window_resume_into_slots() {
let deep = prefix(300);
let source = format!(
"function* gen() {{ var r = [{deep} yield 1, yield* [2, 3]]; return r.slice(-2).join(); }}\n\
var it = gen();\n\
print(it.next().value, it.next('a').value, it.next().value, JSON.stringify(it.next('b')));\n\
var thrown = gen(); thrown.next();\n\
try {{ thrown.throw(new Error('in')); }} catch (e) {{ print(e.message); }}\n\
var returned = gen(); returned.next(); print(JSON.stringify(returned.return(9)));\n\
async function f() {{ var r = [{deep} await 1, await Promise.resolve(2)]; \
for await (var v of [3, 4]) r.push(v); return r.slice(-4).join(); }}\n\
f().then(v => print('async', v));"
);
assert_eq!(
run_deep(source),
"1 2 3 {\"value\":\"a,\",\"done\":true}\nin\n{\"value\":9,\"done\":true}\nasync 1,2,3,4\n"
);
}
#[test]
fn statements_nested_past_the_window_close_and_unwind() {
let mut source = String::from(
"var log = [];\n\
function it(k) { var i = 0; return { [Symbol.iterator]() { return this; }, \
next() { return { value: i++, done: i > 1 }; }, \
return() { log.push(k); return {}; } }; }\n",
);
let loops: String = (0..100)
.map(|index| format!("for (var v{index} of it({index})) "))
.collect();
source.push_str(&format!(
"function a() {{ try {{ {loops}{{ throw new Error('x'); }} }} \
catch (e) {{ log.push(e.message); }} return log.length + ':' + log[0] + ',' + log[99] + ',' + log[100]; }}\n\
print(a());\n"
));
let tries = "try { ".repeat(300);
let catches: String = (0..300)
.map(|index| format!(" }} catch (e{index}) {{ log.push({index}); throw e{index} + 1; }}"))
.collect();
source.push_str(&format!(
"function b() {{ log = []; try {{ {tries}throw 1;{catches} }} \
catch (z) {{ return z + ':' + log.length; }} }}\nprint(b());\n"
));
let finallies: String = (0..300)
.map(|index| format!(" }} finally {{ log.push({index}); }}"))
.collect();
source.push_str(&format!(
"function c() {{ {tries}return 'r';{finallies} }}\n\
log = []; print(c(), log.length, log[0], log[299]);\n"
));
let enumerations: String = (0..260)
.map(|index| format!("for (var k{index} in {{ a: 1 }}) "))
.collect();
source.push_str(&format!(
"var count = 0; function d() {{ {enumerations}count++; return count; }}\nprint(d());\n"
));
let generators: String = (0..90)
.map(|index| format!("for (var y{index} of it({index})) "))
.collect();
source.push_str(&format!(
"function* e() {{ {generators}yield 5; }}\n\
log = []; var g = e(); print(g.next().value, JSON.stringify(g.return(9)), log.length, log[0], log[89]);\n"
));
assert_eq!(
run_deep(source),
"101:99,0,x\n301:300\nr 300 0 299\n1\n5 {\"value\":9,\"done\":true} 90 89 0\n"
);
}
#[test]
fn spilled_locals_and_temporaries_share_the_scope() {
let locals: String = (0..300)
.map(|index| format!("var l{index} = {index};"))
.collect();
let reads: String = (0..300)
.map(|index| format!("l{index}"))
.collect::<Vec<_>>()
.join(",");
let source = format!(
"function f() {{ {locals} var inner = () => l299 + l0; \
var all = [{reads}]; return all.length + ':' + all[299] + ':' + Math.max({reads}) + ':' + inner(); }}\n\
print(f());\n\
function g() {{ 'use strict'; return h({reads2}); }} function h() {{ return arguments.length; }}\n\
print(g());\n\
function K() {{ this.n = arguments.length; }} print(new K({reads2}).n);",
reads2 = digits(400)
);
assert_eq!(run_deep(source), "300:299:299:299\n400\n400\n");
}