#!/usr/bin/env python3 """Deterministic grammar fuzzing for the parser, runtime, and ownership paths. Usage: python3 tests/drivers/fuzz_compiler.py [path/to/zy] [path/to/zym] [--js [path/to/tinyvm.js]] (--js alone runs build/js/tinyvm.js, from make js) """ import random import subprocess import sys argv = sys.argv[1:] js = None if "--js" in argv: k = argv.index("--js") del argv[k] js = argv.pop(k) if k < len(argv) and argv[k].endswith(".js") else "build/js/tinyvm.js" args = argv zy = args[0] if len(args) > 0 else "build/bin/zy" zym = args[1] if len(args) > 1 else "build/bin/zym" rng = random.Random(0x5A17) values = [] library_values = [] def expr(depth): if depth == 0 or rng.randrange(4) == 0: value = rng.randrange(10) return str(value), value left, a = expr(depth - 1) right, b = expr(depth - 1) op = rng.choice(("+", "-", "*")) if op == "+": value = a + b elif op == "-": value = a - b else: value = a * b return f"({left} {op} {right})", value lines = ["namespace;", "include rt, run, std;", "fn churn(n) {", " int i = 0;", " while (i < n) {", " { int* p = rt.new(int); *p = i; }", " i++;", " }", " return 0;", "}", "fn stable() { return 73; }", "fn main() {", " let base = rt.live_objects();"] for _ in range(128): source, value = expr(4) lines.append(f" rt.put_n({source}); rt.put_c(32);") values.append(value) for _ in range(32): a = "".join(rng.choice("abc") for _ in range(rng.randrange(11))) b = "".join(rng.choice("abc") for _ in range(rng.randrange(6))) lines.append(f' rt.put_n(std.Text.len("{a}")); rt.put_c(32); rt.put_n(std.Text.eq("{a}", "{b}")); rt.put_c(32);') lines.append(f' rt.put_n(std.Text.starts("{a}", "{b}")); rt.put_c(32); rt.put_n(std.Text.ends("{a}", "{b}")); rt.put_c(32); rt.put_n(std.Text.find("{a}", "{b}")); rt.put_c(32);') lines.append(f' rt.put_n(std.Bytes.len(b"{a}")); rt.put_c(32); rt.put_n(std.Bytes.eq(b"{a}", b"{b}")); rt.put_c(32);') lines.append(f' rt.put_n(std.Bytes.starts(b"{a}", b"{b}")); rt.put_c(32); rt.put_n(std.Bytes.ends(b"{a}", b"{b}")); rt.put_c(32); rt.put_n(std.Bytes.find(b"{a}", b"{b}")); rt.put_c(32);') library_values += [len(a), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b), len(a.encode()), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b)] lines += [ " churn(300); rt.gc(); rt.put_n(rt.live_objects() - base); rt.put_c(32);", ' char* source = "fn f() { return 7; }"; let parsed = run.tokens(source);', ' rt.put_n(parsed[0] > 0); rt.put_c(32); rt.put_n(rt.eq_w(source, "fn f() { return 7; }")); rt.put_c(32);', ' char* eval_source = "return 42;"; int e = 0;', ' while (e < 16) { let q = run.tokens(eval_source); rt.put_n(run.eval(q)); rt.put_c(32); e++; }', ' rt.put_n(stable()); rt.put_c(32); rt.put_n(rt.eq_w(eval_source, "return 42;")); rt.put_c(10);', " return 0;", "}", ] program = "\n".join(lines) + "\n" expected = " ".join(str(x) for x in values + library_values + [0, 1, 1, *([42] * 16), 73, 1]) + "\n" def run(binary): result = subprocess.run(binary, input=program, text=True, capture_output=True, timeout=30) if result.returncode or result.stdout != expected: actual_words = result.stdout.split() expected_words = expected.split() mismatch = next((i for i, pair in enumerate(zip(actual_words, expected_words)) if pair[0] != pair[1]), min(len(actual_words), len(expected_words))) raise SystemExit( f"{' '.join(binary)} failed deterministic fuzz seed 0x5a17\n" f"exit={result.returncode}, token mismatch at {mismatch}\n" f"expected={expected_words[max(0, mismatch - 3):mismatch + 4]!r}\n" f"stdout={actual_words[max(0, mismatch - 3):mismatch + 4]!r}\n" f"stderr={result.stderr!r}" ) return result.stdout hosts = [(zy, [zy]), (zym, [zym])] if js: hosts.append(("javascript", ["node", js])) outputs = [run(command) for _, command in hosts] if any(output != outputs[0] for output in outputs[1:]): raise SystemExit("compiler hosts disagreed on deterministic fuzz output") print("ok compiler/runtime/stdlib fuzz: 128 generated expressions, 300 scoped allocations and GC, " "16 eval recompiles, unchanged source strings, and 32 randomized Text/Bytes cases on " + ", ".join(name for name, _ in hosts))