From 394e29ba13b3c07edbd86639a623d5308f5bbdf3 Mon Sep 17 00:00:00 2001 From: Lahfir Date: Thu, 16 Apr 2026 06:18:04 -0700 Subject: [PATCH] fix(ffi): UTF-8 fail-closed for optional filter pointers (todo 010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the UTF-8 portion of P2 todo 010. The resolver relaxed-pass stays — it's the right behavior for ad_find's lossy-rebuild fallback and the recently-landed bounds_hash preservation already constrains the duplicate-label drift case. The UTF-8 side needed a tighter contract. `c_to_string` returned `Option` and collapsed null + invalid-bytes into the same `None`, so an app_filter passed in with hostile bytes was silently treated as "no filter" and widened ad_list_windows, ad_dismiss_all_notifications, ad_find, etc. to every app on the box. Add `try_c_to_string` returning `Result, ()>`: - `Ok(None)` — null pointer (caller treats as absent). - `Ok(Some(s))` — valid UTF-8. - `Err(())` — non-null + invalid UTF-8 (caller must surface AD_RESULT_ERR_INVALID_ARGS). Add `decode_optional_filter!` macro so call sites read as a single line: let role_filter = decode_optional_filter!(q.role, "query.role"); The macro shortcircuits the enclosing AdResult fn with InvalidArgs and a tailored last-error ("