terminal: wrap implicit hyperlink identifiers

Implicit OSC 8 hyperlinks incremented a u32 identifier with checked arithmetic even though the cursor contract allows the sequence to wrap. Reaching the maximum identifier therefore caused a runtime safety panic before the link could be installed.

Use wrapping arithmetic for both the successful increment and the error rollback. The identifier now returns to zero at the boundary, while failed allocation attempts still restore the original value.
This commit is contained in:
Mitchell Hashimoto
2026-07-09 21:37:26 -07:00
parent b287f6d1ab
commit 04810273af

View File

@@ -2413,13 +2413,13 @@ pub fn startHyperlink(
.id = if (id_) |id| .{
.explicit = id,
} else implicit: {
defer self.cursor.hyperlink_implicit_id += 1;
defer self.cursor.hyperlink_implicit_id +%= 1;
break :implicit .{ .implicit = self.cursor.hyperlink_implicit_id };
},
};
errdefer switch (link.id) {
.explicit => {},
.implicit => self.cursor.hyperlink_implicit_id -= 1,
.implicit => self.cursor.hyperlink_implicit_id -%= 1,
};
// Loop until we have enough page memory to add the hyperlink
@@ -9996,6 +9996,43 @@ test "Screen: hyperlink start/end" {
}
}
test "Screen: implicit hyperlink ID wraps" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
s.cursor.hyperlink_implicit_id = std.math.maxInt(size.OffsetInt);
try s.startHyperlink("http://example.com", null);
try testing.expectEqual(@as(size.OffsetInt, 0), s.cursor.hyperlink_implicit_id);
try testing.expectEqual(
std.math.maxInt(size.OffsetInt),
s.cursor.hyperlink.?.id.implicit,
);
// A failed allocation must roll the wrapped counter back to its
// original value as well.
s.endHyperlink();
s.cursor.hyperlink_implicit_id = std.math.maxInt(size.OffsetInt);
var failing = testing.FailingAllocator.init(alloc, .{});
failing.fail_index = failing.alloc_index;
{
const original_alloc = s.alloc;
defer s.alloc = original_alloc;
s.alloc = failing.allocator();
try testing.expectError(
error.OutOfMemory,
s.startHyperlink("http://example.com", null),
);
}
try testing.expectEqual(
std.math.maxInt(size.OffsetInt),
s.cursor.hyperlink_implicit_id,
);
}
test "Screen: hyperlink reuse" {
const testing = std.testing;
const alloc = testing.allocator;