mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-05 07:08:39 +00:00
datastruct/circ_buf: fix metadata after shrinking (#13515)
## Summary - normalize circular-buffer metadata after every resize - retain the oldest values when shrinking below the current length - cover partial shrink, exact-length shrink, and empty-to-zero boundaries ## Root cause `resize` rotated live values to index zero before reallocating, but only repaired `head` and `full` when capacity grew. Shrinking a partially filled buffer could therefore leave `head` beyond the new allocation and report a length greater than capacity. A later append could index outside the resized storage. ## Validation - `zig fmt --check src/datastruct/circ_buf.zig` - `zig test src/circ_buf_test.zig --test-filter 'CircBuf resize'` using a temporary import harness: 8 tests passed
This commit is contained in:
@@ -167,18 +167,18 @@ pub fn CircBuf(comptime T: type, comptime default: T) type {
|
||||
const prev_len = self.len();
|
||||
const prev_cap = self.storage.len;
|
||||
self.storage = try alloc.realloc(self.storage, size);
|
||||
const retained_len = @min(prev_len, size);
|
||||
|
||||
// If we grew, we need to set our new defaults. We can add it
|
||||
// at the end since we rotated to start.
|
||||
if (size > prev_cap) {
|
||||
@memset(self.storage[prev_cap..], default);
|
||||
|
||||
// Fix up our head/tail
|
||||
if (self.full) {
|
||||
self.head = prev_len;
|
||||
self.full = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix up our head/tail for the new capacity.
|
||||
self.head = if (retained_len == size) 0 else retained_len;
|
||||
self.tail = 0;
|
||||
self.full = retained_len == size;
|
||||
}
|
||||
|
||||
/// Rotate the data so that it is zero-aligned.
|
||||
@@ -793,6 +793,61 @@ test "CircBuf resize shrink" {
|
||||
}
|
||||
}
|
||||
|
||||
test "CircBuf resize shrink partially full" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const Buf = CircBuf(u8, 0);
|
||||
var buf = try Buf.init(alloc, 5);
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try buf.append(1);
|
||||
try buf.append(2);
|
||||
try buf.append(3);
|
||||
|
||||
try buf.resize(alloc, 2);
|
||||
try testing.expect(buf.full);
|
||||
try testing.expectEqual(@as(usize, 2), buf.len());
|
||||
try testing.expectEqual(@as(usize, 2), buf.capacity());
|
||||
|
||||
var it = buf.iterator(.forward);
|
||||
try testing.expectEqual(@as(u8, 1), it.next().?.*);
|
||||
try testing.expectEqual(@as(u8, 2), it.next().?.*);
|
||||
try testing.expect(it.next() == null);
|
||||
try testing.expectError(error.OutOfMemory, buf.append(4));
|
||||
}
|
||||
|
||||
test "CircBuf resize shrink to length" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const Buf = CircBuf(u8, 0);
|
||||
var buf = try Buf.init(alloc, 5);
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try buf.append(1);
|
||||
try buf.append(2);
|
||||
try buf.resize(alloc, 2);
|
||||
|
||||
try testing.expect(buf.full);
|
||||
try testing.expectEqual(@as(usize, 2), buf.len());
|
||||
try testing.expectEqual(@as(usize, 2), buf.capacity());
|
||||
}
|
||||
|
||||
test "CircBuf resize empty to zero" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const Buf = CircBuf(u8, 0);
|
||||
var buf = try Buf.init(alloc, 5);
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try buf.resize(alloc, 0);
|
||||
try testing.expect(buf.full);
|
||||
try testing.expectEqual(@as(usize, 0), buf.len());
|
||||
try testing.expectEqual(@as(usize, 0), buf.capacity());
|
||||
}
|
||||
|
||||
test "CircBuf append empty slice" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
Reference in New Issue
Block a user