wcc/check: A7 honest-floor tagged-subset reject closes wide→narrow miscompile (wwstage)
wwstage's tagged→tagged subset-assign arm accepted all (lenient escape), silently miscompiling implicit wide→narrow: a (int|bool|str) holding a str, assigned to (int|bool), ran the int arm and read the str pointer as int. cstage rejects loud; this escape was the lone divergence. Replace the escape with cstage's subset walk (src ⊆ dst): a src variant is covered iff typeeqast matches (structural — []u8/nested/primitives) OR both are N_TNAME with equal leaf names. The leaf bridge covers cross-module forwards where a callee's bare inline-union variant (utf8's `done`) meets the consumer's qualified `utf8.done` — raw typeeqast can't, and the escape was masking it for every forward. Mirrors casecovers (check.ww:4136). cstage untouched (align-up); spread-bearing unions keep the escape (#199b orthogonal). Honest floor: leaf-only defers true module identity to #10 — a callee's defining module for an inline-union return is unrecoverable at check-time (the call node is gone; #211 fnretlookupmod is cgen-only). Retained divergence = cross-module same-leaf-collision over-accept (absent from bootstrap), documented at the site and filed as #10-A7 / census cat-A. Test 989_tagged_subset_reject is table-driven with composition-discriminating rows: []u8 subset (typeeqast-only), bare↔qualified xmod forward (leaf-only), xmod named genuine-absence (reject). 352 green; w6c unchanged, w6c_ww 4b4496b8→4b316f01.
This commit is contained in:
488
test/wcc/989_tagged_subset_reject.c
Normal file
488
test/wcc/989_tagged_subset_reject.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* 989_tagged_subset_reject — A7: wwstage align UP to cstage on the
|
||||
* tagged-union → tagged-union assignability subset check (CHECKER-ONLY).
|
||||
*
|
||||
* THE BUG: an implicit (no-cast) WIDE → NARROW tagged assignment
|
||||
* (`(int|bool|str)` returned where `(int|bool)` is wanted) was SILENTLY
|
||||
* ACCEPTED by wwstage (its tagged→tagged arm fell through a lenient
|
||||
* `*confident=false; return true` escape) while cstage correctly REJECTS
|
||||
* it (structural subset loop, cmd/wcc/type.c:360-367). The accepted
|
||||
* payload (`str`) was then read through the `int` arm by cgen → garbage
|
||||
* → exit 1. Pure wwstage-align-UP: cstage gets ZERO touch.
|
||||
*
|
||||
* THE FIX (selfhost/cmd/wcc/check.ww, tagged→tagged arm): after the #205
|
||||
* NAMED-variant nominal loop, replace the lenient escape with cstage's
|
||||
* structural subset loop — every src variant must appear in dst (nominal
|
||||
* identity via Layer-1 typeeqast); a missing variant rejects loud. GUARD:
|
||||
* the subset loop runs ONLY when neither side carries a `...spread`
|
||||
* (TK_ELLIPSIS) member; a spread-bearing union keeps the lenient escape
|
||||
* (cstage flattens spreads in resolve, wwstage stays AST-keyed — routing
|
||||
* a spread through the strict loop would over-reject a valid spread-widen
|
||||
* cstage accepts; spread decl-form layout is #199b, deferred).
|
||||
*
|
||||
* The fix's predicate is a COMPOSITION (drew ruling A, the honest floor):
|
||||
* per src variant, typeeqast FIRST (exact / structural — primitives, []u8
|
||||
* slices, nested unions, Layer-1 nominal aliases), THEN a leaf-only name
|
||||
* bridge (qualleaf, module ignored) for the bare-vs-qualified spelling mix
|
||||
* an inline-union callee return forces (variants spelled BARE in the
|
||||
* callee, QUALIFIED at the consumer; the module is unrecoverable there).
|
||||
* The two single-file rows below cannot exercise either half distinctly
|
||||
* (one module → every name is bare → typeeqast's streq matches), so the
|
||||
* teeth for the COMPOSITION are the dedicated rows:
|
||||
* - slice_struct_subset: a []u8 (N_TSLICE, not N_TNAME) src variant is
|
||||
* matched ONLY by typeeqast — a pure-leaf-bridge regression would
|
||||
* wrongly reject it.
|
||||
* - xmod_inline_forward: a bare inline-union variant vs its qualified
|
||||
* consumer spelling is matched ONLY by the leaf bridge (typeeqast /
|
||||
* aliassym cannot resolve a bare inline-return variant in the
|
||||
* consumer's scope) — dropping the bridge regresses cs=0/ww=1.
|
||||
*
|
||||
* row | src → dst | verdict
|
||||
* -------------------------+---------------------------------+-----------
|
||||
* wide_narrow_nocast | (int|bool|str) → (int|bool) | REJECT [bug]
|
||||
* flatten_subset | (bool|str) → (int|bool|str) | ok 0 +byteid
|
||||
* slice_struct_subset | (int|[]u8) → (int|bool|[]u8) | ok 0 +byteid
|
||||
* nested_wrap | inner → (int|inner) | ok 42 +byteid
|
||||
* concrete_spread_widen | 42,"hi" → (...formattable|bool) | ok 0
|
||||
* spread_named_casematch | case e1 of (str|...e1) | REJECT [#115]
|
||||
* spread_wider_subset | (str|...e1) → (str|bool|int|f64)| ok 0 [lenient]
|
||||
*
|
||||
* Cross-module rows (839-style -I build; wwstage-gated; byte-id NOT
|
||||
* asserted — receiving + re-passing a cross-module tagged return crosses a
|
||||
* pre-existing cgen frame-layout divergence, cf. 839):
|
||||
* xmod_inline_forward | e.next (i64|done) → (i64|e.done)| ok 0
|
||||
* xmod_inline_narrow | (i64|done|more) → (i64|e.done) | REJECT
|
||||
*
|
||||
* Rows flatten_subset / slice_struct_subset / nested_wrap also assert w6c
|
||||
* vs w6c_ww asm is BYTE-IDENTICAL (rule-10): the fix only changes the
|
||||
* REJECT decision and a confident-vs-lenient accept on inputs cstage
|
||||
* already accepted, so the emitted code is unchanged. The two spread-accept
|
||||
* rows (concrete_spread_widen / spread_wider_subset) assert accept-on-both-
|
||||
* stages only — they ride the deferred #199b spread path where the stages
|
||||
* intentionally diverge (wwstage AST-keyed vs cstage pre-flattened), so
|
||||
* byte-id is NOT asserted (cf. 839's cross-module-return note).
|
||||
* spread_named_casematch is a #115 case-match reject (not the subset arm),
|
||||
* pinned here as a symmetric-both-stage regression guard.
|
||||
*
|
||||
* Both stages must agree (rule-10): REJECT rows build-FAIL on both; accept
|
||||
* rows build+run to the same exit on cstage (`ww`) and wwstage (`ww_ww`).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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 expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit; /* meaningful only when expect_build */
|
||||
int byteid; /* 1 = also assert w6c vs w6c_ww .s byte-id */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* (1) THE BUG — wide → narrow, no cast → REJECT both stages.
|
||||
* `str` is a src variant absent from dst → subset loop rejects. */
|
||||
{ "wide_narrow_nocast",
|
||||
"package main;\n"
|
||||
"type big = (int | bool | str);\n"
|
||||
"type small = (int | bool);\n"
|
||||
"fn f(b: big) small = { return b; };\n"
|
||||
"export fn main() int = { return 0; };\n",
|
||||
0, 0, 0 },
|
||||
|
||||
/* (2) D flatten-subset, union→union → ACCEPT + byte-id.
|
||||
* {bool,str} ⊆ {int,bool,str}; cstage flattens inner + remaps tags. */
|
||||
{ "flatten_subset",
|
||||
"package main;\n"
|
||||
"type inner = (bool | str);\n"
|
||||
"type flat = (int | bool | str);\n"
|
||||
"fn f(x: inner) flat = { return x; };\n"
|
||||
"export fn main() int = { return 0; };\n",
|
||||
1, 0, 1 },
|
||||
|
||||
/* (2b) []u8-bearing structural subset → ACCEPT + byte-id. The `[]u8`
|
||||
* src variant is N_TSLICE, not N_TNAME, so it is matched ONLY by
|
||||
* typeeqast — never the leaf bridge. Teeth for the composition: a
|
||||
* pure-leaf-bridge predicate would drop typeeqast and wrongly reject
|
||||
* this (cs=0/ww=1). {int,[]u8} ⊆ {int,bool,[]u8}. */
|
||||
{ "slice_struct_subset",
|
||||
"package main;\n"
|
||||
"type a = (int | []u8);\n"
|
||||
"type b = (int | bool | []u8);\n"
|
||||
"fn f(x: a) b = { return x; };\n"
|
||||
"export fn main() int = { return 0; };\n",
|
||||
1, 0, 1 },
|
||||
|
||||
/* (3) B nested-wrap → ACCEPT + byte-id + runtime 42. inner is a
|
||||
* DIRECT variant of outer → #205 nominal loop accepts (wrap, no
|
||||
* flatten). Regression pin that the subset loop never reaches it. */
|
||||
{ "nested_wrap",
|
||||
"package main;\n"
|
||||
"type inner = (bool | str);\n"
|
||||
"type outer = (int | inner);\n"
|
||||
"fn wrap(x: inner) outer = { return x; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let v: inner = \"hi\";\n"
|
||||
" let o: outer = wrap(v);\n"
|
||||
" match (o) {\n"
|
||||
" case let i: int => { return 1; };\n"
|
||||
" case let n: inner =>\n"
|
||||
" match (n) {\n"
|
||||
" case let b: bool => { return 2; };\n"
|
||||
" case let s: str => { return 42; };\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
1, 42, 1 },
|
||||
|
||||
/* (4) concrete → spread-field widen → ACCEPT (A6 concrete→tagged
|
||||
* path, NOT the tagged→tagged subset arm). Pins the spread path is
|
||||
* untouched; byte-id NOT asserted (#199b spread divergence). */
|
||||
{ "concrete_spread_widen",
|
||||
"package main;\n"
|
||||
"type formattable = (int | str | bool);\n"
|
||||
"type field = (...formattable | bool);\n"
|
||||
"fn take(f: field) int = { return 0; };\n"
|
||||
"export fn main() int = { take(42); take(\"hi\"); return 0; };\n",
|
||||
1, 0, 0 },
|
||||
|
||||
/* (5) `...spread` member vs NAMED case pattern → REJECT both stages
|
||||
* (#115, check.ww:929 case-match path — NOT the subset arm). A
|
||||
* symmetric regression pin; the A7 fix must not perturb it. */
|
||||
{ "spread_named_casematch",
|
||||
"package main;\n"
|
||||
"type e1 = (bool | int);\n"
|
||||
"type sp = (str | ...e1);\n"
|
||||
"fn f(x: sp) int = {\n"
|
||||
" match (x) {\n"
|
||||
" case let s: str => { return 1; };\n"
|
||||
" case let e: e1 => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"export fn main() int = { return 0; };\n",
|
||||
0, 0, 0 },
|
||||
|
||||
/* (5b) spread → wider subset assign (latent) → KEEP lenient accept.
|
||||
* sp's src side carries a `...e1` spread → the guard keeps the
|
||||
* lenient escape (do NOT newly reject); cstage flattens & accepts.
|
||||
* byte-id NOT asserted (#199b spread divergence). */
|
||||
{ "spread_wider_subset",
|
||||
"package main;\n"
|
||||
"type e1 = (bool | int);\n"
|
||||
"type sp = (str | ...e1);\n"
|
||||
"type big = (str | bool | int | f64);\n"
|
||||
"fn g(x: sp) big = { return x; };\n"
|
||||
"export fn main() int = { return 0; };\n",
|
||||
1, 0, 0 },
|
||||
};
|
||||
|
||||
/* run_build — build+run `src` via `driver`; returns the binary's exit
|
||||
* code, or -1 on a build failure. */
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/tsr_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tsr_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
/* build_should_fail — the build must error on `driver`; returns 0 when it
|
||||
* correctly FAILS, non-zero when it wrongly succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char s[64], tmpdir[64], cmd[1024];
|
||||
snprintf(s, sizeof s, "/tmp/tsrn_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tsrn_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, s);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
unlink(s);
|
||||
const char *base = strrchr(s, '/');
|
||||
base = base ? base + 1 : s;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
unlink(outbin);
|
||||
rmdir(tmpdir);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/tsr_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/tsr_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/tsr_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Cross-module rows — the leaf-bridge teeth. A bare inline-union variant
|
||||
* (callee) vs its qualified consumer spelling is matched ONLY by the leaf
|
||||
* bridge, and that mix is intrinsically multi-module (one file → all names
|
||||
* bare → typeeqast's streq matches). 839-style `-I` build; byte-id NOT
|
||||
* asserted (cross-module tagged return + re-pass crosses a pre-existing
|
||||
* cgen frame-layout divergence, cf. 839). */
|
||||
struct xrow {
|
||||
const char *label;
|
||||
const char *esrc; /* package e */
|
||||
const char *msrc; /* package main; import e */
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
static const struct xrow xrows[] = {
|
||||
/* leaf-bridge ACCEPT: e.next returns the inline union (i64|done)
|
||||
* spelled BARE; forward into qualified (i64|e.done). typeeqast/
|
||||
* aliassym cannot resolve the bare `done` in main's scope, so only
|
||||
* the leaf bridge equates them. Dropping the bridge → cs=0/ww=1. */
|
||||
{ "xmod_inline_forward",
|
||||
"package e;\n"
|
||||
"export type done = !i64;\n"
|
||||
"export fn next() (i64 | done) = { return 0; };\n",
|
||||
"package main;\n"
|
||||
"import e;\n"
|
||||
"type myres = (i64 | e.done);\n"
|
||||
"fn forward() myres = { return e.next(); };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
1, 0 },
|
||||
|
||||
/* named genuine-absence REJECT: src carries `more`, absent from dst
|
||||
* (i64|e.done) → the subset loop rejects a NAMED variant, both
|
||||
* stages. Guards the over-accept the lenient escape used to allow. */
|
||||
{ "xmod_inline_narrow",
|
||||
"package e;\n"
|
||||
"export type done = !i64;\n"
|
||||
"export type more = !i64;\n"
|
||||
"export fn next() (i64 | done | more) = { return 0; };\n",
|
||||
"package main;\n"
|
||||
"import e;\n"
|
||||
"type myres = (i64 | e.done);\n"
|
||||
"fn forward() myres = { return e.next(); };\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
0, 0 },
|
||||
};
|
||||
|
||||
/* run_xmod — write e.ww + main.ww into a fresh dir, `ww build -I dir
|
||||
* main.ww`, run dir/main. Returns the binary's exit, or -1 on build
|
||||
* failure (matches the run_build/build_should_fail convention). */
|
||||
static int
|
||||
run_xmod(const char *driver, const struct xrow *x)
|
||||
{
|
||||
char dir[] = "/tmp/tsrx_XXXXXX";
|
||||
if (mkdtemp(dir) == NULL) return -2;
|
||||
|
||||
char path[1024], cmd[4096];
|
||||
int wrote = 0;
|
||||
snprintf(path, sizeof path, "%s/e.ww", dir);
|
||||
FILE *fe = fopen(path, "wb");
|
||||
if (fe) { fputs(x->esrc, fe); fclose(fe); wrote++; }
|
||||
snprintf(path, sizeof path, "%s/main.ww", dir);
|
||||
FILE *fm = fopen(path, "wb");
|
||||
if (fm) { fputs(x->msrc, fm); fclose(fm); wrote++; }
|
||||
if (wrote != 2) {
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||
(void)runwait(cmd);
|
||||
return -2;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build -I %s %s/main.ww "
|
||||
"2>/dev/null", dir, driver, dir, dir);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) {
|
||||
snprintf(path, sizeof path, "%s/main", dir);
|
||||
got = runwait(path);
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||
(void)runwait(cmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
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 *path; 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].path, X_OK) != 0) {
|
||||
fprintf(stderr, "tagged_subset_reject: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].path, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"tagged_subset_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_fail(drivers[d].path,
|
||||
rows[i].src, 100 + i) != 0) {
|
||||
fprintf(stderr,
|
||||
"tagged_subset_reject[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int xn = (int)(sizeof xrows / sizeof xrows[0]);
|
||||
for (int i = 0; i < xn; i++) {
|
||||
total++;
|
||||
int got = run_xmod(drivers[d].path, &xrows[i]);
|
||||
if (xrows[i].expect_build) {
|
||||
if (got != xrows[i].want_exit) {
|
||||
fprintf(stderr,
|
||||
"tagged_subset_reject[%s][%s]: "
|
||||
"exit=%d want=%d\n",
|
||||
drivers[d].name, xrows[i].label,
|
||||
got, xrows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else if (got != -1) {
|
||||
fprintf(stderr,
|
||||
"tagged_subset_reject[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, xrows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* byte-id rows: w6c vs w6c_ww .s must match (rule-10). */
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!rows[i].byteid) continue;
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"tagged_subset_reject: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_subset_reject: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user