diff --git a/src/terminal/ref_counted_set.zig b/src/terminal/ref_counted_set.zig index 8040039ae..dd76079f4 100644 --- a/src/terminal/ref_counted_set.zig +++ b/src/terminal/ref_counted_set.zig @@ -497,6 +497,15 @@ pub fn RefCountedSet( return self.lookupContext(base, value, self.context); } pub fn lookupContext(self: *const Self, base: anytype, value: T, ctx: Context) ?Id { + // A zero-capacity set (a valid special case of Layout.init) + // contains nothing and has a zero-size table, so we can't + // probe it: table[0] would read whatever memory follows the + // set in the backing buffer. + if (self.layout.table_cap == 0) { + @branchHint(.cold); + return null; + } + const table = self.table.ptr(base); const items = self.items.ptr(base); diff --git a/src/terminal/style.zig b/src/terminal/style.zig index ae9488af8..720789bb2 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -966,6 +966,36 @@ test "Set capacities" { _ = Set.Layout.init(16384); } +test "Set zero capacity" { + const testing = std.testing; + const alloc = testing.allocator; + + // A zero-capacity set is a valid special case (see Layout.init). + // It occurs in practice for pages with exact capacities where no + // cell is styled (see Page.exactRowCapacity). + const layout: Set.Layout = .init(0); + try testing.expectEqual(0, layout.total_size); + + // We allocate a nonzero buffer filled with 0xFF to simulate the + // set being embedded in a larger structure (e.g. a Page) where + // other data follows it. Lookups must not probe the zero-size + // table: table[0] would read this adjacent memory and treat it + // as an item ID, leading to far out-of-bounds item reads. + const buf = try alloc.alignedAlloc(u8, Set.base_align, 64); + defer alloc.free(buf); + @memset(buf, 0xFF); + + var set = Set.init(.init(buf), layout, .{}); + + const style: Style = .{ .flags = .{ .bold = true } }; + try testing.expectEqual(null, set.lookup(buf, style)); + try testing.expectError(error.OutOfMemory, set.add(buf, style)); + try testing.expectEqual(0, set.count()); + + // The adjacent memory must be untouched. + for (buf) |b| try testing.expectEqual(0xFF, b); +} + test "Style HTML formatting basic bold" { const testing = std.testing; const alloc = testing.allocator;