Skip to content

Commit d382075

Browse files
Fix #130: Wire in codegen pipeline to ECHIDNA and property corpus (#219)
Closes #130
1 parent 435b37d commit d382075

3 files changed

Lines changed: 134 additions & 52 deletions

File tree

Cargo.lock

Lines changed: 38 additions & 50 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/echidna/echidna-harness.mjs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,9 +453,10 @@ console.log(
453453
// provide ECHIDNA with proof-of-absence obligations that can be independently
454454
// verified.
455455

456-
import { readFileSync as _readFileSync } from "node:fs";
456+
import { readFileSync as _readFileSync, writeFileSync as _writeFileSync, rmSync as _rmSync, existsSync as _existsSync } from "node:fs";
457457
import { resolve as _resolve } from "node:path";
458458
import { fileURLToPath as _fileURLToPath } from "node:url";
459+
import { execSync as _execSync } from "node:child_process";
459460

460461
// _thisFile is the path to this harness file, not a directory.
461462
// The resolve chain strips the filename (first ..) then navigates to the layout dir.
@@ -556,6 +557,54 @@ console.log(recCheck.ok ? " ✓ Types.idr: WHT_Var, WHT_Rec, WHT_Any present"
556557
console.log(listCheck.ok ? " ✓ Stdlib.idr: List uses WHT_Var 0 (no placeholder)"
557558
: ` ✗ list-layout: ${listCheck.reason}`);
558559

560+
// ============================================================================
561+
// Property 6: Fuzzing Round-Trip Soundness (verify(codegen(parse)))
562+
// ============================================================================
563+
console.log("\nProperty 6: Fuzzing Round-Trip Soundness");
564+
565+
const TW_BIN = _resolve(_thisFile, "..", "..", "..", "target", "debug", "tw");
566+
const TW_VERIFY_BIN = _resolve(_thisFile, "..", "..", "..", "target", "debug", "tw-verify");
567+
568+
let soundnessPass = 0;
569+
let soundnessFail = 0;
570+
let soundnessSkipped = 0;
571+
572+
if (!_existsSync(TW_BIN) || !_existsSync(TW_VERIFY_BIN)) {
573+
console.log(" SKIP: Codegen binaries not found. Run `cargo build` first.");
574+
soundnessSkipped = Math.min(iterations, 50);
575+
} else {
576+
const maxIterations = process.env.EXHAUSTIVE_FUZZ ? iterations : Math.min(iterations, 50);
577+
if (!process.env.EXHAUSTIVE_FUZZ && iterations > 50) {
578+
console.log(` (Capping codegen fuzzing to 50 iterations. Set EXHAUSTIVE_FUZZ=1 for all ${iterations})`);
579+
}
580+
581+
for (let i = 0; i < maxIterations; i++) {
582+
const rng = new RNG(i + 50000);
583+
const prog = genProgram(rng);
584+
const result = parseModule(prog);
585+
586+
if (result.TAG === "Ok") {
587+
property(`round-trip soundness seed=${i}`, () => {
588+
const tempSrc = _resolve(_thisFile, "..", `temp_${i}.twasm`);
589+
const tempWasm = _resolve(_thisFile, "..", `temp_${i}.wasm`);
590+
try {
591+
_writeFileSync(tempSrc, prog);
592+
_execSync(`${TW_BIN} build ${tempSrc} -o ${tempWasm}`, { stdio: 'pipe' });
593+
_execSync(`${TW_VERIFY_BIN} ${tempWasm}`, { stdio: 'pipe' });
594+
soundnessPass++;
595+
} catch (e) {
596+
soundnessFail++;
597+
const stderr = e.stderr ? e.stderr.toString() : e.message;
598+
throw new Error(`Codegen or verify failed: ${stderr}`);
599+
} finally {
600+
try { _rmSync(tempSrc); } catch {}
601+
try { _rmSync(tempWasm); } catch {}
602+
}
603+
});
604+
}
605+
}
606+
}
607+
559608
// ============================================================================
560609
// ECHIDNA Submission
561610
// ============================================================================
@@ -624,6 +673,16 @@ try {
624673
`unchanged modulo proof-only keys (effects, caps, loc) in ` +
625674
`${erasureMatched}/${erasurePairs} successful pairs.`,
626675
},
676+
{
677+
name: "fuzz-round-trip-soundness",
678+
status: soundnessSkipped > 0 ? "info" : (soundnessFail === 0 && soundnessPass > 0 ? "proved" : "failed"),
679+
successes: soundnessPass,
680+
failures: soundnessFail,
681+
skipped: soundnessSkipped,
682+
detail: soundnessSkipped > 0
683+
? "Skipped due to missing codegen binaries"
684+
: `${soundnessPass} passed, ${soundnessFail} failed round-trip`,
685+
},
627686
],
628687
}),
629688
});

tests/property/property_test.mjs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
//
2626
// Run: node tests/property/property_test.mjs
2727

28-
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
28+
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, copyFileSync } from "node:fs";
29+
import { execFileSync } from "node:child_process";
2930
import { resolve, dirname, join } from "node:path";
3031
import { fileURLToPath } from "node:url";
3132

@@ -229,6 +230,40 @@ for (const path of EXAMPLES.slice(0, 3)) {
229230
}
230231
}
231232

233+
// ----------------------------------------------------------------------
234+
// P9 Round-trip soundness (verify(codegen(parse(src))) == OK)
235+
// ----------------------------------------------------------------------
236+
section("P9. Round-trip soundness (verify(codegen(parse(src))) == OK)");
237+
238+
const TW_BIN = join(ROOT, "target/debug/tw");
239+
const TW_VERIFY_BIN = join(ROOT, "target/debug/tw-verify");
240+
const FIXTURES_DIR = join(ROOT, "crates/typed-wasm-verify/tests/fixtures/c5_real");
241+
242+
if (!existsSync(TW_BIN) || !existsSync(TW_VERIFY_BIN)) {
243+
skip("P9: Codegen binaries not found (run `cargo build` first)");
244+
} else {
245+
mkdirSync(FIXTURES_DIR, { recursive: true });
246+
for (const path of EXAMPLES) {
247+
const filename = path.split("/").pop();
248+
const tempWasm = join(ROOT, `temp_${filename}.wasm`);
249+
const fixturePath = join(FIXTURES_DIR, `${filename.replace(".twasm", ".wasm")}`);
250+
251+
try {
252+
execFileSync(TW_BIN, ['build', path, '-o', tempWasm], { stdio: 'pipe' });
253+
execFileSync(TW_VERIFY_BIN, [tempWasm], { stdio: 'pipe' });
254+
copyFileSync(tempWasm, fixturePath);
255+
ok(`${path}: verify(codegen) == OK`);
256+
} catch (e) {
257+
const stderr = e.stderr ? e.stderr.toString() : e.message;
258+
bad(`${path}: codegen or verify failed:\n${stderr}`);
259+
} finally {
260+
if (existsSync(tempWasm)) {
261+
try { import("node:fs").then(fs => fs.rmSync(tempWasm)); } catch {}
262+
}
263+
}
264+
}
265+
}
266+
232267
// ----------------------------------------------------------------------
233268
// Summary
234269
// ----------------------------------------------------------------------

0 commit comments

Comments
 (0)