use std::io::Read as _;
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::{Duration, Instant};
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed ^ 0x9E37_79B9_7F4A_7C15)
}
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: u64) -> u64 {
if n == 0 {
0
} else {
self.next_u64() % n
}
}
}
fn pick<'a, T>(rng: &mut Rng, xs: &'a [T]) -> &'a T {
&xs[rng.below(xs.len() as u64) as usize]
}
fn ours_bin() -> PathBuf {
if let Ok(p) = std::env::var("CARGO_BIN_EXE_node") {
return PathBuf::from(p);
}
if let Some(d) = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_path_buf()))
{
let cand = d.join("node");
if cand.exists() {
return cand;
}
}
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("debug")
.join("node")
}
fn resolve_oracle(ours: &Path) -> String {
let chosen = match std::env::var("NODE_JS_FUZZ_NODE") {
Ok(p) => {
let Some(abs) = absolutize(&p) else {
eprintln!("parity-fuzz: NODE_JS_FUZZ_NODE={p}: not found");
std::process::exit(2);
};
if version_of(&abs).is_none() {
eprintln!("parity-fuzz: NODE_JS_FUZZ_NODE={p}: not a usable node");
std::process::exit(2);
}
abs
}
Err(_) => {
let mut found = None;
for p in [
"node",
"/opt/homebrew/bin/node",
"/usr/local/bin/node",
"/usr/bin/node",
] {
if let Some(abs) = absolutize(p) {
if version_of(&abs).is_some() {
found = Some(abs);
break;
}
}
}
match found {
Some(p) => p,
None => {
eprintln!("parity-fuzz: no reference node found; set NODE_JS_FUZZ_NODE");
std::process::exit(2);
}
}
}
};
let real = std::fs::canonicalize(&chosen).unwrap_or_else(|_| PathBuf::from(&chosen));
if std::fs::canonicalize(ours)
.map(|o| o == real)
.unwrap_or(false)
{
eprintln!(
"parity-fuzz: the resolved oracle IS the binary under test ({}) — \
a run against itself compares nothing; set NODE_JS_FUZZ_NODE",
real.display()
);
std::process::exit(2);
}
let v = version_of(&chosen).unwrap_or_default();
if !is_node_version(&v) {
eprintln!(
"parity-fuzz: {} answers --version with {v:?}, which is not a Node \
version (vX.Y.Z) — refusing to treat it as the oracle",
real.display()
);
std::process::exit(2);
}
real.to_string_lossy().into_owned()
}
fn is_node_version(v: &str) -> bool {
let Some(rest) = v.strip_prefix('v') else {
return false;
};
let parts: Vec<&str> = rest.split('.').collect();
parts.len() == 3
&& parts
.iter()
.all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
}
fn absolutize(prog: &str) -> Option<String> {
let p = Path::new(prog);
if p.components().count() > 1 {
return p.exists().then(|| prog.to_string());
}
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|d| d.join(prog))
.find(|c| c.is_file())
.map(|c| c.to_string_lossy().into_owned())
})
}
fn version_of(prog: &str) -> Option<String> {
let o = Command::new(prog).arg("--version").output().ok()?;
if !o.status.success() && o.stdout.is_empty() && o.stderr.is_empty() {
return None;
}
let mut s = String::from_utf8_lossy(&o.stdout).trim().to_string();
if s.is_empty() {
s = String::from_utf8_lossy(&o.stderr).trim().to_string();
}
if s.is_empty() {
None
} else {
Some(s)
}
}
fn oracle_id(oracle: &str) -> String {
let v = version_of(oracle).unwrap_or_else(|| "unknown".to_string());
format!("{oracle} ({v})")
}
static CMP_STDERR: AtomicBool = AtomicBool::new(false);
struct RunOut {
stdout: Vec<u8>,
stderr: Vec<u8>,
exit: i32,
timed_out: bool,
}
fn render(bytes: &[u8]) -> String {
let text = String::from_utf8_lossy(bytes);
let text = text.trim_end_matches('\n');
if std::str::from_utf8(bytes).is_err() {
let hex: Vec<String> = bytes.iter().map(|b| format!("{b:02x}")).collect();
return format!("{text}\n (hex) {}", hex.join(" "));
}
text.to_string()
}
fn norm_stderr(s: &[u8]) -> Vec<u8> {
let text = String::from_utf8_lossy(s);
let last = text
.lines()
.map(|l| l.trim())
.rfind(|l| !l.is_empty())
.unwrap_or("")
.to_lowercase();
last.into_bytes()
}
fn differs(oracle: &RunOut, ours: &RunOut) -> bool {
if oracle.exit != ours.exit {
return true;
}
if oracle.stdout != ours.stdout {
return true;
}
if CMP_STDERR.load(Ordering::Relaxed)
&& norm_stderr(&oracle.stderr) != norm_stderr(&ours.stderr)
{
return true;
}
false
}
fn run_prog(prog: &Path, src: &str, timeout: Duration) -> RunOut {
let mut cmd = Command::new(prog);
cmd.arg("-e")
.arg(src)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
for (k, v) in [
("TZ", "UTC"),
("LANG", "en_US.UTF-8"),
("LC_ALL", "en_US.UTF-8"),
] {
cmd.env(k, v);
}
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(_) => {
return RunOut {
stdout: Vec::new(),
stderr: Vec::new(),
exit: -1,
timed_out: false,
}
}
};
let mut out_h = child.stdout.take().map(|mut o| {
std::thread::spawn(move || {
let mut b = Vec::new();
let _ = o.read_to_end(&mut b);
b
})
});
let mut err_h = child.stderr.take().map(|mut e| {
std::thread::spawn(move || {
let mut b = Vec::new();
let _ = e.read_to_end(&mut b);
b
})
});
let deadline = Instant::now() + timeout;
let mut timed_out = false;
let exit;
loop {
match child.try_wait() {
Ok(Some(status)) => {
exit = status.code().unwrap_or(-1);
break;
}
Ok(None) => {
if Instant::now() >= deadline {
let _ = child.kill();
let s = child.wait().ok();
exit = s.and_then(|s| s.code()).unwrap_or(-1);
timed_out = true;
break;
}
std::thread::sleep(Duration::from_millis(2));
}
Err(_) => {
exit = -1;
break;
}
}
}
let stdout = out_h.take().and_then(|h| h.join().ok()).unwrap_or_default();
let stderr = err_h.take().and_then(|h| h.join().ok()).unwrap_or_default();
RunOut {
stdout,
stderr,
exit,
timed_out,
}
}
fn build_program(stmts: &[String]) -> String {
stmts.join("\n")
}
const INTS: &[&str] = &[
"0", "1", "2", "3", "5", "7", "10", "-1", "-3", "-7", "42", "100", "-100", "255", "1000",
];
const POSINTS: &[&str] = &["1", "2", "3", "4", "5", "6", "8", "10"];
const FLOATS: &[&str] = &[
"0.1",
"0.2",
"0.5",
"1.5",
"2.5",
"3.14",
"10.0",
"-1.5",
"100.0",
"0.0",
"-0.0",
"1e21",
"1e-7",
"1e-6",
"1e100",
"1.5e300",
"123.456",
"0.0001234",
"9.999999e20",
"1e22",
"-0.4",
"1.005",
"8.575",
];
const SPECIALS: &[&str] = &["NaN", "Infinity", "-Infinity", "0/0", "1/0", "-1/0", "-0"];
const STRS: &[&str] = &[
"'hello'",
"'World'",
"'abc'",
"'JavaScript'",
"''",
"'a'",
"'foo bar'",
"' pad '",
"'AbC'",
"'café'",
"'𝒳'",
"'ab𝒳cd'",
"'😀🎉'",
];
const CHAR_CODES: &[&str] = &["65", "97, 98", "0x263A", "0xD835, 0xDCB3", "0x1D4B3"];
const CODE_POINTS: &[&str] = &["65", "97, 98", "0x263A", "0x1D4B3", "0x1F600, 0x41"];
fn gen_num(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, FLOATS);
let b = pick(r, FLOATS);
let k = r.below(7); let e = match r.below(11) {
0 => "0.1 + 0.2".to_string(),
1 => format!("{a} + {b}"),
2 => format!("{a} * {b}"),
3 => format!("{a} / {b}"),
4 => format!("({a}).toFixed({k})"),
5 => format!("({a}).toPrecision({})", 1 + r.below(6)),
6 => format!("({}).toString({})", pick(r, INTS), 2 + r.below(35)),
7 => pick(r, SPECIALS).to_string(),
8 => format!("{} + {}", pick(r, SPECIALS), a),
9 => a.to_string(),
_ => format!("{a} - {b} + {a}"),
};
vec![format!("console.log({e})")]
}
fn gen_bitwise(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let big = &[
"0xffffffff",
"0x7fffffff",
"0x80000000",
"4294967296",
"-1",
"2147483648",
"255",
"16",
"1",
"0",
"-256",
];
let a = pick(r, big);
let b = pick(r, &["0", "1", "4", "8", "16", "31", "32", "33"]);
let op = pick(r, &["&", "|", "^", "<<", ">>", ">>>"]);
let e = match r.below(6) {
0 => format!("{a} {op} {b}"),
1 => format!("~{a}"),
2 => format!("{a} >>> {b}"),
3 => format!("{a} << {b}"),
4 => format!("({a} & {}) | {b}", pick(r, big)),
_ => format!("{a} ^ {}", pick(r, big)),
};
vec![format!("console.log({e})")]
}
fn gen_equality(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let vals = &[
"0",
"1",
"''",
"'0'",
"'1'",
"'a'",
"null",
"undefined",
"true",
"false",
"[]",
"[0]",
"NaN",
];
let a = pick(r, vals);
let b = pick(r, vals);
let op = pick(r, &["==", "!=", "===", "!=="]);
let e = match r.below(4) {
0 => format!("{a} {op} {b}"),
1 => format!("{a} == {b}"),
2 => format!("{a} === {b}"),
_ => format!("({a} == {b}) === ({b} == {a})"),
};
vec![format!("console.log({e})")]
}
fn gen_strmeth(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let s = pick(r, STRS);
let idx = &["0", "1", "2", "-1", "-2", "3", "10"];
let a = pick(r, idx);
let b = pick(r, idx);
let e = match r.below(25) {
0 => format!("{s}.slice({a}, {b})"),
1 => format!("{s}.substring({a}, {b})"),
2 => format!("{s}.substr({a}, {b})"),
3 => format!("{s}.toUpperCase()"),
4 => format!("{s}.padStart({}, '*')", r.below(8)),
5 => format!("{s}.padEnd({}, '.')", r.below(8)),
6 => format!("{s}.repeat({})", r.below(4)),
7 => format!(
"{}.split('')",
pick(r, &["'abc'", "'a'", "''", "'hi'", "'abcde'", "'Wor'"])
),
8 => format!("{s}.replace('a', 'X')"),
9 => format!("{s}.replaceAll('a', 'X')"),
10 => format!("{s}.indexOf('o')"),
11 => format!("{s}.at({a})"),
12 => format!("`[${{{s}}}]-[${{{}}}]`", pick(r, INTS)),
13 => format!("`${{{}}}${{{}}}`", pick(r, INTS), s),
14 => format!("{s}.length"),
15 => format!("{s}.charCodeAt({a})"),
16 => format!("{s}.codePointAt({a})"),
17 => format!("{s}.charAt({a})"),
18 => format!("{s}[{a}]"),
19 => format!("{s}.indexOf('o', {a})"),
20 => format!("{s}.lastIndexOf('o', {a})"),
21 => format!("{s}.includes('o', {a})"),
22 => format!("{s}.startsWith('a', {a})"),
23 => format!("String.fromCharCode({})", pick(r, CHAR_CODES)),
_ => format!("String.fromCodePoint({})", pick(r, CODE_POINTS)),
};
vec![format!("console.log({e})")]
}
fn gen_array(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let arr = pick(
r,
&[
"[3, 1, 2, 5, 4]",
"[10, 9, 100, 20, 1]",
"[1, 2, 3]",
"['b', 'a', 'c']",
"[[1, 2], [3], [4, 5]]",
"[1, 'a', true, null]",
],
);
let e = match r.below(14) {
0 => format!("{arr}.map(x => x)"),
1 => "[3, 1, 2, 5, 4].sort()".to_string(),
2 => "[10, 9, 100, 20, 1].sort()".to_string(),
3 => "[3, 1, 2, 5, 4].sort((a, b) => a - b)".to_string(),
4 => format!("{arr}.slice({}, {})", r.below(5), 1 + r.below(5)),
5 => format!("{arr}.join('-')"),
6 => format!("{arr}.concat([9])"),
7 => format!("{arr}.indexOf({})", pick(r, INTS)),
8 => format!("{arr}.includes({})", pick(r, INTS)),
9 => "[[1, 2], [3], [4, 5]].flat()".to_string(),
10 => "[1, 2, 3].flatMap(x => [x, x * 2])".to_string(),
11 => "[1, 2, 3, 4].filter(x => x % 2 === 0)".to_string(),
12 => "[1, 2, 3, 4].reduce((a, b) => a + b, 0)".to_string(),
_ => format!("[...{arr}, 7]"),
};
vec![format!("console.log({e})")]
}
fn gen_plus(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let vals = &[
"1", "2", "'a'", "'x'", "[1, 2]", "[3]", "[]", "true", "null", "3.5", "'5'",
];
let a = pick(r, vals);
let b = pick(r, vals);
let e = match r.below(5) {
0 => format!("{a} + {b}"),
1 => format!("{a} + {b} + {}", pick(r, vals)),
2 => format!("[1, 2] + {}", pick(r, INTS)),
3 => format!("'' + {a}"),
_ => format!("{a} + '' + {b}"),
};
vec![format!("console.log({e})")]
}
fn gen_json(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let obj = pick(
r,
&[
"{a: 1, b: [2, 3], c: 'x'}",
"{name: 'Ada', nums: [1, 2, 3], ok: true}",
"[1, [2, [3, [4]]]]",
"{nested: {deep: {value: 42}}, list: []}",
"{n: null, u: undefined, f: false, s: 'hi'}",
],
);
let e = match r.below(4) {
0 => format!("JSON.stringify({obj})"),
1 => format!("JSON.stringify({obj}, null, 2)"),
2 => format!("JSON.stringify({obj}, null, ' ')"),
_ => format!("JSON.stringify(JSON.parse(JSON.stringify({obj})))"),
};
vec![format!("console.log({e})")]
}
fn gen_logic(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let vals = &[
"0",
"1",
"''",
"'x'",
"null",
"undefined",
"true",
"false",
"[]",
"{}",
"NaN",
];
let a = pick(r, vals);
let b = pick(r, vals);
let e = match r.below(7) {
0 => format!("typeof {a}"),
1 => format!("{a} && {b}"),
2 => format!("{a} || {b}"),
3 => format!("{a} ?? {b}"),
4 => format!("!!{a}"),
5 => format!("{a} ? {b} : {}", pick(r, vals)),
_ => format!("typeof ({a} || {b})"),
};
vec![format!("console.log({e})")]
}
fn gen_parse(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let strs = &[
"'42'",
"'42px'",
"' 17 '",
"'0x1f'",
"'101'",
"'3.14'",
"'1e3'",
"''",
"'abc'",
"'-5'",
"' '",
"'0.5e2'",
"'Infinity'",
"' 12.5abc'",
];
let s = pick(r, strs);
let e = match r.below(6) {
0 => format!("parseInt({s})"),
1 => format!("parseInt({s}, {})", pick(r, &["2", "8", "10", "16"])),
2 => format!("parseFloat({s})"),
3 => format!("Number({s})"),
4 => "parseInt('101', 2)".to_string(),
_ => format!("Number({s}) + 1"),
};
vec![format!("console.log({e})")]
}
fn gen_object(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let obj = pick(
r,
&[
"{b: 2, a: 1, c: 3}",
"{x: 10, y: 20}",
"{name: 'Ada', age: 36}",
"{}",
],
);
let e = match r.below(8) {
0 => format!("Object.keys({obj})"),
1 => format!("Object.values({obj})"),
2 => format!("Object.entries({obj})"),
3 => format!("Object.assign({{}}, {obj}, {{d: 4}})"),
4 => "Object.fromEntries([['a', 1], ['b', 2]])".to_string(),
5 => format!("({{...{obj}, z: 9}})"),
6 => "({['k' + 1]: 'v', ['k' + 2]: 'w'})".to_string(),
_ => obj.to_string(),
};
vec![format!("console.log({e})")]
}
fn gen_arith(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, INTS);
let b = pick(r, INTS);
let c = pick(r, INTS);
let exp = pick(r, &["2", "3", "0", "-1"]);
let op = pick(r, &["+", "-", "*", "/", "%"]);
let e = match r.below(6) {
0 => format!("{a} {op} {b}"),
1 => format!("{a} + {b} * {c}"),
2 => format!("({a} + {b}) * {c}"),
3 => format!("(-({a})) ** {exp}"),
4 => format!("{a} % {b} + {c}"),
_ => format!("{a} {op} {b} {op} {c}"),
};
vec![format!("console.log({e})")]
}
fn gen_math(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let x = pick(r, INTS);
let f = pick(r, FLOATS);
let e = match r.below(12) {
0 => format!("Math.floor({f})"),
1 => format!("Math.ceil({f})"),
2 => format!("Math.round({f})"),
3 => format!("Math.trunc({f})"),
4 => format!("Math.abs({x})"),
5 => format!("Math.sign({x})"),
6 => format!("Math.max({x}, {})", pick(r, INTS)),
7 => format!("Math.min({x}, {})", pick(r, INTS)),
8 => format!(
"Math.pow({}, {})",
pick(r, POSINTS),
pick(r, &["2", "3", "0"])
),
9 => format!("Math.sqrt({})", pick(r, POSINTS)),
10 => "Math.max(...[3, 1, 4, 1, 5])".to_string(),
_ => format!("Math.hypot({}, {})", pick(r, POSINTS), pick(r, POSINTS)),
};
vec![format!("console.log({e})")]
}
fn gen_control(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let n = 3 + r.below(6);
match r.below(4) {
0 => vec![
"let s = 0;".into(),
format!("for (let i = 0; i < {n}; i++) s += i;"),
"console.log(s);".into(),
],
1 => vec![
"const out = [];".into(),
format!("for (const v of [1, 2, 3, 4]) out.push(v * {n});"),
"console.log(out);".into(),
],
2 => vec![
"const f = x => x < 2 ? x : f(x - 1) + f(x - 2);".into(),
format!("console.log(f({}));", 5 + r.below(8)),
],
_ => vec![
"let acc = 1;".into(),
format!("let i = 1; while (i <= {n}) {{ acc *= i; i++; }}"),
"console.log(acc);".into(),
],
}
}
fn gen_class(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, INTS);
let b = pick(r, INTS);
match r.below(9) {
0 => vec![
"class C { constructor(x) { this.x = x; } dbl() { return this.x * 2; } }".into(),
format!("console.log(new C({a}).dbl());"),
],
1 => vec![
"class A { constructor(n) { this.n = n; } val() { return this.n; } }".into(),
"class B extends A { constructor(n) { super(n); this.m = n + 1; } val() { return super.val() + this.m; } }".into(),
format!("console.log(new B({a}).val());"),
],
2 => vec![
format!("class C {{ static add(a, b) {{ return a + b; }} static k = {a}; }}"),
format!("console.log(C.add({a}, {b}), C.k);"),
],
3 => vec![
"class Temp { constructor(c) { this._c = c; } get f() { return this._c * 2; } set f(v) { this._c = v - 1; } }".into(),
format!("const t = new Temp({a}); const before = t.f; t.f = {b}; console.log(before, t.f);"),
],
4 => vec![
"class Base {} class Mid extends Base {} class Leaf extends Mid {}".into(),
"const x = new Leaf();".into(),
"console.log(x instanceof Leaf, x instanceof Mid, x instanceof Base, x instanceof Object);".into(),
],
5 => vec![
format!("class C {{ #secret = {a}; reveal() {{ return this.#secret + {b}; }} }}"),
"const c = new C();".into(),
"console.log(c.reveal(), JSON.stringify(c), Object.keys(c).length);".into(),
],
6 => vec![
"class Counter { constructor() { this.c = 0; } inc() { this.c++; return this; } }".into(),
"const k = new Counter(); k.inc().inc().inc(); console.log(k.c);".into(),
],
7 => vec![
"class P { constructor(x, y) { this.x = x; this.y = y; } toString() { return `(${this.x},${this.y})`; } }".into(),
format!("console.log(String(new P({a}, {b})), new P({a}, {b}).constructor.name);"),
],
_ => vec![
"class Shape { area() { return 0; } }".into(),
"class Sq extends Shape { constructor(s) { super(); this.s = s; } area() { return this.s * this.s; } }".into(),
format!("const arr = [new Sq({}), new Sq({})]; console.log(arr.map(x => x.area()));", 1 + r.below(6), 1 + r.below(6)),
],
}
}
fn gen_generator(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let n = 2 + r.below(4);
match r.below(7) {
0 => vec![
format!("function* g() {{ yield {}; yield {}; yield {}; }}", pick(r, INTS), pick(r, INTS), pick(r, INTS)),
"console.log([...g()]);".into(),
],
1 => vec![
format!("function* g() {{ for (let i = 0; i < {n}; i++) yield i * {n}; }}"),
"console.log(Array.from(g()));".into(),
],
2 => vec![
format!("function* g() {{ yield* [{}, {}]; yield {}; }}", pick(r, INTS), pick(r, INTS), pick(r, INTS)),
"console.log([...g()]);".into(),
],
3 => vec![
"function* g() { let x = yield 1; let y = yield x + 1; yield y + 1; }".into(),
format!("const it = g(); console.log(it.next().value, it.next({a}).value, it.next({b}).value);", a = 10 + r.below(5), b = 20 + r.below(5)),
],
4 => vec![
format!("function* g() {{ yield {}; yield {}; }}", pick(r, POSINTS), pick(r, POSINTS)),
"let sum = 0; for (const v of g()) sum += v; console.log(sum);".into(),
],
5 => vec![
"function* range(n) { for (let i = 0; i < n; i++) yield i; }".into(),
format!("console.log(Math.max(...range({})));", 1 + r.below(6)),
],
_ => vec![
"function* g() { yield 1; return 99; yield 2; }".into(),
"const it = g(); console.log(it.next().value, it.next().value, it.next().done);".into(),
],
}
}
fn gen_mapset(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, INTS);
let b = pick(r, INTS);
match r.below(8) {
0 => vec![format!(
"const m = new Map(); m.set('a', {a}); m.set('b', {b}); console.log(m.size, m.get('a'), m.has('b'), [...m.keys()]);"
)],
1 => vec![format!(
"const s = new Set([{a}, {b}, {a}, {b}]); console.log(s.size, [...s]);"
)],
2 => vec![format!(
"const m = new Map([['x', {a}], ['y', {b}]]); console.log([...m.values()], [...m.entries()]);"
)],
3 => vec![format!(
"const s = new Set(); s.add({a}); s.add({b}); s.delete({a}); console.log(s.has({a}), s.size);"
)],
4 => vec![
format!("const m = new Map([['k', {a}]]); let out = []; m.forEach((v, k) => out.push(k + ':' + v)); console.log(out);"),
],
5 => vec![format!(
"const s = new Set([1, 2, 3, 4]); const doubled = [...s].map(x => x * {}); console.log(doubled);", 1 + r.below(4)
)],
6 => vec![
"const m = new Map(); m.set(1, 'a'); m.set(2, 'b'); m.clear(); console.log(m.size, [...m.keys()]);".into()
],
_ => vec![format!(
"const wm = new WeakMap(); const k = {{}}; wm.set(k, {a}); console.log(wm.get(k), wm.has(k), wm.has({{}}));"
)],
}
}
fn gen_setops(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const OPS: &[&str] = &["union", "intersection", "difference", "symmetricDifference"];
const PREDS: &[&str] = &["isSubsetOf", "isSupersetOf", "isDisjointFrom"];
const LEFT: &[&str] = &["[1,2,3]", "[3,1]", "[]", "[2]", "[1,2,3,4,5]"];
const RIGHT: &[&str] = &["[2,3]", "[9]", "[]", "[1,2,3]", "[3,4]"];
let l = pick(r, LEFT);
let rt = pick(r, RIGHT);
match r.below(7) {
0 => vec![format!(
"console.log([...new Set({l}).{}(new Set({rt}))]);",
pick(r, OPS)
)],
1 => vec![format!(
"console.log(new Set({l}).{}(new Set({rt})));",
pick(r, PREDS)
)],
2 => vec![
format!("const other = {{ size: {rt}.length, has: v => {rt}.includes(v), keys: () => {rt}[Symbol.iterator]() }};"),
format!("console.log([...new Set({l}).{}(other)]);", pick(r, OPS)),
],
3 => vec![
format!("const other = {{ size: {rt}.length, has: v => {rt}.includes(v), keys: () => {rt}[Symbol.iterator]() }};"),
format!("console.log(new Set({l}).{}(other));", pick(r, PREDS)),
],
4 => vec![
format!("class S extends Set {{}}; const a = new S({l}); const b = new Set({rt});"),
format!("const c = a.{}(b);", pick(r, OPS)),
"console.log(c.constructor.name, c === a, c === b, c instanceof Set);".into(),
],
5 => vec![
format!("const bad = {};", pick(r, &["7", "'s'", "null", "{ size: NaN, has(){}, keys(){} }", "{ size: -1, has(){}, keys(){} }", "{ size: 1, has: 1, keys(){} }"])),
format!("try {{ new Set({l}).{}(bad); console.log('no throw'); }} catch (e) {{ console.log(e.constructor.name, e.message); }}", pick(r, OPS)),
],
_ => vec![format!(
"console.log([...new Set([-0, NaN, 1]).{}(new Set([0, NaN, 2]))]);",
pick(r, OPS)
)],
}
}
fn gen_strkeys(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const STRS: &[&str] = &["''", "'a'", "'ab'", "'abc'", "'a b'", "'\\u00e9x'"];
let s = pick(r, STRS);
let boxed = if r.below(2) == 0 {
s.to_string()
} else {
format!("new String({s})")
};
match r.below(8) {
0 => vec![format!("console.log(Object.keys({boxed}), Object.values({boxed}));")],
1 => vec![format!("console.log(Object.entries({boxed}));")],
2 => vec![format!(
"console.log(Object.getOwnPropertyNames({boxed}));"
)],
3 => vec![format!(
"console.log(Object.getOwnPropertyDescriptor({boxed}, {}), Object.getOwnPropertyDescriptor({boxed}, 'length'));",
r.below(4)
)],
4 => vec![format!(
"console.log(JSON.stringify(Object.assign({{}}, {boxed})), JSON.stringify({{ ...{s} }}));"
)],
5 => vec![format!(
"const out = []; for (const k in {boxed}) out.push(k); console.log(out);"
)],
6 => vec![format!(
"console.log(Object.getOwnPropertyDescriptors({boxed}));"
)],
_ => vec![
format!("const o = {}; ", pick(r, &["{ [Symbol.toStringTag]: 'T' }", "new (class { get [Symbol.toStringTag]() { return 'D'; } })()", "{}", "new Map()"])),
"console.log(String(o), `${o}`, o + '', o.toString(), Object.prototype.toString.call(o));".into(),
],
}
}
fn gen_proto(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, INTS);
match r.below(13) {
0 => vec![format!(
"const o = {{ a: {a}, b: 2 }}; console.log('a' in o, 'c' in o, o.hasOwnProperty('a'), o.hasOwnProperty('toString'));"
)],
1 => vec![
"const proto = { greet() { return 'hi'; } };".into(),
"const o = Object.create(proto); o.x = 1;".into(),
"console.log(o.greet(), o.hasOwnProperty('greet'), Object.getPrototypeOf(o) === proto);".into(),
],
2 => vec![
"function Animal(n) { this.n = n; } Animal.prototype.speak = function() { return this.n; };".into(),
"const d = new Animal('rex'); console.log(d.speak(), d instanceof Animal, d.constructor === Animal);".into(),
],
3 => vec![
"console.log([] instanceof Array, [] instanceof Object, ({}) instanceof Object, (() => {}) instanceof Function);".into()
],
4 => vec![
"const a = Symbol('x'), b = Symbol('x');".into(),
"console.log(typeof a, a.description, a === b, Symbol.for('k') === Symbol.for('k'));".into(),
],
5 => vec![
"const o = {};".into(),
"o[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; };".into(),
"console.log([...o]);".into(),
],
6 => vec![
format!("const base = {{ v: {a} }}; const o = {{ __proto__: base }};"),
"console.log(o.v, Object.getPrototypeOf(o) === base);".into(),
],
7 => vec![
"class A {} class B extends A {}".into(),
"console.log(Object.getPrototypeOf(B.prototype) === A.prototype, new B() instanceof A);".into(),
],
8 => vec![
"const o = { get x() { return 42; } };".into(),
"console.log(o.x, Object.getOwnPropertyDescriptor(o, 'x').get !== undefined);".into(),
],
9 => vec![format!(
"console.log([].constructor.name, ({{}}).constructor.name, ({a}).constructor.name, 'x'.constructor.name, true.constructor.name);"
)],
10 => vec![
"console.log(new Map().constructor.name, new Set().constructor.name, Promise.resolve(1).constructor.name);".into(),
],
11 => vec![
"console.log(Array.name, Object.name, Number.name, String.name, Symbol.name, Promise.name, Map.name, Set.name, Error.name, TypeError.name, typeof Math.name, typeof JSON.name);".into(),
],
_ => vec![
"let r;".into(),
"try { null.x; } catch (e) { r = [e instanceof TypeError, e instanceof Error, e.constructor.name]; }".into(),
"console.log(r);".into(),
],
}
}
fn gen_async(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let a = pick(r, INTS);
let b = pick(r, INTS);
match r.below(8) {
0 => vec![format!(
"Promise.resolve({a}).then(v => v * 2).then(v => console.log(v));"
)],
1 => vec![
"console.log('sync');".into(),
"Promise.resolve().then(() => console.log('micro'));".into(),
"console.log('sync2');".into(),
],
2 => vec![
format!("async function f() {{ const x = await Promise.resolve({a}); const y = await {b}; return x + y; }}"),
"f().then(v => console.log(v));".into(),
],
3 => vec![format!(
"Promise.all([Promise.resolve({a}), Promise.resolve({b}), 7]).then(a => console.log(a));"
)],
4 => vec![
"console.log(1);".into(),
"Promise.resolve().then(() => console.log(3));".into(),
"process.nextTick(() => console.log(2));".into(),
"console.log('end');".into(),
],
5 => vec![
"setTimeout(() => console.log('timer'), 0);".into(),
"Promise.resolve().then(() => console.log('promise'));".into(),
"console.log('main');".into(),
],
6 => vec![
format!("async function f() {{ try {{ await Promise.reject(new Error('e{a}')); }} catch (e) {{ return e.message; }} }}"),
"f().then(v => console.log(v));".into(),
],
_ => vec![format!(
"Promise.race([Promise.resolve('a'), Promise.resolve('b')]).then(v => console.log(v)); Promise.allSettled([Promise.resolve({a}), Promise.reject({b})]).then(r => console.log(r.map(x => x.status)));"
)],
}
}
fn gen_bigint(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let bigs = &[
"0n", "1n", "2n", "3n", "7n", "10n", "-3n", "-7n", "42n", "100n", "255n", "1000n",
];
let a = pick(r, bigs);
let b = pick(r, bigs);
let nb = pick(r, &["1", "2", "10", "-3", "0", "100"]); let e = match r.below(14) {
0 => format!("{a} + {b}"),
1 => format!("{a} - {b}"),
2 => format!("{a} * {b}"),
3 => format!("({b} === 0n) ? 0n : {a} / {b}"),
4 => format!("({b} === 0n) ? 0n : {a} % {b}"),
5 => format!("({a}) ** {}", pick(r, &["0n", "1n", "2n", "3n"])),
6 => format!("{a} < {b}, {a} > {b}, {a} <= {b}, {a} >= {b}"),
7 => format!("{a} == {nb}, {a} === {nb}, {a} != {nb}"),
8 => format!("typeof {a}, String({a}), {a}.toString()"),
9 => format!("{a}.toString({})", 2 + r.below(35)),
10 => format!("{a} & {b}, {a} | {b}, {a} ^ {b}, ~{a}"),
11 => format!(
"(() => {{ try {{ return {a} + {nb}; }} catch (e) {{ return e.message; }} }})()"
),
12 => format!(
"BigInt({}), BigInt('{}'), BigInt(true)",
pick(r, &["5", "42", "-7", "0"]),
pick(r, &["10", "255", "0x1f"])
),
_ => format!("{a} + {b} * {}", pick(r, bigs)),
};
vec![format!("console.log({e})")]
}
fn gen_regex(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let cases: &[(&str, &str)] = &[
(r"\d+", "abc123def456"),
(r"[a-z]+", "Hello World"),
(r"\w+", "foo_bar baz"),
(r"a|b", "xayb"),
(r"^\w+", "start here"),
(r"\d{2,3}", "a1b22c333"),
(r"(ab)+", "xababy"),
(r"\s+", "a b c"),
(r"[0-9]{4}", "year 2024 end"),
(r"[A-Z]\w*", "Foo bar Baz"),
(r"\d+", "a𝒳b123def"),
(r"[a-z]+", "𝒳Hello World"),
(r"[0-9]{4}", "😀year 2024 end"),
];
let (pat, input) = *pick(r, cases);
let e = match r.below(10) {
0 => format!("/{pat}/.test(\"{input}\")"),
1 => format!("/{pat}/.exec(\"{input}\")[0]"),
2 => format!("/{pat}/.exec(\"{input}\").index"),
3 => format!("\"{input}\".match(/{pat}/g)"),
4 => format!("\"{input}\".replace(/{pat}/g, \"#\")"),
5 => format!("\"{input}\".replace(/{pat}/, \"#\")"),
6 => format!("\"{input}\".split(/{pat}/)"),
7 => format!("\"{input}\".search(/{pat}/)"),
8 => format!("[...\"{input}\".matchAll(/{pat}/g)].map(m => m[0])"),
_ => format!("/{pat}/i.test(\"{input}\"), /{pat}/.source, /{pat}/g.flags"),
};
vec![format!("console.log({e})")]
}
fn gen_descriptor(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let enumerable = pick(r, &["true", "false"]);
let writable = pick(r, &["true", "false"]);
let configurable = pick(r, &["true", "false"]);
let acc_enum = pick(r, &["true", "false"]);
let mut setup = vec![
"const o = {first: 1};".to_string(),
format!(
"Object.defineProperty(o, 'mid', {{value: 2, enumerable: {enumerable}, \
writable: {writable}, configurable: {configurable}}});"
),
format!(
"Object.defineProperty(o, 'acc', {{get() {{ return 3; }}, enumerable: {acc_enum}, \
configurable: true}});"
),
"o.last = 4;".to_string(),
];
if r.below(2) == 0 {
setup.push("o.mid = 99;".to_string());
}
let e = match r.below(11) {
0 => "Object.keys(o)",
1 => "Object.values(o)",
2 => "Object.entries(o)",
3 => "JSON.stringify(o)",
4 => "JSON.stringify({...o})",
5 => "JSON.stringify(Object.assign({}, o))",
6 => "Object.getOwnPropertyNames(o)",
7 => "JSON.stringify(Object.getOwnPropertyDescriptor(o, 'mid'))",
8 => "[o.propertyIsEnumerable('mid'), o.propertyIsEnumerable('acc'), o.propertyIsEnumerable('last')]",
9 => "[Object.prototype.hasOwnProperty.call(o, 'mid'), Object.hasOwn(o, 'acc'), Object.hasOwn(o, 'nope')]",
_ => "(() => { const ks = []; for (const k in o) ks.push(k); return ks; })()",
};
setup.push(format!("console.log({e});"));
setup
}
fn gen_freeze(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let op = pick(r, &["freeze", "seal", "preventExtensions"]);
let lit = pick(r, &["{a: 1, b: 2}", "{x: 'v'}", "{}", "{n: 0, m: null}"]);
let mutation = pick(
r,
&["o.a = 9;", "o.fresh = 1;", "delete o.a;", "o.b = o.b;"],
);
let e = match r.below(5) {
0 => "JSON.stringify(o)",
1 => "[Object.isFrozen(o), Object.isSealed(o), Object.isExtensible(o)]",
2 => "Object.keys(o)",
3 => "Object.getOwnPropertyNames(o)",
_ => "JSON.stringify(Object.getOwnPropertyDescriptors(o))",
};
vec![
format!("const o = Object.{op}({lit});"),
mutation.to_string(),
format!("console.log({e});"),
]
}
fn gen_identity(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let e = match r.below(12) {
0 => "[Math === Math, JSON === JSON, Reflect === Reflect]",
1 => "[Array.prototype === Array.prototype, Object.prototype === Object.prototype]",
2 => "[Object.getPrototypeOf([]) === Array.prototype, Object.getPrototypeOf({}) === Object.prototype]",
3 => "[Object.getPrototypeOf(new Map()) === Map.prototype, Object.getPrototypeOf(new Set()) === Set.prototype]",
4 => "String(Object.getPrototypeOf(Object.create(null)))",
5 => "(() => { class C {} return [Object.getPrototypeOf(new C()) === C.prototype, Object.getPrototypeOf(C.prototype) === Object.prototype]; })()",
6 => "(() => { class A {} class B extends A {} return [Object.getPrototypeOf(B.prototype) === A.prototype, new B() instanceof A]; })()",
7 => "(() => { function F() {} return [Object.getPrototypeOf(new F()) === F.prototype, F.prototype.constructor === F]; })()",
8 => "[[] instanceof Array, [] instanceof Object, /x/ instanceof RegExp, new Date(0) instanceof Date]",
9 => "[Object.keys(Object.prototype).length, Object.keys(Array.prototype).length]",
10 => "(() => { const p = {i: 1}; const o = Object.create(p); o.own = 2; const ks = []; for (const k in o) ks.push(k); return [ks, Object.keys(o)]; })()",
_ => "[Object.prototype.toString.call([]), Object.prototype.toString.call(null), Object.prototype.toString.call(new Map())]",
};
vec![format!("console.log({e});")]
}
fn gen_clone(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let e = match r.below(9) {
0 => "JSON.stringify(structuredClone({a: 1, b: [1, [2, 3]], c: {d: 'x'}}))",
1 => "(() => { const s = {a: [1, 2]}; const c = structuredClone(s); c.a.push(3); return [JSON.stringify(s), JSON.stringify(c)]; })()",
2 => "(() => { const sh = {id: 1}; const c = structuredClone({p: sh, q: sh}); return [c.p === c.q, c.p !== sh]; })()",
3 => "(() => { const cy = {n: 'r'}; cy.self = cy; const c = structuredClone(cy); return [c.self === c, c.n]; })()",
4 => "(() => { const c = structuredClone(new Map([['k', [1, 2]]])); return [c instanceof Map, c.size, JSON.stringify([...c])]; })()",
5 => "(() => { const c = structuredClone(new Set([1, 2, 2])); return [c instanceof Set, c.size, JSON.stringify([...c])]; })()",
6 => "(() => { const c = structuredClone(new Date(86400000)); return [c instanceof Date, c.toISOString()]; })()",
7 => "(() => { const c = structuredClone(/a+b/gim); return [c instanceof RegExp, c.source, c.flags]; })()",
_ => "(() => { const c = structuredClone(new Uint8Array([7, 8, 9])); return [c instanceof Uint8Array, c.length, c[1]]; })()",
};
vec![format!("console.log({e});")]
}
fn gen_error(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let ctor = pick(
r,
&[
"Error",
"TypeError",
"RangeError",
"SyntaxError",
"EvalError",
],
);
let msg = pick(r, &["boom", "", "with spaces", "sym#1"]);
let e = match r.below(9) {
0 => format!("JSON.stringify(Object.keys(new {ctor}('{msg}')))"),
1 => format!("Object.getOwnPropertyNames(new {ctor}('{msg}')).sort()"),
2 => format!("JSON.stringify(new {ctor}('{msg}'))"),
3 => format!("[String(new {ctor}('{msg}')), new {ctor}('{msg}').name, new {ctor}('{msg}').message]"),
4 => format!("JSON.stringify(Object.getOwnPropertyDescriptor(new {ctor}('{msg}'), 'message'))"),
5 => format!("(() => {{ const e = new {ctor}('{msg}', {{cause: 'why'}}); return [e.cause, JSON.stringify(Object.keys(e))]; }})()"),
6 => format!("(() => {{ const e = new {ctor}('{msg}'); e.extra = 1; return [JSON.stringify(Object.keys(e)), JSON.stringify(e)]; }})()"),
7 => format!("(() => {{ class E extends {ctor} {{ constructor(m) {{ super(m); this.name = 'E'; }} }} const e = new E('{msg}'); return [String(e), e instanceof {ctor}, e instanceof Error, JSON.stringify(Object.keys(e))]; }})()"),
_ => format!("(() => {{ const a = new AggregateError([new {ctor}('{msg}')], 'agg'); return [a.errors.length, a.message, JSON.stringify(Object.keys(a)), JSON.stringify(a)]; }})()"),
};
vec![format!("console.log({e});")]
}
fn abrupt(r: &mut Rng, kinds: &[&str], guard: Option<&str>) -> String {
let s = *pick(r, kinds);
match guard {
Some(g) => format!("if ({g}) {{ {s} }}"),
None => s.to_string(),
}
}
fn loop_wrap(kind: u64, var: &str, label: Option<&str>, n: u64, body: &str) -> String {
let lab = label.map(|l| format!("{l}: ")).unwrap_or_default();
let body =
format!("{{ let z_{var} = 'z' + {var}; if (z_{var} === '') out.push(z_{var}); {body} }}");
match kind % 5 {
0 => format!("{lab}for (let {var} = 0; {var} < {n}; {var}++) {{ {body} }}"),
1 => format!("{lab}for (const {var} of [0, 1, 2, 3].slice(0, {n})) {{ {body} }}"),
2 => format!("{lab}for (const {var} in {{ 0: 'a', 1: 'b', 2: 'c' }}) {{ {body} }}"),
3 => format!("{{ let {var} = -1; {lab}while ({var} + 1 < {n}) {{ {var}++; {body} }} }}"),
_ => {
format!("{{ let {var} = -1; {lab}do {{ {var}++; {body} }} while ({var} + 1 < {n}); }}")
}
}
}
fn gen_unwind(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let n = 2 + r.below(3);
let k = r.below(n);
let guard = format!("String(j) === '{k}'");
let g = Some(guard.as_str());
let sw = "(step - 1) % 3";
let in_loop: &[&str] = &["break;", "continue;", "throw new Error('E');"];
let in_loop2: &[&str] = &[
"break;",
"continue;",
"break L0;",
"continue L0;",
"throw new Error('E');",
];
let in_fn_loop: &[&str] = &[
"break;",
"continue;",
"return 'R';",
"throw new Error('E');",
];
let wrapped = r.below(2) == 0;
let fn_ret_or_throw: &[&str] = &["throw new Error('E');", "return 'R';"];
let in_switch: &[&str] = if wrapped {
&["break;", "throw new Error('E');", "return 'R';"]
} else {
&["break;", "throw new Error('E');"]
};
let labeled: &[&str] = if wrapped {
&["break L0;", "throw new Error('E');", "return 'R';"]
} else {
&["break L0;", "throw new Error('E');"]
};
let (body, needs_fn) = match r.below(10) {
0 => {
let a = abrupt(r, in_loop, g);
let b = abrupt(r, in_loop, g);
(
loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; try {{ {a} out.push('t' + j); }} \
catch (e) {{ out.push('c' + e.message); }} \
finally {{ out.push('f' + j); {b} }}"
),
),
false,
)
}
1 => {
let a = abrupt(r, in_fn_loop, g);
let b = abrupt(r, in_fn_loop, g);
(
loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; try {{ {a} out.push('t' + j); }} finally {{ out.push('f' + j); {b} }}"
),
),
true,
)
}
2 => {
let a = abrupt(r, in_loop, g);
let b = abrupt(r, in_loop, g);
(
loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; switch ({sw}) {{ case 0: out.push('s0'); {a} \
case 1: out.push('s1'); break; default: out.push('sd'); {b} }} \
out.push('post' + j);"
),
),
false,
)
}
3 => {
let a = abrupt(r, in_loop, g);
let b = abrupt(r, in_loop, g);
(
loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; switch ({sw}) {{ case 0: try {{ {a} out.push('a' + j); }} \
finally {{ out.push('f' + j); {b} }} default: out.push('d' + j); }}"
),
),
false,
)
}
4 => {
let a = abrupt(r, in_loop2, g);
let b = abrupt(r, in_loop2, g);
let inner = loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; try {{ {a} out.push('t' + j); }} \
catch (e) {{ out.push('c' + e.message); }} \
finally {{ out.push('f' + j); {b} }}"
),
);
(
loop_wrap(
r.next_u64(),
"i",
Some("L0"),
n,
&format!("out.push('i' + i); {inner}"),
),
false,
)
}
5 => {
let a = abrupt(r, in_loop2, g);
let inner = loop_wrap(
r.next_u64(),
"j",
None,
n,
&format!(
"step++; switch ({sw}) {{ case 0: try {{ {a} }} \
finally {{ out.push('f' + j); }} default: out.push('d' + j); }}"
),
);
(
loop_wrap(
r.next_u64(),
"i",
Some("L0"),
n,
&format!("out.push('i' + i); {inner}"),
),
false,
)
}
6 => {
let a = abrupt(r, in_switch, None);
let b = abrupt(r, in_switch, None);
(
format!(
"const j = 0; step++; switch ({sw}) {{ case 0: try {{ {a} out.push('a'); }} \
finally {{ out.push('f'); {b} }} out.push('post'); \
default: out.push('d'); }}"
),
wrapped,
)
}
7 => {
let a = abrupt(r, labeled, None);
(
format!(
"const j = 0; step++; L0: switch ({sw}) {{ case 0: try {{ {a} out.push('a'); }} \
finally {{ out.push('f'); }} default: out.push('d'); }} out.push('end');"
),
wrapped,
)
}
8 => {
let a = abrupt(r, labeled, None);
let b = abrupt(r, labeled, None);
(
format!(
"const j = 0; L0: {{ try {{ {a} out.push('a'); }} \
finally {{ out.push('f'); {b} }} out.push('post'); }} out.push('end');"
),
wrapped,
)
}
_ => {
let a = abrupt(r, fn_ret_or_throw, None);
let b = abrupt(r, fn_ret_or_throw, None);
let inner = format!(
"try {{ out.push('t'); {a} }} finally {{ out.push('f'); {b} }} out.push('post');"
);
let shaped = match r.below(3) {
0 => inner,
1 => format!("try {{ {inner} }} catch (e) {{ out.push('c' + e.message); }}"),
_ => format!("try {{ {inner} }} finally {{ out.push('F'); }}"),
};
(format!("const j = 0; step++; {shaped}"), true)
}
};
let body = if needs_fn {
format!("const run = () => {{ {body} return 'end'; }}; out.push(run());")
} else {
body
};
vec![
"const out = []; let step = 0;".into(),
format!("try {{ {body} }} catch (e) {{ out.push('X' + e.message); }}"),
"const tail = out.length; const probe = () => tail;".into(),
"console.log(out.join(',') + '|' + probe());".into(),
]
}
fn gen_thenable(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let ruler = 4 + r.below(6);
let val = *pick(
r,
&[
"'v'",
"Promise.resolve('v')",
"{ then(res) { res('v'); } }",
"{ then(res) { Promise.resolve().then(() => res('v')); } }",
"(async () => 'v')()",
"Promise.resolve(Promise.resolve('v'))",
],
);
let probe = match r.below(8) {
0 => format!("(async () => {{ log('a0'); log('a1:' + await ({val})); }})();"),
1 => format!("Promise.resolve().then(() => ({val})).then(v => log('t:' + v));"),
2 => format!("new Promise(res => res({val})).then(v => log('n:' + v));"),
3 => format!("Promise.resolve({val}).then(v => log('r:' + v));"),
4 => format!(
"(async function* () {{ yield {val}; yield 'w'; }})()[Symbol.asyncIterator]; \
(async () => {{ for await (const v of (async function* () {{ yield {val}; yield 'w'; }})()) log('g:' + v); log('gdone'); }})();"
),
5 => format!(
"(async () => {{ for await (const v of [{val}, 'w']) log('l:' + v); log('ldone'); }})();"
),
6 => format!(
"const it = (async function* () {{ yield {val}; }})(); \
it.next().then(s => log('s:' + s.value + ',' + s.done)); \
it.next().then(s => log('e:' + s.value + ',' + s.done));"
),
_ => format!(
"(async () => {{ for await (const v of (function* () {{ yield {val}; yield 'w'; }})()) log('y:' + v); log('ydone'); }})();"
),
};
vec![
"const log = s => console.log(s);".into(),
probe,
format!(
"let p = Promise.resolve(); for (let i = 1; i <= {ruler}; i++) {{ const n = i; p = p.then(() => log('p' + n)); }}"
),
]
}
fn gen_exit(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let code = *pick(r, &["0", "1", "3", "7", "42", "255", "300", "-1"]);
let strcode = *pick(r, &["'3'", "'0x10'", "' '", "'1e3'"]);
match r.below(10) {
0 => vec![format!("process.exitCode = {code};")],
1 => vec![format!("process.exitCode = {strcode};")],
2 => vec![format!("console.log('a'); process.exit({code});")],
3 => vec![format!(
"process.exitCode = {code}; process.exit(); console.log('unreachable');"
)],
4 => vec![
"process.on('exit', c => console.log('exit', c));".into(),
format!("process.exitCode = {code};"),
],
5 => vec![
"process.on('exit', c => console.log('exit', c));".into(),
format!("process.exit({code});"),
],
6 => vec![format!(
"process.on('exit', () => {{ process.exitCode = {code}; }});"
)],
7 => vec![
"process.on('beforeExit', c => console.log('beforeExit', c));".into(),
"process.on('exit', c => console.log('exit', c));".into(),
format!("process.exitCode = {code};"),
],
8 => vec![
"process.once('e', () => console.log('once'));".into(),
"process.on('e', () => console.log('on'));".into(),
"process.emit('e'); process.emit('e');".into(),
"console.log('left', process.listeners('e').length);".into(),
],
_ => vec![
"process.on('exit', c => console.log('exit', c));".into(),
format!("setTimeout(() => {{ process.exitCode = {code}; }}, 0);"),
],
}
}
fn gen_stdio(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let enc = *pick(r, &["'utf8'", "'hex'", "'base64'", "'latin1'", "'utf16le'"]);
let text = *pick(r, &["'4142'", "'QUJD'", "'ab'", "'\\u00e9'", "'0f10'"]);
match r.below(9) {
0 => vec![format!("process.stdout.write({text}, {enc});")],
1 => vec!["process.stdout.write('a'); process.stdout.write('b');".into()],
2 => vec!["process.stdout.write(Buffer.from([0xff, 0xfe, 0x41, 0x00, 0x7f]));".into()],
3 => vec![format!(
"process.stdout.write(new Uint8Array([{}, {}, 65]));",
r.below(256),
r.below(256)
)],
4 => vec![
"console.log('before');".into(),
"process.stdout.write(Buffer.from('mid'));".into(),
"console.log('after');".into(),
],
5 => vec!["process.stdout.end('tail');".into()],
6 => vec![format!(
"try {{ process.stdout.write({}); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, &["[65, 66]", "65", "undefined", "null", "{}"])
)],
7 => vec![format!(
"process.stdout.write(Buffer.from({text}, {enc}));"
)],
_ => vec!["process.stdout.write(''); console.log('empty-ok');".into()],
}
}
fn gen_entry(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let probe = *pick(
r,
&[
"typeof this",
"this === globalThis",
"this === module.exports",
"globalThis === globalThis",
"globalThis === global",
"typeof globalThis",
"typeof global",
"typeof require",
"typeof module",
"typeof exports",
"typeof arguments",
"typeof __filename",
"typeof __dirname",
"module.id",
"require.main === module",
"process.argv.length",
"Array.isArray(process.argv)",
"typeof process.exitCode",
],
);
match r.below(4) {
0 => vec![format!("console.log({probe});")],
1 => vec![
"globalThis.__probe = 7;".into(),
"console.log(globalThis.__probe, typeof globalThis.__probe);".into(),
],
2 => vec![
"this.__x = 5;".into(),
"console.log(this.__x, module.exports.__x);".into(),
],
_ => vec![
"const g = globalThis;".into(),
format!("g.__y = 1; console.log(globalThis.__y, {probe});"),
],
}
}
fn gen_byteview(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const KINDS: &[&str] = &[
"Uint8Array",
"Int8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
];
const BYTES: &[&str] = &[
"[0xff, 0xfe, 0x00, 0x41, 0x80]",
"[1, 2, 3]",
"[]",
"[255, 0]",
"[3, 1, 2, 1]",
"[0x80, 0x7f, 0xff, 0x01]",
];
const IDX: &[&str] = &["0", "1", "2", "-1", "-2", "5", "100"];
let k = pick(r, KINDS);
let b = pick(r, BYTES);
match r.below(10) {
0 => vec![
format!("const a = new {k}({b});"),
format!(
"const ret = a.fill({}, {}, {});",
pick(r, &["0", "9", "255", "-1", "1.7"]),
pick(r, IDX),
pick(r, IDX)
),
"console.log(a, ret === a);".into(),
],
1 => vec![
format!("const buf = new ArrayBuffer(8); const a = new {k}(buf); const b = new {k}(buf);"),
format!("a.fill({});", pick(r, &["7", "255", "-3"])),
"console.log(b, a.byteLength, b.buffer === a.buffer);".into(),
],
2 => vec![
format!("const a = new {k}({b});"),
format!(
"const c = a.{};",
pick(r, &["toReversed()", "toSorted()", "toSorted((x, y) => y - x)"])
),
"console.log(a, c, c === a, c.constructor.name);".into(),
],
3 => vec![
format!("const a = new {k}({b});"),
format!(
"try {{ console.log(a.with({}, {})); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, IDX),
pick(r, &["9", "300", "-1", "1.5", "'7'"])
),
"console.log(a);".into(),
],
4 => vec![
"const dv = new DataView(new ArrayBuffer(8));".into(),
format!(
"dv.{}(0, {});",
pick(r, &["setUint16", "setInt16", "setUint32", "setInt32", "setFloat32", "setFloat64", "setUint8"]),
pick(r, &["0xbeef", "-1", "255", "1.5", "0"])
),
"console.log(dv.getUint8(0), dv.getUint8(1), dv.getUint16(0), dv.byteLength, dv.byteOffset);".into(),
],
5 => vec![
"const dv = new DataView(new ArrayBuffer(4), 1);".into(),
format!(
"try {{ console.log(dv.{}({})); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, &["getUint32", "getUint16", "getUint8"]),
pick(r, IDX)
),
],
6 => vec![
format!("const a = new {}(1);", pick(r, &["BigInt64Array", "BigUint64Array"])),
format!(
"try {{ a[0] = {}; console.log(a[0]); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, &["1", "true", "'12'", "''", "[]", "['3']", "{}", "5n", "-1n", "null", "undefined", "1.5"])
),
],
7 => vec![
format!("const buf = Buffer.from({b});"),
format!(
"console.log(buf, buf.toString('{}'), buf.length);",
pick(r, &["hex", "base64", "latin1", "utf8", "base64url"])
),
],
8 => vec![
format!("const a = new {k}({b});"),
format!(
"console.log(a.{}, {k}.BYTES_PER_ELEMENT, a.BYTES_PER_ELEMENT, a.byteLength);",
pick(r, &["subarray(1)", "slice(0, 2)", "at(-1)", "join('-')", "indexOf(255)", "includes(0)", "lastIndexOf(1)"])
),
],
_ => vec![format!(
"try {{ Uint8Array.prototype.{}.call({}); console.log('no throw'); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, &["slice", "fill", "subarray", "join", "sort"]),
pick(r, &["[]", "{}", "5", "'s'", "new Map()", "new Set()", "null", "new Date(0)"])
)],
}
}
fn gen_builtinmeta(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const FNS: &[&str] = &[
"Math.max",
"Math.floor",
"Object.keys",
"Object.assign",
"JSON.parse",
"parseInt",
"isNaN",
"Array.from",
"Array.prototype.slice",
"String.prototype.replace",
"Object.prototype.toString",
"Set.prototype.union",
"Uint8Array.prototype.set",
"Promise.resolve",
"Number.isInteger",
"Reflect.ownKeys",
"encodeURIComponent",
];
const CTORS: &[&str] = &[
"Array",
"Object",
"Map",
"Set",
"Promise",
"Number",
"String",
"Boolean",
"Symbol",
"WeakMap",
"Uint8Array",
"Error",
"TypeError",
"RegExp",
"Date",
];
const NS: &[&str] = &[
"Math", "JSON", "Reflect", "Number", "Object", "Array", "Symbol",
];
const MEMBERS: &[&str] = &[
"'PI'",
"'floor'",
"'max'",
"'parse'",
"'keys'",
"'MAX_SAFE_INTEGER'",
"'EPSILON'",
"'isNaN'",
"'prototype'",
"'name'",
"'length'",
"'nope'",
];
match r.below(9) {
0 => vec![format!(
"const f = {}; console.log(f.name, f.length, typeof f.call, typeof f.bind);",
pick(r, FNS)
)],
1 => vec![format!("console.log(String({}));", pick(r, FNS))],
2 => vec![format!(
"const C = {}; console.log(C.name, C.length, typeof C.prototype);",
pick(r, CTORS)
)],
3 => vec![format!(
"console.log(Object.getOwnPropertyDescriptor({}, {}));",
pick(r, NS),
pick(r, MEMBERS)
)],
4 => vec![format!(
"console.log(Object.getOwnPropertyDescriptor({}, 'name'), Object.getOwnPropertyNames({}));",
pick(r, FNS),
pick(r, FNS)
)],
5 => vec![format!(
"console.log(Object.keys({}).length, JSON.stringify({}));",
pick(r, NS),
pick(r, NS)
)],
6 => vec![format!(
"try {{ {}.call({}); console.log('no throw'); }} catch (e) {{ console.log(e.constructor.name, e.message); }}",
pick(r, &[
"Set.prototype.union",
"Set.prototype.has",
"Map.prototype.get",
"WeakMap.prototype.has",
"Promise.prototype.then",
"Date.prototype.getTime",
"Date.prototype.toISOString",
"Uint8Array.prototype.slice",
]),
pick(r, &[
"[]", "{}", "5", "'s'", "null", "undefined", "new Map()", "new Set()",
"new Date(0)", "Object.create(null)", "new (class A {})()", "true", "9n",
"new Uint8Array(1)", "Promise.resolve(1)", "new WeakSet()",
])
)],
7 => vec![format!(
"console.log(typeof {0}, {0} instanceof Function, Object.prototype.toString.call({0}));",
pick(r, &["Math", "JSON", "Reflect", "Math.max", "Array", "console.log"])
)],
_ => vec![format!(
"console.log(Object.getOwnPropertyDescriptor(globalThis, {}));",
pick(r, &["'Math'", "'parseInt'", "'undefined'", "'NaN'", "'Infinity'", "'JSON'", "'structuredClone'", "'nope'"])
)],
}
}
fn gen_locale(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
let n = *pick(
r,
&[
"0",
"1234.5",
"1234567.891",
"-9876.5",
"1e21",
"0.000001",
"123456789012345",
"-0",
],
);
let s = *pick(
r,
&["'abc'", "'Straße'", "'ÄÖÜ'", "'I'", "'i'", "'\\u00e9'"],
);
let ms = *pick(r, &["0", "86400000", "1700000000123", "-86400000", "NaN"]);
let e = match r.below(12) {
0 => format!("({n}).toLocaleString()"),
1 => format!("({n}).toLocaleString('en-US')"),
2 => format!("BigInt(Math.trunc({n} || 0)).toLocaleString()"),
3 => format!("[{n}, {s}, null, undefined].toLocaleString()"),
4 => format!("({s}).toLocaleUpperCase()"),
5 => format!("({s}).toLocaleLowerCase()"),
6 => format!("({s}).toLocaleString()"),
7 => format!("new Date({ms}).toLocaleDateString()"),
8 => format!("new Date({ms}).toLocaleTimeString()"),
9 => format!("new Date({ms}).toLocaleString()"),
10 => format!("({s}).normalize('NFC') === ({s})"),
_ => "({}).toLocaleString()".to_string(),
};
vec![format!("console.log({e});")]
}
fn gen_assign(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const BINOPS: &[&str] = &[
"+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>=",
];
const LOGOPS: &[&str] = &["&&=", "||=", "??="];
const INITS: &[&str] = &[
"0",
"1",
"5",
"-7",
"255",
"null",
"undefined",
"''",
"'ab'",
"NaN",
"2.5",
"-0",
];
const RHS: &[&str] = &["1", "0", "2", "3", "-1", "'x'", "32", "33"];
let op = if r.below(2) == 0 {
pick(r, BINOPS)
} else {
pick(r, LOGOPS)
};
let init = pick(r, INITS);
let rhs = pick(r, RHS);
match r.below(8) {
0 => vec![
"let n = 0;".into(),
"const k = () => { n++; return 'a'; };".into(),
format!("const o = {{ a: {init} }};"),
format!("try {{ const v = (o[k()] {op} {rhs}); console.log(n, o.a, v); }} catch (e) {{ console.log(n, e.constructor.name); }}"),
],
1 => vec![
"let n = 0;".into(),
format!("const o = {{ a: {init} }};"),
"const g = () => { n++; return o; };".into(),
format!("try {{ const v = (g().a {op} {rhs}); console.log(n, o.a, v); }} catch (e) {{ console.log(n, e.constructor.name); }}"),
],
2 => vec![
"const log = [];".into(),
format!("const o = {{ a: {init} }};"),
"const g = () => { log.push('o'); return o; };".into(),
"const k = () => { log.push('k'); return 'a'; };".into(),
format!("const v = () => {{ log.push('v'); return {rhs}; }};"),
format!("try {{ g()[k()] {op} v(); console.log(log.join(''), o.a); }} catch (e) {{ console.log(log.join(''), e.constructor.name); }}"),
],
3 => vec![
"let gets = 0, sets = 0, stored;".into(),
format!("const o = {{ get a() {{ gets++; return {init}; }}, set a(v) {{ sets++; stored = v; }} }};"),
format!("try {{ o.a {op} {rhs}; console.log(gets, sets, stored); }} catch (e) {{ console.log(gets, sets, e.constructor.name); }}"),
],
4 => vec![
"let i = 0;".into(),
format!("const a = [{init}, 9, 9];"),
format!("try {{ a[i++] {op} {rhs}; console.log(i, JSON.stringify(a)); }} catch (e) {{ console.log(i, e.constructor.name); }}"),
],
5 => vec![
format!("let x = {init};"),
format!("try {{ const v = (x {op} {rhs}); console.log(x, v, typeof x); }} catch (e) {{ console.log(e.constructor.name); }}"),
],
6 => {
let bop = pick(r, &["+=", "-=", "*=", "&=", "|=", "^=", "<<=", ">>=", "**=", "??=", "||="]);
let brhs = pick(r, &["2n", "0n", "1", "3n"]);
vec![
format!("const o = {{ v: {} }};", pick(r, &["8n", "0n", "-3n", "255n"])),
format!("try {{ o.v {bop} {brhs}; console.log(o.v, typeof o.v); }} catch (e) {{ console.log(e.constructor.name); }}"),
]
}
_ => vec![
format!("const o = Object.freeze({{ a: {init} }});"),
"let n = 0;".into(),
"const k = () => { n++; return 'a'; };".into(),
format!("try {{ const v = (o[k()] {op} {rhs}); console.log(n, o.a, v); }} catch (e) {{ console.log(n, e.constructor.name); }}"),
],
}
}
fn gen_optional(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const BASES: &[&str] = &[
"null",
"undefined",
"{ a: { b: 1 } }",
"{ a: null }",
"{ a: undefined }",
"{ a: { b: 0 } }",
"0",
"''",
"false",
];
let base = pick(r, BASES);
match r.below(7) {
0 => vec![
"let n = 0;".into(),
"const f = () => { n++; return 1; };".into(),
format!("const o = {base};"),
"try { console.log(o?.a.b, n); } catch (e) { console.log(e.constructor.name, n); }"
.into(),
"try { console.log(o?.a?.b?.(f()), n); } catch (e) { console.log(e.constructor.name, n); }".into(),
],
1 => vec![
"let n = 0;".into(),
"const k = () => { n++; return 'a'; };".into(),
format!("const o = {base};"),
"try { console.log(o?.[k()], n); } catch (e) { console.log(e.constructor.name, n); }"
.into(),
],
2 => vec![
"let n = 0;".into(),
"const arg = () => { n++; return 1; };".into(),
format!("const o = {};", pick(r, &["null", "undefined", "{ f: x => x + 1 }", "{ f: null }", "{ f: undefined }"])),
"try { console.log(o?.f?.(arg()), n); } catch (e) { console.log(e.constructor.name, n); }".into(),
],
3 => vec![
format!("const o = {base};"),
"try { console.log(typeof o?.a, delete o?.a?.b); } catch (e) { console.log(e.constructor.name); }".into(),
],
4 => vec![
format!("const o = {base};"),
format!("console.log(o?.a ?? 'd', o?.a {} 'e', o?.a?.b === undefined);", pick(r, &["||", "&&"])),
],
5 => vec![
format!("const o = {};", pick(r, &["null", "undefined", "{ v: 3, m() { return this.v; } }", "{ v: 3, m: null }"])),
"try { console.log(o?.m?.()); } catch (e) { console.log(e.constructor.name); }".into(),
],
_ => vec![
format!("const o = {base};"),
"try { console.log(o?.a.b.c); } catch (e) { console.log(e.constructor.name); }".into(),
"try { console.log((o?.a).b); } catch (e) { console.log(e.constructor.name); }".into(),
],
}
}
fn gen_destructure(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const ARRS: &[&str] = &[
"[1, 2, 3]",
"[]",
"[1]",
"[undefined, 2]",
"[null, 2]",
"'abc'",
"new Set([1, 2])",
"[1, , 3]",
];
const OBJS: &[&str] = &[
"{ a: 1, b: 2 }",
"{}",
"{ a: undefined }",
"{ a: null }",
"{ a: { b: 1 } }",
"{ 1: 'x', a: 2 }",
];
match r.below(8) {
0 => vec![
format!("const src = {};", pick(r, ARRS)),
"try { const [a, , b = 9, ...rest] = src; console.log(a, b, JSON.stringify(rest)); } catch (e) { console.log(e.constructor.name); }".into(),
],
1 => vec![
"let n = 0;".into(),
"const d = () => { n++; return 'D'; };".into(),
format!("const src = {};", pick(r, ARRS)),
"try { const [a = d(), b = d()] = src; console.log(a, b, n); } catch (e) { console.log(e.constructor.name, n); }".into(),
],
2 => vec![
format!("const src = {};", pick(r, OBJS)),
format!("const key = {};", pick(r, &["'a'", "'b'", "'zz'", "1"])),
"try { const { a: x = 7, [key]: y, ...rest } = src; console.log(x, y, JSON.stringify(rest)); } catch (e) { console.log(e.constructor.name); }".into(),
],
3 => vec![
format!("const src = {};", pick(r, &["{ a: [1, 2], b: { c: 3 } }", "{ a: [], b: {} }", "{ a: [1], b: { c: undefined } }", "{ a: null, b: null }"])),
"try { const { a: [p = 'P', q = 'Q'] = [], b: { c = 'C' } = {} } = src; console.log(p, q, c); } catch (e) { console.log(e.constructor.name); }".into(),
],
4 => vec![
"let a = 1, b = 2; const o = {};".into(),
format!("const src = {};", pick(r, ARRS)),
"try { [a, b] = [b, a]; console.log(a, b); } catch (e) { console.log(e.constructor.name); }".into(),
"try { ({ a: o.p = 5 } = src); console.log(JSON.stringify(o)); } catch (e) { console.log(e.constructor.name); }".into(),
],
5 => vec![
"function f({ a = 1, b: { c = 2 } = {} } = {}, [d = 3] = []) { return [a, c, d]; }".into(),
format!("try {{ console.log(JSON.stringify(f({}))); }} catch (e) {{ console.log(e.constructor.name); }}", pick(r, &["", "{}", "{ a: 9 }", "{ b: { c: 9 } }", "undefined, [8]", "{ a: 0 }, []"])),
],
6 => vec![
format!("const src = {};", pick(r, &["[[1, 'a'], [2, 'b']]", "new Map([[1, 'a']])", "[[1], []]", "Object.entries({ x: 1, y: 2 })"])),
"try { const out = []; for (const [k, v = 'V'] of src) out.push(k + ':' + v); console.log(out.join(',')); } catch (e) { console.log(e.constructor.name); }".into(),
],
_ => vec![
format!("const src = {};", pick(r, &["null", "undefined", "5", "{}", "{ [Symbol.iterator]: null }"])),
"try { const [a] = src; console.log(a); } catch (e) { console.log(e.constructor.name); }".into(),
"try { const { b } = src; console.log(b); } catch (e) { console.log(e.constructor.name); }".into(),
],
}
}
fn gen_sparse(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const SPARSE: &[&str] = &[
"[1, , 3]",
"new Array(3)",
"[, , ]",
"[1, 2, 3]",
"Array(2).fill(0)",
"[undefined, 1]",
];
let a = pick(r, SPARSE);
match r.below(6) {
0 => vec![
format!("const a = {a};"),
"const seen = []; a.forEach((v, i) => seen.push(i + '=' + v));".into(),
"console.log(a.length, seen.join(','), JSON.stringify([...a]));".into(),
],
1 => vec![
format!("const a = {a};"),
"console.log(0 in a, 1 in a, JSON.stringify(Object.keys(a)), a.hasOwnProperty(1));"
.into(),
],
2 => vec![
format!("const a = {a};"),
format!("const m = a.{};", pick(r, &["map(x => x)", "filter(() => true)", "slice()", "concat([])", "flat()", "toReversed()", "toSorted()"])),
"console.log(m.length, JSON.stringify(m), 1 in m);".into(),
],
3 => vec![
format!("const a = {a};"),
format!("a.length = {};", pick(r, &["0", "1", "5", "2"])),
"console.log(a.length, JSON.stringify(a), a.join('|'));".into(),
],
4 => vec![
format!("const a = {a};"),
format!("console.log(delete a[{}], a.length, JSON.stringify(a), a.join('|'));", pick(r, &["0", "1", "9"])),
],
_ => vec![
format!("const a = {a};"),
"console.log(JSON.stringify(a), a.join('|'), String(a), a.indexOf(undefined), a.includes(undefined));".into(),
],
}
}
fn gen_tagged(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const TMPL: &[&str] = &[
"`a${1}b`",
"`${1}`",
"``",
"`x`",
"`\\n\\t${'q'}`",
"`a${1}b${2}c`",
"`${'s'}${2}`",
];
let t = pick(r, TMPL);
match r.below(8) {
0 => vec![
"const id = (s) => s;".into(),
format!("const site = () => id{t};"),
format!("const twin = () => id{t};"),
"console.log(site() === site(), site() === twin(), site().raw === site().raw);".into(),
],
1 => vec![
"const id = (s) => s;".into(),
format!("const s = id{t};"),
"console.log(Object.isFrozen(s), Object.isFrozen(s.raw), Object.isExtensible(s));"
.into(),
"s[0] = 'MUT'; s.raw[0] = 'MUT'; s.extra = 1;".into(),
"console.log(JSON.stringify(s), JSON.stringify(s.raw), s.extra);".into(),
],
2 => vec![
"const id = (s) => s;".into(),
format!("const s = id{t};"),
format!(
"console.log(JSON.stringify(Object.getOwnPropertyDescriptor(s, {})));",
pick(r, &["'0'", "'raw'", "'length'", "'1'"])
),
"console.log(JSON.stringify(Object.keys(s)), JSON.stringify(Object.getOwnPropertyNames(s)));".into(),
],
3 => vec![
"let n = 0;".into(),
"const o = { p: 'P', m(s, ...v) { return String(this && this.p) + s.join('|') + v.join(','); } };".into(),
"const get = () => { n++; return o; };".into(),
format!("console.log(get().m{t}, n);"),
],
4 => vec![
"function tag(s, ...v) { return [this === undefined, this === globalThis, s.length, v.length].join(' '); }".into(),
format!("console.log(tag{t});"),
],
5 => {
let esc = pick(
r,
&[
"`\\n`",
"`\\t${1}\\\\`",
"`\\x41${'z'}`",
"`\\u0042`",
"`a\\`b`",
"`\\0`",
],
);
vec![
"const tag = (s, ...v) => JSON.stringify([s, s.raw, v]);".into(),
format!("console.log(tag{esc});"),
]
}
6 => vec![
format!("console.log(String.raw{t});"),
format!(
"console.log(String.raw({{ raw: {} }}, {}));",
pick(r, &["['x','y','z']", "['a']", "[]", "'ab'"]),
pick(r, &["1, 2", "'-'", "", "0, 0, 0"])
),
],
_ => vec![
"const id = (s) => s;".into(),
format!("function f() {{ return id{t}; }}"),
"const seen = [];".into(),
"for (let i = 0; i < 3; i++) seen.push(f());".into(),
"console.log(seen[0] === seen[1], seen[1] === seen[2], new Set(seen).size);".into(),
],
}
}
fn gen_wellknown(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
match r.below(7) {
0 => vec![
"const seen = [];".into(),
"function F() { seen.push(new.target === undefined ? 'undef' : new.target.name); }"
.into(),
"class A { constructor() { seen.push('A:' + (new.target && new.target.name)); } }"
.into(),
"class B extends A { constructor() { super(); seen.push('B:' + new.target.name); } }"
.into(),
format!("{};", pick(r, &["F()", "new F()", "new A()", "new B()", "Reflect.construct(F, [], A)", "Reflect.construct(A, [], B)"])),
"console.log(seen.join(' '));".into(),
],
1 => vec![
"const log = [];".into(),
format!(
"class S {{ static a = {}; static {{ log.push('blk ' + this.name + ' ' + S.a); S.b = S.a + 1; }} static c = S.b * 2; }}",
pick(r, &["1", "0", "'x'", "S_INIT"]).replace("S_INIT", "2")
),
"console.log(log.join('|'), S.a, S.b, S.c);".into(),
],
2 => {
let pred = pick(
r,
&[
"typeof v === 'number'",
"typeof v === 'string'",
"false",
"true",
"v != null && 'q' in Object(v)",
],
);
vec {{ return {pred}; }} }}"),
"const probes = [1, 'x', {}, { q: 1 }, null, undefined, [], H];".into(),
"console.log(probes.map((p) => { try { return String(p instanceof H); } catch (e) { return e.constructor.name; } }).join(' '));".into(),
]
}
3 => {
let sp = pick(r, &["Array", "MyArr", "undefined", "null"]);
vec![
format!("class MyArr extends Array {{ static get [Symbol.species]() {{ return {sp}; }} }}"),
"const m = MyArr.from([1, 2, 3]);".into(),
format!("const d = m.{};", pick(r, &["map((x) => x * 2)", "filter(() => true)", "slice(1)", "concat([4])", "flat()"])),
"console.log(m instanceof MyArr, d instanceof MyArr, d instanceof Array, JSON.stringify(d), d.constructor.name);".into(),
]
}
4 => {
let body = pick(
r,
&[
"h === 'number' ? 42 : 'S:' + h",
"h",
"h === 'default' ? 7 : 'x'",
"1",
],
);
vec![
"const log = [];".into(),
format!("const o = {{ [Symbol.toPrimitive](h) {{ log.push(h); return {body}; }}, valueOf() {{ log.push('valueOf'); return 1; }}, toString() {{ log.push('toString'); return 't'; }} }};"),
"const out = [];".into(),
"out.push(String(`${o}`)); out.push(String(+o)); out.push(String(o + '')); out.push(String(o == 1));".into(),
"console.log(out.join(' '), '|', log.join(','));".into(),
]
}
5 => {
let ret = pick(r, &["({})", "[]", "Symbol('s')", "'ok'", "5"]);
vec![
format!("const o = {{ [Symbol.toPrimitive]() {{ return {ret}; }} }};"),
"for (const f of [(v) => `${v}`, (v) => +v, (v) => v + 1]) { try { console.log(String(f(o))); } catch (e) { console.log(e.constructor.name); } }".into(),
]
}
_ => {
let sym = pick(
r,
&[
"Symbol.iterator",
"Symbol.toStringTag",
"Symbol.hasInstance",
"Symbol.toPrimitive",
"Symbol.asyncIterator",
],
);
let holder = pick(
r,
&[
"Array.prototype",
"String.prototype",
"Map.prototype",
"Set.prototype",
"Function.prototype",
"Date.prototype",
"Symbol.prototype",
"Object.prototype",
],
);
vec![format!(
"const h = {holder}[{sym}]; console.log(typeof h, h === undefined ? '-' : h.name, h === undefined ? '-' : h.length);"
)]
}
}
}
fn gen_proxytrap(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const TARGET: &[&str] = &[
"{ a: 1 }",
"{}",
"[1, 2]",
"{ a: 1, b: 2 }",
"Object.create({ inherited: 1 })",
];
let t = pick(r, TARGET);
let traps = pick(
r,
&[
"get,set,has,deleteProperty,ownKeys,getOwnPropertyDescriptor,defineProperty",
"get,has,ownKeys,getOwnPropertyDescriptor,defineProperty",
"getOwnPropertyDescriptor,defineProperty,ownKeys",
"get,set",
"ownKeys,getOwnPropertyDescriptor",
"",
],
);
let handler = {
let mut parts = vec!["const log = [];".to_string(), "const H = {};".to_string()];
for name in traps.split(',').filter(|s| !s.is_empty()) {
let body = match name {
"get" => "H.get = (t, k, rc) => { if (typeof k !== 'symbol') log.push('get:' + String(k)); return Reflect.get(t, k, rc); };",
"set" => "H.set = (t, k, v, rc) => { log.push('set:' + String(k)); return Reflect.set(t, k, v, rc); };",
"has" => "H.has = (t, k) => { log.push('has:' + String(k)); return Reflect.has(t, k); };",
"deleteProperty" => "H.deleteProperty = (t, k) => { log.push('del:' + String(k)); return Reflect.deleteProperty(t, k); };",
"ownKeys" => "H.ownKeys = (t) => { log.push('ownKeys'); return Reflect.ownKeys(t); };",
"getOwnPropertyDescriptor" => "H.getOwnPropertyDescriptor = (t, k) => { if (typeof k !== 'symbol') log.push('gopd:' + String(k)); return Reflect.getOwnPropertyDescriptor(t, k); };",
_ => "H.defineProperty = (t, k, d) => { log.push('def:' + String(k)); return Reflect.defineProperty(t, k, d); };",
};
parts.push(body.to_string());
}
parts
};
let op = pick(
r,
&[
"p.a",
"p.zz",
"p.b = 9",
"p.newKey = 1",
"'a' in p",
"delete p.a",
"Object.keys(p)",
"JSON.stringify(p)",
"Object.assign({}, p)",
"({ ...p })",
"Object.entries(p)",
"Object.getOwnPropertyNames(p)",
"Object.freeze(p)",
"String(p)",
],
);
let mut stmts = handler;
stmts.push(format!("const p = new Proxy({t}, H);"));
stmts.push(format!(
"let out; try {{ const v = ({op}); out = v === p ? 'self' : (v !== null && typeof v === 'object' ? JSON.stringify(v) : String(v)); }} catch (e) {{ out = e.constructor.name; }}"
));
stmts.push("console.log(out, '|', log.join(' '));".into());
stmts
}
fn gen_microtask(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
match r.below(5) {
0 => vec![
"const log = [];".into(),
"process.nextTick(() => log.push('tick1'));".into(),
"queueMicrotask(() => log.push('qm1'));".into(),
"Promise.resolve().then(() => log.push('p1'));".into(),
"process.nextTick(() => log.push('tick2'));".into(),
"queueMicrotask(() => log.push('qm2'));".into(),
"setTimeout(() => { log.push('timer'); console.log(log.join(' ')); }, 0);".into(),
],
1 => vec![
"const log = [];".into(),
"queueMicrotask(() => { log.push('a'); queueMicrotask(() => log.push('a2')); process.nextTick(() => log.push('a-tick')); });".into(),
"Promise.resolve().then(() => { log.push('b'); queueMicrotask(() => log.push('b2')); });".into(),
"setTimeout(() => { log.push('end'); console.log(log.join(' ')); }, 0);".into(),
],
2 => {
let bad = pick(
r,
&["1", "'s'", "null", "undefined", "{}", "[]", "true", "9n"],
);
let f = pick(
r,
&[
"queueMicrotask",
"process.nextTick",
"setTimeout",
"setImmediate",
"setInterval",
],
);
vec![
format!("try {{ {f}({bad}, 0); console.log('accepted'); }} catch (e) {{ console.log(e.constructor.name, e.code, e.message); }}"),
"console.log('still running');".into(),
]
}
3 => {
let n = pick(r, &["1", "2", "3"]);
vec![
"const log = [];".into(),
"let i = 0;".into(),
format!("const h = setInterval(() => {{ log.push('iv' + i); if (++i === {n}) {{ clearInterval(h); console.log(log.join(' '), i); }} }}, 1);"),
]
}
_ => vec![
format!(
"const f = {};",
pick(
r,
&[
"queueMicrotask",
"setImmediate",
"setTimeout",
"setInterval",
"clearTimeout",
"process.nextTick"
]
)
),
"console.log(typeof f, f.name, f.length);".into(),
format!(
"const h = {};",
pick(
r,
&[
"setTimeout(() => {}, 1000)",
"setInterval(() => {}, 1000)",
"setImmediate(() => {})"
]
)
),
"console.log(typeof h, typeof h.ref, typeof h.unref, typeof h.hasRef, h.hasRef());"
.into(),
"h.unref(); console.log(h.hasRef());".into(),
"clearTimeout(h); clearInterval(h); clearImmediate(h);".into(),
],
}
}
fn gen_arraylate(seed: u64) -> Vec<String> {
let r = &mut Rng::new(seed);
const ARR: &[&str] = &[
"[1, 2, 3, 4, 5]",
"[]",
"[7]",
"['a', 'b', 'c']",
"[1, , 3]",
"[0, -0, NaN, 2]",
"[[1], [2, 3]]",
];
let a = pick(r, ARR);
let i = pick(r, &["0", "1", "2", "-1", "-2", "5", "-9"]);
match r.below(7) {
0 => vec![
format!("const a = {a};"),
format!(
"try {{ console.log(JSON.stringify(a.reduceRight({}))); }} catch (e) {{ console.log(e.constructor.name + ': ' + e.message); }}",
pick(r, &["(x, y) => String(x) + '-' + String(y)", "(x, y) => [x, y]", "(x, y) => x, 'SEED'", "(x, y, i) => String(x) + i"])
),
],
1 => vec![
format!("const a = {a};"),
format!(
"console.log(JSON.stringify(a.copyWithin({i}, {})), a.length);",
pick(r, &["0", "1", "-2", "3", "-1, -1", "0, 2"])
),
],
2 => {
let p = pick(
r,
&[
"(x) => x > 1",
"() => false",
"() => true",
"(x) => x === undefined",
"(x, idx) => idx === 1",
],
);
vec![
format!("const a = {a};"),
format!("console.log(String(a.findLast({p})), a.findLastIndex({p}), String(a.find({p})), a.findIndex({p}));"),
]
}
3 => {
let m = pick(
r,
&[
"toSpliced(1, 1, 'X', 'Y')",
"toSpliced(0, 0)",
"toSpliced(-1, 5)",
"toReversed()",
"toSorted()",
"toSorted((x, y) => String(y).localeCompare(String(x)))",
],
);
vec![
format!("const a = {a};"),
format!("const b = a.{m};"),
"console.log(JSON.stringify(b), JSON.stringify(a), b === a, Array.isArray(b));"
.into(),
]
}
4 => vec![
format!("const a = {a};"),
format!("try {{ console.log(JSON.stringify(a.with({i}, 'W'))); }} catch (e) {{ console.log(e.constructor.name); }}"),
format!("console.log(String(a.at({i})), String('abcd'.at({i})), JSON.stringify(a));"),
],
5 => {
let k = pick(
r,
&[
"(x) => (typeof x === 'number' && x % 2 ? 'odd' : 'even')",
"(x) => String(x)",
"(x, idx) => idx % 2",
"() => 'all'",
],
);
vec![
format!("const a = {a};"),
format!("const g = Object.groupBy(a, {k});"),
"console.log(JSON.stringify(g), String(Object.getPrototypeOf(g)), JSON.stringify(Object.keys(g)));".into(),
format!("const m = Map.groupBy(a, {k});"),
"console.log(m instanceof Map, m.size, JSON.stringify([...m.entries()]));".into(),
]
}
_ => vec![
format!(
"console.log(JSON.stringify(Array.of({0})), Array.of({0}).length, JSON.stringify(Array({0})), Array({0}).length);",
pick(r, &["7", "0", "1, 2", "'x'", ""])
),
format!(
"console.log(JSON.stringify({}.flat({})));",
pick(r, &["[1, [2, [3, [4]]]]", "[[], [[]]]", "[1, , [2, , 3]]"]),
pick(r, &["", "0", "1", "2", "Infinity", "-1"])
),
],
}
}
#[derive(Clone, Copy)]
enum Mode {
Mixed,
Num,
Bitwise,
Equality,
Strmeth,
Array,
Plus,
Json,
Logic,
Parse,
Object,
Arith,
Math,
Control,
Class,
Generator,
MapSet,
SetOps,
StrKeys,
Proto,
Async,
Bigint,
Regex,
Descriptor,
Freeze,
Identity,
Clone,
ErrorShape,
Unwind,
Thenable,
Exit,
Stdio,
Entry,
Locale,
ByteView,
BuiltinMeta,
Assign,
Optional,
Destructure,
Sparse,
Tagged,
WellKnown,
ProxyTrap,
Microtask,
ArrayLate,
}
const REAL_MODES: &[Mode] = &[
Mode::Num,
Mode::Bitwise,
Mode::Equality,
Mode::Strmeth,
Mode::Array,
Mode::Plus,
Mode::Json,
Mode::Logic,
Mode::Parse,
Mode::Object,
Mode::Arith,
Mode::Math,
Mode::Control,
Mode::Class,
Mode::Generator,
Mode::MapSet,
Mode::SetOps,
Mode::StrKeys,
Mode::Proto,
Mode::Async,
Mode::Bigint,
Mode::Regex,
Mode::Descriptor,
Mode::Freeze,
Mode::Identity,
Mode::Clone,
Mode::ErrorShape,
Mode::Unwind,
Mode::Thenable,
Mode::Exit,
Mode::Stdio,
Mode::Entry,
Mode::Locale,
Mode::ByteView,
Mode::BuiltinMeta,
Mode::Assign,
Mode::Optional,
Mode::Destructure,
Mode::Sparse,
Mode::Tagged,
Mode::WellKnown,
Mode::ProxyTrap,
Mode::Microtask,
Mode::ArrayLate,
];
fn gen_case(seed: u64, mode: Mode) -> Vec<String> {
match mode {
Mode::Mixed => {
let m = REAL_MODES[(seed % REAL_MODES.len() as u64) as usize];
gen_case(seed, m)
}
Mode::Num => gen_num(seed),
Mode::Bitwise => gen_bitwise(seed),
Mode::Equality => gen_equality(seed),
Mode::Strmeth => gen_strmeth(seed),
Mode::Array => gen_array(seed),
Mode::Plus => gen_plus(seed),
Mode::Json => gen_json(seed),
Mode::Logic => gen_logic(seed),
Mode::Parse => gen_parse(seed),
Mode::Object => gen_object(seed),
Mode::Arith => gen_arith(seed),
Mode::Math => gen_math(seed),
Mode::Control => gen_control(seed),
Mode::Class => gen_class(seed),
Mode::Generator => gen_generator(seed),
Mode::MapSet => gen_mapset(seed),
Mode::SetOps => gen_setops(seed),
Mode::StrKeys => gen_strkeys(seed),
Mode::Proto => gen_proto(seed),
Mode::Async => gen_async(seed),
Mode::Bigint => gen_bigint(seed),
Mode::Regex => gen_regex(seed),
Mode::Descriptor => gen_descriptor(seed),
Mode::Freeze => gen_freeze(seed),
Mode::Identity => gen_identity(seed),
Mode::Clone => gen_clone(seed),
Mode::ErrorShape => gen_error(seed),
Mode::Unwind => gen_unwind(seed),
Mode::Thenable => gen_thenable(seed),
Mode::Exit => gen_exit(seed),
Mode::Stdio => gen_stdio(seed),
Mode::Entry => gen_entry(seed),
Mode::Locale => gen_locale(seed),
Mode::ByteView => gen_byteview(seed),
Mode::BuiltinMeta => gen_builtinmeta(seed),
Mode::Assign => gen_assign(seed),
Mode::Optional => gen_optional(seed),
Mode::Destructure => gen_destructure(seed),
Mode::Sparse => gen_sparse(seed),
Mode::ArrayLate => gen_arraylate(seed),
Mode::Microtask => gen_microtask(seed),
Mode::ProxyTrap => gen_proxytrap(seed),
Mode::WellKnown => gen_wellknown(seed),
Mode::Tagged => gen_tagged(seed),
}
}
fn mode_name(m: Mode) -> &'static str {
match m {
Mode::Mixed => "mixed",
Mode::Num => "num",
Mode::Bitwise => "bitwise",
Mode::Equality => "equality",
Mode::Strmeth => "strmeth",
Mode::Array => "array",
Mode::Plus => "plus",
Mode::Json => "json",
Mode::Logic => "logic",
Mode::Parse => "parse",
Mode::Object => "object",
Mode::Arith => "arith",
Mode::Math => "math",
Mode::Control => "control",
Mode::Class => "class",
Mode::Generator => "generator",
Mode::MapSet => "mapset",
Mode::SetOps => "setops",
Mode::StrKeys => "strkeys",
Mode::Proto => "proto",
Mode::Async => "async",
Mode::Bigint => "bigint",
Mode::Regex => "regex",
Mode::Descriptor => "descriptor",
Mode::Freeze => "freeze",
Mode::Identity => "identity",
Mode::Clone => "clone",
Mode::ErrorShape => "error",
Mode::Unwind => "unwind",
Mode::Thenable => "thenable",
Mode::Exit => "exit",
Mode::Stdio => "stdio",
Mode::Entry => "entry",
Mode::Locale => "locale",
Mode::ByteView => "byteview",
Mode::BuiltinMeta => "builtinmeta",
Mode::Assign => "assign",
Mode::Optional => "optional",
Mode::Destructure => "destructure",
Mode::Sparse => "sparse",
Mode::ArrayLate => "arraylate",
Mode::Microtask => "microtask",
Mode::ProxyTrap => "proxytrap",
Mode::WellKnown => "wellknown",
Mode::Tagged => "tagged",
}
}
const ALL_MODES: &[Mode] = &[
Mode::Mixed,
Mode::Num,
Mode::Bitwise,
Mode::Equality,
Mode::Strmeth,
Mode::Array,
Mode::Plus,
Mode::Json,
Mode::Logic,
Mode::Parse,
Mode::Object,
Mode::Arith,
Mode::Math,
Mode::Control,
Mode::Class,
Mode::Generator,
Mode::MapSet,
Mode::SetOps,
Mode::StrKeys,
Mode::Proto,
Mode::Async,
Mode::Bigint,
Mode::Regex,
Mode::Descriptor,
Mode::Freeze,
Mode::Identity,
Mode::Clone,
Mode::ErrorShape,
Mode::Unwind,
Mode::Thenable,
Mode::Exit,
Mode::Stdio,
Mode::Entry,
Mode::Locale,
Mode::ByteView,
Mode::BuiltinMeta,
Mode::Assign,
Mode::Optional,
Mode::Destructure,
Mode::Sparse,
Mode::Tagged,
Mode::WellKnown,
Mode::ProxyTrap,
Mode::Microtask,
Mode::ArrayLate,
];
fn mode_from_name(s: &str) -> Option<Mode> {
ALL_MODES.iter().copied().find(|&m| mode_name(m) == s)
}
fn diverges(script: &str, bin: &Path, oracle: &str, timeout: Duration) -> bool {
let o = run_prog(Path::new(oracle), script, timeout);
let r = run_prog(bin, script, timeout);
!o.timed_out && differs(&o, &r)
}
fn minimize(stmts: Vec<String>, bin: &Path, oracle: &str, timeout: Duration) -> Vec<String> {
let mut cur = stmts;
let mut changed = true;
while changed && cur.len() > 1 {
changed = false;
for i in 0..cur.len() {
let mut cand = cur.clone();
cand.remove(i);
if cand.is_empty() {
continue;
}
if diverges(&build_program(&cand), bin, oracle, timeout) {
cur = cand;
changed = true;
break;
}
}
}
cur
}
fn signature(program: &str) -> String {
let body = program
.lines()
.map(|l| l.trim())
.rfind(|l| !l.is_empty())
.unwrap_or("")
.to_string();
mask_words(&mask_numbers(&body))
}
fn mask_words(s: &str) -> String {
let bytes: Vec<char> = s.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if c == '\'' || c == '"' {
let quote = c;
i += 1;
while i < bytes.len() && bytes[i] != quote {
i += 1;
}
i += 1; out.push('W');
} else {
out.push(c);
i += 1;
}
}
out
}
fn mask_numbers(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
let prev_alnum = out
.chars()
.last()
.map(|p| p.is_alphanumeric() || p == '_')
.unwrap_or(false);
if c.is_ascii_digit() && !prev_alnum {
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
i += 1;
}
out.push('N');
} else {
out.push(c);
i += 1;
}
}
out
}
struct Args {
count: u64,
dump: bool,
base_seed: u64,
once: bool,
timeout_ms: u64,
out_path: PathBuf,
max_report: usize,
jobs: usize,
mode: Mode,
verify: usize,
baseline: Option<PathBuf>,
}
fn parse_args() -> Args {
let mut count = 2000u64;
let mut base_seed = 1u64;
let mut once = false;
let mut dump = false;
let mut timeout_ms = 5000u64;
let mut max_report = 200usize;
let mut mode = Mode::Mixed;
let mut verify = 1usize;
let mut baseline: Option<PathBuf> = None;
let mut jobs = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let mut out_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("parity-fuzz")
.join("divergences.txt");
let argv: Vec<String> = std::env::args().skip(1).collect();
let mut i = 0;
while i < argv.len() {
match argv[i].as_str() {
"--count" | "-c" => {
i += 1;
count = argv.get(i).and_then(|s| s.parse().ok()).unwrap_or(count);
}
"--seed" | "-s" => {
i += 1;
base_seed = argv
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(base_seed);
}
"--once" => once = true,
"--dump" => dump = true,
"--timeout-ms" => {
i += 1;
timeout_ms = argv
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(timeout_ms);
}
"--out" | "-o" => {
i += 1;
if let Some(p) = argv.get(i) {
out_path = PathBuf::from(p);
}
}
"--max-report" => {
i += 1;
max_report = argv
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(max_report);
}
"--jobs" | "-j" => {
i += 1;
jobs = argv
.get(i)
.and_then(|s| s.parse().ok())
.filter(|&j| j >= 1)
.unwrap_or(jobs);
}
"--mode" | "-m" => {
i += 1;
match argv.get(i).and_then(|s| mode_from_name(s)) {
Some(m) => mode = m,
None => {
eprintln!(
"unknown --mode '{}'",
argv.get(i).map(|s| s.as_str()).unwrap_or("")
);
std::process::exit(2);
}
}
}
a if a.starts_with("--") && mode_from_name(&a[2..]).is_some() => {
mode = mode_from_name(&a[2..]).unwrap();
}
"--verify" => {
i += 1;
verify = argv
.get(i)
.and_then(|s| s.parse().ok())
.filter(|&k| k >= 1)
.unwrap_or(verify);
}
"--baseline" => {
i += 1;
baseline = argv.get(i).map(PathBuf::from);
}
"--stderr" => {
CMP_STDERR.store(true, Ordering::Relaxed);
}
"--help" | "-h" => {
print_help();
std::process::exit(0);
}
_ => {}
}
i += 1;
}
Args {
count,
dump,
base_seed,
once,
timeout_ms,
out_path,
max_report,
jobs,
mode,
verify,
baseline,
}
}
fn mode_list() -> String {
let names: Vec<&str> = ALL_MODES.iter().copied().map(mode_name).collect();
let mut out = String::new();
for chunk in names.chunks(8) {
if !out.is_empty() {
out.push_str("\n ");
}
out.push_str(&chunk.join(", "));
}
out
}
fn print_help() {
eprintln!(
"parity-fuzz — differential node/node-js parity fuzzer\n\
\n\
--count N number of cases (default 2000)\n\
--seed N base seed; case i uses seed+i (default 1)\n\
--mode M one of ({} modes; `mixed` rotates every other one):\n\
\x20 {}\n\
(each also accepted as a `--<mode>` shorthand)\n\
--stderr also require the normalized error line to match\n\
--once run a single case (seed) and print both outputs\n\
--dump print the generated corpus and exit; runs no child.\n\
Feeds the coverage census: grep the dump for a\n\
construct that appears ZERO times, then add a mode.\n\
--timeout-ms N per-process wall-clock timeout (default 5000)\n\
--out PATH divergence corpus file\n\
--max-report N stop after N divergences (default 200)\n\
--jobs N parallel workers (default = CPU count)\n\
--verify K require K consecutive divergences to report (default 1)\n\
--baseline FILE allowlist of known-gap signatures; only a NEW\n\
divergence (not in FILE) fails the run (exit 1)\n\
\n\
env NODE_JS_FUZZ_NODE=PATH the reference Node to compare against\n\
(HARD ERROR if set but unusable). Every run prints the oracle it used.\n\
Both children run with TZ=UTC LANG=LC_ALL=en_US.UTF-8, pinned rather\n\
than inherited, so a run reproduces on any machine.",
ALL_MODES.len(),
mode_list()
);
}
fn main() {
let args = parse_args();
if args.dump {
let out = std::io::stdout();
let mut w = std::io::BufWriter::new(out.lock());
for i in 0..args.count {
let seed = args.base_seed.wrapping_add(i);
let stmts = gen_case(seed, args.mode);
let _ = writeln!(w, "{}", build_program(&stmts));
}
let _ = w.flush();
return;
}
let bin = ours_bin();
let oracle = resolve_oracle(&bin);
let timeout = Duration::from_millis(args.timeout_ms);
if !bin.exists() {
eprintln!(
"node-js `node` binary not found at {}; run `cargo build` first",
bin.display()
);
std::process::exit(2);
}
if args.once {
let stmts = gen_case(args.base_seed, args.mode);
let script = build_program(&stmts);
let o = run_prog(Path::new(&oracle), &script, timeout);
let r = run_prog(&bin, &script, timeout);
let diverged = !o.timed_out && differs(&o, &r);
println!("seed : {}", args.base_seed);
println!("mode : {}", mode_name(args.mode));
let (show, o, r) = if diverged && stmts.len() > 1 {
let m = minimize(stmts, &bin, &oracle, timeout);
let ms = build_program(&m);
let mo = run_prog(Path::new(&oracle), &ms, timeout);
let mr = run_prog(&bin, &ms, timeout);
(ms, mo, mr)
} else {
(script, o, r)
};
println!("program:\n {}", show.replace('\n', "\n "));
println!("--- node exit={} timeout={} ---", o.exit, o.timed_out);
let _ = std::io::stdout().write_all(&o.stdout);
println!("--- node-js exit={} timeout={} ---", r.exit, r.timed_out);
let _ = std::io::stdout().write_all(&r.stdout);
println!("--- {} ---", if diverged { "DIVERGE" } else { "match" });
std::process::exit(if diverged { 1 } else { 0 });
}
let next = AtomicU64::new(0);
let checked = AtomicU64::new(0);
let timeouts = AtomicU64::new(0);
let oracle_failed = AtomicU64::new(0);
let oracle_silent = AtomicU64::new(0);
let oracle_inert = AtomicU64::new(0);
let oracle_timeouts = AtomicU64::new(0);
let stop = AtomicBool::new(false);
let divergences: Mutex<Vec<(u64, String)>> = Mutex::new(Vec::new());
let start = Instant::now();
eprintln!("oracle: {}", oracle_id(&oracle));
eprintln!("ours : {}", bin.display());
eprintln!(
"fuzzing {} cases ({}) across {} workers…",
args.count,
mode_name(args.mode),
args.jobs
);
std::thread::scope(|scope| {
for _ in 0..args.jobs {
scope.spawn(|| loop {
if stop.load(Ordering::Relaxed) {
break;
}
let idx = next.fetch_add(1, Ordering::Relaxed);
if idx >= args.count {
break;
}
let seed = args.base_seed.wrapping_add(idx);
let stmts = gen_case(seed, args.mode);
let script = build_program(&stmts);
let o = run_prog(Path::new(&oracle), &script, timeout);
let r = run_prog(&bin, &script, timeout);
let done = checked.fetch_add(1, Ordering::Relaxed) + 1;
if o.timed_out || r.timed_out {
timeouts.fetch_add(1, Ordering::Relaxed);
}
if o.timed_out {
oracle_timeouts.fetch_add(1, Ordering::Relaxed);
}
if !o.timed_out && o.exit != 0 {
oracle_failed.fetch_add(1, Ordering::Relaxed);
}
if !o.timed_out && o.stdout.is_empty() {
oracle_silent.fetch_add(1, Ordering::Relaxed);
}
if !o.timed_out && o.stdout.is_empty() && o.exit == 0 {
oracle_inert.fetch_add(1, Ordering::Relaxed);
}
if !o.timed_out && differs(&o, &r) {
let minimal = minimize(stmts, &bin, &oracle, timeout);
let mscript = build_program(&minimal);
let mo = run_prog(Path::new(&oracle), &mscript, timeout);
let mr = run_prog(&bin, &mscript, timeout);
let mut confirmed = differs(&mo, &mr);
for _ in 1..args.verify.max(1) {
if !confirmed {
break;
}
confirmed = diverges(&mscript, &bin, &oracle, timeout);
}
if !confirmed {
continue;
}
let err_of = |o: &RunOut| -> String {
if CMP_STDERR.load(Ordering::Relaxed) {
format!(
"\n stderr: {}",
render(&norm_stderr(&o.stderr)).replace('\n', "\n ")
)
} else {
String::new()
}
};
let rec = format!(
"==== seed {seed} ====\n\
program:\n {}\n\
node : exit={} timeout={}{}\n{}\n\
node-js : exit={} timeout={}{}\n{}\n",
mscript.replace('\n', "\n "),
mo.exit,
mo.timed_out,
err_of(&mo),
render(&mo.stdout),
mr.exit,
mr.timed_out,
err_of(&mr),
render(&mr.stdout),
);
let mut d = divergences.lock().unwrap();
d.push((seed, rec));
if d.len() >= args.max_report {
stop.store(true, Ordering::Relaxed);
}
}
if done % 500 == 0 {
let n = divergences.lock().unwrap().len();
eprintln!(
" {done}/{} checked, {n} divergences, {:.0}/s",
args.count,
done as f64 / start.elapsed().as_secs_f64().max(0.001)
);
}
});
}
});
let checked = checked.load(Ordering::Relaxed);
let timeouts = timeouts.load(Ordering::Relaxed);
let mut divergences: Vec<(u64, String)> = divergences.into_inner().unwrap();
divergences.sort_by_key(|(seed, _)| *seed);
let divergences: Vec<String> = divergences.into_iter().map(|(_, r)| r).collect();
let elapsed = start.elapsed();
let sig_of = |rec: &str| -> String {
let prog = rec
.split("program:\n")
.nth(1)
.and_then(|s| s.split("\nnode ").next())
.unwrap_or(rec);
signature(prog)
};
let allowed: std::collections::HashSet<String> = match &args.baseline {
Some(bp) => std::fs::read_to_string(bp)
.unwrap_or_default()
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect(),
None => std::collections::HashSet::new(),
};
let mut new_records: Vec<&String> = Vec::new();
let mut new_sigs: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let mut known = 0usize;
for rec in &divergences {
let sig = sig_of(rec);
if args.baseline.is_some() && allowed.contains(&sig) {
known += 1;
} else {
new_records.push(rec);
new_sigs.insert(sig);
}
}
let oracle = oracle_id(&oracle);
let oracle_failed = oracle_failed.load(Ordering::Relaxed);
let oracle_silent = oracle_silent.load(Ordering::Relaxed);
let oracle_inert = oracle_inert.load(Ordering::Relaxed);
println!(
"\nfuzzed {checked} cases in {:.1}s ({:.0}/s)\n\
oracle : {}\n\
divergences : {} ({} known / {} new)\n\
timeouts : {}\n\
ref timeout : {} (reference timed out — SKIPPED, never counted as agreement)\n\
ref failed : {} (reference exited non-zero — a COMPARED value since\n\
\x20 exit codes are matched exactly; only `ref inert` means nothing was seen)\n\
ref silent : {} (reference printed nothing on stdout)\n\
ref inert : {} (reference printed nothing AND exited 0 — no value was\n\
\x20 observed on either compared channel)",
elapsed.as_secs_f64(),
checked as f64 / elapsed.as_secs_f64().max(0.001),
oracle,
divergences.len(),
known,
new_records.len(),
timeouts,
oracle_timeouts.load(Ordering::Relaxed),
oracle_failed,
oracle_silent,
oracle_inert,
);
if !divergences.is_empty() {
if let Some(parent) = args.out_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(mut f) = std::fs::File::create(&args.out_path) {
let _ = writeln!(f, "# oracle: {oracle}");
for d in &divergences {
let _ = writeln!(f, "{d}");
}
println!(
"wrote {} divergences to {}",
divergences.len(),
args.out_path.display()
);
}
}
if !new_records.is_empty() {
println!(
"\n--- {} NEW gap signature(s) (add to baseline once triaged) ---",
new_sigs.len()
);
for s in &new_sigs {
println!("{s}");
}
println!(
"\n--- first {} new divergence record(s) ---",
new_records.len().min(5)
);
for d in new_records.iter().take(5) {
println!("{d}");
}
std::process::exit(1);
}
if known > 0 {
println!("all {known} divergences are known (in baseline) — OK");
}
}