mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-06 03:18:19 +00:00
PageList: increase capacity for hyperlink OOM during reflow
It's possible for the hyperlink or string capacity to be exceeded in a single row, in which case it doesn't matter if we move the row to a new page, it will still be a problem. This was causing actual crashes under some circumstances.
This commit is contained in:
@@ -1032,25 +1032,67 @@ const ReflowCursor = struct {
|
|||||||
const src_id = src_page.lookupHyperlink(cell).?;
|
const src_id = src_page.lookupHyperlink(cell).?;
|
||||||
const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
|
const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
|
||||||
|
|
||||||
// If our page can't support an additional cell with
|
// If our page can't support an additional cell
|
||||||
// a hyperlink ID then we create a new page for this row.
|
// with a hyperlink then we increase capacity.
|
||||||
if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
|
if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
try self.adjustCapacity(list, .{
|
||||||
|
.hyperlink_bytes = cap.hyperlink_bytes * 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the string alloc has sufficient capacity
|
||||||
|
// to dupe the link (and the ID if it's not implicit).
|
||||||
|
const additional_required_string_capacity =
|
||||||
|
src_link.uri.len +
|
||||||
|
switch (src_link.id) {
|
||||||
|
.explicit => |v| v.len,
|
||||||
|
.implicit => 0,
|
||||||
|
};
|
||||||
|
if (self.page.string_alloc.alloc(
|
||||||
|
u8,
|
||||||
|
self.page.memory,
|
||||||
|
additional_required_string_capacity,
|
||||||
|
)) |slice| {
|
||||||
|
// We have enough capacity, free the test alloc.
|
||||||
|
self.page.string_alloc.free(self.page.memory, slice);
|
||||||
|
} else |_| {
|
||||||
|
// Grow our capacity until we can
|
||||||
|
// definitely fit the extra bytes.
|
||||||
|
var new_string_capacity: usize = cap.string_bytes;
|
||||||
|
while (new_string_capacity - cap.string_bytes < additional_required_string_capacity) {
|
||||||
|
new_string_capacity *= 2;
|
||||||
|
}
|
||||||
|
try self.adjustCapacity(list, .{
|
||||||
|
.string_bytes = new_string_capacity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const dst_id = self.page.hyperlink_set.addWithIdContext(
|
const dst_id = self.page.hyperlink_set.addWithIdContext(
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
|
// We made sure there was enough capacity for this above.
|
||||||
try src_link.dupe(src_page, self.page),
|
try src_link.dupe(src_page, self.page),
|
||||||
src_id,
|
src_id,
|
||||||
.{ .page = self.page },
|
.{ .page = self.page },
|
||||||
) catch id: {
|
) catch |err| id: {
|
||||||
// We have no space for this link,
|
// If the add failed then either the set needs to grow
|
||||||
// so make a new page for this row.
|
// or it needs to be rehashed. Either one of those can
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
// be accomplished by adjusting capacity, either with
|
||||||
|
// no actual change or with an increased hyperlink cap.
|
||||||
|
try self.adjustCapacity(list, switch (err) {
|
||||||
|
error.OutOfMemory => .{
|
||||||
|
.hyperlink_bytes = cap.hyperlink_bytes * 2,
|
||||||
|
},
|
||||||
|
error.NeedsRehash => .{},
|
||||||
|
});
|
||||||
|
|
||||||
break :id try self.page.hyperlink_set.addContext(
|
// We assume this one will succeed. We dupe the link
|
||||||
|
// again, and don't have to worry about the other one
|
||||||
|
// because adjusting the capacity naturally clears up
|
||||||
|
// any managed memory not associated with a cell yet.
|
||||||
|
break :id try self.page.hyperlink_set.addWithIdContext(
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
try src_link.dupe(src_page, self.page),
|
try src_link.dupe(src_page, self.page),
|
||||||
|
src_id,
|
||||||
.{ .page = self.page },
|
.{ .page = self.page },
|
||||||
);
|
);
|
||||||
} orelse src_id;
|
} orelse src_id;
|
||||||
@@ -1150,6 +1192,22 @@ const ReflowCursor = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adjust the capacity of the current page.
|
||||||
|
fn adjustCapacity(
|
||||||
|
self: *ReflowCursor,
|
||||||
|
list: *PageList,
|
||||||
|
adjustment: AdjustCapacity,
|
||||||
|
) !void {
|
||||||
|
const old_x = self.x;
|
||||||
|
const old_y = self.y;
|
||||||
|
|
||||||
|
self.* = .init(try list.adjustCapacity(
|
||||||
|
self.node,
|
||||||
|
adjustment,
|
||||||
|
));
|
||||||
|
self.cursorAbsolute(old_x, old_y);
|
||||||
|
}
|
||||||
|
|
||||||
/// True if this cursor is at the bottom of the page by capacity,
|
/// True if this cursor is at the bottom of the page by capacity,
|
||||||
/// i.e. we can't scroll anymore.
|
/// i.e. we can't scroll anymore.
|
||||||
fn bottom(self: *const ReflowCursor) bool {
|
fn bottom(self: *const ReflowCursor) bool {
|
||||||
@@ -7862,6 +7920,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" {
|
|||||||
try testing.expectEqual(@as(u21, 'A'), cps[0]);
|
try testing.expectEqual(@as(u21, 'A'), cps[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "PageList resize reflow less cols cursor in wrapped row" {
|
test "PageList resize reflow less cols cursor in wrapped row" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@@ -185,6 +185,25 @@ pub const PageEntry = struct {
|
|||||||
other.uri.offset.ptr(other_base)[0..other.uri.len],
|
other.uri.offset.ptr(other_base)[0..other.uri.len],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Free the memory for this entry from its page.
|
||||||
|
pub fn free(
|
||||||
|
self: *const PageEntry,
|
||||||
|
page: *Page,
|
||||||
|
) void {
|
||||||
|
const alloc = &page.string_alloc;
|
||||||
|
switch (self.id) {
|
||||||
|
.implicit => {},
|
||||||
|
.explicit => |v| alloc.free(
|
||||||
|
page.memory,
|
||||||
|
v.offset.ptr(page.memory)[0..v.len],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
alloc.free(
|
||||||
|
page.memory,
|
||||||
|
self.uri.offset.ptr(page.memory)[0..self.uri.len],
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The set of hyperlinks. This is ref-counted so that a set of cells
|
/// The set of hyperlinks. This is ref-counted so that a set of cells
|
||||||
@@ -215,19 +234,7 @@ pub const Set = RefCountedSet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deleted(self: *const @This(), link: PageEntry) void {
|
pub fn deleted(self: *const @This(), link: PageEntry) void {
|
||||||
const page = self.page.?;
|
link.free(self.page.?);
|
||||||
const alloc = &page.string_alloc;
|
|
||||||
switch (link.id) {
|
|
||||||
.implicit => {},
|
|
||||||
.explicit => |v| alloc.free(
|
|
||||||
page.memory,
|
|
||||||
v.offset.ptr(page.memory)[0..v.len],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
alloc.free(
|
|
||||||
page.memory,
|
|
||||||
link.uri.offset.ptr(page.memory)[0..link.uri.len],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user