From dace6d1c6d4a4351e9abc1d9816ed6dd9cb5a446 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Jul 2026 21:53:38 -0700 Subject: [PATCH] terminal: handle aliased title updates setTitle can receive the slice returned by getTitle. Clearing the list retained its allocation, so appending that same slice used memcpy with aliased source and destination ranges and panicked in runtime-safe builds. Resize the list within its reserved capacity and copy the value forward before writing the sentinel. This supports the complete current value and its subslices without weakening allocation-failure atomicity. --- src/terminal/Terminal.zig | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 64b8d9452..e5bfd93b7 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -3627,9 +3627,9 @@ pub fn setTitle(self: *Terminal, t: []const u8) !void { return error.OutOfMemory; try self.title.ensureTotalCapacity(self.gpa(), capacity); - self.title.clearRetainingCapacity(); - self.title.appendSliceAssumeCapacity(t); - self.title.appendAssumeCapacity(0); + self.title.items.len = capacity; + std.mem.copyForwards(u8, self.title.items[0..t.len], t); + self.title.items[t.len] = 0; } /// Returns the title for the terminal, if any. The memory is owned by the @@ -3651,6 +3651,15 @@ test "Terminal: setTitle preserves a sentinel on allocation failure" { try testing.expect(t.getTitle() == null); } +test "Terminal: setTitle accepts its current value" { + var t = try init(testing.allocator, .{ .cols = 5, .rows = 1 }); + defer t.deinit(testing.allocator); + + try t.setTitle("Ghostty"); + try t.setTitle(t.getTitle().?); + try testing.expectEqualStrings("Ghostty", t.getTitle().?); +} + /// Switch to the given screen type (alternate or primary). /// /// This does NOT handle behaviors such as clearing the screen,