getopt: free opts at size(option) (32B), not a stale 24B literal (F-E)

option = struct{flag:rune, value:str} is 32B now that str is 24B (Phase 3: cstage type.c:68, wwstage typ.ww:239 -- rune 4 + pad 4 + str 24). The three free sites (tryparse's two error paths + finish) freed (cap)*24u64 -- an 8-byte-per-element under-free of a buffer that append() grew at the real 32B stride, plus a rule-13 hardcoded-size-literal violation. Route all three through size(option).

Also corrects the stale 16B-era layout comments, including the error struct: error is 32B, so its (void|error) return rides sret/MEMORY (the 40B slot exceeds the 24B register cap), not the register ABI -- verified sound at runtime (ken; see #38).

Test (getopttest.ww, test 982_getopt_run): optionsizepin pins size(option)==32 as the regression guard; freeroundtrip is a table over {0,1,3} options exercising tryparse-then-finish. Full make test: 450 green incl. 990-997 byte-id.
This commit is contained in:
2026-06-19 14:24:07 +09:00
parent 801d105c0f
commit e9f64a0727
2 changed files with 73 additions and 12 deletions

View File

@@ -28,9 +28,10 @@
// the same reason (task #5), and because task #6 (arr[i].field
// = v) blocks the obvious `h[i].kind = ...` callsite spelling.
// * [[option]] is a `struct { flag: rune, value: str }` rather
// than Hare's `(rune, str)` 2-tuple: ww's cgen mis-sizes a
// tuple of (i32, str) as 20B (no 4B pad before the str field).
// The struct shape lays out as 24B as expected.
// than Hare's `(rune, str)` 2-tuple: ww's cgen mis-pads a
// tuple of (i32, str) (no 4B pad before the str field). The
// struct shape lays out as rune (4) + pad (4) + str (24) = 32B
// as expected.
// * [[error]] is a flat 24B struct (kind + flag + name). Hare's
// `!(str, []help, (requiresarg | unknownopt | unknownsubcmd))`
// 3-tuple-with-nested-union doesn't map onto ww's single-payload
@@ -161,8 +162,9 @@ export type errorkind = enum i32 {
UNKNOWNOPT = 1, // -X isn't in the help list
};
// error — parse failure. 24B layout (i32 kind + rune flag + str
// name) so `(void | error)` rides the tagged-union return ABI.
// error — parse failure. 32B (i32 kind + rune flag + str name);
// the 40B `(void | error)` slot exceeds the 24B register cap, so
// it returns via sret/MEMORY, not the register ABI (see #38).
export type error = struct {
kind: errorkind,
flag: rune, // the offending letter
@@ -170,9 +172,9 @@ export type error = struct {
};
// option — one element of the parsed option list. Hare's getopts
// uses a `(rune, str)` 2-tuple; ww's cgen mis-sizes that as 20B
// (no 4B pad before the str field), so we use a named struct — the
// struct layout pads correctly.
// uses a `(rune, str)` 2-tuple; ww's cgen mis-pads that (no 4B pad
// before the str field), so we use a named struct — the struct
// layout pads correctly to rune (4) + pad (4) + str (24) = 32B.
export type option = struct {
flag: rune,
value: str,
@@ -283,7 +285,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
case void => {
if (opts.cap > 0) {
os.free(opts.ptr: *void,
(opts.cap: u64) * 24u64);
(opts.cap: u64) * size(option): u64);
};
let e: error;
e.kind = errorkind.UNKNOWNOPT;
@@ -317,7 +319,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
if (i + 1 >= argv.len) {
if (opts.cap > 0) {
os.free(opts.ptr: *void,
(opts.cap: u64) * 24u64);
(opts.cap: u64) * size(option): u64);
};
let e: error;
e.kind = errorkind.REQUIRESARG;
@@ -352,8 +354,8 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
// tryparse; args is a borrowed view into argv.
export fn finish(cmd: *command) void = {
if (cmd.optscap > 0) {
// option layout: rune (4) + pad (4) + str (16) = 24B.
os.free(cmd.optsptr: *void, (cmd.optscap: u64) * 24u64);
// option layout: rune (4) + pad (4) + str (24) = 32B.
os.free(cmd.optsptr: *void, (cmd.optscap: u64) * size(option): u64);
};
cmd.optsptr = nil: *option;
cmd.optslen = 0;