summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/9p_io.zig1
-rw-r--r--src/Mini.zig6
-rw-r--r--src/builtins.zig2
-rw-r--r--src/detached/server.zig1
-rw-r--r--src/esp32p4_9p.zig1
-rw-r--r--src/fonts.zig1
-rw-r--r--src/gui/gui.zig3
-rw-r--r--src/ninep/ctl.zig1
-rw-r--r--src/ninep/pane.zig3
-rw-r--r--src/ninep/pty.zig21
-rw-r--r--src/ninep/tree.zig5
-rw-r--r--src/pdf_view.zig2
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 }),