From 26b104c9e0cc2c013480cc378cf62836cc24d64f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Dec 2025 10:43:35 -0800 Subject: [PATCH] terminal: Fix possible crash on RenderState with invalid mouse point Fixes #10032 --- src/terminal/render.zig | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/terminal/render.zig b/src/terminal/render.zig index 093476f2c..9d75fe4b7 100644 --- a/src/terminal/render.zig +++ b/src/terminal/render.zig @@ -816,6 +816,12 @@ pub const RenderState = struct { const row_pins = row_slice.items(.pin); const row_cells = row_slice.items(.cells); + // Our viewport point is sent in by the caller and can't be trusted. + // If it is outside the valid area then just return empty because + // we can't possibly have a link there. + if (viewport_point.x >= self.cols or + viewport_point.y >= row_pins.len) return result; + // Grab our link ID const link_pin: PageList.Pin = row_pins[viewport_point.y]; const link_page: *page.Page = &link_pin.node.data; @@ -1360,6 +1366,44 @@ test "linkCells with scrollback spanning pages" { try testing.expectEqual(@as(usize, 4), cells.count()); } +test "linkCells with invalid viewport point" { + const testing = std.testing; + const alloc = testing.allocator; + + var t = try Terminal.init(alloc, .{ + .cols = 10, + .rows = 5, + }); + defer t.deinit(alloc); + + var s = t.vtStream(); + defer s.deinit(); + + var state: RenderState = .empty; + defer state.deinit(alloc); + try state.update(alloc, &t); + + // Row out of bound + { + var cells = try state.linkCells( + alloc, + .{ .x = 0, .y = t.rows + 10 }, + ); + defer cells.deinit(alloc); + try testing.expectEqual(0, cells.count()); + } + + // Col out of bound + { + var cells = try state.linkCells( + alloc, + .{ .x = t.cols + 10, .y = 0 }, + ); + defer cells.deinit(alloc); + try testing.expectEqual(0, cells.count()); + } +} + test "dirty row resets highlights" { const testing = std.testing; const alloc = testing.allocator;