/* * 989_dotbasehijack_run — F8-c4 (report-item #20): indexing an `[N]T` field * of a module-GLOBAL struct (`gs.fld[i]`) must address gs's field, not be * hijacked by an unrelated module-global that shares the FIELD's name. * * THE BUG (cat-A silent miscompile, align-UP): cgenexpr.ww dotbaseaddr's * #128b probe (for a module-qualified `mod.arr` array base) did * `letvartnode(base.str)` — base.str is the DOT's FIELD name — whenever the * inner ident wasn't a local. For `gs.fld` with `gs` a module-global struct, * the inner gs is global (no local slot) so the probe fired and, finding an * unrelated module-global `fld: [N]T`, emitted `LEAQ fld(SB)` — addressing * the wrong symbol. cstage cg_dotbase_addr gates the #128b probe on * inner->type == NULL || ty_err (cgen.c:2109 — a module qualifier has no * struct type), so a typed-struct inner skips it and resolves the field off * the struct (LEAQ gs(SB) + foff). 990-997 never hit a field-name/global * collision, so the gate stayed green — a runtime row is the net. * * THE FIX: gate the wwstage #128b probe on tichase(inner.type_) == nil || * TY_ERR (the untyped module-qualifier case); a typed struct inner falls to * the #249 isglobal struct path. align ww UP; the .s is byte-identical. * * Rows (build+run on cstage `ww` and wwstage `ww_ww`; rule-10 — agree+hit): * row | shape | want * -----------+------------------------------------------------+----- * collide_0 | gs:{pad,fld:[2]i64}; gs.fld=[7,8]; global fld= | 7 [#20: * | [9,10]; gs.fld[0] | pre-fix ww=9] * collide_1 | same; gs.fld[1] | 8 [#20: * | | pre-fix ww=10] * no_collide | gs2:{pad,arr:[2]i64}=[5,6]; NO global arr; | 5 (control: * | gs2.arr[0] | typed inner) */ #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_exit; }; #define PRELUDE \ "package main;\n" \ "type s = struct { pad: i64, fld: [2]i64 };\n" \ "type s2 = struct { pad: i64, arr: [2]i64 };\n" \ "let gs: s = s { pad = 0, fld = [7, 8] };\n" \ "let gs2: s2 = s2 { pad = 0, arr = [5, 6] };\n" \ "let fld: [2]i64 = [9, 10];\n" static const struct row rows[] = { { "collide_0", PRELUDE "export fn main() int = { return gs.fld[0]: int; };\n", 7 }, { "collide_1", PRELUDE "export fn main() int = { return gs.fld[1]: int; };\n", 8 }, /* typed-struct inner with NO name collision — the probe-skip must not * break the ordinary global-struct array-field index. */ { "no_collide", PRELUDE "export fn main() int = { return gs2.arr[0]: int; };\n", 5 }, }; /* 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/dbh_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/dbh_%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; } 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 *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, "dotbasehijack_run: skip %s (no %s)\n", drivers[d].name, drivers[d].drv); continue; } for (int i = 0; i < n; i++) { total++; int got = run_build(drivers[d].drv, &rows[i], i); if (got != rows[i].want_exit) { fprintf(stderr, "dotbasehijack_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want_exit); fail++; } } } if (fail) { fprintf(stderr, "dotbasehijack_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("dotbasehijack_run: %d/%d ok\n", total, total); return 0; }