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}