diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 14:37:21 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | d678a63e0abf2ba8994225a9f0fd0047fee4025a (patch) | |
| tree | 9c1a13fe7895ec162e9ee24de2913f2375375241 /src/ninep | |
| parent | 25c847e28ae77f5c648d423d64011736f55eda4a (diff) | |
| download | pardes-d678a63e0abf2ba8994225a9f0fd0047fee4025a.tar.gz pardes-d678a63e0abf2ba8994225a9f0fd0047fee4025a.zip | |
Every catch unreachable, orelse unreachable and syscall assert outside tests is a real refusal or says why it cannot fire
A sweep for round 23's crash: a run's answer (pty/run) was bufPrint'd into 48 bytes with catch unreachable, so a foreground program's long name (macOS gives up to 32 bytes) panicked; it now cuts at the room, keeping its newline, in 96 bytes. The rest were numbers into buffers sized for them, a braille codepoint, pthread calls on the queue's own mutex, and pdf_view's resolved outline entries: each now carries a one-line comment saying why it cannot fire.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/ctl.zig | 1 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 3 | ||||
| -rw-r--r-- | src/ninep/pty.zig | 21 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 5 |
4 files changed, 28 insertions, 2 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 98899edb..397f3339 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -260,6 +260,7 @@ pub fn resultsLen(p: *Pardes) u64 { var n: u64 = 0; for (p.fs.results[0..p.fs.results_len]) |serial| { var digits: [16]u8 = undefined; + // unreachable: a u32 serial and a newline fit 16 n += (std.fmt.bufPrint(&digits, "{d}\n", .{serial}) catch unreachable).len; } return n; diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 929a298a..9f85a75f 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -281,6 +281,7 @@ pub fn fileSize(p: *Pardes, id: usize, f: PaneFile) u64 { .pty_status => pty.status_len, .pty_ctl => pty_ctl: { var buf: [32]u8 = undefined; + // unreachable: two u16s and 9 bytes fit 32 break :pty_ctl (std.fmt.bufPrint(&buf, "winsize {d} {d}\n", .{ pane.cols, pane.rows }) catch unreachable).len; }, .pty_data => if (pf.pty_out.peek()) |chunk| chunk.len else 0, @@ -304,9 +305,11 @@ pub fn indexLen(p: *Pardes) u64 { last = serial; const pane = p.panes[p.paneBySerial(serial).?].?; var digits: [16]u8 = undefined; + // unreachable: a u32 serial is at most 10 digits n += (std.fmt.bufPrint(&digits, "{d}", .{serial}) catch unreachable).len; var name_buf: [4 * 4096]u8 = undefined; n += 1 + kindOf(pane).len + 3 + events.shown(nameOf(pane), &name_buf).len + 1; + // unreachable: a column index is under 16 digits n += 1 + (std.fmt.bufPrint(&digits, "{d}", .{columnOf(p, p.paneBySerial(serial).?)}) catch unreachable).len; } return n; diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index bf5f432c..d32c35fd 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -183,14 +183,20 @@ pub const Run = struct { /// Plan 9's kprint read the same way). read: usize = 0, /// The first line: how it ended. - answer: [48]u8 = undefined, + answer: [96]u8 = undefined, len: u8 = 0, /// Then what the command printed, gpa-owned. output: []u8 = &.{}, }; fn answer(p: *Pardes, slot: *Run, comptime fmt: []const u8, args: anytype) void { - slot.len = @intCast((std.fmt.bufPrint(&slot.answer, fmt ++ "\n", args) catch unreachable).len); + // A program's name comes from the host, whatever its length: an answer + // longer than the room is cut, its newline kept, never a panic. + var w: std.Io.Writer = .fixed(&slot.answer); + w.print(fmt ++ "\n", args) catch { + slot.answer[slot.answer.len - 1] = '\n'; + }; + slot.len = @intCast(w.end); slot.phase = .done; p.fs.news = true; // the read held on it can be answered } @@ -362,6 +368,7 @@ fn finish(p: *Pardes, slot: *Run, pane: *Pane, status_code: ?i32) void { } // A D that carries no status says nothing of how the command went. var code: [16]u8 = undefined; + // unreachable: an exit status fits 16 const status = if (status_code) |s| std.fmt.bufPrint(&code, "{d}", .{s}) catch unreachable else "?"; // The header is the whole first line, so a count there can never be // mistaken for output; `cut` with no count: its start is not there to @@ -387,6 +394,7 @@ pub fn shellExited(p: *Pardes, pane: *Pane, status: ?u8) void { finish(p, slot, pane, code); }; var buf: [4]u8 = undefined; + // unreachable: a u8 exit code is at most 3 digits pardes.exec.noteRun(p, pane, "exit", std.fmt.bufPrint(&buf, "{d}", .{code}) catch unreachable); } @@ -1070,3 +1078,12 @@ test "the pty queue drops the oldest at its cap" { } try testing.expect(seen > 0 and seen <= events.queue_cap); } + +test "a run's answer longer than its room is cut, its newline kept" { + var slot: Run = .{}; + const p = try th.withTerm(testing.allocator); + defer p.deinit(); + answer(p, &slot, "busy: {s} is running", .{"x" ** 200}); + try testing.expectEqual(slot.answer.len, slot.len); + try testing.expectEqual(@as(u8, '\n'), slot.answer[slot.len - 1]); +} diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 8c0e74a6..d6f4211b 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -515,6 +515,7 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr { .col => |c| { if (layout.columnBySerial(p, c.serial) == null) return null; return .{ + // unreachable: a u32 serial fits node_name (16) .name = if (c.file == .dir) (std.fmt.bufPrint(&p.fs.node_name, "{d}", .{c.serial}) catch unreachable) else c.file.fileName(), .node = Node.ofCol(c.serial, c.file), .dir = c.file == .dir, @@ -537,6 +538,7 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr { const pn = p.panes[id].?; if (t.file.inPty() and !pn.isTerminal()) return null; return .{ + // unreachable: a u32 serial fits node_name (16) .name = if (t.file == .dir) (std.fmt.bufPrint(&p.fs.node_name, "{d}", .{t.serial}) catch unreachable) else t.file.fileName(), .node = Node.of(t.serial, t.file), .dir = t.file.isDir(), @@ -582,6 +584,7 @@ fn topSize(p: *Pardes, f: TopFile) u64 { if (p.header_focus) break :focus 0; const pn = p.panes[p.active] orelse break :focus 0; var digits: [16]u8 = undefined; + // unreachable: a u32 serial and a newline fit 16 break :focus (std.fmt.bufPrint(&digits, "{d}\n", .{pn.serial}) catch unreachable).len; }, }; @@ -691,6 +694,7 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply { continue; } var buf: [16]u8 = undefined; + // unreachable: a u32 serial fits 16 const name = std.fmt.bufPrint(&buf, "{d}", .{serial}) catch unreachable; stageDirent(out, p.gpa, Node.of(serial, .dir), true, name); } @@ -702,6 +706,7 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply { } var buf: [16]u8 = undefined; const serial = layout.columnSerial(p, c); + // unreachable: a u32 serial fits 16 stageDirent(out, p.gpa, Node.ofCol(serial, .dir), true, std.fmt.bufPrint(&buf, "{d}", .{serial}) catch unreachable); }, else => return Reply.fail(req.tag, E.NOTDIR), |
