Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 38 additions & 50 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 60 additions & 1 deletion tests/echidna/echidna-harness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,10 @@
// provide ECHIDNA with proof-of-absence obligations that can be independently
// verified.

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

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

// ============================================================================
// Property 6: Fuzzing Round-Trip Soundness (verify(codegen(parse)))
// ============================================================================
console.log("\nProperty 6: Fuzzing Round-Trip Soundness");

const TW_BIN = _resolve(_thisFile, "..", "..", "..", "target", "debug", "tw");
const TW_VERIFY_BIN = _resolve(_thisFile, "..", "..", "..", "target", "debug", "tw-verify");

let soundnessPass = 0;
let soundnessFail = 0;
let soundnessSkipped = 0;

if (!_existsSync(TW_BIN) || !_existsSync(TW_VERIFY_BIN)) {
console.log(" SKIP: Codegen binaries not found. Run `cargo build` first.");
soundnessSkipped = Math.min(iterations, 50);
} else {
const maxIterations = process.env.EXHAUSTIVE_FUZZ ? iterations : Math.min(iterations, 50);
if (!process.env.EXHAUSTIVE_FUZZ && iterations > 50) {
console.log(` (Capping codegen fuzzing to 50 iterations. Set EXHAUSTIVE_FUZZ=1 for all ${iterations})`);
}

for (let i = 0; i < maxIterations; i++) {
const rng = new RNG(i + 50000);
const prog = genProgram(rng);
const result = parseModule(prog);

if (result.TAG === "Ok") {
property(`round-trip soundness seed=${i}`, () => {
const tempSrc = _resolve(_thisFile, "..", `temp_${i}.twasm`);
const tempWasm = _resolve(_thisFile, "..", `temp_${i}.wasm`);
try {
_writeFileSync(tempSrc, prog);
_execSync(`${TW_BIN} build ${tempSrc} -o ${tempWasm}`, { stdio: 'pipe' });

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
_execSync(`${TW_VERIFY_BIN} ${tempWasm}`, { stdio: 'pipe' });

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
soundnessPass++;
} catch (e) {
soundnessFail++;
const stderr = e.stderr ? e.stderr.toString() : e.message;
throw new Error(`Codegen or verify failed: ${stderr}`);
} finally {
try { _rmSync(tempSrc); } catch {}
try { _rmSync(tempWasm); } catch {}
}
});
}
}
}

// ============================================================================
// ECHIDNA Submission
// ============================================================================
Expand Down Expand Up @@ -624,6 +673,16 @@
`unchanged modulo proof-only keys (effects, caps, loc) in ` +
`${erasureMatched}/${erasurePairs} successful pairs.`,
},
{
name: "fuzz-round-trip-soundness",
status: soundnessSkipped > 0 ? "info" : (soundnessFail === 0 && soundnessPass > 0 ? "proved" : "failed"),

Check warning on line 678 in tests/echidna/echidna-harness.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_typed-wasm&issues=AZ-RHK7B9EhOTPXq7h7m&open=AZ-RHK7B9EhOTPXq7h7m&pullRequest=219
successes: soundnessPass,
failures: soundnessFail,
skipped: soundnessSkipped,
detail: soundnessSkipped > 0
? "Skipped due to missing codegen binaries"
: `${soundnessPass} passed, ${soundnessFail} failed round-trip`,
},
],
}),
});
Expand Down
37 changes: 36 additions & 1 deletion tests/property/property_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
//
// Run: node tests/property/property_test.mjs

import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, copyFileSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { resolve, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -229,6 +230,40 @@
}
}

// ----------------------------------------------------------------------
// P9 Round-trip soundness (verify(codegen(parse(src))) == OK)
// ----------------------------------------------------------------------
section("P9. Round-trip soundness (verify(codegen(parse(src))) == OK)");

const TW_BIN = join(ROOT, "target/debug/tw");
const TW_VERIFY_BIN = join(ROOT, "target/debug/tw-verify");
const FIXTURES_DIR = join(ROOT, "crates/typed-wasm-verify/tests/fixtures/c5_real");

if (!existsSync(TW_BIN) || !existsSync(TW_VERIFY_BIN)) {
skip("P9: Codegen binaries not found (run `cargo build` first)");
} else {
mkdirSync(FIXTURES_DIR, { recursive: true });
for (const path of EXAMPLES) {
const filename = path.split("/").pop();
const tempWasm = join(ROOT, `temp_${filename}.wasm`);
const fixturePath = join(FIXTURES_DIR, `${filename.replace(".twasm", ".wasm")}`);

try {
execFileSync(TW_BIN, ['build', path, '-o', tempWasm], { stdio: 'pipe' });
execFileSync(TW_VERIFY_BIN, [tempWasm], { stdio: 'pipe' });
copyFileSync(tempWasm, fixturePath);
ok(`${path}: verify(codegen) == OK`);
} catch (e) {
const stderr = e.stderr ? e.stderr.toString() : e.message;
bad(`${path}: codegen or verify failed:\n${stderr}`);
} finally {
if (existsSync(tempWasm)) {
try { import("node:fs").then(fs => fs.rmSync(tempWasm)); } catch {}

Check warning on line 261 in tests/property/property_test.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using 'await' for the promise inside this 'try' or replace it with 'Promise.prototype.catch(...)' usage.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_typed-wasm&issues=AZ-RHK4H9EhOTPXq7h7k&open=AZ-RHK4H9EhOTPXq7h7k&pullRequest=219

Check warning on line 261 in tests/property/property_test.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer top-level await over using a promise chain.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_typed-wasm&issues=AZ-RHK4H9EhOTPXq7h7l&open=AZ-RHK4H9EhOTPXq7h7l&pullRequest=219
}
}
}
}

// ----------------------------------------------------------------------
// Summary
// ----------------------------------------------------------------------
Expand Down
Loading