check: reject a deref-less multi-level **fn call, not silently miscompile (#14)
wwstage exprtype's N_CALL fn-value arm peeled ALL pointer levels before the TY_FN gate, so a deref-less `pf(21)` where pf:**fn type-checked and lowered -- then segfaulted at runtime (a silent miscompile). cstage peels exactly one level and loud-rejects (the C6a discipline, check.c:1947). Align wwstage DOWN: peel one level (loop -> if); a remaining non-TY_FN callee hits a loud "calling non-function" reject mirroring cstage's message. Both stages now reject the exotic deref-less `**fn`/`***fn` shape; the legitimate `(*pf)(21)` and one-level deref-less `f(21)` (f:*fn) still compile + run. Multi-level autoderef is a separate deferred FEATURE, not a miscompile to lower (rule 7/10 -- align the richer stage down to the leaner, no value ships). ww-only change (cstage is the correct oracle); a reject emits no asm, so the byte-id baselines and LANGBYTEID floor are unchanged. Pins: cfail test/wcc/data/fnptr_pp_derefless_reject (both stages reject, reddens-on-revert -- the silent miscompile resurfaces if the fix is reverted) + test/lang/fnptr_derefless_call_test (positive guard (*pf)(21)==42 and one-level f(21), value-asserted + byte-id, so the fix does not over-reject the legitimate one-level autoderef).
This commit is contained in:
@@ -3760,7 +3760,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// already carries its RETURN type (fn-decl.lhs), not its fn-type —
|
||||
// so a `fn make() fn() void` callee would otherwise mis-yield void.
|
||||
let ct: *syntax.node = resolvealias(c, unwrapbang(exprtype(c, callee, nil)));
|
||||
for (ct != nil && ct.kind == syntax.nkind.N_TPTR) {
|
||||
// #14/#181-cgen: ONE pointer-peel only, mirroring cstage
|
||||
// cmd/wcc/check.c:1947. #181-cgen lowers an indirect call by using the
|
||||
// callee VALUE as the target, the fn address for a single `*fn` but only
|
||||
// the *address of* the fn-ptr for `**fn` — so a deref-less `**fn` call
|
||||
// peeled past one level ships a CALL through a fn-ptr address (segfault).
|
||||
// Multi-level fn-ptr autoderef is a deferred FEATURE (#181); the
|
||||
// unsupported shape stays a loud reject in BOTH stages (rule 7/10).
|
||||
if (ct != nil && ct.kind == syntax.nkind.N_TPTR) {
|
||||
ct = resolvealias(c, unwrapbang(ct.lhs));
|
||||
};
|
||||
if (ct != nil) { if (ct.kind == syntax.nkind.N_TFN) {
|
||||
@@ -3768,6 +3775,19 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
e.type_ = tinfofornode(c, res): *void;
|
||||
return res;
|
||||
}; };
|
||||
// Mirror cstage's loud `n->type = err(c, ..., "calling non-function")`
|
||||
// (check.c:1948): a callee that isn't a TY_FN after the single peel is a
|
||||
// hard reject. The tyerr stamp doubles as a once-guard — exprtype is not
|
||||
// memoized (unlike cstage's cexpr n->type cache), so a second pass over
|
||||
// this N_CALL must not re-emit; it also suppresses the post-checker
|
||||
// asserttyped gate (L6683). One diagnostic, cgen gated off (w6c L186).
|
||||
if (e.type_ == nil) {
|
||||
cerr(e.file); cerr(":");
|
||||
cerr(strconv.i32tos(e.line, strconv.base.DEC));
|
||||
cerr(": error: calling non-function\n");
|
||||
c.errs += 1;
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == syntax.nkind.N_DOT) {
|
||||
|
||||
Reference in New Issue
Block a user