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:
@@ -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;
|
||||
|
||||
@@ -434,6 +434,65 @@ fn streq(a: str, b: str) bool = {
|
||||
};
|
||||
};
|
||||
|
||||
// ---- optionsizepin: option is 32B, not 24B -----------------------------
|
||||
//
|
||||
// tryparse/finish free the parsed-option array at `cap * size(option)`.
|
||||
// option = rune (4) + pad (4) + str (24, #1/Phase 3) = 32B. A hardcoded
|
||||
// `24u64` here was an 8-byte-per-element under-free; pinning the size
|
||||
// keeps the literal from silently drifting back and catches any future
|
||||
// cgen struct mis-sizing before it corrupts the heap.
|
||||
@test fn optionsizepin() void = {
|
||||
assert(!(size(getopt.option) != 32));
|
||||
};
|
||||
|
||||
// ---- freeroundtrip: parse → finish over {0, 1, several} options --------
|
||||
//
|
||||
// Exercises the free path at three distinct cap values so the
|
||||
// `cap * size(option)` free in [[finish]] runs for an empty, single,
|
||||
// and multi-element option array. The under-free is not directly
|
||||
// observable through the allocator here, so this guards the parse →
|
||||
// finish roundtrip doesn't crash/corrupt across the three sizes; the
|
||||
// exact-size guarantee rides on optionsizepin above.
|
||||
@test fn freeroundtrip() void = {
|
||||
let helps: [3]getopt.help;
|
||||
getopt.cmdhelp(&helps[0], "prog");
|
||||
getopt.flaghelp(&helps[1], 'v': rune, "verbose");
|
||||
getopt.flaghelp(&helps[2], 'a': rune, "all");
|
||||
|
||||
// row 0: ["p"] → 0 opts (nil opts, finish no-ops)
|
||||
// row 1: ["p","-v"] → 1 opt (finish frees 1*32)
|
||||
// row 2: ["p","-vav"] → 3 opts (finish frees 3*32)
|
||||
let srcs: [5]str;
|
||||
srcs[0]="p";
|
||||
srcs[1]="p"; srcs[2]="-v";
|
||||
srcs[3]="p"; srcs[4]="-vav";
|
||||
|
||||
let argo: [3]i32;
|
||||
let argn: [3]i32;
|
||||
let wantopts: [3]i32;
|
||||
argo[0]=0; argn[0]=1; wantopts[0]=0;
|
||||
argo[1]=1; argn[1]=2; wantopts[1]=1;
|
||||
argo[2]=3; argn[2]=2; wantopts[2]=3;
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < 3) {
|
||||
let argv: []str;
|
||||
argv.ptr = &srcs[argo[i]];
|
||||
argv.len = argn[i];
|
||||
argv.cap = argn[i];
|
||||
|
||||
let cmd: getopt.command;
|
||||
let r: (void | getopt.error) = getopt.tryparse(&cmd, argv, helps[0:3]);
|
||||
match (r) {
|
||||
case void => {};
|
||||
case let e: getopt.error => abort();
|
||||
};
|
||||
assert(!(cmd.optslen != wantopts[i]));
|
||||
getopt.finish(&cmd);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- printhelp_cases: table of help-output paths ----------------------
|
||||
//
|
||||
// ref/hare/getopt/getopts.ha:281-314.
|
||||
|
||||
Reference in New Issue
Block a user