use idakit_sys as sys;
use idakit_sys::TypeApplyCode;
use crate::Database;
use crate::address::Address;
use crate::error::{Error, Result};
use crate::ffi::{nul_checked, reason_or, with_cstr};
use crate::types::{TypeExpr, TypeInfo, TypeWriteError};
use crate::xref::Xrefs;
macro_rules! location_reads {
() => {
#[inline]
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
#[inline]
#[must_use]
#[doc(alias("get_ea_name"))]
pub fn name(&self) -> Option<String> {
self.db.name(self.address)
}
#[inline]
#[must_use]
#[doc(alias("get_cmt"))]
pub fn comment(&self) -> Option<String> {
self.db.comment(self.address, false)
}
#[inline]
#[must_use]
#[doc(alias("get_cmt"))]
pub fn repeatable_comment(&self) -> Option<String> {
self.db.comment(self.address, true)
}
#[inline]
#[must_use]
#[doc(alias("get_bytes"))]
pub fn bytes(&self, len: usize) -> Vec<u8> {
self.db.bytes(self.address, len)
}
#[inline]
#[doc(alias("get_bytes"))]
pub fn read_into(&self, buf: &mut [u8]) -> usize {
self.db.read_into(self.address, buf)
}
#[inline]
#[must_use]
#[doc(alias("FF_CODE"))]
pub fn is_code(&self) -> bool {
self.db.is_code(self.address)
}
#[inline]
#[must_use]
#[doc(alias("FF_DATA"))]
pub fn is_data(&self) -> bool {
self.db.is_data(self.address)
}
#[inline]
#[must_use]
#[doc(alias("get_strlit_contents"))]
pub fn string_literal(&self) -> Option<String> {
self.db.read_string(self.address)
}
};
}
impl Database {
#[inline]
#[must_use]
pub fn at(&self, address: Address) -> Location<'_> {
Location { db: self, address }
}
#[inline]
#[must_use]
pub fn at_mut(&mut self, address: Address) -> LocationMut<'_> {
LocationMut {
db: self,
address,
auto_invalidate: true,
pending: PendingInvalidation::None,
}
}
pub fn with_location_mut<R>(
&mut self,
address: Address,
f: impl FnOnce(&mut LocationMut<'_>) -> R,
) -> R {
let mut cursor = self.at_mut(address);
f(&mut cursor)
}
pub(crate) fn apply_type_at(&mut self, address: Address, ty: &TypeExpr) -> Result<()> {
match ty {
TypeExpr::Named(name) => {
let result = self.apply_named_type(address, nul_checked(name, "name")?);
match TypeApplyCode::try_from(result.code) {
Ok(TypeApplyCode::Ok) => Ok(()),
Ok(TypeApplyCode::ErrInput) => {
Err(TypeWriteError::NoType { name: name.clone() }.into())
}
Ok(TypeApplyCode::ErrApply) | Err(_) => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: format!("the kernel rejected named type {name:?}"),
}
.into()),
}
}
TypeExpr::Decl(decl) => {
let result = self.apply_type_decl(address, nul_checked(decl, "decl")?, 0);
match TypeApplyCode::try_from(result.code) {
Ok(TypeApplyCode::Ok) => Ok(()),
Ok(TypeApplyCode::ErrInput) => Err(TypeWriteError::ParseFailed {
decl: decl.clone(),
reason: reason_or(&result.reason, "the declaration is not valid"),
}
.into()),
Ok(TypeApplyCode::ErrApply) | Err(_) => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: reason_or(
&result.reason,
"the kernel could not apply the parsed type",
),
}
.into()),
}
}
other => {
let recipe = other.checked_serialize()?;
let result = self.apply_type_recipe(address, &recipe, 0);
match TypeApplyCode::try_from(result.code) {
Ok(TypeApplyCode::Ok) => Ok(()),
Ok(TypeApplyCode::ErrInput) => Err(TypeWriteError::BuildFailed {
reason: reason_or(
&result.reason,
&format!(
"could not build `{other}` (an unknown named type or invalid \
declaration within it)"
),
),
}
.into()),
Ok(TypeApplyCode::ErrApply) | Err(_) => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: reason_or(
&result.reason,
&format!("the kernel could not apply the built type `{other}`"),
),
}
.into()),
}
}
}
}
}
pub(crate) fn tinfo_apply_result(res: &sys::TypeWriteResult, address: Address) -> Result<()> {
match TypeApplyCode::try_from(res.code) {
Ok(TypeApplyCode::Ok) => Ok(()),
_ => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: reason_or(&res.reason, "the kernel could not apply the built type"),
}
.into()),
}
}
#[derive(Clone, Copy)]
pub struct Location<'db> {
db: &'db Database,
address: Address,
}
impl Location<'_> {
location_reads!();
#[inline]
#[must_use]
#[doc(alias("xrefblk_t", "first_to"))]
pub fn xrefs_to(&self) -> Xrefs {
self.db.xrefs_to(self.address)
}
#[inline]
#[must_use]
#[doc(alias("xrefblk_t", "first_from"))]
pub fn xrefs_from(&self) -> Xrefs {
self.db.xrefs_from(self.address)
}
}
impl std::fmt::Debug for Location<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Location")
.field("address", &self.address)
.finish_non_exhaustive()
}
}
key_identity!(Location, address);
pub struct LocationMut<'db> {
db: &'db mut Database,
address: Address,
auto_invalidate: bool,
pending: PendingInvalidation,
}
impl LocationMut<'_> {
#[inline]
pub(crate) fn db(&self) -> &Database {
self.db
}
#[inline]
pub(crate) fn db_mut(&mut self) -> &mut Database {
self.db
}
}
impl LocationMut<'_> {
location_reads!();
#[must_use]
pub fn auto_invalidate(mut self, on: bool) -> Self {
self.auto_invalidate = on;
self
}
pub(crate) fn queue_invalidation(&mut self, level: PendingInvalidation) {
self.pending = self.pending.max(level);
}
pub(crate) fn queued<T>(&mut self, out: Result<T>, level: PendingInvalidation) -> Result<T> {
if out.is_ok() {
self.queue_invalidation(level);
}
out
}
#[doc(alias("set_name"))]
pub fn rename(&mut self, name: impl AsRef<str>) -> Result<()> {
let ok = with_cstr(name.as_ref(), "name", |p| self.db.set_name(self.address, p))?;
let out = if ok {
Ok(())
} else {
Err(self.rejected("rename"))
};
self.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("set_cmt"))]
pub fn set_comment(&mut self, text: impl AsRef<str>, repeatable: bool) -> Result<()> {
let ok = with_cstr(text.as_ref(), "comment", |p| {
self.db.set_cmt(self.address, p, repeatable)
})?;
if ok {
Ok(())
} else {
Err(self.rejected("set_comment"))
}
}
#[doc(alias("patch_bytes"))]
pub fn patch(&mut self, bytes: &[u8]) -> Result<()> {
if bytes.is_empty() {
return Ok(());
}
let out = if self.db.patch_bytes(self.address, bytes) {
Ok(())
} else {
let (errno, reason) = self.db.last_reason();
Err(Error::WriteRejected {
op: "patch",
address: self.address.get(),
errno,
reason: reason.or_else(|| Some("target range is not fully mapped".to_owned())),
})
};
self.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("apply_tinfo", "apply_cdecl", "apply_named_type"))]
pub fn set_type(&mut self, ty: impl Into<TypeExpr>) -> Result<()> {
let out = self.db.apply_type_at(self.address, &ty.into());
self.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("apply_tinfo"))]
pub fn apply_type(&mut self, ty: &TypeInfo) -> Result<()> {
let res = self.db.apply_tinfo(self.address, ty.tinfo(), 0);
let out = tinfo_apply_result(&res, self.address);
self.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("del_tinfo", "set_tinfo"))]
pub fn clear_type(&mut self) -> Result<()> {
let out = match TypeApplyCode::try_from(self.db.clear_type(self.address).code) {
Ok(TypeApplyCode::Ok) => Ok(()),
_ => Err(self.rejected("clear_type")),
};
self.queued(out, PendingInvalidation::Dependents)
}
fn rejected(&self, op: &'static str) -> Error {
let (errno, reason) = self.db.last_reason();
Error::WriteRejected {
op,
address: self.address.get(),
errno,
reason,
}
}
}
impl std::fmt::Debug for LocationMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LocationMut")
.field("address", &self.address)
.finish_non_exhaustive()
}
}
impl Drop for LocationMut<'_> {
fn drop(&mut self) {
if !self.auto_invalidate {
return;
}
match self.pending {
PendingInvalidation::None => {}
PendingInvalidation::SelfOnly => {
self.db.invalidate_decompilation(self.address);
}
PendingInvalidation::Dependents => {
self.db.invalidate_decompilation_dependents(self.address);
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum PendingInvalidation {
None,
SelfOnly,
Dependents,
}
#[cfg(test)]
mod tests {
use assert2::assert;
use rstest::rstest;
use super::*;
#[rstest]
#[case::zero(0)]
#[case::small(0x1000)]
#[case::large(0xdead_beef)]
fn location_identity_compares_by_address(#[case] raw: u64) {
let db = Database::new();
let a = Address::new_const(raw);
let other = Address::new_const(raw.wrapping_add(1).max(1));
assert!(db.at(a) == db.at(a));
assert!(db.at(a) != db.at(other));
}
#[test]
fn location_debug_renders_the_address() {
let db = Database::new();
let loc = db.at(Address::new_const(0xdead_beef));
assert!(format!("{loc:?}") == "Location { address: Address(0xdeadbeef), .. }");
}
#[test]
fn location_mut_debug_renders_the_address() {
let mut db = Database::new();
let cursor = db.at_mut(Address::new_const(0xdead_beef));
assert!(format!("{cursor:?}") == "LocationMut { address: Address(0xdeadbeef), .. }");
}
#[test]
fn pending_invalidation_orders_by_breadth() {
assert!(PendingInvalidation::None < PendingInvalidation::SelfOnly);
assert!(PendingInvalidation::SelfOnly < PendingInvalidation::Dependents);
assert!(
PendingInvalidation::None.max(PendingInvalidation::Dependents)
== PendingInvalidation::Dependents
);
assert!(
PendingInvalidation::Dependents.max(PendingInvalidation::None)
== PendingInvalidation::Dependents
);
}
fn cursor(db: &mut Database) -> LocationMut<'_> {
LocationMut {
db,
address: Address::new_const(0x1000),
auto_invalidate: false,
pending: PendingInvalidation::None,
}
}
#[test]
fn queued_ok_widens_but_never_narrows() {
let mut db = Database::new();
let mut c = cursor(&mut db);
let _: Result<()> = c.queued(Ok(()), PendingInvalidation::SelfOnly);
assert!(c.pending == PendingInvalidation::SelfOnly);
let _: Result<()> = c.queued(Ok(()), PendingInvalidation::Dependents);
assert!(c.pending == PendingInvalidation::Dependents);
let _: Result<()> = c.queued(Ok(()), PendingInvalidation::SelfOnly);
assert!(c.pending == PendingInvalidation::Dependents);
}
#[test]
fn queued_err_leaves_pending_untouched() {
let mut db = Database::new();
let mut c = cursor(&mut db);
c.pending = PendingInvalidation::SelfOnly;
let out = c.queued(
Err::<(), _>(Error::InteriorNul { arg: "name" }),
PendingInvalidation::Dependents,
);
assert!(out == Err(Error::InteriorNul { arg: "name" }));
assert!(c.pending == PendingInvalidation::SelfOnly);
}
#[test]
fn queue_invalidation_widens_only() {
let mut db = Database::new();
let mut c = cursor(&mut db);
c.queue_invalidation(PendingInvalidation::Dependents);
assert!(c.pending == PendingInvalidation::Dependents);
c.queue_invalidation(PendingInvalidation::None);
assert!(c.pending == PendingInvalidation::Dependents);
}
}