From d2e54b7efa9500fa6fc44d8dcec0ff6d95dd071c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:35:39 +0900 Subject: [PATCH] lib: expectabort rows for the restored preconditions; retire 989_libprecond_abort.c The three must-abort runs (u32n/u64n zero, decodedsize unaligned) move into the owning lib suites as test.expectabort rows; the five normal-return controls already live in the r989_libprecond_* fixtures. wwstage parity rides the lib-wide cs==ww byte identity gate. --- lib/encoding/base64/base64_test.ww | 10 ++ lib/math/random/random_test.ww | 17 +++ test/wcc/989_libprecond_abort.c | 214 ----------------------------- 3 files changed, 27 insertions(+), 214 deletions(-) delete mode 100644 test/wcc/989_libprecond_abort.c diff --git a/lib/encoding/base64/base64_test.ww b/lib/encoding/base64/base64_test.ww index e9464cb1..fd94f8d5 100644 --- a/lib/encoding/base64/base64_test.ww +++ b/lib/encoding/base64/base64_test.ww @@ -16,6 +16,7 @@ import errors; import io; import memio; import strings; +import test; fn streq(a: str, b: str) bool = { @@ -162,6 +163,15 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = { assert(!(base64.decodedsize(164) != 123)); }; +// A non-multiple-of-4 input is out of domain and must abort loudly, +// never return garbage. Pins the restored Hare precondition +// (ref/hare/encoding/base64/base64.ha:597, assert(sz % 4 == 0)). + +@test fn decodedsize_unaligned_aborts() void = { + test.expectabort(); + base64.decodedsize(5i32); +}; + // ---- round-trip every byte value 0..255 (std + url) ---- @test fn roundtrip_all_bytes() void = { diff --git a/lib/math/random/random_test.ww b/lib/math/random/random_test.ww index 6183943b..0996cb2f 100644 --- a/lib/math/random/random_test.ww +++ b/lib/math/random/random_test.ww @@ -1,6 +1,7 @@ package random_test; import random; +import test; @test fn seq() void = { let r: random.random = random.init(1234567u64); @@ -52,3 +53,19 @@ import random; i += 1; }; }; + +// n == 0 is out of domain and must abort loudly, never return +// garbage. Pins the restored Hare preconditions +// (ref/hare/math/random/random.ha:26,42). + +@test fn u32n_zero_aborts() void = { + test.expectabort(); + let r: random.random = random.init(1u64); + random.u32n(&r, 0u32); +}; + +@test fn u64n_zero_aborts() void = { + test.expectabort(); + let r: random.random = random.init(1u64); + random.u64n(&r, 0u64); +}; diff --git a/test/wcc/989_libprecond_abort.c b/test/wcc/989_libprecond_abort.c deleted file mode 100644 index 9b84d1e0..00000000 --- a/test/wcc/989_libprecond_abort.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 989_libprecond_abort (F-Q) — three lib functions had lost their Hare - * loud-abort PRECONDITIONS, so an out-of-domain argument silently returned - * garbage instead of aborting. This pins the restored asserts: - * - * lib/math/random/random.ww u32n assert(n != 0) ref/hare/.../random.ha:26 - * lib/math/random/random.ww u64n assert(n != 0) ref/hare/.../random.ha:42 - * lib/encoding/base64/... decodedsize assert(sz % 4 == 0) base64.ha:597 - * - * Pre-fix every abort row BUILT rc=0 and RAN rc=0 (the precondition was - * absent — silent garbage). The fix is a lib source change, source-bundled - * into each build, so it takes effect identically on both stages with no - * codegen change (byte-id NEUTRAL). - * - * row | call | result (cs == ww) - * --------------------------+-----------------------+------------------- - * u32n_zero_abort | u32n(&g, 0) | run aborts (rc!=0) - * u32n_valid_ok | u32n(&g, 10) in [0,10)| run rc == 0 - * u32n_one_ok | u32n(&g, 1) in [0,1) | run rc == 0 - * u64n_zero_abort | u64n(&g, 0) | run aborts (rc!=0) - * u64n_valid_ok | u64n(&g, 10) in [0,10)| run rc == 0 - * u64n_one_ok | u64n(&g, 1) in [0,1) | run rc == 0 - * decodedsize_unalign_abort | decodedsize(5) | run aborts (rc!=0) - * decodedsize_align_ok | decodedsize(8) == 6 | run rc == 0 - * - * The five normal-return controls now live in r989_libprecond_* fixtures; - * this wrapper retains the three signal/abort expectations unavailable in - * the current fixture grammar. - * - * The abort rows BUILD clean and abort at RUNTIME (unlike a reject row, - * which fails the build) — this harness builds then runs each fixture and - * checks the run exit, not the build exit. Rows run on cstage `ww` and, - * when present, wwstage `ww_ww`; both must agree. Sibling idiom: - * 989_taggedcompoundderef_reject.c (driver loop, /tmp fixture scaffolding). - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - int want_abort; /* 1 = run must abort (rc!=0); 0 = run rc==want_exit */ - int want_exit; -}; - -#define RNG_HDR \ - "package main;\n" \ - "import random;\n" \ - "export fn main() int = {\n" \ - "\tlet g: random.random = random.init(1u64);\n" - -#define B64_HDR \ - "package main;\n" \ - "import base64;\n" \ - "export fn main() int = {\n" - -static const struct row rows[] = { - /* Success boundaries moved to r989_libprecond_* fixtures. */ - { "u32n_zero_abort", - RNG_HDR "\tlet x: u32 = random.u32n(&g, 0u32);\n\treturn 0;\n};\n", - 1, 0 }, - { "u64n_zero_abort", - RNG_HDR "\tlet x: u64 = random.u64n(&g, 0u64);\n\treturn 0;\n};\n", - 1, 0 }, - { "decodedsize_unalign_abort", - B64_HDR "\tlet c: i32 = base64.decodedsize(5i32);\n\treturn 0;\n};\n", - 1, 0 }, -}; - -/* Build `r->src` with `driver`, then run it. Returns the RUN exit code, or - * -1 if the build failed (the harness treats a build miss as a hard error: - * every row here must build clean — only the RUN may abort). `incs` is the - * colon-free `-I a -I b ...` include flag string already composed by main. */ -static int -build_and_run(const char *driver, const char *incs, const struct row *r, int i, - int *cleanup_failed) -{ - char tmpdir[64], src[128], outbin[128], sepwork[160], cmd[2048]; - int brc = -1, got = -2, setupfail = 1, cleanfail = 0; - *cleanup_failed = 0; - snprintf(tmpdir, sizeof tmpdir, "/tmp/lpc_%d_d_%d", getpid(), i); - if (mkdir(tmpdir, 0755) != 0) return -2; - snprintf(src, sizeof src, "%s/lpc_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/lpc_%d_%d", tmpdir, getpid(), i); - snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin); - - FILE *f = fopen(src, "wb"); - if (!f) goto cleanup; - fputs(r->src, f); - if (fclose(f) != 0) goto cleanup; - - snprintf(cmd, sizeof cmd, "%s build -o %s %s %s 2>/dev/null", - driver, outbin, incs, src); - brc = runwait(cmd); - setupfail = 0; - - if (brc == 0) { - const char *base = strrchr(outbin, '/'); - base = base ? base + 1 : outbin; - snprintf(cmd, sizeof cmd, "cd %s && ulimit -c 0 && ./%s", tmpdir, - base); - got = runwait(cmd); - } else { - got = -1; - } - -cleanup: - snprintf(cmd, sizeof cmd, "rm -rf %s", sepwork); - if (runwait(cmd) != 0) cleanfail = 1; - if (unlink(outbin) != 0 && access(outbin, F_OK) == 0) cleanfail = 1; - if (unlink(src) != 0 && access(src, F_OK) == 0) cleanfail = 1; - if (rmdir(tmpdir) != 0) cleanfail = 1; - *cleanup_failed = cleanfail; - if (setupfail) return -2; - return brc == 0 ? got : -1; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - char absbin[1024]; - if (bin[0] != '/') { - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - /* random lives at lib/math/random, base64 at lib/encoding/base64; - * the leaf-only imports resolve via these -I dirs (base64's own - * bytes/io/... deps resolve via the driver's default lib srcdir). - * Absolute so include resolution does not depend on the build cwd. */ - char incs[1024]; - snprintf(incs, sizeof incs, "-I %s/lib -I %s/lib/math -I %s/lib/encoding", - cwd, cwd, cwd); - - char cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { const char *name; const char *drv; int gated; } - drivers[] = { - { "cstage", cdrv, 0 }, - { "wwstage", wdrv, 1 }, - { NULL, NULL, 0 }, - }; - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int d = 0; drivers[d].name; d++) { - if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) { - fprintf(stderr, "libprecond_abort: skip %s (no %s)\n", - drivers[d].name, drivers[d].drv); - continue; - } - for (int i = 0; i < n; i++) { - total++; - int cleanfail = 0; - int got = build_and_run(drivers[d].drv, incs, &rows[i], i, - &cleanfail); - if (got == -2) { - fprintf(stderr, "libprecond_abort[%s][%s]: setup failed\n", - drivers[d].name, - rows[i].label); - fail++; - } - if (got != -2 && rows[i].want_abort) { - if (got == 0 || got < 0) { - fprintf(stderr, "libprecond_abort[%s][%s]: run rc=%d, " - "expected a loud abort (rc!=0)%s\n", - drivers[d].name, rows[i].label, got, - got < 0 ? " [build failed]" : ""); - fail++; - } - } else if (got != -2) { - if (got != rows[i].want_exit) { - fprintf(stderr, "libprecond_abort[%s][%s]: run rc=%d " - "want=%d\n", drivers[d].name, rows[i].label, got, - rows[i].want_exit); - fail++; - } - } - if (cleanfail) { - fprintf(stderr, "libprecond_abort[%s][%s]: temporary " - "cleanup failed\n", drivers[d].name, rows[i].label); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, "libprecond_abort: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("libprecond_abort: %d/%d ok\n", total, total); - return 0; -}