Files
ddidderr 8af6ab68d3 fix: C allocator contract, shadertoy overloads/MSL stride, minidumps
Closes the remaining library rows of the final completeness audit
(docs/rewrite/notes/final-Z3.md):
- libghostty-vt: OSC parser storage goes through the caller-supplied C
  allocator via a ByteStorage abstraction; ghostty_osc_new reports
  GHOSTTY_OUT_OF_MEMORY and failed grows invalidate the OSC like Zig.
  Tested with a counting/failing/limiting C allocator.
- shadertoy: sampler parameter rewriting is keyed by full signature so
  overloads work; the Metal uniform block keeps std140 strides for
  iChannelTime[4] (MSL offsets checked against the upload layout).
- tripwire.zig ported (named trip points, hit counts, verification) and
  used to port the previously skipped allocation-failure tests that have
  a fallible Rust path; the rest are documented.
- crash: fatal signals capture siginfo + registers and a forked helper
  writes a minidump (minidump-writer) as an envelope attachment, like
  sentry-native/Breakpad; tests parse it with the minidump crate.
- kitty LoadingImage::debug_dump (debug builds) and the Darwin
  Instruments signpost around benchmark runs.

Test Plan:
- cargo test -p ghostty-vt-capi --test c_allocator
- cargo test -p ghostty-crash --test minidump
2026-10-12 08:14:02 +02:00

6.1 KiB

Z3 final fixes (vt-capi, shadertoy, tripwire, crash, kitty debug dump, bench signpost)

Supersedes the stale statements in libghostty-vt.md ("interior collections use the global allocator"), crash-CR.md ("no minidump") and macos-M1-renderer.md ("iChannelTime stride limitation").

1. C allocator governs parser storage (lib/allocator.zig, terminal/c/osc.zig)

  • ghostty-vt::osc gets a small allocator abstraction: trait ByteStorage (fallible try_reserve/try_extend, reset releases growth) behind Parser::with_storage; default stays a Vec<u8> (also implements the trait). A failed grow sets the parser state invalid, exactly like Zig's WriteFailed; a failed 2048-byte reserve when switching to the allocating writer falls back to the fixed buffer (Zig initCapacity catch).
  • ghostty-vt-capi/alloc.rs: CByteStorage (payload buffer, 2048 bytes allocated at ghostty_osc_new, growth by alloc-copy-free through the vtable) and CBlock (NUL-terminated title copy). ghostty_osc_new returns GHOSTTY_OUT_OF_MEMORY when the parser object or its buffer cannot be allocated; if the title copy fails, ghostty_osc_end reports an invalid command. OscCommand now holds the type key + C-allocated title only.
  • Remaining global-allocator use: transient Command payload copies made inside ghostty_osc_end (Zig borrows the parser buffer); they never outlive the call.
  • Tests: tests/c/alloc_test.c + tests/c_allocator.rs (counting/failing/limiting C allocator: leak-free accounting, OOM at each creation allocation, live-bytes limit invalidating a large OSC 52, title-copy failure, mid-payload failure then reuse); unit test storage_failure_invalidates_osc in ghostty-vt.

2. Shadertoy

  • shadertoy_sampler.rs: functions keyed by full signature (per-parameter sampler layout, all overloads kept); a call picks the overload by arity and which arguments are samplers (iChannelN or a sampler param of the enclosing function). Tests overloads_are_keyed_by_signature, overloads_through_sampler_parameters.
  • shadertoy.rs: msl_from_compiled post-processes naga's MSL: uniform-block scalar arrays with std140 stride (iChannelTime[4], found by reflection over the naga module, not by name) become metal::vec<T,N> inner[K] wrappers and element reads get .x. Limitation: copying the whole array into a local variable is not rewritten. Test msl_uniform_block_matches_std140_upload_layout parses the emitted struct Globals, computes MSL offsets/size and compares every member to offset_of!(Uniforms, ..) and size_of::<Uniforms>() (4496).

3. Tripwire (tripwire.zig)

  • ghostty-terminal/src/tripwire.rs: tripwire_module!(name, PointEnum, Error) -> name::{check, error_always, error_after, end(ResetMode), reset}; end returns UntrippedError (warn-logs untripped points); state is thread-local so parallel tests are isolated; check is a no-op outside cfg(test). 5 tests ported/added (the 3 Zig tests + retain/reset + thread isolation).
  • Ported OOM tests: Tabstops: resize alloc failure preserves state (tabstops.rs, resize_tw); PageList init error (page_list.rs, init_tw + init_pages_tw; Zig init_memory_pool has no Rust analogue (no pool), viewport_pin/viewport_pin_track are both checked before the single track_pin reserve); both reloadActive partial history cleanup tests (search/screen.rs, reload_active_tw; assert OOM is returned and history_results is unchanged).
  • Not portable (infallible Rust code, documented): selectionString map allocation failure cleanup (selection_string* return String/Vec, no fallible copy; nothing to unwind). Font: Atlas init/reserve/grow errors (Atlas storage is infallible Vec) and SharedGrid init error (no allocation points; the render-rollback test already exists as render_glyph_error_after_cache_insert_rolls_back_cache_entry). ghostty-font can use ghostty_terminal::tripwire if fallible paths are added later.
  • Fixed the enum_variant_names clippy lint reported on InitPagesPoint (variants renamed Node/BufStd/BufNonStd).

4. Crash minidump (crash/sentry.zig)

  • Linux fatal-signal path: handler keeps building head+event in static memory, then captures siginfo + machine context (+FP state) into a static crash_context::CrashContext and calls minidump::capture: raw clone (fork semantics, own 4 MiB mmap'd stack because the handler runs on a tiny altstack, no atfork handlers), PR_SET_PTRACER_ANY, parent polls waitpid with a 20 s timeout. The child uses minidump-writer 0.13 (MinidumpWriterConfig + set_crash_context, the crate's documented clone-and-ptrace pattern for the local process) and writes the full envelope: event + minidump.dmp attachment (attachment_type: event.minidump). Any failure falls back to the previous event-only envelope. Stack overflow: if the SP page is unreadable (guard page) the writer is retried with SP moved to the first readable page above it (reported RSP is then approximate, stack contents captured).
  • Tests (crates/ghostty-crash/tests/minidump.rs): SIGSEGV, SIGABRT and stack overflow on a spawned thread; parse with the minidump crate; assert thread list contains the crashing tid with stack memory and the main thread, exception stream blames that tid with the signal number and registers, modules/memory lists present. Existing root tests/crash.rs still pass.
  • Not done: macOS/Windows native minidumps (sentry-native uses its own backend there); no symbol upload.

5. kitty debugDump, bench signpost

  • LoadingImage::{debug_dump_filename, debug_dump_to, debug_dump} (debug builds only), same file name format as Zig; test.
  • ghostty-bench/src/signpost.rs (apple only): os_log_create + os_signpost_id_make_with_pointer + _os_signpost_emit_with_name_impl interval begin/end around the benchmark loop, subsystem com.mitchellh.ghostty, category PointsOfInterest, name ghostty. Compile/clippy-checked for aarch64-apple-darwin only (not run).

Gate

workspace cargo clippy --all-targets -D warnings, cargo +nightly fmt --check, cargo test --workspace pass; aarch64-apple-darwin clippy passes for bench, renderer, crash, terminal, vt-capi.