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)
This commit is contained in:
1 parent
7b3fffded3
commit
da9599140b
21 files changed
+479
-133
No files matched your search
@@ -67,6 +67,8 @@ struct GetCache {
|
||||
|
||||
impl CConfig {
|
||||
pub fn new(config: Config) -> CConfig {
|
||||
// The embedded library has no apprt (Zig `app_runtime = .none`).
|
||||
ghostty_config::config::set_app_runtime(ghostty_config::config::AppRuntime::None);
|
||||
CConfig {
|
||||
config,
|
||||
cache: RefCell::default(),
|
||||
@@ -302,7 +304,10 @@ pub unsafe extern "C" fn ghostty_config_finalize(ptr: ghostty_config_t) {
|
||||
let Some(c) = (unsafe { cconfig(ptr) }) else {
|
||||
return;
|
||||
};
|
||||
if let Err(err) = c.config.finalize() {
|
||||
if let Err(err) = c
|
||||
.config
|
||||
.finalize_for(ghostty_config::config::AppRuntime::None)
|
||||
{
|
||||
log::error!("error finalizing config err={err}");
|
||||
}
|
||||
}
|
||||
@@ -575,7 +580,7 @@ pub unsafe extern "C" fn ghostty_config_get_diagnostic(
|
||||
while msgs.len() < items.len() {
|
||||
let mut text = String::new();
|
||||
let _ = items[msgs.len()].format(&mut text);
|
||||
msgs.push(cstring(&text));
|
||||
msgs.push(cstring_value(&text));
|
||||
}
|
||||
ghostty_diagnostic_s {
|
||||
message: msgs[idx].as_ptr(),
|
||||
@@ -774,3 +779,55 @@ mod tests {
|
||||
unsafe { ghostty_config_free(h) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
mod runtime_and_diag_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn embedded_config_keeps_gtk_single_instance_detect() {
|
||||
let h = ghostty_config_new();
|
||||
// SAFETY: just created.
|
||||
let c = unsafe { cconfig(h) }.unwrap();
|
||||
c.config.gtk_single_instance = ghostty_config::types_b::GtkSingleInstance::Detect;
|
||||
// SAFETY: valid handle.
|
||||
unsafe { ghostty_config_finalize(h) };
|
||||
let mut out: *const c_char = std::ptr::null();
|
||||
let key = "gtk-single-instance";
|
||||
// SAFETY: the key's C type is a C string.
|
||||
assert!(unsafe {
|
||||
ghostty_config_get(h, (&raw mut out).cast(), key.as_ptr().cast(), key.len())
|
||||
});
|
||||
// SAFETY: valid NUL terminated string owned by the config.
|
||||
assert_eq!(unsafe { CStr::from_ptr(out) }.to_bytes(), b"detect");
|
||||
// SAFETY: valid handle.
|
||||
unsafe { ghostty_config_free(h) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagnostic_getter_exports_original_bytes() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let dir = std::env::temp_dir().join(format!("ghostty-z13-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let file = dir.join("cfg");
|
||||
std::fs::write(&file, b"font-size = \xff\n").unwrap();
|
||||
let path = CString::new(file.as_os_str().as_bytes()).unwrap();
|
||||
let h = ghostty_config_new();
|
||||
// SAFETY: valid handle and NUL terminated path.
|
||||
unsafe { ghostty_config_load_file(h, path.as_ptr()) };
|
||||
// SAFETY: valid handle.
|
||||
assert!(unsafe { ghostty_config_diagnostics_count(h) } >= 1);
|
||||
// SAFETY: valid handle, index in range.
|
||||
let d = unsafe { ghostty_config_get_diagnostic(h, 0) };
|
||||
// SAFETY: NUL terminated string owned by the config.
|
||||
let msg = unsafe { CStr::from_ptr(d.message) }.to_bytes().to_vec();
|
||||
assert!(msg.windows(1).any(|w| w == [0xff]), "{msg:?}");
|
||||
assert!(
|
||||
!msg.windows(5).any(|w| w == [0xef, 0xb7, 0x90, 0xc3, 0xbf]),
|
||||
"{msg:?}"
|
||||
);
|
||||
// SAFETY: valid handle.
|
||||
unsafe { ghostty_config_free(h) };
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user