From 57edc0d47011b6da14f9bc7ffc52d5e7c34feac0 Mon Sep 17 00:00:00 2001 From: Lahfir Date: Wed, 10 Jun 2026 03:54:38 -0700 Subject: [PATCH] fix: add AdDragParams size guard and document the ABI breaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AdDragParams gained a drop_delay_ms field but, unlike AdRefEntry, had no size guard — an old caller's smaller allocation would let Rust read past it and turn stack garbage into a real drop delay. Add AD_DRAG_PARAMS_SIZE, ad_drag_params_size(), a compile-time layout assertion, and a zero-init note, matching the ref-entry pattern. This branch makes several consumer-visible contract changes that release tooling must cut as a major. They are gathered here because the release workflow ships the C header as an artifact. BREAKING CHANGE: the C ABI and CLI/JSON contract changed on this branch. - AdPolicyKind: AD_POLICY_KIND_PHYSICAL is renamed AD_POLICY_KIND_HEADED (discriminant 2 unchanged, so compiled binaries are safe; source-level C consumers must rename). No back-compat alias is kept — "physical" is gone. - AdRefEntry grew (caller-allocated input); validate layout with AD_REF_ENTRY_SIZE / ad_ref_entry_size(). - AdDragParams grew; validate with AD_DRAG_PARAMS_SIZE / ad_drag_params_size() and zero-initialize before use. - close-app graceful response no longer includes closed:true; it returns { method: "graceful", requested: true } because a graceful quit cannot be synchronously confirmed. --- crates/ffi/include/agent_desktop.h | 10 ++++++++++ crates/ffi/src/types/drag_params.rs | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/ffi/include/agent_desktop.h b/crates/ffi/include/agent_desktop.h index f720d26..be5ad83 100644 --- a/crates/ffi/include/agent_desktop.h +++ b/crates/ffi/include/agent_desktop.h @@ -196,6 +196,12 @@ typedef struct AdPoint { double y; } AdPoint; +/* + * Caller-allocated drag parameters. Zero-initialize the whole struct before + * setting fields: `duration_ms`/`drop_delay_ms` treat 0 as the adapter-default + * sentinel, so stack garbage in an unset field would become a real delay. + * Validate layout with `AD_DRAG_PARAMS_SIZE` / `ad_drag_params_size()`. + */ typedef struct AdDragParams { struct AdPoint from; struct AdPoint to; @@ -205,6 +211,10 @@ typedef struct AdDragParams { uint64_t drop_delay_ms; } AdDragParams; +#define AD_DRAG_PARAMS_SIZE (sizeof(AdDragParams)) + +uintptr_t ad_drag_params_size(void); + /** * Action dispatched by `ad_execute_action`. * diff --git a/crates/ffi/src/types/drag_params.rs b/crates/ffi/src/types/drag_params.rs index 43ebafb..4b63880 100644 --- a/crates/ffi/src/types/drag_params.rs +++ b/crates/ffi/src/types/drag_params.rs @@ -1,6 +1,11 @@ use crate::types::point::AdPoint; use agent_desktop_core::action::{DragParams as CoreDragParams, Point as CorePoint}; +/// Caller-allocated drag parameters. Callers must zero-initialize the whole +/// struct before setting fields so unset numeric fields read as the `0` +/// adapter-default sentinel rather than stack garbage. Verify layout against +/// `AD_DRAG_PARAMS_SIZE` / `ad_drag_params_size()` when binding from a language +/// whose struct layout may diverge. #[repr(C)] pub struct AdDragParams { pub from: AdPoint, @@ -9,6 +14,15 @@ pub struct AdDragParams { pub drop_delay_ms: u64, } +pub const AD_DRAG_PARAMS_SIZE: usize = 48; + +const _: () = assert!(std::mem::size_of::() == AD_DRAG_PARAMS_SIZE); + +#[unsafe(no_mangle)] +pub extern "C" fn ad_drag_params_size() -> usize { + std::mem::size_of::() +} + impl AdDragParams { /// Converts the C drag params into the core type. `duration_ms` and /// `drop_delay_ms` use `0` as the "adapter default" sentinel because the