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:
@@ -14,13 +14,19 @@
|
||||
// non-function"), so this whole file fails to build in the cstage `ww
|
||||
// test` / byte-id legs — a hard red, not a silent value drift.
|
||||
//
|
||||
// NOT covered here: a `**fn` deref-less call (`let pf: **fn(...) = &f;
|
||||
// pf(21)`). The #181-cgen path uses the callee VALUE as the call target,
|
||||
// so it autodereferences exactly ONE level; a `**fn` deref-less call
|
||||
// drops a `MOVQ (AX),AX` and miscompiles on BOTH stages (byte-id-blind,
|
||||
// returns garbage). cstage deliberately stays loud on `**fn` (one-level
|
||||
// peel); multi-level fn-ptr autoderef is deferred (#181). The explicit
|
||||
// `(**pf)(21)` is correct (cgexpr emits both loads).
|
||||
// The #181-cgen path uses the callee VALUE as the call target, so it
|
||||
// autodereferences exactly ONE level: a `*fn` callee is the fn address, but
|
||||
// a `**fn` callee is only the *address of* the fn-ptr. So a deref-less
|
||||
// `**fn` call (`let pf: **fn(...) = &f; pf(21)`) drops a `MOVQ (AX),AX` and
|
||||
// is a hard reject in BOTH stages — cstage at cmd/wcc/check.c:1947 (one-level
|
||||
// peel), wwstage aligned DOWN at selfhost/cmd/wcc/check.ww (task #14, was the
|
||||
// SILENT-miscompile side). That loud reject is pinned in
|
||||
// test/wcc/data/fnptr_pp_derefless_reject. The LEGAL one-explicit-deref
|
||||
// `(*pf)(21)` (which yields a `*fn`, then autoderefs the remaining single
|
||||
// level) is covered by derefless_pp_one_explicit_deref below — the shape the
|
||||
// one-level peel must NOT over-reject. Multi-level fn-ptr autoderef is a
|
||||
// deferred FEATURE (#181); the explicit `(**pf)(21)` is correct (cgexpr emits
|
||||
// both loads).
|
||||
|
||||
package fnptr_derefless_call_test;
|
||||
|
||||
@@ -61,3 +67,13 @@ fn pair(x: i32) (i32, i32) = { return (x, x + 1); };
|
||||
let a, b = f(7);
|
||||
assert(a + b == 15);
|
||||
};
|
||||
|
||||
@test fn derefless_pp_one_explicit_deref() void = {
|
||||
// #14 positive guard: a `**fn` with ONE explicit deref `(*pf)` yields a
|
||||
// `*fn`, whose call autodereferences the remaining single level — legal,
|
||||
// the shape the one-level peel must NOT over-reject. Its deref-less twin
|
||||
// `pf(21)` is the loud reject pinned in fnptr_pp_derefless_reject.
|
||||
let f: *fn(x: i32) i32 = &add1;
|
||||
let pf: **fn(x: i32) i32 = &f;
|
||||
assert((*pf)(21) == 42);
|
||||
};
|
||||
|
||||
21
test/wcc/data/fnptr_pp_derefless_reject/case.ww
Normal file
21
test/wcc/data/fnptr_pp_derefless_reject/case.ww
Normal file
@@ -0,0 +1,21 @@
|
||||
//ww:error "calling non-function"
|
||||
// #14: a deref-less call through a pointer-to-pointer-to-fn (`pf(21)` where
|
||||
// pf: **fn) is a hard reject in BOTH stages. The #181-cgen path lowers an
|
||||
// indirect call by using the callee VALUE as the target, so it autoderefs
|
||||
// exactly ONE pointer level — correct for a single `*fn`, but for a `**fn`
|
||||
// the value is only the *address of* the fn-ptr, so a deref-less call ships
|
||||
// a CALL through that address and segfaults. cstage rejected at one-level
|
||||
// peel (cmd/wcc/check.c:1947); wwstage was the SILENT-miscompile side until
|
||||
// it was aligned DOWN to the same one-level peel (selfhost/cmd/wcc/check.ww,
|
||||
// task #14). Multi-level fn-ptr autoderef is a deferred FEATURE (#181); the
|
||||
// legal one-explicit-deref `(*pf)(21)` stays accepted (test/lang/
|
||||
// fnptr_derefless_call_test.ww derefless_pp_one_explicit_deref). REVERT
|
||||
// MANIFESTATION: revert the wwstage peel loop->if and this case COMPILES
|
||||
// again under w6c_ww (the runww ERROR arm runs both stages).
|
||||
package main;
|
||||
fn add1(x: i32) i32 = { return x + 21; };
|
||||
export fn main() i32 = {
|
||||
let f: *fn(x: i32) i32 = &add1;
|
||||
let pf: **fn(x: i32) i32 = &f;
|
||||
return pf(21);
|
||||
};
|
||||
Reference in New Issue
Block a user