Skip to main content

idakit_sys/
bridge_typewalk.rs

1//! `cxx` `extern "Rust"` opaque-visitor bridge for the tinfo type walk (`idakit_cxx::typewalk_*`).
2//!
3//! The production type walk drives an [`extern "Rust"`] **opaque visitor** ([`TypeWalkVisitor`])
4//! whose `&mut self` methods `cxx` exposes to C++ as callable member functions. The C++ driver in
5//! `facade/typewalk_cxx.cc` (`visit_walker_t`) walks a `tinfo_t` and calls `visitor.scalar(...)`,
6//! `visitor.named_ref(...)`, `visitor.fill_struct(...)` per node. There is no function-pointer
7//! table, no `void*` context, no offset-indexed struct: the visitor forwards each call straight
8//! into a [`TypeWalkSink`] the consumer supplies, so `idakit` interns nodes into its own type table
9//! without a C-ABI shim layer.
10//!
11//! Per-call data crosses zero-copy, borrowed only for the duration of that one call:
12//!
13//! - names as `&str` (`rust::Str`), copied out by the sink only if it keeps them;
14//! - child-handle arrays as `&[u32]` (`rust::Slice<const u32>`);
15//! - struct members / enum constants as `&[MemberInfo]` / `&[EnumConstInfo]`
16//!   (`rust::Slice<const T>`), where [`MemberInfo`]/[`EnumConstInfo`] are **lifetime-generic
17//!   shared structs** carrying a borrowed `name: &str` field, the borrowed-name-in-array case with
18//!   no owned-`String` copy forced across the boundary.
19//!
20//! The recursion-safe placeholder + `defined`-set dedup stays C++-side in `visit_walker_t`.
21//!
22//! # Panic safety
23//!
24//! A `cxx` `extern "Rust"` function (including an opaque type's methods) that panics is caught at
25//! the boundary: `cxx` logs and calls `std::process::abort`, so a panic never unwinds into C++.
26//! The visitor methods here are panic-free in the normal path regardless; this is the contract if
27//! one ever did.
28use std::ptr::NonNull;
29
30/// A walk target the type visitor drives inline, one method per tinfo node kind.
31///
32/// The consumer (`idakit`) implements it over its own interned type table, and
33/// [`TypeWalkVisitor`] forwards every C++ call straight into it. Handle-returning methods mint
34/// and return the walk-local id the parent will reference (children are visited before parents);
35/// the `fill_*` methods complete a placeholder minted earlier by
36/// [`named_ref`](Self::named_ref)/[`anon`](Self::anon). Names and slices are borrowed for the one
37/// call only.
38pub trait TypeWalkSink {
39    /// A scalar leaf (`kind`: 0 unknown, 1 void, 2 bool, 3 integral, 4 float); returns its handle.
40    fn scalar(&mut self, kind: u32, bytes: u32, signed: u32, size: u64, has_size: u32) -> u32;
41    /// A pointer to the already-visited `target`; returns its handle.
42    fn ptr(&mut self, target: u32, size: u64, has_size: u32) -> u32;
43    /// An array of `nelems` of the already-visited `elem`; returns its handle.
44    fn array(&mut self, elem: u32, nelems: u64, size: u64, has_size: u32) -> u32;
45    /// A function of the already-visited `ret` and `params`; returns its handle.
46    fn func(&mut self, ret: u32, params: &[u32], vararg: u32) -> u32;
47    /// A named-but-bodyless / unresolved leaf carrying its resolved `name`; returns its handle.
48    fn opaque(&mut self, name: &str) -> u32;
49    /// A by-name placeholder for a named aggregate/typedef; returns its handle.
50    fn named_ref(&mut self, name: &str) -> u32;
51    /// An anonymous-aggregate placeholder; returns its handle.
52    fn anon(&mut self) -> u32;
53    /// Fills the struct/union placeholder `id` with its `members`.
54    fn fill_struct(
55        &mut self,
56        id: u32,
57        is_union: bool,
58        members: &[ffi::MemberInfo],
59        size: u64,
60        has_size: u32,
61    );
62    /// Fills the enum placeholder `id` with its `consts` over the already-visited `underlying`.
63    fn fill_enum(
64        &mut self,
65        id: u32,
66        underlying: u32,
67        consts: &[ffi::EnumConstInfo],
68        size: u64,
69        has_size: u32,
70    );
71    /// Fills the typedef placeholder `id` with its already-visited `underlying`.
72    fn fill_typedef(&mut self, id: u32, underlying: u32);
73}
74
75/// The `cxx` `extern "Rust"` opaque the C++ type walk drives by calling its `&mut self` methods,
76/// each forwarding into the [`TypeWalkSink`] it was built over.
77///
78/// `cxx` generates a C++ class with a member function per method below; `facade/typewalk_cxx.cc`
79/// receives a `TypeWalkVisitor&` and calls them. The visitor holds the sink as a lifetime-erased
80/// raw pointer: the [`walk_type_named`]/[`walk_type_ordinal`]/[`walk_func_type`] drivers are its
81/// only constructors, and each keeps the borrowed sink alive across the one synchronous walk, so
82/// the pointer is always valid and unaliased when a method reborrows it.
83pub struct TypeWalkVisitor {
84    sink: NonNull<dyn TypeWalkSink>,
85}
86
87impl TypeWalkVisitor {
88    /// Wrap a sink for one walk. The visitor must not outlive `sink`; the drivers below enforce
89    /// this by keeping `sink` borrowed for the whole `ffi` call and never letting the visitor
90    /// escape.
91    fn new(sink: &mut dyn TypeWalkSink) -> Self {
92        // Erase the sink's lifetime into the visitor. Sound because the visitor is confined to the
93        // driver call that borrows `sink`; the raw pointer never escapes it, so it cannot be used
94        // after `sink` ends. The transmute changes only the lifetime of an otherwise identical fat
95        // pointer.
96        let sink: NonNull<dyn TypeWalkSink> = unsafe { std::mem::transmute(NonNull::from(sink)) };
97        Self { sink }
98    }
99
100    /// Reborrow the erased sink for one callback.
101    ///
102    /// # Safety
103    /// See the type docs: the pointer is valid and unaliased for the walk (single-threaded,
104    /// non-reentrant, the walk holds the only borrow).
105    fn sink(&mut self) -> &mut dyn TypeWalkSink {
106        unsafe { self.sink.as_mut() }
107    }
108
109    fn scalar(&mut self, kind: u32, bytes: u32, is_signed: u32, size: u64, has_size: u32) -> u32 {
110        self.sink().scalar(kind, bytes, is_signed, size, has_size)
111    }
112    fn ptr(&mut self, target: u32, size: u64, has_size: u32) -> u32 {
113        self.sink().ptr(target, size, has_size)
114    }
115    fn array(&mut self, elem: u32, nelems: u64, size: u64, has_size: u32) -> u32 {
116        self.sink().array(elem, nelems, size, has_size)
117    }
118    fn func(&mut self, ret: u32, params: &[u32], vararg: u32) -> u32 {
119        self.sink().func(ret, params, vararg)
120    }
121    fn opaque(&mut self, name: &str) -> u32 {
122        self.sink().opaque(name)
123    }
124    fn named_ref(&mut self, name: &str) -> u32 {
125        self.sink().named_ref(name)
126    }
127    fn anon(&mut self) -> u32 {
128        self.sink().anon()
129    }
130    fn fill_struct(
131        &mut self,
132        id: u32,
133        is_union: bool,
134        members: &[ffi::MemberInfo],
135        size: u64,
136        has_size: u32,
137    ) {
138        self.sink()
139            .fill_struct(id, is_union, members, size, has_size);
140    }
141    fn fill_enum(
142        &mut self,
143        id: u32,
144        underlying: u32,
145        consts: &[ffi::EnumConstInfo],
146        size: u64,
147        has_size: u32,
148    ) {
149        self.sink()
150            .fill_enum(id, underlying, consts, size, has_size);
151    }
152    fn fill_typedef(&mut self, id: u32, underlying: u32) {
153        self.sink().fill_typedef(id, underlying);
154    }
155}
156
157#[cxx::bridge(namespace = "idakit_cxx")]
158mod ffi {
159    /// One struct/union member, a **lifetime-generic shared struct** whose `name` borrows a C++
160    /// stack temporary for the duration of one [`fill_struct`](TypeWalkVisitor) call.
161    ///
162    /// The `name: &str` compiles to `rust::Str` on the C++ side; passed inside a
163    /// `rust::Slice<const MemberInfo>`, it is the borrowed-name-in-array case with no owned-copy.
164    struct MemberInfo<'a> {
165        /// The member's name, borrowed for the call.
166        name: &'a str,
167        /// Offset from the aggregate start, in bits.
168        bit_offset: u64,
169        /// Walk-local handle of the member's type.
170        ty: u32,
171        /// Bit width for a bitfield member (0 for an ordinary field).
172        bitfield_width: u32,
173    }
174
175    /// One enum constant, a lifetime-generic shared struct with a borrowed `name`, the enum twin
176    /// of [`MemberInfo`].
177    struct EnumConstInfo<'a> {
178        /// The constant's name, borrowed for the call.
179        name: &'a str,
180        /// The constant's value.
181        value: u64,
182    }
183
184    /// One stack-frame variable, an **owned** shared struct returned in a [`FrameWalk`].
185    ///
186    /// Unlike [`MemberInfo`], the frame walk returns its variables as a batch after the walk, so
187    /// each `name` is copied into an owned `String` rather than borrowed for a call.
188    struct FrameVar {
189        /// The variable's name.
190        name: String,
191        /// Frame-pointer-relative byte offset.
192        offset: i64,
193        /// Size in bytes.
194        size: u64,
195        /// Reserved-slot flags (return address / saved registers); 0 for an ordinary variable.
196        flags: u32,
197        /// Walk-local handle of the variable's type, or `IDAKIT_NONE` for a reserved/untyped slot.
198        ty: u32,
199    }
200
201    /// A walked stack frame: its total byte size and its variables, returned by
202    /// [`frame_type_walk_visit`].
203    struct FrameWalk {
204        /// Total frame size in bytes.
205        size: u64,
206        /// The frame's variables, in frame order.
207        vars: Vec<FrameVar>,
208    }
209
210    extern "Rust" {
211        type TypeWalkVisitor;
212
213        fn scalar(
214            self: &mut TypeWalkVisitor,
215            kind: u32,
216            bytes: u32,
217            is_signed: u32,
218            size: u64,
219            has_size: u32,
220        ) -> u32;
221        fn ptr(self: &mut TypeWalkVisitor, target: u32, size: u64, has_size: u32) -> u32;
222        fn array(
223            self: &mut TypeWalkVisitor,
224            elem: u32,
225            nelems: u64,
226            size: u64,
227            has_size: u32,
228        ) -> u32;
229        fn func(self: &mut TypeWalkVisitor, ret: u32, params: &[u32], vararg: u32) -> u32;
230        fn opaque(self: &mut TypeWalkVisitor, name: &str) -> u32;
231        fn named_ref(self: &mut TypeWalkVisitor, name: &str) -> u32;
232        fn anon(self: &mut TypeWalkVisitor) -> u32;
233        fn fill_struct(
234            self: &mut TypeWalkVisitor,
235            id: u32,
236            is_union: bool,
237            members: &[MemberInfo],
238            size: u64,
239            has_size: u32,
240        );
241        fn fill_enum(
242            self: &mut TypeWalkVisitor,
243            id: u32,
244            underlying: u32,
245            consts: &[EnumConstInfo],
246            size: u64,
247            has_size: u32,
248        );
249        fn fill_typedef(self: &mut TypeWalkVisitor, id: u32, underlying: u32);
250    }
251
252    unsafe extern "C++" {
253        include!("typewalk_cxx.h");
254
255        /// Walk the local type named `name`, driving `visitor`'s methods per node; returns the
256        /// root handle. `Err` when no such type exists (or a thrown SDK error).
257        fn type_walk_visit_named(name: &str, visitor: &mut TypeWalkVisitor) -> Result<u32>;
258
259        /// Walk the local type at `ordinal`, driving `visitor`'s methods per node; returns the
260        /// root handle. `Err` when no type occupies the ordinal (or a thrown SDK error).
261        fn type_walk_visit_ordinal(ordinal: u32, visitor: &mut TypeWalkVisitor) -> Result<u32>;
262
263        /// Walk the stored prototype of the function at `ea`, driving `visitor`; returns the root
264        /// handle. `Err` when the function has no type info.
265        fn func_type_walk_visit(ea: u64, visitor: &mut TypeWalkVisitor) -> Result<u32>;
266
267        /// Walk the stack frame of the function at `ea`: each variable's type through `visitor`,
268        /// returning the frame size and variables. `Err` when there is no function or frame at
269        /// `ea`.
270        fn frame_type_walk_visit(ea: u64, visitor: &mut TypeWalkVisitor) -> Result<FrameWalk>;
271    }
272}
273
274pub use ffi::{EnumConstInfo, FrameVar, FrameWalk, MemberInfo};
275
276/// Walk the local type named `name` into `sink` via the `cxx` opaque visitor; returns the root
277/// handle, or `None` when no such type exists (or a name was not valid UTF-8, surfaced as a thrown
278/// `Err`).
279pub fn walk_type_named(name: &str, sink: &mut dyn TypeWalkSink) -> Option<u32> {
280    ffi::type_walk_visit_named(name, &mut TypeWalkVisitor::new(sink)).ok()
281}
282
283/// Walk the local type at `ordinal` into `sink`; the ordinal twin of [`walk_type_named`].
284pub fn walk_type_ordinal(ordinal: u32, sink: &mut dyn TypeWalkSink) -> Option<u32> {
285    ffi::type_walk_visit_ordinal(ordinal, &mut TypeWalkVisitor::new(sink)).ok()
286}
287
288/// Walk the prototype of the function at `ea` into `sink`; the function-address twin of
289/// [`walk_type_named`]. `None` when the function has no type info.
290pub fn walk_func_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<u32> {
291    ffi::func_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
292}
293
294/// Walk the stack frame of the function at `ea`, interning each variable's type into `sink` and
295/// returning the frame size and variables. `None` when there is no function or frame at `ea`. The
296/// returned [`FrameVar::ty`] handles index the table `sink` built.
297pub fn walk_frame_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<FrameWalk> {
298    ffi::frame_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
299}
300
301/// Wrap a raw sink pointer as a [`TypeWalkVisitor`] for a walk driven outside this crate.
302///
303/// The self-contained `walk_*` drivers cover the standalone walks; this exists for the ctree walk,
304/// where the visitor must be handed to [`idakit_cfunc_walk_ctree`](crate::idakit_cfunc_walk_ctree)
305/// alongside a raw node context that aliases the same builder. Passing `sink` as a raw pointer (not
306/// `&mut`) lets the caller derive both it and the node context from one provenance, so per-callback
307/// reborrows never conflict.
308///
309/// # Safety
310/// `sink` must be non-null and point to a live [`TypeWalkSink`] that outlives every use of the
311/// returned visitor, and stay unaliased for the duration of each callback (the walk is
312/// single-threaded and non-reentrant).
313#[must_use]
314pub unsafe fn type_walk_visitor(sink: *mut dyn TypeWalkSink) -> TypeWalkVisitor {
315    TypeWalkVisitor {
316        sink: NonNull::new(sink).expect("sink pointer must be non-null"),
317    }
318}