idakit_sys/bridge_qvec.rs
1//! `cxx` bindings for IDA's own generic container `qvector<T>` (`bridge::*vec_*`).
2//!
3//! `cxx` has no generic-template support and its built-in `CxxVector<T>` is `std::vector`-only,
4//! so it cannot match `qvector`'s `{ T* array; size_t n; size_t alloc; }` ABI. This module binds
5//! each concrete `qvector<T>` instantiation as its own `Opaque` [`cxx::ExternType`] (one distinct
6//! `type_id!` per instantiation), the mechanical per-`T` boilerplate emitted by the
7//! [`qvec_opaque!`] macro; this is the per-instantiation recipe KDAB's cxx-qt uses for its own
8//! Qt container bindings. Two instantiations prove the recipe from scalar `T` to a
9//! `Trivial`-struct `T`:
10//!
11//! - [`IntVec`] binds the SDK's `intvec_t` (`qvector<int>`), sourced from a flow-chart block's
12//! successor edge list.
13//! - [`RangeVec`] binds the SDK's `rangevec_t` (`struct : qvector<range_t>`), built from a
14//! function's chunk ranges, with [`RangeT`](crate::bridge_gen::RangeT) (the `Trivial` ExternType
15//! from the generated [`bridge_gen`](crate::bridge_gen)) as its element.
16//!
17//! Both `intvec_t` and `rangevec_t` are real global SDK names, so `#[cxx_name]` names them
18//! directly with no C++ `using` alias (angle-bracketed `#[cxx_name = "qvector<int>"]` does not
19//! parse in `type_id!`/`cxx_name`, which require an identifier path).
20//!
21//! Each instantiation is read two ways, contrasting cost:
22//!
23//! - **Copy** (`intvec_copy`): one linear copy into an owned [`Vec`], no lifetime tie. The safe
24//! default when the data outlives the container.
25//! - **Zero-copy** (`intvec_slice`, `rangevec_slice`): a `&[T]` reconstructed on the C++ side
26//! from the container's `{array, n}` and returned as `rust::Slice`, borrowing the live
27//! backing store with no allocation and no copy. Sound only because the returned slice's
28//! lifetime is tied to the borrowed container (`&'a V -> &'a [T]`), the access is read-only
29//! (`alloc`/`qalloc`/`qfree` are never touched), and the `{array, n}` layout is fixed by the
30//! ABI-per-minor pin. The bodies are hand-written in `facade/qvec_bridge.cpp`.
31//!
32//! This is a recipe demonstration, not yet consumed by the `idakit` library.
33
34// cxx::bridge's own attribute allowlist rejects #[expect]/#[allow] on individual fn items
35// (`unsupported attribute`), so the three zero-copy fns' required explicit `<'a>` -- cxx has no
36// lifetime elision -- is silenced here at module scope instead.
37#![expect(
38 unused_lifetimes,
39 reason = "cxx::bridge requires the lifetime spelled out; elided, it no longer compiles"
40)]
41// cxx::bridge's own expansion mis-attributes a missing_errors_doc warning to the #[cxx::bridge]
42// attribute's own span, though every Result-returning fn below already documents its `Err`
43// condition.
44#![expect(
45 clippy::missing_errors_doc,
46 reason = "false positive misattributed to the #[cxx::bridge] attribute by its own expansion"
47)]
48
49/// Emit the per-instantiation `Opaque` handle for one `qvector<T>`: a zero-sized `!Unpin`
50/// mirror struct plus its [`cxx::ExternType`] impl bound to the SDK type named by `$cxx`.
51///
52/// This is the reusable core of the recipe: the ExternType boilerplate that differs only by
53/// `(RustName, "cxx_name")` per instantiation. The `#[cxx::bridge]` fn declarations and the
54/// `impl UniquePtr<T> {}` container glue cannot be folded in here, because `cxx_build` parses
55/// the source file textually (with `syn`) and cannot expand a `macro_rules!`, so anything it
56/// must see, the bridge module itself, has to be written literally below.
57macro_rules! qvec_opaque {
58 ($rust:ident, $cxx:literal) => {
59 #[doc = concat!("An `Opaque` `cxx` binding of the SDK container `", $cxx, "`.")]
60 ///
61 /// Never held by value in Rust; only borrowed (`&Self`) or owned behind
62 /// [`UniquePtr`](cxx::UniquePtr). The opaque body mirrors `cxx`'s own generated
63 /// representation, so the type is zero-sized and `!Unpin`.
64 #[repr(C)]
65 pub struct $rust {
66 _private: cxx::private::Opaque,
67 }
68
69 // SAFETY: the type id names the real global SDK type; Opaque is correct because a
70 // qvector owns a heap buffer (nontrivial destructor), so it may only cross the bridge
71 // behind a reference or UniquePtr, never by value.
72 unsafe impl cxx::ExternType for $rust {
73 type Id = cxx::type_id!($cxx);
74 type Kind = cxx::kind::Opaque;
75 }
76 };
77}
78
79qvec_opaque!(IntVec, "intvec_t");
80qvec_opaque!(RangeVec, "rangevec_t");
81
82#[cxx::bridge(namespace = "bridge")]
83mod ffi {
84 unsafe extern "C++" {
85 include!("qvec_bridge.h");
86
87 /// The SDK's `range_t`, shared from the generated [`bridge_gen`](crate::bridge_gen) so it
88 /// can be a zero-copy [`RangeVec`] slice element (one ExternType across both bridges).
89 #[namespace = ""]
90 #[cxx_name = "range_t"]
91 type RangeT = crate::bridge_gen::RangeT;
92
93 /// The SDK's `qflow_chart_t`, shared from the generated [`bridge_gen`](crate::bridge_gen);
94 /// the source of the borrowed [`IntVec`].
95 #[namespace = ""]
96 #[cxx_name = "qflow_chart_t"]
97 type FlowChart = crate::bridge_gen::FlowChart;
98
99 /// `qvector<int>`, bound by [`super::IntVec`]'s macro-generated `ExternType` impl.
100 #[namespace = ""]
101 #[cxx_name = "intvec_t"]
102 type IntVec = super::IntVec;
103
104 /// `qvector<range_t>`, bound by [`super::RangeVec`]'s macro-generated `ExternType` impl.
105 #[namespace = ""]
106 #[cxx_name = "rangevec_t"]
107 type RangeVec = super::RangeVec;
108
109 /// Borrow block `n`'s successor edge list (`qbasic_block_t::succ`, an `intvec_t`) out of
110 /// the live flow chart; `Err` when `n` is out of range. The returned `&IntVec` borrows
111 /// `fc`, so it cannot outlive it.
112 ///
113 /// # Errors
114 /// `Err` when `n` is out of range for `fc`'s block count.
115 fn cfg_succ_vec<'a>(fc: &'a FlowChart, n: usize) -> Result<&'a IntVec>;
116
117 /// Element count of an `intvec_t`, via its `qvector::size()`.
118 fn intvec_len(v: &IntVec) -> usize;
119 /// Copy an `intvec_t` into an owned [`Vec<i32>`] (the allocation/copy path).
120 fn intvec_copy(v: &IntVec) -> Vec<i32>;
121 /// Borrow an `intvec_t`'s backing store as `&[i32]` (the zero-copy path). The slice
122 /// borrows `v`, so it cannot outlive it.
123 fn intvec_slice<'a>(v: &'a IntVec) -> &'a [i32];
124
125 /// Build a `rangevec_t` of every chunk (entry plus tails) of the function at `ea`, owned
126 /// by a [`UniquePtr`](cxx::UniquePtr); `Err` when no function is there.
127 ///
128 /// # Errors
129 /// `Err` when there is no function at `ea`.
130 fn rangevec_build_chunks(ea: u64) -> Result<UniquePtr<RangeVec>>;
131 /// Element count of a `rangevec_t`, via its inherited `qvector::size()`.
132 fn rangevec_len(v: &RangeVec) -> usize;
133 /// Borrow a `rangevec_t`'s backing store as `&[RangeT]` (the zero-copy path). The slice
134 /// borrows `v`, so it cannot outlive it.
135 fn rangevec_slice<'a>(v: &'a RangeVec) -> &'a [RangeT];
136 }
137
138 // Force cxx to emit the UniquePtr<RangeVec> glue. A hand-written ExternType is not declared
139 // by an in-bridge `type X;`, so this empty impl is what triggers its container support.
140 impl UniquePtr<RangeVec> {}
141}
142
143pub use ffi::{
144 cfg_succ_vec, intvec_copy, intvec_len, intvec_slice, rangevec_build_chunks, rangevec_len,
145 rangevec_slice,
146};