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
This commit is contained in:
ddidderr committed 2026-10-12 08:14:02 +02:00
1 parent 96314c1a8b
commit f6002d7519
22 files changed
+2079 -57

No files matched your search

@@ -353,6 +353,42 @@ impl LoadingImage {
Ok(())
}
/// The file name `debug_dump` writes (Zig `@tagName` spellings).
#[cfg(debug_assertions)]
#[must_use]
pub fn debug_dump_filename(&self) -> String {
let format = match self.image.format {
TransmissionFormat::Rgb => "rgb",
TransmissionFormat::Rgba => "rgba",
TransmissionFormat::Png => "png",
TransmissionFormat::GrayAlpha => "gray_alpha",
TransmissionFormat::Gray => "gray",
};
let compression = match self.image.compression {
TransmissionCompression::None => "none",
TransmissionCompression::ZlibDeflate => "zlib_deflate",
};
format!(
"image-{format}-{compression}-{}x{}-{}.data",
self.image.width, self.image.height, self.image.id
)
}
/// Debug function to write the data to a file in `dir`. This is useful
/// for capturing some test data for unit tests. Only exists in debug
/// builds (Zig: `@compileError("debugDump in non-debug")`).
#[cfg(debug_assertions)]
pub fn debug_dump_to(&self, dir: &std::path::Path) -> std::io::Result<()> {
std::fs::write(dir.join(self.debug_dump_filename()), &self.data)
}
/// Same as [`Self::debug_dump_to`] using the current working directory,
/// like Zig's `debugDump`.
#[cfg(debug_assertions)]
pub fn debug_dump(&self) -> std::io::Result<()> {
self.debug_dump_to(std::path::Path::new("."))
}
/// Adds a chunk of data to the image. Use this if the image
/// is coming in chunks (the "m" parameter in the protocol).
pub fn add_data(&mut self, data: &[u8]) -> Result<(), Error> {
@@ -623,6 +659,37 @@ mod tests {
use super::*;
#[cfg(debug_assertions)]
#[test]
fn loading_image_debug_dump_writes_data() {
let mut li = LoadingImage {
image: Image {
id: 7,
width: 3,
height: 2,
format: TransmissionFormat::GrayAlpha,
compression: TransmissionCompression::ZlibDeflate,
..Image::default()
},
data: vec![1, 2, 3, 4],
display: None,
quiet: Quiet::default(),
};
assert_eq!(
"image-gray_alpha-zlib_deflate-3x2-7.data",
li.debug_dump_filename()
);
let dir = std::env::temp_dir().join(format!("ghostty-dump-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
li.debug_dump_to(&dir).unwrap();
let got = std::fs::read(dir.join(li.debug_dump_filename())).unwrap();
assert_eq!(vec![1, 2, 3, 4], got);
li.image.format = TransmissionFormat::Png;
li.image.compression = TransmissionCompression::None;
assert_eq!("image-png-none-3x2-7.data", li.debug_dump_filename());
std::fs::remove_dir_all(&dir).unwrap();
}
const RGB_NONE_20X15: &[u8] =
include_bytes!("testdata/image-rgb-none-20x15-2147483647-raw.data");
const RGB_ZLIB_128X96: &[u8] =