@@ -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" ;
457457import { resolve as _resolve } from "node:path" ;
458458import { 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"
556557console . 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 } ) ;
0 commit comments