Skip to main content

idakit_sys/
bridge_visitors.rs

1//! `cxx` `extern "Rust"` opaque-visitor bridges for the ctree and tinfo walks (namespace
2//! `bridge`).
3//!
4//! Two production walks each drive an [`extern "Rust"`] **opaque visitor** whose `&mut self`
5//! methods `cxx` exposes to C++ as callable member functions, sharing one bridge module:
6//!
7//! - the ctree walk ([`CtreeVisitor`], driver `facade/ctree_bridge.cpp`'s `walker_t`) walks a
8//!   decompiled `cfunc_t` depth-first, calling one method per expression, statement, and local;
9//! - the tinfo type walk ([`TypeWalkVisitor`], driver `facade/typewalk_bridge.cpp`'s `visit_walker_t`)
10//!   walks a `tinfo_t`, calling one method per node kind.
11//!
12//! Neither uses a function-pointer table or a `void*` context: each visitor forwards every C++ call
13//! straight into a sink ([`CtreeSink`] / [`TypeWalkSink`]) the consumer supplies, so `idakit`
14//! interns nodes into its own tables without a C-ABI shim layer. The ctree walk resolves node types
15//! through the same type walker, handed [`cfunc_walk_ctree`]'s `type_visitor` address.
16//!
17//! Per-call data crosses borrowed for the duration of that one call, except names. Names, string
18//! literals, and comments cross as owned `String` (`rust::String`), decoded leniently facade-side
19//! (IDA emits UTF-8; any undecodable unit is U+FFFD), so the sink never re-decodes. Child-handle
20//! arrays cross as `&[u32]`; struct members and enum constants cross as `&[MemberInfo]` /
21//! `&[EnumConstInfo]`, plain POD shared structs with an owned `name: String`.
22//!
23//! The sink traits, opaque visitors, and the bridge module itself are generated by the
24//! `idakit-sys-codegen` crate's `emit` engine from the declarative manifest in its
25//! `visitors` module, so their signatures never drift out of sync across the trait, the
26//! forwarding `impl`, and the `extern "Rust"` decl; only the hand-written constructors/drivers
27//! below stay in this file.
28//!
29//! # Panic safety
30//!
31//! A `cxx` `extern "Rust"` function (including an opaque type's methods) that panics is caught at
32//! the boundary: `cxx` logs and calls `std::process::abort`, so a panic never unwinds into C++.
33//! The visitor methods here are panic-free in the normal path regardless; this is the contract if
34//! one ever did.
35
36// cxx::bridge auto-derives Clone/Debug/etc. for each shared struct it declares, and that
37// generated code trips unused_qualifications on the struct's own name; scoped here since the
38// generated module can't carry its own attribute.
39#![expect(
40    unused_qualifications,
41    reason = "false positive from cxx::bridge's auto-derived impls for shared structs"
42)]
43
44use std::ptr::NonNull;
45
46include!(concat!(env!("OUT_DIR"), "/gen_visitors.rs"));
47
48impl TypeWalkVisitor {
49    /// Wrap a sink for one walk. The visitor must not outlive `sink`; the drivers below enforce
50    /// this by keeping `sink` borrowed for the whole `ffi` call and never letting the visitor
51    /// escape.
52    fn new(sink: &mut dyn TypeWalkSink) -> Self {
53        // Erase the sink's lifetime into the visitor. Sound because the visitor is confined to the
54        // driver call that borrows `sink`; the raw pointer never escapes it, so it cannot be used
55        // after `sink` ends. The transmute changes only the lifetime of an otherwise identical fat
56        // pointer.
57        let sink: NonNull<dyn TypeWalkSink> = unsafe { std::mem::transmute(NonNull::from(sink)) };
58        Self { sink }
59    }
60
61    /// Wrap a raw sink pointer as a [`TypeWalkVisitor`] for a walk driven outside this crate.
62    ///
63    /// The self-contained `walk_*` drivers cover the standalone walks; this exists for the ctree
64    /// walk, where the visitor is handed to [`cfunc_walk_ctree`] alongside a [`CtreeVisitor`]
65    /// that shares the same builder. Passing `sink` as a raw pointer (not `&mut`) lets the
66    /// caller derive both from one provenance, so per-callback reborrows never conflict.
67    ///
68    /// # Safety
69    /// `sink` must be non-null and point to a live [`TypeWalkSink`] that outlives every use of
70    /// the returned visitor, and stay unaliased for the duration of each callback (the walk is
71    /// single-threaded and non-reentrant).
72    ///
73    /// # Panics
74    /// Panics if `sink` is null.
75    #[must_use]
76    pub unsafe fn from_raw(sink: *mut dyn TypeWalkSink) -> Self {
77        Self {
78            sink: NonNull::new(sink).expect("sink pointer must be non-null"),
79        }
80    }
81}
82
83impl CtreeVisitor {
84    /// Wrap a raw sink pointer as a [`CtreeVisitor`] for a walk driven by [`cfunc_walk_ctree`].
85    ///
86    /// Passing `sink` as a raw pointer (not `&mut`) lets the caller derive both it and any node
87    /// context it aliases (e.g. a shared builder) from one provenance, so per-callback reborrows
88    /// never conflict.
89    ///
90    /// # Safety
91    /// `sink` must be non-null and point to a live [`CtreeSink`] that outlives every use of the
92    /// returned visitor, and stay unaliased for the duration of each callback (the walk is
93    /// single-threaded and non-reentrant).
94    ///
95    /// # Panics
96    /// Panics if `sink` is null.
97    #[must_use]
98    pub unsafe fn from_raw(sink: *mut dyn CtreeSink) -> Self {
99        Self {
100            sink: NonNull::new(sink).expect("sink pointer must be non-null"),
101        }
102    }
103}
104
105pub use ffi::{EnumConstInfo, FrameVar, FrameWalk, LocPiece, MemberInfo, cfunc_walk_ctree};
106
107/// Walk the local type named `name` into `sink` via the `cxx` opaque visitor; returns the root
108/// handle, or `None` when no such type exists (or a name was not valid UTF-8, surfaced as a thrown
109/// `Err`).
110pub fn walk_type_named(name: &str, sink: &mut dyn TypeWalkSink) -> Option<u32> {
111    ffi::type_walk_visit_named(name, &mut TypeWalkVisitor::new(sink)).ok()
112}
113
114/// Walk the local type at `ordinal` into `sink`; the ordinal twin of [`walk_type_named`].
115pub fn walk_type_ordinal(ordinal: u32, sink: &mut dyn TypeWalkSink) -> Option<u32> {
116    ffi::type_walk_visit_ordinal(ordinal, &mut TypeWalkVisitor::new(sink)).ok()
117}
118
119/// Walk the prototype of the function at `ea` into `sink`; the function-address twin of
120/// [`walk_type_named`]. `None` when the function has no type info.
121pub fn walk_func_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<u32> {
122    ffi::func_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
123}
124
125/// Walk the stack frame of the function at `ea`, interning each variable's type into `sink`.
126///
127/// Returns the frame size and variables, or `None` when there is no function or frame at `ea`.
128/// The returned [`FrameVar::ty`] handles index the table `sink` built.
129pub fn walk_frame_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<FrameWalk> {
130    ffi::frame_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
131}