selfhost+cstage+test: graduate enumlookup same-module-first + N_DOT enumlookupmod (#4a)
Class A silent miscompile, latent until two modules export the same enum leaf name. Wwstage's enumlookup (selfhost/cmd/wcc/cgen.ww) walked c.enums head-first by ename; cgdot handed it the bare leaf from N_DOT.lhs.str for both `Color.MEMBER` (lhs N_IDENT) and `pkg.Color.MEMBER` (lhs N_DOT) shapes, silently dropping the explicit qualifier on the second. Cstage's enum-member fold (cmd/wcc/check.c cexpr N_DOT) was carrying the same head-pick on the lhs-ident lookup — pre-fix the mismatch surfaced as a "not assignable to <same-leaf>" checker error rather than a silent wrong-constant because resolve_typename for the fn return spec already used scope_lookup_prefer correctly, so the rhs's wrong- module-Color clashed with the return type's right-module-Color. No in-tree corpus currently declares two same-leaf enums, so 995_self_rebuild stayed green and the latent miscompile only surfaces once a stdlib port introduces the collision (same shape as #27 surfacing when lib/strings dragged utf8's invalid alias into the chain alongside strconv's invalid). Fourth leaf of the trio leaf-name lookup graduation (after #27 aliaslookup, #28 fnparamslookupmod, #31 fnretlookupmod): wwstage enumlookup grows a same-module-first walk before the head-walk fallback, mirroring aliaslookup's two-pass shape (cgen.ww:75). The N_DOT consumer surface — `pkg.Enum.MEMBER`, already used in-corpus by os.flag.RDONLY, temp.mode.RDWR, os.whence.SET etc. — routes through a new enumlookupmod variant with the explicit N_DOT.lhs.lhs.str as the mod qualifier (mirror of fnret/ fnparamslookupmod). Cstage's check.c cexpr N_DOT lhs lookup graduates from scope_lookup to scope_lookup_prefer to align symmetrically (rule 10: both stages pick same-module-first on the bare-leaf shape). 733_enum_modshadow pins both surfaces with 3 rows: row 1 bare-leaf in module M must fold against M's own Color even with another module's same-leaf Color at the head of c.enums; row 2 same-module `mod.Color.MEMBER` from inside that mod pins the API surface; row 3 cross-module `othermod.Color.MEMBER` from a third module with no local Color sentinel-flips the cgdot etmod tracking + enumlookupmod path independently of row 1's same-module-first fallback. Asserts the matching \$N, immediate inside the right TEXT sym + bad_imm NOT-presence anti-check on both stages plus byte-id between stages per row.
This commit is contained in:
@@ -11843,11 +11843,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
// Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER`
|
||||
// → inline the pre-computed constant. With driver-side
|
||||
// concatenation, both forms key off the leaf type name.
|
||||
// → inline the pre-computed constant. `pkg.Enum.MEMBER` keeps
|
||||
// `pkg` so enumlookupmod can prefer the explicit module on a
|
||||
// leaf collision; bare `Enum.MEMBER` falls back to c.curmod via
|
||||
// enumlookup's same-module-first walk.
|
||||
if (lhs != nil) {
|
||||
let etname: str;
|
||||
let etmod: str;
|
||||
etname.ptr = nil; etname.len = 0;
|
||||
etmod.ptr = nil; etmod.len = 0;
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
};
|
||||
@@ -11855,11 +11859,12 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs.lhs != nil) {
|
||||
if (lhs.lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
etmod = lhs.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (etname.len > 0) {
|
||||
let en: *enumtype = enumlookup(c, etname);
|
||||
let en: *enumtype = enumlookupmod(c, etname, etmod);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
@@ -18972,18 +18977,26 @@ fn collectenums(c: *cgen, file: *node) void = {
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
// Exact match first: bare-from-source idents and already-leafed
|
||||
// names hit here directly.
|
||||
// Same-module first, then any. Trio-leaf graduation mirroring
|
||||
// aliaslookup (#27) and fnret/fnparamslookupmod (#28/#31): without
|
||||
// the prefer pass a bare-leaf enum ident in module M can collapse
|
||||
// onto another module's same-leaf enum prepended earlier in
|
||||
// c.enums, silently folding `Foo.MEMBER` to the wrong constant.
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, c.curmod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
e = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
// Module-qualified form: `pkg.enum` → match the leaf scoped to
|
||||
// its originating module. Mirrors aliaslookup's mod-filter; the
|
||||
// `emod == pkg` guard is what prevents two modules with same-
|
||||
// leaf-name enums from collapsing into whichever entry appears
|
||||
// first in the chain.
|
||||
// Module-qualified form embedded in name (`pkg.enum`): scope the
|
||||
// leaf to its originating module. The `emod == pkg` guard prevents
|
||||
// same-leaf enums in two modules from collapsing.
|
||||
let i: i32 = name.len - 1;
|
||||
for (i >= 0) {
|
||||
if (name[i] == 46u8) { // '.'
|
||||
@@ -19009,6 +19022,23 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// enumlookupmod — same-module-first leaf walk for `pkg.Enum.MEMBER`
|
||||
// where the qualifier is an explicit N_IDENT module name. Mirrors
|
||||
// fnparamslookupmod / fnretlookupmod (#28 / #31). Falls back to the
|
||||
// bare enumlookup so a missing or empty mod still finds the leaf.
|
||||
fn enumlookupmod(c: *cgen, name: str, mod: str) *enumtype = {
|
||||
if (mod.len > 0) {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, mod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
};
|
||||
return enumlookup(c, name);
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
|
||||
@@ -256,18 +256,26 @@ fn collectenums(c: *cgen, file: *node) void = {
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
// Exact match first: bare-from-source idents and already-leafed
|
||||
// names hit here directly.
|
||||
// Same-module first, then any. Trio-leaf graduation mirroring
|
||||
// aliaslookup (#27) and fnret/fnparamslookupmod (#28/#31): without
|
||||
// the prefer pass a bare-leaf enum ident in module M can collapse
|
||||
// onto another module's same-leaf enum prepended earlier in
|
||||
// c.enums, silently folding `Foo.MEMBER` to the wrong constant.
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, c.curmod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
e = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
// Module-qualified form: `pkg.enum` → match the leaf scoped to
|
||||
// its originating module. Mirrors aliaslookup's mod-filter; the
|
||||
// `emod == pkg` guard is what prevents two modules with same-
|
||||
// leaf-name enums from collapsing into whichever entry appears
|
||||
// first in the chain.
|
||||
// Module-qualified form embedded in name (`pkg.enum`): scope the
|
||||
// leaf to its originating module. The `emod == pkg` guard prevents
|
||||
// same-leaf enums in two modules from collapsing.
|
||||
let i: i32 = name.len - 1;
|
||||
for (i >= 0) {
|
||||
if (name[i] == 46u8) { // '.'
|
||||
@@ -293,6 +301,23 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// enumlookupmod — same-module-first leaf walk for `pkg.Enum.MEMBER`
|
||||
// where the qualifier is an explicit N_IDENT module name. Mirrors
|
||||
// fnparamslookupmod / fnretlookupmod (#28 / #31). Falls back to the
|
||||
// bare enumlookup so a missing or empty mod still finds the leaf.
|
||||
fn enumlookupmod(c: *cgen, name: str, mod: str) *enumtype = {
|
||||
if (mod.len > 0) {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, mod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
};
|
||||
return enumlookup(c, name);
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
|
||||
@@ -1177,11 +1177,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
// Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER`
|
||||
// → inline the pre-computed constant. With driver-side
|
||||
// concatenation, both forms key off the leaf type name.
|
||||
// → inline the pre-computed constant. `pkg.Enum.MEMBER` keeps
|
||||
// `pkg` so enumlookupmod can prefer the explicit module on a
|
||||
// leaf collision; bare `Enum.MEMBER` falls back to c.curmod via
|
||||
// enumlookup's same-module-first walk.
|
||||
if (lhs != nil) {
|
||||
let etname: str;
|
||||
let etmod: str;
|
||||
etname.ptr = nil; etname.len = 0;
|
||||
etmod.ptr = nil; etmod.len = 0;
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
};
|
||||
@@ -1189,11 +1193,12 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs.lhs != nil) {
|
||||
if (lhs.lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
etmod = lhs.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (etname.len > 0) {
|
||||
let en: *enumtype = enumlookup(c, etname);
|
||||
let en: *enumtype = enumlookupmod(c, etname, etmod);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
|
||||
@@ -11843,11 +11843,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
// Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER`
|
||||
// → inline the pre-computed constant. With driver-side
|
||||
// concatenation, both forms key off the leaf type name.
|
||||
// → inline the pre-computed constant. `pkg.Enum.MEMBER` keeps
|
||||
// `pkg` so enumlookupmod can prefer the explicit module on a
|
||||
// leaf collision; bare `Enum.MEMBER` falls back to c.curmod via
|
||||
// enumlookup's same-module-first walk.
|
||||
if (lhs != nil) {
|
||||
let etname: str;
|
||||
let etmod: str;
|
||||
etname.ptr = nil; etname.len = 0;
|
||||
etmod.ptr = nil; etmod.len = 0;
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
};
|
||||
@@ -11855,11 +11859,12 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (lhs.lhs != nil) {
|
||||
if (lhs.lhs.kind == nkind.N_IDENT) {
|
||||
etname = lhs.str;
|
||||
etmod = lhs.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (etname.len > 0) {
|
||||
let en: *enumtype = enumlookup(c, etname);
|
||||
let en: *enumtype = enumlookupmod(c, etname, etmod);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
@@ -18972,18 +18977,26 @@ fn collectenums(c: *cgen, file: *node) void = {
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
// Exact match first: bare-from-source idents and already-leafed
|
||||
// names hit here directly.
|
||||
// Same-module first, then any. Trio-leaf graduation mirroring
|
||||
// aliaslookup (#27) and fnret/fnparamslookupmod (#28/#31): without
|
||||
// the prefer pass a bare-leaf enum ident in module M can collapse
|
||||
// onto another module's same-leaf enum prepended earlier in
|
||||
// c.enums, silently folding `Foo.MEMBER` to the wrong constant.
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, c.curmod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
e = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
// Module-qualified form: `pkg.enum` → match the leaf scoped to
|
||||
// its originating module. Mirrors aliaslookup's mod-filter; the
|
||||
// `emod == pkg` guard is what prevents two modules with same-
|
||||
// leaf-name enums from collapsing into whichever entry appears
|
||||
// first in the chain.
|
||||
// Module-qualified form embedded in name (`pkg.enum`): scope the
|
||||
// leaf to its originating module. The `emod == pkg` guard prevents
|
||||
// same-leaf enums in two modules from collapsing.
|
||||
let i: i32 = name.len - 1;
|
||||
for (i >= 0) {
|
||||
if (name[i] == 46u8) { // '.'
|
||||
@@ -19009,6 +19022,23 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// enumlookupmod — same-module-first leaf walk for `pkg.Enum.MEMBER`
|
||||
// where the qualifier is an explicit N_IDENT module name. Mirrors
|
||||
// fnparamslookupmod / fnretlookupmod (#28 / #31). Falls back to the
|
||||
// bare enumlookup so a missing or empty mod still finds the leaf.
|
||||
fn enumlookupmod(c: *cgen, name: str, mod: str) *enumtype = {
|
||||
if (mod.len > 0) {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) {
|
||||
if (streq(e.emod, mod)) { return e; };
|
||||
};
|
||||
e = e.etnext;
|
||||
};
|
||||
};
|
||||
return enumlookup(c, name);
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
|
||||
Reference in New Issue
Block a user