bug: PageList.resizeCols panics on unreachable reflowing hyperlinks — string_bytes precondition invalidated by increaseCapacity(.hyperlink_bytes)
#13522
Summary
This only aborts in safety-on builds. Ghostty's own app ships I found it via zmx, which embeds The defectIn // 1909: hyperlink capacity is topped up FIRST — fine.
if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
try self.increaseCapacity(list, .hyperlink_bytes);
}
// 1922-1941: establish the string_bytes precondition against *this* self.page
while (true) {
if (self.page.string_alloc.alloc(u8, self.page.memory,
additional_required_string_capacity)) |slice| {
self.page.string_alloc.free(self.page.memory, slice);
break;
} else |_| {
try self.increaseCapacity(list, .string_bytes);
}
}
const dst_link = src_link.dupe(src_page, self.page) catch |err| { ... }; // 1943 — precondition valid
const dst_id = self.page.hyperlink_set.addWithIdContext(...) catch |err| id: {
dst_link.free(self.page);
try self.increaseCapacity(list, switch (err) { // 1972 <-- invalidates it
error.OutOfMemory => .hyperlink_bytes,
error.NeedsRehash => null,
});
const dst_link2 = src_link.dupe(src_page, self.page) catch |err2| { // 1978
// This shouldn't fail since we did a capacity
// check above.
log.err("link dupe failed with capacity check err={}", .{err2});
if (comptime std.debug.runtime_safety) {
unreachable; // 1987 <-- panic
}
break :hyperlink;
};The comment at 1982-1983 is the bug: the capacity check "above" was performed against a page that no longer exists at line 1978.
const node = ... try list.increaseCapacity(self.node, adjustment);
errdefer comptime unreachable;
self.* = .init(node);
self.cursorAbsolute(old_x, old_y);
self.total_rows = old_total_rows;So after the The same stale-precondition shape exists at the third site (line ~2002, EvidenceTwo independent aborts, ~3h45m apart, byte-identical stacks. Symbolicated frames from the macOS crash reports (addresses, UUIDs and paths stripped):
The Crash 1 — column count 319 -> 159: Crash 2 — column count 239 -> 119: Note the signature in crash 1: Both crashes are a halving of the column count (319→159, 239→119), i.e. a vertical pane split / window halving, which forces a full-scrollback reflow. Affected versionThe crashing binary embeds I diffed the whole of For the avoidance of doubt, this is not a regression of #12907 / #12935 ( ReproductionI do not have a minimal repro, and I want to be upfront about that rather than pad the report. I drove ~700 scripted The static argument above stands on its own, though: line 1972 provably rebinds Suggested fixRe-establish the precondition after the capacity change rather than assuming it survived — e.g. hoist the Failing that, the two Happy to open a PR if you'd like it fixed this way. |
Fixed here: #13524
Thank you