wcc,ww: prepend synth use test; user fn run coexists with runner (M4 E2, #80)
The -T harness synthesized `use test;` after name-binding, so the lib/test runner run keyed the bare scope and collided with a user-defined bare fn run — a spurious "duplicate fn run" reject (the E1 tolerance seam). Prepending the synth use before binding keys the runner as test.run in the test module namespace, distinct from the user bare run; the two coexist. Hare-faithful: the runner is its own test module (ref/hare/test/+test.ha:97). Inverts attest_userrun.ww from the #23-mandated reject to a coexist fixture; gate asserts exactly 1 TEXT run + 1 TEXT test.run on the -T asm (distinct symbols, not a dead-dup). Closes #80.
This commit is contained in:
@@ -2856,6 +2856,24 @@ check_file(Checker *c, Node *file)
|
|||||||
if (file == NULL || file->kind != N_FILE) return;
|
if (file == NULL || file->kind != N_FILE) return;
|
||||||
c->file = file;
|
c->file = file;
|
||||||
|
|
||||||
|
/* #80: under -T, PREPEND the synth `use test;` BEFORE pass 1 so decl_mod
|
||||||
|
* (called per-decl during install) sees a matching `use test` when it
|
||||||
|
* keys lib/test's `run` — keying it under module="test", not "". A pure
|
||||||
|
* ORDER fix: the synth N_USE was appended AFTER install (below), too late
|
||||||
|
* for decl_mod, so `run` keyed under "" — leaving the synth `test.run`
|
||||||
|
* ty_err (the E1 bridge) and colliding with a user root `fn run` (also
|
||||||
|
* module=""). Decoupled from cgen: the symbol mangle keys off d->module
|
||||||
|
* (the //ww:module directive, mod_collect), NOT this scope keying — cgen
|
||||||
|
* already emits the correct CALL test.run. wwstage twin in check.ww. */
|
||||||
|
if (c->is_test) {
|
||||||
|
Node *usenode = newnode(c->a, N_USE, file->pos);
|
||||||
|
usenode->str = "test";
|
||||||
|
usenode->strlen = 4;
|
||||||
|
usenode->usepath = "test";
|
||||||
|
usenode->next = file->list;
|
||||||
|
file->list = usenode;
|
||||||
|
}
|
||||||
|
|
||||||
/* pass 1: install names (types first, then defs/fns).
|
/* pass 1: install names (types first, then defs/fns).
|
||||||
* For self-referential types we install the named-type placeholder
|
* For self-referential types we install the named-type placeholder
|
||||||
* BEFORE resolving its body; the body may legitimately mention
|
* BEFORE resolving its body; the body may legitimately mention
|
||||||
@@ -3196,19 +3214,12 @@ check_file(Checker *c, Node *file)
|
|||||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||||
ret->lhs = call;
|
ret->lhs = call;
|
||||||
body->list = ret;
|
body->list = ret;
|
||||||
/* synth `use test;` so the N_DOT base ident binds as a
|
/* #80: the synth `use test;` (the N_DOT base qualifier +
|
||||||
* module qualifier (SK_USE) and use_path maps `test` to
|
* the use_path source mapping `test`→its path) is now
|
||||||
* its import path. Mirror the typedecl-pass N_USE install
|
* PREPENDED before pass 1 at the top of check_file, so
|
||||||
* (check.c:2892). Appended to file->list below; emits no
|
* decl_mod keys lib/test's `run` under "test" and the synth
|
||||||
* asm (cgen keys off module-tagged decls). */
|
* `test.run` type-resolves. Pass 1's N_USE arm installs it
|
||||||
Node *usenode = newnode(c->a, N_USE, fp);
|
* (SK_USE). */
|
||||||
usenode->str = "test";
|
|
||||||
usenode->strlen = 4;
|
|
||||||
usenode->usepath = "test";
|
|
||||||
scope_define(c->cur, "test", SK_USE, NULL, usenode);
|
|
||||||
Node *ul = file->list;
|
|
||||||
if (ul == NULL) file->list = usenode;
|
|
||||||
else { while (ul->next) ul = ul->next; ul->next = usenode; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Node *m = newnode(c->a, N_FNDECL, fp);
|
Node *m = newnode(c->a, N_FNDECL, fp);
|
||||||
|
|||||||
@@ -53,13 +53,14 @@ import os;
|
|||||||
// lib/test symbol isolation, every -T build flat-bundles this module into
|
// lib/test symbol isolation, every -T build flat-bundles this module into
|
||||||
// the SAME bare-leaf scope as the test's own modules, so an un-prefixed
|
// the SAME bare-leaf scope as the test's own modules, so an un-prefixed
|
||||||
// `puts`/`runone` collides with a same-named module-private fn (lib/dirs
|
// `puts`/`runone` collides with a same-named module-private fn (lib/dirs
|
||||||
// and lib/temp both ship a private `puts(off, s)`). `run` stays the bound
|
// and lib/temp both ship a private `puts(off, s)`). `run` is the bound
|
||||||
// runner name (the synth's callee). A user `fn run` (or `const __wwtests`)
|
// runner name (the synth's callee), reached module-qualified as `test.run`:
|
||||||
// in a @test unit collides with the synth: cstage — the shipping `ww test`
|
// the -T synth prepends `use test;` before name-binding (#80), so this
|
||||||
// path — rejects it loudly ("duplicate fn run" / type mismatch, rc!=0).
|
// runner keys into the `test` module namespace — a symbol distinct from any
|
||||||
// wwstage TOLERATES the duplicate (#23, pre-existing rule-10 gap: cstage
|
// bare user `fn run` in the @test unit. The two COEXIST: `run` and
|
||||||
// rejects a duplicate fn, wwstage does not), so the collision is loud on
|
// `test.run` are separate symbols (ref/hare/test/+test.ha:97 — the runner
|
||||||
// the reference stage but silent on wwstage until #23 lands.
|
// is its own `test` module), so a user `fn run` is no longer a duplicate and
|
||||||
|
// both stages accept it under @test/-T.
|
||||||
export fn run(tests: [](str, *fn() void)) i32 = {
|
export fn run(tests: [](str, *fn() void)) i32 = {
|
||||||
let av: []str = os.args();
|
let av: []str = os.args();
|
||||||
let nfail: i32 = 0;
|
let nfail: i32 = 0;
|
||||||
|
|||||||
@@ -14232,14 +14232,15 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// Module-qualified callee whose leaf isn't scope-keyed
|
// Module-qualified callee whose leaf isn't scope-keyed
|
||||||
// under its module: align to cstage, which stamps ty_err
|
// under its module: align to cstage, which stamps ty_err
|
||||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
// 1843; rule-10). This now tolerates only a genuinely-
|
||||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
// extern leaf (raw w6c on a single module-qualified file
|
||||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
// with no driver concat), the module-qualified arm of #27.
|
||||||
// vs declmod-for-scope keying split, the module-qualified
|
// The -T synth's `test.run` NO LONGER lands here: #80
|
||||||
// arm of #27. cgen still emits the correct CALL test.run
|
// PREPENDS the synth `use test;` before Pass 1, so declmod
|
||||||
// from run's `//ww:module test` directive. Load-bearing
|
// keys lib/test's `run` under "test" (not "") and the synth
|
||||||
// until #80 module-keys run under sep so the synth call
|
// `test.run` type-resolves — the E1 bridge is closed. cgen
|
||||||
// type-resolves; NOT an M4-transient.
|
// keys the CALL off run's `//ww:module test` directive
|
||||||
|
// either way, so the resolution change is asm-neutral.
|
||||||
if (s == nil) {
|
if (s == nil) {
|
||||||
e.type_ = c.tc.tyerr: *void;
|
e.type_ = c.tc.tyerr: *void;
|
||||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||||
@@ -17108,6 +17109,24 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
if (file.kind != syntax.nkind.N_FILE) { return; };
|
if (file.kind != syntax.nkind.N_FILE) { return; };
|
||||||
c.file = file;
|
c.file = file;
|
||||||
|
|
||||||
|
// #80: under -T, PREPEND the synth `use test;` BEFORE Pass 1 so declmod
|
||||||
|
// (called per-decl during install) sees a matching `use test` when it
|
||||||
|
// keys lib/test's `run` — keying it under mod="test", not "". This is a
|
||||||
|
// pure ORDER fix: the synth N_USE was appended AFTER install (below),
|
||||||
|
// too late for declmod, so `run` keyed under "" — leaving the synth
|
||||||
|
// `test.run` ty_err (the E1 bridge) and colliding with a user root
|
||||||
|
// `fn run` (also mod=""). Decoupled from cgen: the symbol mangle keys
|
||||||
|
// off d.module (the //ww:module directive, cgen mod_collect), NOT this
|
||||||
|
// scope keying — cgen already emits the correct CALL test.run. Twin of
|
||||||
|
// cstage cmd/wcc/check.c.
|
||||||
|
if (c.istest != 0) {
|
||||||
|
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
|
||||||
|
usenode.str = "test";
|
||||||
|
usenode.usepath = "test";
|
||||||
|
usenode.next = file.list;
|
||||||
|
file.list = usenode;
|
||||||
|
};
|
||||||
|
|
||||||
// Pass 1: install all top-level names.
|
// Pass 1: install all top-level names.
|
||||||
let d: *syntax.node = file.list;
|
let d: *syntax.node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -17308,17 +17327,11 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||||
ret.lhs = call;
|
ret.lhs = call;
|
||||||
body.list = ret;
|
body.list = ret;
|
||||||
// synth `use test;` so the N_DOT base binds as a module
|
// #80: the synth `use test;` (the N_DOT base qualifier + the
|
||||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
// usepathfor source mapping `test`→its path) is now PREPENDED
|
||||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
// before Pass 1 at the top of checkfile, so declmod keys
|
||||||
// file.list below; emits no asm.
|
// lib/test's `run` under "test" and the synth `test.run`
|
||||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
// type-resolves. It is installed (SK_USE) by Pass 1's N_USE arm.
|
||||||
usenode.str = "test";
|
|
||||||
usenode.usepath = "test";
|
|
||||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
|
||||||
let ul: *syntax.node = file.list;
|
|
||||||
if (ul == nil) { file.list = usenode; }
|
|
||||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
|
||||||
};
|
};
|
||||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
|
|||||||
@@ -3489,14 +3489,15 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// Module-qualified callee whose leaf isn't scope-keyed
|
// Module-qualified callee whose leaf isn't scope-keyed
|
||||||
// under its module: align to cstage, which stamps ty_err
|
// under its module: align to cstage, which stamps ty_err
|
||||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
// 1843; rule-10). This now tolerates only a genuinely-
|
||||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
// extern leaf (raw w6c on a single module-qualified file
|
||||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
// with no driver concat), the module-qualified arm of #27.
|
||||||
// vs declmod-for-scope keying split, the module-qualified
|
// The -T synth's `test.run` NO LONGER lands here: #80
|
||||||
// arm of #27. cgen still emits the correct CALL test.run
|
// PREPENDS the synth `use test;` before Pass 1, so declmod
|
||||||
// from run's `//ww:module test` directive. Load-bearing
|
// keys lib/test's `run` under "test" (not "") and the synth
|
||||||
// until #80 module-keys run under sep so the synth call
|
// `test.run` type-resolves — the E1 bridge is closed. cgen
|
||||||
// type-resolves; NOT an M4-transient.
|
// keys the CALL off run's `//ww:module test` directive
|
||||||
|
// either way, so the resolution change is asm-neutral.
|
||||||
if (s == nil) {
|
if (s == nil) {
|
||||||
e.type_ = c.tc.tyerr: *void;
|
e.type_ = c.tc.tyerr: *void;
|
||||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||||
@@ -6365,6 +6366,24 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
if (file.kind != syntax.nkind.N_FILE) { return; };
|
if (file.kind != syntax.nkind.N_FILE) { return; };
|
||||||
c.file = file;
|
c.file = file;
|
||||||
|
|
||||||
|
// #80: under -T, PREPEND the synth `use test;` BEFORE Pass 1 so declmod
|
||||||
|
// (called per-decl during install) sees a matching `use test` when it
|
||||||
|
// keys lib/test's `run` — keying it under mod="test", not "". This is a
|
||||||
|
// pure ORDER fix: the synth N_USE was appended AFTER install (below),
|
||||||
|
// too late for declmod, so `run` keyed under "" — leaving the synth
|
||||||
|
// `test.run` ty_err (the E1 bridge) and colliding with a user root
|
||||||
|
// `fn run` (also mod=""). Decoupled from cgen: the symbol mangle keys
|
||||||
|
// off d.module (the //ww:module directive, cgen mod_collect), NOT this
|
||||||
|
// scope keying — cgen already emits the correct CALL test.run. Twin of
|
||||||
|
// cstage cmd/wcc/check.c.
|
||||||
|
if (c.istest != 0) {
|
||||||
|
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
|
||||||
|
usenode.str = "test";
|
||||||
|
usenode.usepath = "test";
|
||||||
|
usenode.next = file.list;
|
||||||
|
file.list = usenode;
|
||||||
|
};
|
||||||
|
|
||||||
// Pass 1: install all top-level names.
|
// Pass 1: install all top-level names.
|
||||||
let d: *syntax.node = file.list;
|
let d: *syntax.node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -6565,17 +6584,11 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||||
ret.lhs = call;
|
ret.lhs = call;
|
||||||
body.list = ret;
|
body.list = ret;
|
||||||
// synth `use test;` so the N_DOT base binds as a module
|
// #80: the synth `use test;` (the N_DOT base qualifier + the
|
||||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
// usepathfor source mapping `test`→its path) is now PREPENDED
|
||||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
// before Pass 1 at the top of checkfile, so declmod keys
|
||||||
// file.list below; emits no asm.
|
// lib/test's `run` under "test" and the synth `test.run`
|
||||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
// type-resolves. It is installed (SK_USE) by Pass 1's N_USE arm.
|
||||||
usenode.str = "test";
|
|
||||||
usenode.usepath = "test";
|
|
||||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
|
||||||
let ul: *syntax.node = file.list;
|
|
||||||
if (ul == nil) { file.list = usenode; }
|
|
||||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
|
||||||
};
|
};
|
||||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
|
|||||||
@@ -14232,14 +14232,15 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
// Module-qualified callee whose leaf isn't scope-keyed
|
// Module-qualified callee whose leaf isn't scope-keyed
|
||||||
// under its module: align to cstage, which stamps ty_err
|
// under its module: align to cstage, which stamps ty_err
|
||||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
// 1843; rule-10). This now tolerates only a genuinely-
|
||||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
// extern leaf (raw w6c on a single module-qualified file
|
||||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
// with no driver concat), the module-qualified arm of #27.
|
||||||
// vs declmod-for-scope keying split, the module-qualified
|
// The -T synth's `test.run` NO LONGER lands here: #80
|
||||||
// arm of #27. cgen still emits the correct CALL test.run
|
// PREPENDS the synth `use test;` before Pass 1, so declmod
|
||||||
// from run's `//ww:module test` directive. Load-bearing
|
// keys lib/test's `run` under "test" (not "") and the synth
|
||||||
// until #80 module-keys run under sep so the synth call
|
// `test.run` type-resolves — the E1 bridge is closed. cgen
|
||||||
// type-resolves; NOT an M4-transient.
|
// keys the CALL off run's `//ww:module test` directive
|
||||||
|
// either way, so the resolution change is asm-neutral.
|
||||||
if (s == nil) {
|
if (s == nil) {
|
||||||
e.type_ = c.tc.tyerr: *void;
|
e.type_ = c.tc.tyerr: *void;
|
||||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||||
@@ -17108,6 +17109,24 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
if (file.kind != syntax.nkind.N_FILE) { return; };
|
if (file.kind != syntax.nkind.N_FILE) { return; };
|
||||||
c.file = file;
|
c.file = file;
|
||||||
|
|
||||||
|
// #80: under -T, PREPEND the synth `use test;` BEFORE Pass 1 so declmod
|
||||||
|
// (called per-decl during install) sees a matching `use test` when it
|
||||||
|
// keys lib/test's `run` — keying it under mod="test", not "". This is a
|
||||||
|
// pure ORDER fix: the synth N_USE was appended AFTER install (below),
|
||||||
|
// too late for declmod, so `run` keyed under "" — leaving the synth
|
||||||
|
// `test.run` ty_err (the E1 bridge) and colliding with a user root
|
||||||
|
// `fn run` (also mod=""). Decoupled from cgen: the symbol mangle keys
|
||||||
|
// off d.module (the //ww:module directive, cgen mod_collect), NOT this
|
||||||
|
// scope keying — cgen already emits the correct CALL test.run. Twin of
|
||||||
|
// cstage cmd/wcc/check.c.
|
||||||
|
if (c.istest != 0) {
|
||||||
|
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, file.file, file.line, file.col);
|
||||||
|
usenode.str = "test";
|
||||||
|
usenode.usepath = "test";
|
||||||
|
usenode.next = file.list;
|
||||||
|
file.list = usenode;
|
||||||
|
};
|
||||||
|
|
||||||
// Pass 1: install all top-level names.
|
// Pass 1: install all top-level names.
|
||||||
let d: *syntax.node = file.list;
|
let d: *syntax.node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -17308,17 +17327,11 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||||
ret.lhs = call;
|
ret.lhs = call;
|
||||||
body.list = ret;
|
body.list = ret;
|
||||||
// synth `use test;` so the N_DOT base binds as a module
|
// #80: the synth `use test;` (the N_DOT base qualifier + the
|
||||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
// usepathfor source mapping `test`→its path) is now PREPENDED
|
||||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
// before Pass 1 at the top of checkfile, so declmod keys
|
||||||
// file.list below; emits no asm.
|
// lib/test's `run` under "test" and the synth `test.run`
|
||||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
// type-resolves. It is installed (SK_USE) by Pass 1's N_USE arm.
|
||||||
usenode.str = "test";
|
|
||||||
usenode.usepath = "test";
|
|
||||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
|
||||||
let ul: *syntax.node = file.list;
|
|
||||||
if (ul == nil) { file.list = usenode; }
|
|
||||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
|
||||||
};
|
};
|
||||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
@@ -282,17 +283,49 @@ accept(const char *bin, const char *comp, const char *fixture, const char *what)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* collide_run — a @test unit defining a user `fn run` collides with
|
/* count_text — number of `TEXT <sym>,` directive lines in an asm file
|
||||||
* lib/test's bound runner once `<drv> test -c` bundles it; `<comp> -T` of
|
* (Plan-9 `TEXT name,$frame`), -1 on open failure. A COUNT (not presence)
|
||||||
* the combined must loud-reject the duplicate (#23). Pre-fix wwstage built
|
* is what distinguishes the #84 dead-dup: the bug yields 0x `TEXT run` +
|
||||||
* a binary that called the user run and silently skipped every @test. */
|
* 2x `TEXT test.run`, which a mere presence check would miss. */
|
||||||
static int
|
static int
|
||||||
collide_run(const char *bin, const char *comp, const char *drv)
|
count_text(const char *path, const char *sym)
|
||||||
|
{
|
||||||
|
char want[128];
|
||||||
|
snprintf(want, sizeof want, "TEXT %s,", sym);
|
||||||
|
size_t wlen = strlen(want);
|
||||||
|
FILE *f = fopen(path, "r");
|
||||||
|
if (f == NULL) return -1;
|
||||||
|
char line[8192];
|
||||||
|
int n = 0;
|
||||||
|
while (fgets(line, sizeof line, f) != NULL)
|
||||||
|
if (strncmp(line, want, wlen) == 0) n++;
|
||||||
|
fclose(f);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* coexist_run — a @test unit that ALSO defines a user `fn run` must
|
||||||
|
* COEXIST with lib/test's bound runner, not collide. Post-#80 the synth
|
||||||
|
* `use test;` is prepended before binding, so lib/test's `run` keys under
|
||||||
|
* "test" (not "") and no longer duplicate-collides with the user's bare
|
||||||
|
* `run` (which mangles bare via #84). `<comp> -T` of the `<drv> test -c`
|
||||||
|
* combined now ACCEPTS; the -T asm carries EXACTLY 1 `TEXT run` (user,
|
||||||
|
* bare) + 1 `TEXT test.run` (lib runner) — the static-label proof that the
|
||||||
|
* user run is distinct in the real @test/-T path where #84 lives; then,
|
||||||
|
* assembled, linked and run, the synth entry exits 0 (the user `run` did
|
||||||
|
* not hijack the runner and the @test passes). Pre-#80 this loud-rejected
|
||||||
|
* "duplicate fn run". */
|
||||||
|
static int
|
||||||
|
coexist_run(const char *bin, const char *comp, const char *drv)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char stem[256], comb[300], cmd[4096];
|
char stem[256], comb[300], asmf[320], obj[320], exe[320];
|
||||||
|
char rt[1024], cmd[4096];
|
||||||
snprintf(stem, sizeof stem, "/tmp/at910cr_%s_%d", comp, pid);
|
snprintf(stem, sizeof stem, "/tmp/at910cr_%s_%d", comp, pid);
|
||||||
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
|
snprintf(asmf, sizeof asmf, "%s.run.s", stem);
|
||||||
|
snprintf(obj, sizeof obj, "%s.run.o", stem);
|
||||||
|
snprintf(exe, sizeof exe, "%s.exe", stem);
|
||||||
|
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/%s test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
|
"%s/%s test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
|
||||||
bin, drv, stem);
|
bin, drv, stem);
|
||||||
@@ -301,15 +334,40 @@ collide_run(const char *bin, const char *comp, const char *drv)
|
|||||||
fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb);
|
fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
snprintf(cmd, sizeof cmd, "%s/%s -T %s -o /dev/null 2>/dev/null",
|
|
||||||
bin, comp, comb);
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
if (runwait(cmd) == 0) {
|
snprintf(cmd, sizeof cmd, "%s/%s -T %s -o %s 2>/dev/null",
|
||||||
fprintf(stderr, "910 FAIL: %s -T accepted user `fn run` collision "
|
bin, comp, comb, asmf);
|
||||||
"(expected duplicate-fn reject)\n", comp);
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "910 FAIL: %s -T rejected user `fn run` coexist "
|
||||||
|
"(expected accept post-#80)\n", comp);
|
||||||
rc = 1;
|
rc = 1;
|
||||||
}
|
}
|
||||||
unlink(comb);
|
if (rc == 0) {
|
||||||
|
int nr = count_text(asmf, "run");
|
||||||
|
int nt = count_text(asmf, "test.run");
|
||||||
|
if (nr != 1 || nt != 1) {
|
||||||
|
fprintf(stderr, "910 FAIL: %s -T asm label count run=%d "
|
||||||
|
"test.run=%d (want 1/1) — user `fn run` not distinct from "
|
||||||
|
"lib runner (a #84 dead-dup gives run=0/test.run=2)\n",
|
||||||
|
comp, nr, nt);
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj, asmf);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: coexist w6a\n"); rc = 1; }
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null",
|
||||||
|
bin, exe, obj, rt);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: coexist w6l\n"); rc = 1; }
|
||||||
|
}
|
||||||
|
if (rc == 0 && runwait(exe) != 0) {
|
||||||
|
fprintf(stderr, "910 FAIL: coexist synth entry nonzero — user "
|
||||||
|
"`fn run` hijacked the runner or the @test failed\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
unlink(comb); unlink(asmf); unlink(obj); unlink(exe);
|
||||||
char tmp[320];
|
char tmp[320];
|
||||||
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||||
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||||
@@ -384,11 +442,11 @@ main(void)
|
|||||||
"use-before-value") != 0) return 1;
|
"use-before-value") != 0) return 1;
|
||||||
if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_vbu_ok.ww",
|
if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_vbu_ok.ww",
|
||||||
"value-before-use") != 0) return 1;
|
"value-before-use") != 0) return 1;
|
||||||
if (collide_run(bin, "w6c", "ww") != 0) return 1;
|
if (coexist_run(bin, "w6c", "ww") != 0) return 1;
|
||||||
|
|
||||||
printf("@test -T: run ok + user-main and bad-signature rejected + "
|
printf("@test -T: run ok + user-main and bad-signature rejected + "
|
||||||
"non-T @test drop + checked-body + dangling-call link-fail (#6) + "
|
"non-T @test drop + checked-body + dangling-call link-fail (#6) + "
|
||||||
"dup fn/type/def/let reject + xpkg + builtin-redecl accept + "
|
"dup fn/type/def/let reject + xpkg + builtin-redecl accept + "
|
||||||
"modfn coexist (#30) + fn-run collision (#23)\n");
|
"modfn coexist (#30) + fn-run coexist (#80)\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,34 +330,113 @@ accept_byteid(const char *bin, const char *fixture, const char *what)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* collide_run — a @test unit defining a user `fn run` collides with
|
/* count_text — number of `TEXT <sym>,` directive lines in an asm file
|
||||||
* lib/test's bound runner once `ww test -c` bundles it; `w6c_ww -T` of the
|
* (Plan-9 `TEXT name,$frame`), -1 on open failure. A COUNT (not presence)
|
||||||
* combined must loud-reject the duplicate (#23). Pre-fix wwstage built a
|
* is what distinguishes the #84 dead-dup: the bug yields 0x `TEXT run` +
|
||||||
* binary that called the user run and silently skipped every @test. */
|
* 2x `TEXT test.run`, which a mere presence check would miss. */
|
||||||
static int
|
static int
|
||||||
collide_run(const char *bin)
|
count_text(const char *path, const char *sym)
|
||||||
|
{
|
||||||
|
char want[128];
|
||||||
|
snprintf(want, sizeof want, "TEXT %s,", sym);
|
||||||
|
size_t wlen = strlen(want);
|
||||||
|
FILE *f = fopen(path, "r");
|
||||||
|
if (f == NULL) return -1;
|
||||||
|
char line[8192];
|
||||||
|
int n = 0;
|
||||||
|
while (fgets(line, sizeof line, f) != NULL)
|
||||||
|
if (strncmp(line, want, wlen) == 0) n++;
|
||||||
|
fclose(f);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* coexist_run — a @test unit that ALSO defines a user `fn run` must
|
||||||
|
* COEXIST with lib/test's bound runner, not collide. Post-#80 the synth
|
||||||
|
* `use test;` is prepended before binding, so lib/test's `run` keys under
|
||||||
|
* "test" (not "") and no longer duplicate-collides with the user's bare
|
||||||
|
* `run` (which mangles bare via #84). `w6c_ww -T` of the `ww_ww test -c`
|
||||||
|
* combined now ACCEPTS; the -T asm carries EXACTLY 1 `TEXT run` (user,
|
||||||
|
* bare) + 1 `TEXT test.run` (lib runner) and is byte-identical to w6c's
|
||||||
|
* (cs==ww) — the static-label proof that the user run is distinct in the
|
||||||
|
* real @test/-T path where #84 lives; then, assembled, linked and run, the
|
||||||
|
* synth entry exits 0 (the user `run` did not hijack the runner and the
|
||||||
|
* @test passes). Pre-#80 this loud-rejected "duplicate fn run". */
|
||||||
|
static int
|
||||||
|
coexist_run(const char *bin)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char stem[256], comb[300], cmd[4096];
|
char stem[256], comb[300], asmf[320], csasm[320], obj[320], exe[320];
|
||||||
|
char rt[1024], cmd[4096];
|
||||||
snprintf(stem, sizeof stem, "/tmp/at997cr_%d", pid);
|
snprintf(stem, sizeof stem, "/tmp/at997cr_%d", pid);
|
||||||
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
|
snprintf(asmf, sizeof asmf, "%s.run.s", stem);
|
||||||
|
snprintf(csasm, sizeof csasm, "%s.cs.s", stem);
|
||||||
|
snprintf(obj, sizeof obj, "%s.run.o", stem);
|
||||||
|
snprintf(exe, sizeof exe, "%s.exe", stem);
|
||||||
|
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/ww test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
|
"%s/ww_ww test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
|
||||||
bin, stem);
|
bin, stem);
|
||||||
runwait(cmd);
|
runwait(cmd);
|
||||||
if (access(comb, 0) != 0) {
|
if (access(comb, 0) != 0) {
|
||||||
fprintf(stderr, "997 FAIL: ww build produced no %s\n", comb);
|
fprintf(stderr, "997 FAIL: ww_ww build produced no %s\n", comb);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T %s -o /dev/null 2>/dev/null",
|
|
||||||
bin, comb);
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
if (runwait(cmd) == 0) {
|
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T %s -o %s 2>/dev/null",
|
||||||
fprintf(stderr, "997 FAIL: w6c_ww -T accepted user `fn run` collision "
|
bin, comb, asmf);
|
||||||
"(expected duplicate-fn reject)\n");
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: w6c_ww -T rejected user `fn run` coexist "
|
||||||
|
"(expected accept post-#80)\n");
|
||||||
rc = 1;
|
rc = 1;
|
||||||
}
|
}
|
||||||
unlink(comb);
|
if (rc == 0) {
|
||||||
|
int nr = count_text(asmf, "run");
|
||||||
|
int nt = count_text(asmf, "test.run");
|
||||||
|
if (nr != 1 || nt != 1) {
|
||||||
|
fprintf(stderr, "997 FAIL: w6c_ww -T asm label count run=%d "
|
||||||
|
"test.run=%d (want 1/1) — user `fn run` not distinct from "
|
||||||
|
"lib runner (a #84 dead-dup gives run=0/test.run=2)\n",
|
||||||
|
nr, nt);
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -T %s -o %s 2>/dev/null",
|
||||||
|
bin, comb, csasm);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: w6c -T (coexist cs==ww)\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
char *bc = NULL, *bw = NULL;
|
||||||
|
size_t nc = 0, nw = 0;
|
||||||
|
if (slurp(csasm, &bc, &nc) < 0 || slurp(asmf, &bw, &nw) < 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: slurp coexist asm\n");
|
||||||
|
rc = 1;
|
||||||
|
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: coexist -T asm cs!=ww "
|
||||||
|
"(cs %zu, ww %zu)\n", nc, nw);
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
free(bc); free(bw);
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6a_ww -o %s %s 2>/dev/null", bin, obj, asmf);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: coexist w6a_ww\n"); rc = 1; }
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6l_ww -o %s %s %s 2>/dev/null",
|
||||||
|
bin, exe, obj, rt);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: coexist w6l_ww\n"); rc = 1; }
|
||||||
|
}
|
||||||
|
if (rc == 0 && runwait(exe) != 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: coexist synth entry nonzero — user "
|
||||||
|
"`fn run` hijacked the runner or the @test failed\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
unlink(comb); unlink(asmf); unlink(csasm); unlink(obj); unlink(exe);
|
||||||
char tmp[320];
|
char tmp[320];
|
||||||
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||||
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||||
@@ -434,11 +513,11 @@ main(void)
|
|||||||
"use-before-value") != 0) return 1;
|
"use-before-value") != 0) return 1;
|
||||||
if (modfn_run(bin, "test/wcc/data/modfn_coexist_vbu_ok.ww",
|
if (modfn_run(bin, "test/wcc/data/modfn_coexist_vbu_ok.ww",
|
||||||
"value-before-use") != 0) return 1;
|
"value-before-use") != 0) return 1;
|
||||||
if (collide_run(bin) != 0) return 1;
|
if (coexist_run(bin) != 0) return 1;
|
||||||
|
|
||||||
printf("@test -T (ww_ww): run ok + cs/ww byte-id + rejects + "
|
printf("@test -T (ww_ww): run ok + cs/ww byte-id + rejects + "
|
||||||
"non-T @test drop cs/ww byte-id + checked-body (#6) + "
|
"non-T @test drop cs/ww byte-id + checked-body (#6) + "
|
||||||
"dup fn/type/def/let reject + xpkg/builtin-redecl byte-id + "
|
"dup fn/type/def/let reject + xpkg/builtin-redecl byte-id + "
|
||||||
"modfn coexist byte-id (#30) + fn-run collision (#23)\n");
|
"modfn coexist byte-id (#30) + fn-run coexist (#80)\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
// #23 -T collision — a user `fn run` collides with lib/test's bound
|
// #80/#84 -T coexist — a user `fn run` COEXISTS with lib/test's bound
|
||||||
// runner `run` (the synth's callee) once `ww test -c` bundles lib/test.
|
// runner `run` (the synth main's callee). Once `ww test -c` bundles
|
||||||
// Both stages must loud-reject "duplicate fn run" (rc!=0), never silently
|
// lib/test, the synth `use test;` keys lib/test's `run` under module
|
||||||
// build a binary that calls the wrong run and skips every @test.
|
// "test" (#80: prepended before binding) and the user's package-less
|
||||||
fn run() void = { return; };
|
// `run` mangles to a DISTINCT bare `run` (#84) — no duplicate. Both
|
||||||
|
// stages accept under -T and the synth runner runs the @test to exit 0;
|
||||||
|
// the user `run` must NOT hijack the entry. Pre-#80 this loud-rejected
|
||||||
|
// "duplicate fn run". The gate (910/997 coexist_run) static-label-counts
|
||||||
|
// the -T asm — exactly 1 `TEXT run` (user, bare) + 1 `TEXT test.run` (lib
|
||||||
|
// runner) — pinning #84 distinctness here in the REAL @test/-T path (a
|
||||||
|
// dead-dup regression yields 0x run / 2x test.run). General callability of
|
||||||
|
// the bare run is gated separately by 989_barefn_collide.
|
||||||
|
fn run() i32 = { return 9; };
|
||||||
|
|
||||||
@test fn t_one() void = {
|
@test fn t_one() void = {
|
||||||
assert(1 == 1);
|
assert(1 == 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user