Files
ghostty-rs/crates/ghostty-lib/src/benchmark.rs
T
ddidderr da9599140b fix: C action buffer lengths, byte-faithful callbacks, runtime finalize
Resolves the 8 gaps in docs/rewrite/notes/review-rv10-codex.md
(final-Z13.md):
- P1 memory safety: the C key-table action (and hovered-link/open-URL
  payloads) could advertise a length larger than the NUL-truncated
  allocation. A counted_bytes helper now passes the full bytes plus a
  trailing NUL with len == byte count, like Zig's backing slices; all
  C pointer+len structs were audited and tested to read exactly len.
- ghostty-bench args follow cli/args.zig (ordered duplicates, empty
  value resets, first bad value errors, parseBool spellings); the C
  benchmark tokenizer uses the ArgIteratorGeneral port.
- OSC 7 derived titles, desktop notifications and OSC 8 hover links
  forward raw bytes to the runtime; GTK converts only for display.
- C diagnostic getter exports original bytes; escaped surrogates report
  Utf8CannotEncodeSurrogateHalf; config float formatters print `nan`.
- Config::finalize_for(AppRuntime): GTK-only finalization (gtk-single-
  instance=detect) and termio's cgroup hooks apply only to the GTK
  runtime, so libghostty keeps `detect` like Zig.

Test Plan:
- cargo test --workspace --no-fail-fast (exit 0)
- cargo test -p ghostty-lib --features test-platform
- just check-wasm (macOS/Windows clippy re-run after the concurrent
  terminal changes land)
2026-10-12 13:09:04 +02:00

103 lines
3.0 KiB
Rust

//! `ghostty_benchmark_cli` (ZIG/src/benchmark/CApi.zig).
#![allow(unsafe_code)]
use std::ffi::{CStr, c_char};
use ghostty_bench::{args::Args, benches};
/// Split the argument string like `std.process.ArgIteratorGeneral(.{})`
/// (backslashes are only special before a double quote). Works on raw bytes:
/// file names need not be UTF-8.
fn split_args(s: &[u8]) -> Vec<std::ffi::OsString> {
ghostty_os::arg_iterator::ArgIteratorGeneral::new(
ghostty_os::arg_iterator::Options::default(),
s,
)
.map(ghostty_os::osbytes::os_from_bytes)
.collect()
}
/// Run the Ghostty benchmark CLI with the given action and arguments.
///
/// # Safety
/// `action_name` and `args` must be NUL terminated strings.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ghostty_benchmark_cli(
action_name: *const c_char,
args: *const c_char,
) -> bool {
if action_name.is_null() || args.is_null() {
return false;
}
// SAFETY: per the function contract.
let (action, args) = unsafe { (CStr::from_ptr(action_name), CStr::from_ptr(args)) };
let action = action.to_string_lossy();
if !benches::ACTIONS.contains(&action.as_ref()) {
log::warn!("unknown action={action}");
return false;
}
let argv = split_args(args.to_bytes());
let parsed = match Args::parse(argv) {
Ok(a) => a,
Err(err) => {
log::warn!("failed to run action={action} err={err}");
return false;
}
};
match benches::main_action(&action, parsed) {
Ok(_) => true,
Err(err) => {
log::warn!("failed to run action={action} err={err}");
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_args_basic() {
assert_eq!(split_args(b"a b c"), ["a", "b", "c"]);
assert_eq!(split_args(b"--x=\"a b\" c"), ["--x=a b", "c"]);
assert_eq!(split_args(b" ").len(), 0);
}
#[test]
fn split_args_backslashes_are_literal_except_before_quotes() {
assert_eq!(
split_args(br#"--terminal-rows="1\0""#),
[r"--terminal-rows=1\0"]
);
assert_eq!(split_args(b"a\\ b"), [r"a\", "b"]);
assert_eq!(split_args(br#""a\"b""#), ["a\"b"]);
}
#[test]
fn backslash_zero_row_count_fails_like_zig() {
// SAFETY: valid NUL terminated strings.
assert!(!unsafe {
ghostty_benchmark_cli(
c"terminal-stream".as_ptr(),
c"--terminal-rows=\"1\\0\"".as_ptr(),
)
});
}
#[cfg(unix)]
#[test]
fn split_args_keeps_non_utf8_bytes() {
use std::os::unix::ffi::OsStrExt;
let v = split_args(b"--data=/tmp/a\xffb x");
assert_eq!(v[0].as_bytes(), b"--data=/tmp/a\xffb");
assert_eq!(v[1], "x");
}
#[test]
fn unknown_action_returns_false() {
// SAFETY: valid NUL terminated strings.
assert!(!unsafe { ghostty_benchmark_cli(c"nope".as_ptr(), c"".as_ptr()) });
}
}