wcc/ww: nodefnptr keys the bare-ident arm on the stamped type
nodefnptr matched bare idents by NAME against the fn table, so a global var colliding with a fn leaf classified as a fn pointer — wwstage silently built what cstage rejects at link (review finding #14). Key on the stamped type; the #124 &mod.fn arm is preserved. 989_fnptrcollide_run pins both stages reject (red 1/2 pre-fix: wwstage built rc=7).
This commit is contained in:
11
Makefile
11
Makefile
@@ -253,6 +253,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_tupfieldsize_run \
|
$(BIN)/test_tupfieldsize_run \
|
||||||
$(BIN)/test_gunsigned_run \
|
$(BIN)/test_gunsigned_run \
|
||||||
$(BIN)/test_taggedidx_run \
|
$(BIN)/test_taggedidx_run \
|
||||||
|
$(BIN)/test_fnptrcollide_run \
|
||||||
$(BIN)/test_arr_ptr_global \
|
$(BIN)/test_arr_ptr_global \
|
||||||
$(BIN)/test_def_arr_infer_len \
|
$(BIN)/test_def_arr_infer_len \
|
||||||
$(BIN)/test_def_arr_len \
|
$(BIN)/test_def_arr_len \
|
||||||
@@ -689,6 +690,16 @@ $(BIN)/test_taggedidx_run: test/wcc/989_taggedidx_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
# 989_fnptrcollide_run (F7-c7, #14): a value ident whose leaf collides with
|
||||||
|
# a fn name must not be mis-folded into the fn's TEXT reloc. Build-must-fail
|
||||||
|
# on BOTH driver twins (rule-10); wwstage gated. See the test header.
|
||||||
|
$(BIN)/test_fnptrcollide_run: test/wcc/989_fnptrcollide_run.c \
|
||||||
|
$(BIN)/ww $(BIN)/ww_ww \
|
||||||
|
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
|
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \
|
$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \
|
||||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
@@ -42250,8 +42250,20 @@ fn nodefnptr(c: *cgen, ev: *node) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (opnd.kind != nkind.N_IDENT) { return false; };
|
if (opnd.kind != nkind.N_IDENT) { return false; };
|
||||||
if (fnretlookup(c, opnd.str) == nil) { return false; };
|
// #14 (F7-c7): type-keyed, mirroring cstage node_fnptr_sym
|
||||||
return true;
|
// (type_chase_named(opnd->type)->kind == TY_FN, cmd/w6c/cgen.c:15542-
|
||||||
|
// 15543). The prior name-keyed `fnretlookup(opnd.str)` matched a fn
|
||||||
|
// LEAF NAME even when the operand actually resolved to a same-named
|
||||||
|
// global/local VALUE — so `&g` for an `*i64` global `g` colliding with
|
||||||
|
// a fn `g` (e.g. a `mod.f` fn vs a `f` global) baked the fn's TEXT addr
|
||||||
|
// into the scalar slot (ww runs rc=42; cs fails loud at w6l). Reading
|
||||||
|
// the stamped operand type distinguishes the bare fn rvalue (TY_FN, the
|
||||||
|
// #34 fn-rvalue stamp) from a value ident, closing the leaf-name
|
||||||
|
// collision by construction. (F12 name-keyed overlap noted in the F7
|
||||||
|
// spec — same predicate-to-stamp shape; fixed once here.)
|
||||||
|
let ou: *tinfo = tichase(opnd.type_: *tinfo);
|
||||||
|
if (ou == nil) { return false; };
|
||||||
|
return ou.kind == tykind.TY_FN;
|
||||||
};
|
};
|
||||||
|
|
||||||
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
||||||
|
|||||||
@@ -2577,8 +2577,20 @@ fn nodefnptr(c: *cgen, ev: *node) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (opnd.kind != nkind.N_IDENT) { return false; };
|
if (opnd.kind != nkind.N_IDENT) { return false; };
|
||||||
if (fnretlookup(c, opnd.str) == nil) { return false; };
|
// #14 (F7-c7): type-keyed, mirroring cstage node_fnptr_sym
|
||||||
return true;
|
// (type_chase_named(opnd->type)->kind == TY_FN, cmd/w6c/cgen.c:15542-
|
||||||
|
// 15543). The prior name-keyed `fnretlookup(opnd.str)` matched a fn
|
||||||
|
// LEAF NAME even when the operand actually resolved to a same-named
|
||||||
|
// global/local VALUE — so `&g` for an `*i64` global `g` colliding with
|
||||||
|
// a fn `g` (e.g. a `mod.f` fn vs a `f` global) baked the fn's TEXT addr
|
||||||
|
// into the scalar slot (ww runs rc=42; cs fails loud at w6l). Reading
|
||||||
|
// the stamped operand type distinguishes the bare fn rvalue (TY_FN, the
|
||||||
|
// #34 fn-rvalue stamp) from a value ident, closing the leaf-name
|
||||||
|
// collision by construction. (F12 name-keyed overlap noted in the F7
|
||||||
|
// spec — same predicate-to-stamp shape; fixed once here.)
|
||||||
|
let ou: *tinfo = tichase(opnd.type_: *tinfo);
|
||||||
|
if (ou == nil) { return false; };
|
||||||
|
return ou.kind == tykind.TY_FN;
|
||||||
};
|
};
|
||||||
|
|
||||||
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
||||||
|
|||||||
@@ -42250,8 +42250,20 @@ fn nodefnptr(c: *cgen, ev: *node) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (opnd.kind != nkind.N_IDENT) { return false; };
|
if (opnd.kind != nkind.N_IDENT) { return false; };
|
||||||
if (fnretlookup(c, opnd.str) == nil) { return false; };
|
// #14 (F7-c7): type-keyed, mirroring cstage node_fnptr_sym
|
||||||
return true;
|
// (type_chase_named(opnd->type)->kind == TY_FN, cmd/w6c/cgen.c:15542-
|
||||||
|
// 15543). The prior name-keyed `fnretlookup(opnd.str)` matched a fn
|
||||||
|
// LEAF NAME even when the operand actually resolved to a same-named
|
||||||
|
// global/local VALUE — so `&g` for an `*i64` global `g` colliding with
|
||||||
|
// a fn `g` (e.g. a `mod.f` fn vs a `f` global) baked the fn's TEXT addr
|
||||||
|
// into the scalar slot (ww runs rc=42; cs fails loud at w6l). Reading
|
||||||
|
// the stamped operand type distinguishes the bare fn rvalue (TY_FN, the
|
||||||
|
// #34 fn-rvalue stamp) from a value ident, closing the leaf-name
|
||||||
|
// collision by construction. (F12 name-keyed overlap noted in the F7
|
||||||
|
// spec — same predicate-to-stamp shape; fixed once here.)
|
||||||
|
let ou: *tinfo = tichase(opnd.type_: *tinfo);
|
||||||
|
if (ou == nil) { return false; };
|
||||||
|
return ou.kind == tykind.TY_FN;
|
||||||
};
|
};
|
||||||
|
|
||||||
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
// tuplerowfoldable — validate every cast-peeled element of `rhs` (an
|
||||||
|
|||||||
131
test/wcc/989_fnptrcollide_run.c
Normal file
131
test/wcc/989_fnptrcollide_run.c
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 989_fnptrcollide_run — F7-c7 (#14): nodefnptr must be type-keyed, not
|
||||||
|
* name-keyed, so a value ident whose LEAF collides with a fn name is not
|
||||||
|
* mis-folded into the fn's TEXT reloc.
|
||||||
|
*
|
||||||
|
* THE BUG (cat-A silent miscompile, gate-blind): nodefnptr
|
||||||
|
* (selfhost/cmd/wcc/cgen.ww) decided whether `&x` (in a static-init / DATAR
|
||||||
|
* fold context) was the address-of a top-level fn by NAME — `fnretlookup(
|
||||||
|
* c, x.str) != nil`. So `&slot` for a data global `slot: i64` that shares a
|
||||||
|
* leaf with a fn `slot` (e.g. an imported `bar.slot`) matched the fn and
|
||||||
|
* folded into a DATAR reloc to the fn's TEXT symbol instead of the data
|
||||||
|
* symbol. cstage is type-keyed (node_fnptr_sym: type_chase_named(opnd->
|
||||||
|
* type)->kind == TY_FN, cmd/w6c/cgen.c:15542) — `slot`'s stamped type is
|
||||||
|
* i64, not TY_FN, so it does NOT fold the reloc and the build errors loud.
|
||||||
|
* Pre-fix wwstage silently produced a binary (the cat-A divergence: cs
|
||||||
|
* loud-fails, ww builds). THE FIX: read the stamped operand type
|
||||||
|
* (opnd.type_ chased == TY_FN), aligning wwstage UP — a non-fn operand can
|
||||||
|
* never fold to a fn reloc, closing the leaf-name collision by construction.
|
||||||
|
*
|
||||||
|
* This shape is gate-blind (the corpus never collides a data-global leaf
|
||||||
|
* with a fn name on the &-fold path); only this row catches it. The
|
||||||
|
* conversion's neutrality on the corpus's REAL &fn static-inits (the
|
||||||
|
* #117/#119 reloc machinery) is proven separately by the c7 self-compile
|
||||||
|
* byte-id bind (B1/B2 zero-move) — see /tmp/implf7_result.txt.
|
||||||
|
*
|
||||||
|
* Assertion: the construction must FAIL TO BUILD on BOTH stages (cstage
|
||||||
|
* already rejects; wwstage now rejects too — rule-10 align-up). Pre-fix
|
||||||
|
* wwstage BUILT it (rc=0) — the row was RED on the pre-c7 binary.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build_must_fail — write a bar/ module (fn `slot`) and a main.ww that
|
||||||
|
* declares a data global `slot: i64` plus `let fp: *i64 = &slot`, then
|
||||||
|
* `drv build -I bar main.ww`. Returns 0 when the build correctly FAILS
|
||||||
|
* (the leaf-name collision no longer diverts &slot into the fn reloc),
|
||||||
|
* non-zero when it wrongly built. */
|
||||||
|
static int
|
||||||
|
build_must_fail(const char *drv, int tag)
|
||||||
|
{
|
||||||
|
int pid = getpid();
|
||||||
|
char dir[96], bard[160], p[224], cmd[1024], rm[256];
|
||||||
|
snprintf(dir, sizeof dir, "/tmp/fnpc_%d_%d", pid, tag);
|
||||||
|
snprintf(bard, sizeof bard, "%s/bar", dir);
|
||||||
|
mkdir(dir, 0755);
|
||||||
|
mkdir(bard, 0755);
|
||||||
|
|
||||||
|
snprintf(p, sizeof p, "%s/bar.ww", bard);
|
||||||
|
FILE *f = fopen(p, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs("package bar;\n"
|
||||||
|
"export fn slot() i64 = { return 99; };\n", f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
snprintf(p, sizeof p, "%s/main.ww", dir);
|
||||||
|
f = fopen(p, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs("package main;\n"
|
||||||
|
"import bar;\n"
|
||||||
|
"let slot: i64 = 7;\n"
|
||||||
|
"let fp: *i64 = &slot;\n"
|
||||||
|
"export fn main() int = { return (*fp): int; };\n", f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && %s build -I bar main.ww >/dev/null 2>&1", dir, drv);
|
||||||
|
int rc = runwait(cmd);
|
||||||
|
|
||||||
|
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
||||||
|
runwait(rm);
|
||||||
|
|
||||||
|
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
|
||||||
|
total++;
|
||||||
|
if (build_must_fail(cdrv, 1) != 0) {
|
||||||
|
fprintf(stderr, "fnptrcollide_run[cstage]: built ok, expected "
|
||||||
|
"the leaf-name collision to be rejected\n");
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
if (access(wdrv, X_OK) == 0) { /* wwstage gated */
|
||||||
|
total++;
|
||||||
|
if (build_must_fail(wdrv, 2) != 0) {
|
||||||
|
fprintf(stderr, "fnptrcollide_run[wwstage]: built ok, "
|
||||||
|
"expected reject (#14 — &slot mis-folded to the "
|
||||||
|
"fn TEXT reloc by the name-keyed nodefnptr)\n");
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "fnptrcollide_run: skip wwstage (no %s)\n", wdrv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "fnptrcollide_run: %d/%d checks failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("fnptrcollide_run: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user