fix(runtime): don't deref a poisoned class's NULL fmap during migration

- wo_schema_diff poisons a class (fmap=NULL, new_cid=NONE) when a
  referenced/nested type changed or a field type is incompatible — the
  field-level map does not apply and wo_wal_migrate transcodes it instead.
- main.c's pre-migration "migrating `X`: +/-fields" print loop dereferenced
  fmap unconditionally, so a poisoned-but-field-added class (e.g. a wmux
  Window whose nested Vte gained fields) was a NULL read → SIGSEGV at boot,
  before the migrate call could refuse or transcode.
- guard the field detail on fmap != NULL; for a poisoned class print
  "(a referenced type changed — cannot migrate in place)" and let
  wo_wal_migrate proceed. It then transcodes cleanly when no record blocks
  it — so an additive nested change (Vte +oscbuf +title) now migrates and
  the session replays, instead of crashing serve.
- test_wal 5966/0; verified against the real WAL that crashed (recovers
  session `main`); wmux gate 52/0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 35efa214d20dd7b055910ae66e4bfcc6201821c9)
This commit is contained in:
shoney.arickathil 2026-09-04 13:27:20 +02:00
parent 5e8e0960bc
commit 1d78e0fa70

View file

@ -338,6 +338,12 @@ int main(int argc, char **argv) {
const wo_schema_class *ok = &stored->classes[c];
fprintf(stderr, "wovm: %s: migrating `%.*s`:", wal_path,
(int)ok->name_len, (const char *)ok->name);
/* A poisoned class (a referenced/nested type changed, or a
* field's type is incompatible) has fmap == NULL and
* new_cid == NONE — the field-level diff does not apply.
* Dereferencing fmap here was a NULL read (SEGV); name the
* situation and let wo_wal_migrate below refuse cleanly. */
if (plan.classes[c].fmap) {
for (uint32_t f = 0; f < ok->field_cnt; f++)
if (plan.classes[c].fmap[f] == -1)
fprintf(stderr, " -%.*s", (int)ok->fields[f].name_len,
@ -352,6 +358,9 @@ int main(int argc, char **argv) {
fprintf(stderr, " +%.*s", (int)nk->fields[f].name_len,
(const char *)nk->fields[f].name);
}
} else {
fprintf(stderr, " (a referenced type changed — cannot migrate in place)");
}
fprintf(stderr, "\n");
}
char *merr = NULL;