pub mod ctree;
use std::collections::HashSet;
use std::marker::PhantomData;
use idakit_sys as sys;
use crate::Database;
use crate::address::Address;
use crate::decompiler::ctree::{Ctree, ExtractError, walk};
use crate::error::{Error, Result};
impl Database {
pub fn ctree(&self, address: Address) -> Result<Ctree> {
self.function(address).ctree()
}
#[doc(alias("decompile_func"))]
pub fn decompile(&self, address: Address) -> Result<DecompiledFunction<'_>> {
crate::claim::ensure_kernel_thread();
if !self.hexrays_ready.get() {
if !self.hexrays_init() {
return Err(Error::HexRaysInit);
}
self.hexrays_ready.set(true);
}
match sys::decompile(address.get()) {
Ok(handle) => Ok(DecompiledFunction::from_handle(handle, self)),
Err(e) => {
if self.was_trapped() {
return Err(self.kernel_exit_error());
}
Err(Error::Decompile {
address: address.get(),
reason: e.what().to_owned(),
})
}
}
}
#[doc(alias("mark_cfunc_dirty"))]
pub fn invalidate_decompilation(&mut self, address: Address) -> bool {
self.hexrays_ready.get() && self.mark_cfunc_dirty(address, false)
}
#[doc(alias("clear_cached_cfuncs"))]
pub fn clear_decompilation_cache(&mut self) {
if self.hexrays_ready.get() {
self.clear_cached_cfuncs();
}
}
#[must_use]
#[doc(alias("has_cached_cfunc"))]
pub fn is_decompilation_cached(&self, address: Address) -> bool {
self.hexrays_ready.get() && self.has_cached_cfunc(address)
}
pub(crate) fn invalidate_decompilation_dependents(&mut self, entry: Address) {
if !self.hexrays_ready.get() {
return;
}
self.mark_cfunc_dirty(entry, false);
let mut seen = HashSet::from([entry]);
for xref in self.xrefs_to(entry) {
let Some(referrer) = Address::try_new(self.func_start(xref.from)) else {
continue;
};
if seen.insert(referrer) {
self.mark_cfunc_dirty(referrer, false);
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[doc(alias("ctree_visitor_t"))]
pub struct CtreeCounts {
pub statements: i32,
pub expressions: i32,
pub calls: i32,
}
#[doc(alias("cfuncptr_t", "cfunc_t"))]
pub struct DecompiledFunction<'db> {
handle: cxx::UniquePtr<sys::CFunc>,
_db: PhantomData<&'db Database>,
_not_send: PhantomData<*const ()>,
}
impl<'db> DecompiledFunction<'db> {
#[inline]
pub(crate) fn from_handle(handle: cxx::UniquePtr<sys::CFunc>, _db: &'db Database) -> Self {
debug_assert!(!handle.is_null());
Self {
handle,
_db: PhantomData,
_not_send: PhantomData,
}
}
#[inline]
fn cfunc(&self) -> &sys::CFunc {
self.handle.as_ref().expect("live handle")
}
#[must_use]
#[doc(alias("get_pseudocode"))]
pub fn pseudocode(&self) -> Option<String> {
sys::cfunc_pseudocode(self.cfunc()).ok()
}
#[must_use]
#[doc(alias("refresh_func_ctext"))]
pub fn refresh_text(&self) -> Option<String> {
sys::cfunc_refresh_text(self.cfunc()).ok()
}
#[must_use]
pub fn counts(&self) -> CtreeCounts {
let c = sys::cfunc_counts(self.cfunc());
CtreeCounts {
statements: c.statements,
expressions: c.expressions,
calls: c.calls,
}
}
#[must_use]
pub fn expr_extraction_expectation(&self) -> (i32, i32) {
let g = sys::cfunc_expr_gap(self.cfunc());
(g.visitor_total, g.expected)
}
pub fn ctree(&self) -> Result<Ctree, ExtractError> {
walk(self.cfunc())
}
}
impl std::fmt::Debug for DecompiledFunction<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecompiledFunction")
.field("counts", &self.counts())
.finish()
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
#[test]
fn ctree_counts_hashes() {
let a = CtreeCounts {
statements: 3,
expressions: 7,
calls: 1,
};
let b = CtreeCounts {
statements: 3,
expressions: 7,
calls: 1,
};
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
}
}