summaryrefslogtreecommitdiff
path: root/src/snapshot.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-15 11:31:50 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-15 11:31:50 +0300
commitacd338ea33de36d223cc6d301712eed4a9501e6c (patch)
treed93c46a97e2ed4e8a86e3402de5f847cdc144ec8 /src/snapshot.c
parentf97c3a585e2efc1d0a4bc10e6ac04b96ee6428ea (diff)
Fix snapshot storage and allocation failure correctness
Account for cluster records discarded by snapshot scrolling, propagate snapshot allocation failures with an empty retryable view, and allocate resized screens before mutating visible state. Add bounded-memory and allocation-failure regressions, and update the saved diagnostic and audit report.
Diffstat (limited to 'src/snapshot.c')
-rw-r--r--src/snapshot.c59
1 files changed, 47 insertions, 12 deletions
diff --git a/src/snapshot.c b/src/snapshot.c
index 29ed98c..6578c9a 100644
--- a/src/snapshot.c
+++ b/src/snapshot.c
@@ -73,6 +73,20 @@ static bool reserve(struct term_snapshot *s, int cols, int rows)
return true;
}
+/* The public contract makes every failed update an empty, retryable snapshot. */
+static void invalidate(struct term_snapshot *s)
+{
+ s->view = (struct term_view){ .palette = s->palette };
+ s->source = 0;
+ s->arena_len = 0;
+ s->arena_dead = 0;
+ if (s->row_cap > 0) {
+ memset(s->keys, 0, (size_t)s->row_cap * sizeof(*s->keys));
+ memset(s->row_clusters, 0, (size_t)s->row_cap * sizeof(*s->row_clusters));
+ memset(s->selection, 0, (size_t)s->row_cap * sizeof(*s->selection));
+ }
+}
+
/* Attribute contributions of the low and high byte of cell flags. */
static uint16_t lo_attrs[256], hi_attrs[256];
static uint8_t hi_underline[256];
@@ -100,7 +114,8 @@ void term_snapshot_tables_init(void)
}
}
-static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint32_t handle)
+static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint32_t handle,
+ bool *ok)
{
uint32_t cps[GRAPHEME_MAX_LEN];
int n = term_gstore_expand(g, handle, cps, GRAPHEME_MAX_LEN);
@@ -110,8 +125,10 @@ static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint3
uint32_t cap = term_grow_cap32(s->arena_cap ? s->arena_cap * 2 : 256,
s->arena_len + (uint32_t)n + 1);
uint32_t *a = realloc(s->arena, cap * sizeof(*a));
- if (!a)
- return cps[0];
+ if (!a) {
+ *ok = false;
+ return 0;
+ }
s->arena = a;
s->arena_cap = cap;
}
@@ -125,7 +142,7 @@ static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint3
/* Convert one stored row; returns the number of arena words it added. */
static uint32_t convert_row(struct term_snapshot *s, const struct gstore *g,
struct term_cell *dst,
- const struct cell *src, int cols)
+ const struct cell *src, int cols, bool *ok)
{
uint32_t before = s->arena_len;
for (int x = 0; x < cols; x++) {
@@ -138,8 +155,11 @@ static uint32_t convert_row(struct term_snapshot *s, const struct gstore *g,
uint32_t text = c->c;
if (TERM_EXPECT(text < 0x20, 0))
text = text ? ' ' : 0;
- else if (TERM_EXPECT(grapheme_is_handle(text), 0))
- text = arena_add(s, g, text);
+ else if (TERM_EXPECT(grapheme_is_handle(text), 0)) {
+ text = arena_add(s, g, text, ok);
+ if (!*ok)
+ return 0;
+ }
dst[x] = (struct term_cell){
.text = text,
.fg = c->fg,
@@ -158,8 +178,7 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t)
return false;
int cols = t->cols, rows = t->rows;
if (!reserve(s, cols, rows)) {
- s->view.size = (struct term_vector2i){ 0 };
- s->source = 0;
+ invalidate(s);
return false;
}
@@ -175,13 +194,17 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t)
bool changed = all;
term_hist_prepare_rows(&t->hist, cols);
- if (!term_hist_view_sync(&t->view, &t->hist, cols))
+ if (!term_hist_view_sync(&t->view, &t->hist, cols)) {
+ invalidate(s);
return false;
+ }
term_hist_view_refine(&t->view, &t->hist, cols, rows);
uint64_t stack_keys[256];
uint64_t *new_keys = rows <= 256 ? stack_keys : malloc((size_t)rows * sizeof(uint64_t));
- if (!new_keys)
+ if (!new_keys) {
+ invalidate(s);
return false;
+ }
for (int y = 0; y < rows; y++)
new_keys[y] = term_screen_view_row_key(t, y);
@@ -230,6 +253,8 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t)
if (scrolled > 0) {
int k = scrolled;
+ for (int y = 0; y < k; y++)
+ s->arena_dead += s->row_clusters[y];
memmove(s->cells, s->cells + (size_t)k * cols,
(size_t)(rows - k) * cols * sizeof(struct term_cell));
memmove(s->row_clusters, s->row_clusters + k,
@@ -247,7 +272,15 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t)
if (!all)
s->arena_dead += s->row_clusters[y];
const struct cell *src = term_screen_view_row(t, y);
- s->row_clusters[y] = convert_row(s, t->gstore, s->cells + (size_t)y * cols, src, cols);
+ bool converted = true;
+ s->row_clusters[y] = convert_row(s, t->gstore, s->cells + (size_t)y * cols,
+ src, cols, &converted);
+ if (!converted) {
+ if (new_keys != stack_keys)
+ free(new_keys);
+ invalidate(s);
+ return false;
+ }
s->keys[y] = key;
changed = true;
}
@@ -256,8 +289,10 @@ static bool term_snapshot_update_inner(struct term_snapshot *s, struct term *t)
int stack[2][256];
int *c0 = rows <= 256 ? stack[0] : malloc((size_t)rows * 2 * sizeof(int));
- if (!c0)
+ if (!c0) {
+ invalidate(s);
return false;
+ }
int *c1 = rows <= 256 ? stack[1] : c0 + rows;
term_selection_view_spans(t, c0, c1);
for (int y = 0; y < rows; y++) {