/* * 851_index_int_reject (catB-17) — cstage and wwstage LOUD-REJECT an * N_INDEX whose index operand is not an integer (float, str, pointer, * bool). cstage already rejected at cmd/wcc/check.c:1491-1495 (the * N_INDEX arm: `if (idx != ty_err && !type_isint(idx)) err(...)`). * wwstage silently ACCEPTED — indexresult() computed the index type and * dropped it (`let _idx = exprtype(...)`), so `xs[f64]` / `xs[str]` / * `xs[*T]` / `xs[bool]` compiled. The fix derives the index tinfo and * gates syntax.typeisint (selfhost/cmd/wcc/check.ww indexresult), the * rule-10 twin of cstage's once-per-N_INDEX site, sibling to * validateenummembers (catB-2) / validatestructfields (catB-7/14). * * The ALIAS-INT accept row is load-bearing: typeisint is the tinfo * chaser (follows TY_NAMED.under / TY_ENUM.sub), so `type ix = i32` * indexes clean. The AST-keyed isinttypeast would falsely reject it — * a vacuous plain-int-only test would not catch that. * * Byte-id: pure diagnostic, no n.type_ / checker-state mutation beyond * c.errs; the valid corpus indexes only by integer types, so codegen of * every valid program is unchanged → cstage == wwstage byte-id holds. * * reject row | index operand type | expect * -------------+----------------------+-------- * by_float | f64 | REJECT * by_str | str | REJECT * by_ptr | *i32 | REJECT * by_bool | bool | REJECT * * accept row | index operand type | expect * -------------+----------------------+-------- * by_int | int | BUILD, run 20 * by_uint | uint | BUILD, run 30 * by_enum | named enum value | BUILD, run 20 * by_alias_int | type ix = i32 | BUILD, run 30 (load-bearing) */ #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; } static void outbin(char *dst, size_t n, const char *tmpdir, const char *src) { const char *base = strrchr(src, '/'); base = base ? base + 1 : src; snprintf(dst, n, "%s/%s", tmpdir, base); char *dot = strrchr(dst, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } static int filehas(const char *path, const char *needle) { char buf[8192]; FILE *f = fopen(path, "rb"); if (!f) return 0; size_t n = fread(buf, 1, sizeof buf - 1, f); fclose(f); buf[n] = '\0'; return strstr(buf, needle) != NULL; } /* A reject row passes only when the build FAILS *and* the stage's clean * diagnostic appears; a crash (segfault) wraps as a nonzero "w6c failed" * with no diagnostic, so the substring check distinguishes it (#20). */ static int build_should_fail(const char *driver, const char *label, const char *src, const char *diag, int i) { char s[64], tmpdir[64], cmd[1024], bin[128], errf[64]; snprintf(s, sizeof s, "/tmp/idxr_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/idxr_%d_d_%d", getpid(), i); snprintf(errf, sizeof errf, "/tmp/idxr_%d_e_%d.txt", 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 >/dev/null 2>%s", tmpdir, driver, s, errf); int rc = runwait(cmd); int hasmsg = filehas(errf, diag); outbin(bin, sizeof bin, tmpdir, s); unlink(s); unlink(bin); unlink(errf); rmdir(tmpdir); if (rc == 0) { fprintf(stderr, "idxr[%s][%s]: built ok, expected a reject\n", driver, label); return -1; } if (!hasmsg) { fprintf(stderr, "idxr[%s][%s]: nonzero exit but missing " "diagnostic \"%s\" (a crash, not a clean reject)\n", driver, label, diag); return -1; } return 0; } static int run_build(const char *driver, const char *label, const char *src, int i) { char s[64], tmpdir[64], cmd[1024], bin[128]; snprintf(s, sizeof s, "/tmp/idxr_ok_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/idxr_ok_%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); if (runwait(cmd) != 0) { fprintf(stderr, "idxr[%s][%s]: build failed (integer index " "must compile)\n", driver, label); unlink(s); rmdir(tmpdir); return -1; } outbin(bin, sizeof bin, tmpdir, s); int got = runwait(bin); unlink(s); unlink(bin); rmdir(tmpdir); return got; } struct rejrow { const char *label; const char *src; const char *diag; }; static const struct rejrow reject_rows[] = { { "by_float", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let f: f64 = 1.0;\n" " return a[f];\n" "};\n", "index must be integer" }, { "by_str", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let s: str = \"x\";\n" " return a[s];\n" "};\n", "index must be integer" }, { "by_ptr", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let p: *i32 = nil;\n" " return a[p];\n" "};\n", "index must be integer" }, { "by_bool", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let b: bool = true;\n" " return a[b];\n" "};\n", "index must be integer" }, }; struct okrow { const char *label; const char *src; int want; }; static const struct okrow ok_rows[] = { { "by_int", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let i: int = 1;\n" " return a[i];\n" "};\n", 20 }, { "by_uint", "package main;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let i: uint = 2;\n" " return a[i];\n" "};\n", 30 }, { "by_enum", "package main;\n" "type col = enum { A, B, C };\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " return a[col.B];\n" "};\n", 20 }, { "by_alias_int", "package main;\n" "type ix = i32;\n" "fn main() i32 = {\n" " let a: [3]i32 = [10i32, 20i32, 30i32];\n" " let i: ix = 2;\n" " return a[i];\n" "};\n", 30 }, }; 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 nrej = (int)(sizeof reject_rows / sizeof reject_rows[0]); int nok = (int)(sizeof ok_rows / sizeof ok_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, "idxr: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < nrej; i++) { total++; if (build_should_fail(drivers[d].path, reject_rows[i].label, reject_rows[i].src, reject_rows[i].diag, d * 100 + i) != 0) fail++; } for (int i = 0; i < nok; i++) { total++; int got = run_build(drivers[d].path, ok_rows[i].label, ok_rows[i].src, d * 100 + i); if (got != ok_rows[i].want) { fprintf(stderr, "idxr[%s][%s]: exit=%d want=%d\n", drivers[d].name, ok_rows[i].label, got, ok_rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "idxr: %d/%d fixtures failed\n", fail, total); return 1; } printf("idxr: %d/%d ok\n", total, total); return 0; }