terminal: many more assertions around spacer state

This commit is contained in:
Mitchell Hashimoto
2024-03-24 20:27:47 -07:00
parent 1b8dc0c0c1
commit 36240b897c
3 changed files with 91 additions and 3 deletions

View File

@ -820,6 +820,15 @@ pub fn clearCells(
row: *Row, row: *Row,
cells: []Cell, cells: []Cell,
) void { ) void {
// This whole operation does unsafe things, so we just want to assert
// the end state.
page.pauseIntegrityChecks(true);
defer {
page.pauseIntegrityChecks(false);
page.assertIntegrity();
self.assertIntegrity();
}
// If this row has graphemes, then we need go through a slow path // If this row has graphemes, then we need go through a slow path
// and delete the cell graphemes. // and delete the cell graphemes.
if (row.grapheme) { if (row.grapheme) {
@ -866,8 +875,6 @@ pub fn clearCells(
} }
@memset(cells, self.blankCell()); @memset(cells, self.blankCell());
page.assertIntegrity();
self.assertIntegrity();
} }
/// Clear cells but only if they are not protected. /// Clear cells but only if they are not protected.

View File

@ -536,6 +536,12 @@ fn printCell(
.spacer_tail => { .spacer_tail => {
assert(self.screen.cursor.x > 0); assert(self.screen.cursor.x > 0);
// So integrity checks pass. We fix this up later so we don't
// need to do this without safety checks.
if (comptime std.debug.runtime_safety) {
cell.wide = .narrow;
}
const wide_cell = self.screen.cursorCellLeft(1); const wide_cell = self.screen.cursorCellLeft(1);
self.screen.clearCells( self.screen.clearCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.page.data,
@ -1564,13 +1570,22 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
// "scroll_amount" is the number of such cols. // "scroll_amount" is the number of such cols.
const scroll_amount = rem - adjusted_count; const scroll_amount = rem - adjusted_count;
if (scroll_amount > 0) { if (scroll_amount > 0) {
page.pauseIntegrityChecks(true);
defer page.pauseIntegrityChecks(false);
var x: [*]Cell = left + (scroll_amount - 1); var x: [*]Cell = left + (scroll_amount - 1);
// If our last cell we're shifting is wide, then we need to clear // If our last cell we're shifting is wide, then we need to clear
// it to be empty so we don't split the multi-cell char. // it to be empty so we don't split the multi-cell char.
const end: *Cell = @ptrCast(x); const end: *Cell = @ptrCast(x);
if (end.wide == .wide) { if (end.wide == .wide) {
self.screen.clearCells(page, self.screen.cursor.page_row, end[0..1]); const end_multi: [*]Cell = @ptrCast(end);
assert(end_multi[1].wide == .spacer_tail);
self.screen.clearCells(
page,
self.screen.cursor.page_row,
end_multi[0..2],
);
} }
// We work backwards so we don't overwrite data. // We work backwards so we don't overwrite data.

View File

@ -100,6 +100,11 @@ pub const Page = struct {
/// memory and is fixed at page creation time. /// memory and is fixed at page creation time.
capacity: Capacity, capacity: Capacity,
/// If this is true then verifyIntegrity will do nothing. This is
/// only present with runtime safety enabled.
pause_integrity_checks: if (std.debug.runtime_safety) bool else void =
if (std.debug.runtime_safety) false else void,
/// Initialize a new page, allocating the required backing memory. /// Initialize a new page, allocating the required backing memory.
/// The size of the initialized page defaults to the full capacity. /// The size of the initialized page defaults to the full capacity.
/// ///
@ -191,8 +196,18 @@ pub const Page = struct {
UnmarkedStyleRow, UnmarkedStyleRow,
MismatchedStyleRef, MismatchedStyleRef,
InvalidStyleCount, InvalidStyleCount,
InvalidSpacerTailLocation,
InvalidSpacerHeadLocation,
UnwrappedSpacerHead,
}; };
/// Temporarily pause integrity checks. This is useful when you are
/// doing a lot of operations that would trigger integrity check
/// violations but you know the page will end up in a consistent state.
pub fn pauseIntegrityChecks(self: *Page, v: bool) void {
if (comptime std.debug.runtime_safety) self.pause_integrity_checks = v;
}
/// A helper that can be used to assert the integrity of the page /// A helper that can be used to assert the integrity of the page
/// when runtime safety is enabled. This is a no-op when runtime /// when runtime safety is enabled. This is a no-op when runtime
/// safety is disabled. This uses the libc allocator. /// safety is disabled. This uses the libc allocator.
@ -220,6 +235,10 @@ pub const Page = struct {
// used for the same reason as styles above. // used for the same reason as styles above.
// //
if (comptime std.debug.runtime_safety) {
if (self.pause_integrity_checks) return;
}
if (self.size.rows == 0) { if (self.size.rows == 0) {
log.warn("page integrity violation zero row count", .{}); log.warn("page integrity violation zero row count", .{});
return IntegrityError.ZeroRowCount; return IntegrityError.ZeroRowCount;
@ -282,6 +301,53 @@ pub const Page = struct {
if (!gop.found_existing) gop.value_ptr.* = 0; if (!gop.found_existing) gop.value_ptr.* = 0;
gop.value_ptr.* += 1; gop.value_ptr.* += 1;
} }
switch (cell.wide) {
.narrow => {},
.wide => {},
.spacer_tail => {
// Spacer tails can't be at the start because they follow
// a wide char.
if (x == 0) {
log.warn(
"page integrity violation y={} x={} spacer tail at start",
.{ y, x },
);
return IntegrityError.InvalidSpacerTailLocation;
}
// Spacer tails must follow a wide char
const prev = cells[x - 1];
if (prev.wide != .wide) {
log.warn(
"page integrity violation y={} x={} spacer tail not following wide",
.{ y, x },
);
return IntegrityError.InvalidSpacerTailLocation;
}
},
.spacer_head => {
// Spacer heads must be at the end
if (x != self.size.cols - 1) {
log.warn(
"page integrity violation y={} x={} spacer head not at end",
.{ y, x },
);
return IntegrityError.InvalidSpacerHeadLocation;
}
// The row must be wrapped
if (!row.wrap) {
log.warn(
"page integrity violation y={} spacer head not wrapped",
.{y},
);
return IntegrityError.UnwrappedSpacerHead;
}
},
}
} }
// Check row grapheme data // Check row grapheme data