idakit-sys 0.2.0

Raw FFI bindings to IDA Pro's idalib runtime and the idakit C++ facade
Documentation
//! `cxx` `extern "Rust"` opaque-visitor bridges for the ctree and tinfo walks (namespace
//! `bridge`).
//!
//! Two production walks each drive an [`extern "Rust"`] **opaque visitor** whose `&mut self`
//! methods `cxx` exposes to C++ as callable member functions, sharing one bridge module:
//!
//! - the ctree walk ([`CtreeVisitor`], driver `facade/ctree_bridge.cpp`'s `walker_t`) walks a
//!   decompiled `cfunc_t` depth-first, calling one method per expression, statement, and local;
//! - the tinfo type walk ([`TypeWalkVisitor`], driver `facade/typewalk_bridge.cpp`'s `visit_walker_t`)
//!   walks a `tinfo_t`, calling one method per node kind.
//!
//! Neither uses a function-pointer table or a `void*` context: each visitor forwards every C++ call
//! straight into a sink ([`CtreeSink`] / [`TypeWalkSink`]) the consumer supplies, so `idakit`
//! interns nodes into its own tables without a C-ABI shim layer. The ctree walk resolves node types
//! through the same type walker, handed [`cfunc_walk_ctree`]'s `type_visitor` address.
//!
//! Per-call data crosses borrowed for the duration of that one call, except names. Names, string
//! literals, and comments cross as owned `String` (`rust::String`), decoded leniently facade-side
//! (IDA emits UTF-8; any undecodable unit is U+FFFD), so the sink never re-decodes. Child-handle
//! arrays cross as `&[u32]`; struct members and enum constants cross as `&[MemberInfo]` /
//! `&[EnumConstInfo]`, plain POD shared structs with an owned `name: String`.
//!
//! The sink traits, opaque visitors, and the bridge module itself are generated by the
//! `idakit-sys-codegen` crate's `emit` engine from the declarative manifest in its
//! `visitors` module, so their signatures never drift out of sync across the trait, the
//! forwarding `impl`, and the `extern "Rust"` decl; only the hand-written constructors/drivers
//! below stay in this file.
//!
//! # Panic safety
//!
//! A `cxx` `extern "Rust"` function (including an opaque type's methods) that panics is caught at
//! the boundary: `cxx` logs and calls `std::process::abort`, so a panic never unwinds into C++.
//! The visitor methods here are panic-free in the normal path regardless; this is the contract if
//! one ever did.

// cxx::bridge auto-derives Clone/Debug/etc. for each shared struct it declares, and that
// generated code trips unused_qualifications on the struct's own name; scoped here since the
// generated module can't carry its own attribute.
#![expect(
    unused_qualifications,
    reason = "false positive from cxx::bridge's auto-derived impls for shared structs"
)]

use std::ptr::NonNull;

include!(concat!(env!("OUT_DIR"), "/gen_visitors.rs"));

impl TypeWalkVisitor {
    /// Wrap a sink for one walk. The visitor must not outlive `sink`; the drivers below enforce
    /// this by keeping `sink` borrowed for the whole `ffi` call and never letting the visitor
    /// escape.
    fn new(sink: &mut dyn TypeWalkSink) -> Self {
        // Erase the sink's lifetime into the visitor. Sound because the visitor is confined to the
        // driver call that borrows `sink`; the raw pointer never escapes it, so it cannot be used
        // after `sink` ends. The transmute changes only the lifetime of an otherwise identical fat
        // pointer.
        let sink: NonNull<dyn TypeWalkSink> = unsafe { std::mem::transmute(NonNull::from(sink)) };
        Self { sink }
    }

    /// Wrap a raw sink pointer as a [`TypeWalkVisitor`] for a walk driven outside this crate.
    ///
    /// The self-contained `walk_*` drivers cover the standalone walks; this exists for the ctree
    /// walk, where the visitor is handed to [`cfunc_walk_ctree`] alongside a [`CtreeVisitor`]
    /// that shares the same builder. Passing `sink` as a raw pointer (not `&mut`) lets the
    /// caller derive both from one provenance, so per-callback reborrows never conflict.
    ///
    /// # Safety
    /// `sink` must be non-null and point to a live [`TypeWalkSink`] that outlives every use of
    /// the returned visitor, and stay unaliased for the duration of each callback (the walk is
    /// single-threaded and non-reentrant).
    ///
    /// # Panics
    /// Panics if `sink` is null.
    #[must_use]
    pub unsafe fn from_raw(sink: *mut dyn TypeWalkSink) -> Self {
        Self {
            sink: NonNull::new(sink).expect("sink pointer must be non-null"),
        }
    }
}

impl CtreeVisitor {
    /// Wrap a raw sink pointer as a [`CtreeVisitor`] for a walk driven by [`cfunc_walk_ctree`].
    ///
    /// Passing `sink` as a raw pointer (not `&mut`) lets the caller derive both it and any node
    /// context it aliases (e.g. a shared builder) from one provenance, so per-callback reborrows
    /// never conflict.
    ///
    /// # Safety
    /// `sink` must be non-null and point to a live [`CtreeSink`] that outlives every use of the
    /// returned visitor, and stay unaliased for the duration of each callback (the walk is
    /// single-threaded and non-reentrant).
    ///
    /// # Panics
    /// Panics if `sink` is null.
    #[must_use]
    pub unsafe fn from_raw(sink: *mut dyn CtreeSink) -> Self {
        Self {
            sink: NonNull::new(sink).expect("sink pointer must be non-null"),
        }
    }
}

pub use ffi::{EnumConstInfo, FrameVar, FrameWalk, LocPiece, MemberInfo, cfunc_walk_ctree};

/// Walk the local type named `name` into `sink` via the `cxx` opaque visitor; returns the root
/// handle, or `None` when no such type exists (or a name was not valid UTF-8, surfaced as a thrown
/// `Err`).
pub fn walk_type_named(name: &str, sink: &mut dyn TypeWalkSink) -> Option<u32> {
    ffi::type_walk_visit_named(name, &mut TypeWalkVisitor::new(sink)).ok()
}

/// Walk the local type at `ordinal` into `sink`; the ordinal twin of [`walk_type_named`].
pub fn walk_type_ordinal(ordinal: u32, sink: &mut dyn TypeWalkSink) -> Option<u32> {
    ffi::type_walk_visit_ordinal(ordinal, &mut TypeWalkVisitor::new(sink)).ok()
}

/// Walk the prototype of the function at `ea` into `sink`; the function-address twin of
/// [`walk_type_named`]. `None` when the function has no type info.
pub fn walk_func_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<u32> {
    ffi::func_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
}

/// Walk the stack frame of the function at `ea`, interning each variable's type into `sink`.
///
/// Returns the frame size and variables, or `None` when there is no function or frame at `ea`.
/// The returned [`FrameVar::ty`] handles index the table `sink` built.
pub fn walk_frame_type(ea: u64, sink: &mut dyn TypeWalkSink) -> Option<FrameWalk> {
    ffi::frame_type_walk_visit(ea, &mut TypeWalkVisitor::new(sink)).ok()
}