diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/9p_io.zig | 1 | ||||
| -rw-r--r-- | src/Mini.zig | 6 | ||||
| -rw-r--r-- | src/builtins.zig | 2 | ||||
| -rw-r--r-- | src/detached/server.zig | 1 | ||||
| -rw-r--r-- | src/esp32p4_9p.zig | 1 | ||||
| -rw-r--r-- | src/fonts.zig | 1 | ||||
| -rw-r--r-- | src/gui/gui.zig | 3 | ||||
| -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 | ||||
| -rw-r--r-- | src/pdf_view.zig | 2 |
12 files changed, 45 insertions, 2 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig index 560c4ed6..91240b57 100644 --- a/src/9p_io.zig +++ b/src/9p_io.zig @@ -1638,6 +1638,7 @@ extern "c" fn unsetenv(name: [*:0]const u8) c_int; pub fn start(io: std.Io, gpa: std.mem.Allocator, core: *pardes.Pardes) ?*Listener { var name: [16]u8 = undefined; + // unreachable: a u32 is at most 10 digits const fallback = std.fmt.bufPrint(&name, "{d}", .{@as(u32, @intCast(libc.getpid()))}) catch unreachable; return listen(io, gpa, core, core.opts.ninep_name, fallback, core.opts.ninep_tcp, core.opts.ninep_quic) orelse { core.reportError(0, "9p listener", error.ListenFailed); diff --git a/src/Mini.zig b/src/Mini.zig index ac9a8e85..a6aef15a 100644 --- a/src/Mini.zig +++ b/src/Mini.zig @@ -49,6 +49,7 @@ const Row = struct { const end = modal.nextGrapheme(row.text, row.at); const grapheme = row.text[row.at..end]; row.left = File.graphemeDisplayWidth(grapheme); + // unreachable below: `generate` refuses a source that is no UTF-8 const n = std.unicode.utf8ByteSequenceLength(grapheme[0]) catch unreachable; const cp = std.unicode.utf8Decode(grapheme[0..n]) catch unreachable; const blank = switch (cp) { @@ -108,6 +109,7 @@ fn render(output: ?Result, source: []const u8, styles: []const u8) !usize { if (output) |out| { @memset(out.content[offset..][0..spaces], ' '); @memset(out.colors[offset..][0..spaces], 0); + // unreachable: U+2800 plus a u8 mask is a braille codepoint, always three bytes _ = std.unicode.utf8Encode(@as(u21, 0x2800) + mask, out.content[offset + spaces ..][0..3]) catch unreachable; @memset(out.colors[offset + spaces ..][0..3], color); } @@ -313,3 +315,7 @@ test "Mini publishes only complete snapshots and content replacement drops metad const path = try std.fmt.bufPrint(&path_buf, "{s}/mini.txt", .{dir}); try std.testing.checkAllAllocationFailures(std.testing.allocator, Case.run, .{path}); } + +test "Mini refuses a file that is no UTF-8, before its decoding could panic" { + try std.testing.expectError(error.InvalidUtf8, generate(std.testing.allocator, "ok \xff\xfe bad\n\xc3", "")); +} diff --git a/src/builtins.zig b/src/builtins.zig index 9a49df16..8788e65f 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -1827,6 +1827,7 @@ const Board = struct { writeWord(addr, value); const back = readWord(addr); var buf: [96]u8 = undefined; + // unreachable below: three 8-digit hex words and 18 bytes fit 96 p.setMessage(id, std.fmt.bufPrint( &buf, "{x:0>8}: wrote {x:0>8}, reads {x:0>8}", @@ -1849,6 +1850,7 @@ const Board = struct { if (!toggle(p.host.ctx, pin, &was, &now)) return Error.BadPin; var buf: [48]u8 = undefined; + // unreachable: three small numbers and 10 bytes fit 48 p.setMessage(id, std.fmt.bufPrint(&buf, "GPIO {d}: {d}->{d}", .{ pin, was, now }) catch unreachable); } diff --git a/src/detached/server.zig b/src/detached/server.zig index 676b5b95..b536e4a7 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -1214,6 +1214,7 @@ pub const Session = struct { fn refuseFd(_: *Session, fd: c_int, why: wire.Refusal) void { var buf: [wire.header_len + 1]u8 = undefined; + // unreachable: a refusal is a header and one byte, which buf is sized to const bytes = wire.encodeServer(&buf, .{ .refuse = why }) catch unreachable; var off: usize = 0; while (off < bytes.len) { diff --git a/src/esp32p4_9p.zig b/src/esp32p4_9p.zig index e23235a0..cd59b9df 100644 --- a/src/esp32p4_9p.zig +++ b/src/esp32p4_9p.zig @@ -103,6 +103,7 @@ fn die(msg: []const u8) noreturn { .{ .rerror = .{ .ename = msg[0..@min(msg.len, ninep.errmax)] } }, ninep.notag, &buf, + // unreachable: the message is cut to errmax and buf is sized to it ) catch unreachable; uart.write(bytes); while (true) {} diff --git a/src/fonts.zig b/src/fonts.zig index 4cfba44c..098bf36f 100644 --- a/src/fonts.zig +++ b/src/fonts.zig @@ -324,6 +324,7 @@ fn sfntBase(head: []const u8) ?usize { fn scratchFont(buf: *[64:0]u8, ext: []const u8) [:0]const u8 { return std.fmt.bufPrintSentinel(buf, "/tmp/pardes-fonts-test-{d}{s}", .{ @as(u32, @intCast(libc.getpid())), ext, + // unreachable: 23 bytes, a u32 pid (10) and a short extension fit 64 }, 0) catch unreachable; } diff --git a/src/gui/gui.zig b/src/gui/gui.zig index e110b3ab..b34b314d 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -937,6 +937,8 @@ const Queue = struct { completion_len: usize = 0, fn lock(q: *Queue) void { + // unreachable failures: pthread calls on this queue's own initialised, non-recursive + // mutex and condition fail only on misuse (EINVAL, EDEADLK), never on input. std.debug.assert(libc.pthread_mutex_lock(&q.mutex) == .SUCCESS); } @@ -8334,6 +8336,7 @@ fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, t const pixels: [*]const u8 = @ptrCast(mapped); try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, "latest"); var name: [24]u8 = undefined; + // unreachable below: "transition-" or "frame-" and a u32 fit 24 if (transition) |frame| try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, std.fmt.bufPrint(&name, "transition-{d}", .{frame}) catch unreachable); if (g.capture_series) try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, std.fmt.bufPrint(&name, "frame-{d:0>5}", .{g.captured}) catch unreachable); g.captured +%= 1; 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), diff --git a/src/pdf_view.zig b/src/pdf_view.zig index 58dd2c3c..8f842cde 100644 --- a/src/pdf_view.zig +++ b/src/pdf_view.zig @@ -2763,6 +2763,7 @@ pub const SectionRows = if (enabled) struct { for (entries, 0..) |entry, ordinal| { const resolved = ordinals[ordinal]; if (resolved == std.math.maxInt(usize)) continue; + // unreachable: resolveOrdinals resolves only to usable destinations const destination = usableDestination(entries[resolved].destination) orelse unreachable; total += switch (destination) { .internal => |internal| std.fmt.count("{s}:{d}:{d} ", .{ target, internal.page + 1, ordinal + 1 }), @@ -2777,6 +2778,7 @@ pub const SectionRows = if (enabled) struct { for (entries, 0..) |entry, ordinal| { const resolved = ordinals[ordinal]; if (resolved == std.math.maxInt(usize)) continue; + // unreachable: resolveOrdinals resolves only to usable destinations const destination = usableDestination(entries[resolved].destination) orelse unreachable; const prefix = switch (destination) { .internal => |internal| try std.fmt.bufPrint(out[at..], "{s}:{d}:{d} ", .{ target, internal.page + 1, ordinal + 1 }), |
