From cfce1cd56c339bd23f92a3434f2b0a7f4573a69b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Jul 2026 21:55:08 -0700 Subject: [PATCH] terminal: duplicate hyperlinks before replacement startHyperlink accepts borrowed URI and ID slices. When those slices came from the current cursor hyperlink, startHyperlinkOnce ended and freed that hyperlink before duplicating the replacement, then dereferenced the released URI and segfaulted. Duplicate the new hyperlink before ending the prior one. Aliased inputs remain valid through the copy, and allocation failure leaves the existing cursor hyperlink intact. --- src/terminal/Screen.zig | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 03710352d..8798411b0 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -2460,9 +2460,6 @@ fn startHyperlinkOnce( self: *Screen, source: hyperlink.Hyperlink, ) (Allocator.Error || Page.InsertHyperlinkError)!void { - // End any prior hyperlink - self.endHyperlink(); - // Allocate our new Hyperlink entry in non-page memory. This // lets us quickly get access to URI, ID. const link = try self.alloc.create(hyperlink.Hyperlink); @@ -2470,6 +2467,10 @@ fn startHyperlinkOnce( link.* = try source.dupe(self.alloc); errdefer link.deinit(self.alloc); + // End any prior hyperlink only after duplicating the new value. The + // source slices are allowed to reference our current hyperlink. + self.endHyperlink(); + // Insert the hyperlink into page memory var page = self.cursor.page_pin.node.page(); const id: hyperlink.Id = try page.insertHyperlink(link.*); @@ -10033,6 +10034,21 @@ test "Screen: hyperlink start/end" { } } +test "Screen: hyperlink accepts its current values" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); + defer s.deinit(); + + try s.startHyperlink("http://example.com", "current"); + const current = s.cursor.hyperlink.?; + try s.startHyperlink(current.uri, current.id.explicit); + + try testing.expectEqualStrings("http://example.com", s.cursor.hyperlink.?.uri); + try testing.expectEqualStrings("current", s.cursor.hyperlink.?.id.explicit); +} + test "Screen: implicit hyperlink ID wraps" { const testing = std.testing; const alloc = testing.allocator;