use std::ffi::c_int;
use idakit_sys::SigWriteCode;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum::VariantArray;
use crate::address::Address;
use crate::error::Result;
use crate::ffi::reason_or;
use crate::types::TypeWriteError;
pub(super) fn sig_result(
code: c_int,
address: Address,
arg: Option<(usize, usize)>,
reason: &str,
) -> Result<()> {
match SigWriteCode::try_from(code) {
Ok(SigWriteCode::Ok) => Ok(()),
Ok(SigWriteCode::NoPrototype) => Err(TypeWriteError::NoPrototype {
address: address.get(),
}
.into()),
Ok(SigWriteCode::ArgRange) => {
let (index, arity) = arg.unwrap_or_default();
Err(TypeWriteError::ArgIndexOutOfRange {
address: address.get(),
index,
arity,
}
.into())
}
Ok(SigWriteCode::Build) => Err(TypeWriteError::BuildFailed {
reason: reason_or(
reason,
"an unknown named type or invalid declaration within it",
),
}
.into()),
Ok(SigWriteCode::Apply) => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: reason_or(reason, "the kernel rejected the edited signature"),
}
.into()),
Err(_) => Err(TypeWriteError::ApplyRejected {
address: address.get(),
reason: reason_or(
reason,
&format!(
"the kernel rejected the edited signature (unexpected facade code {code})"
),
),
}
.into()),
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
TryFromPrimitive,
IntoPrimitive,
VariantArray,
)]
#[repr(u8)]
#[doc(alias("cm_t", "CM_CC_MASK"))]
pub enum CallingConvention {
Unknown = 0x10,
Cdecl = 0x30,
Stdcall = 0x50,
Pascal = 0x60,
Fastcall = 0x70,
Thiscall = 0x80,
Swift = 0x90,
Golang = 0xB0,
}
#[cfg(test)]
mod tests {
use assert2::assert;
use idakit_sys as sys;
use rstest::rstest;
use super::*;
#[test]
fn cm_cc_ids_align_with_the_facade() {
let ids = sys::cm_cc_ids();
assert!(
ids.len() == CallingConvention::VARIANTS.len(),
"facade lists {} ids for {} variants",
ids.len(),
CallingConvention::VARIANTS.len()
);
for (i, &cc) in CallingConvention::VARIANTS.iter().enumerate() {
assert!(
ids[i] == u8::from(cc),
"calling convention {cc:?}: facade CM_CC_ {:#x} != discriminant {:#x}",
ids[i],
u8::from(cc)
);
}
}
#[test]
fn calling_convention_round_trips() {
for &cc in CallingConvention::VARIANTS {
assert!(CallingConvention::try_from(u8::from(cc)).ok() == Some(cc));
}
assert!(CallingConvention::try_from(0x20u8).is_err());
}
#[rstest]
#[case(CallingConvention::Unknown, 0x10)]
#[case(CallingConvention::Cdecl, 0x30)]
#[case(CallingConvention::Stdcall, 0x50)]
#[case(CallingConvention::Pascal, 0x60)]
#[case(CallingConvention::Fastcall, 0x70)]
#[case(CallingConvention::Thiscall, 0x80)]
#[case(CallingConvention::Swift, 0x90)]
#[case(CallingConvention::Golang, 0xB0)]
fn calling_convention_pins_cm_cc(#[case] cc: CallingConvention, #[case] raw: u8) {
assert!(u8::from(cc) == raw);
}
#[test]
fn sig_result_classifies_every_known_code() {
use idakit_sys::{SIG_APPLY, SIG_ARG_RANGE, SIG_BUILD, SIG_NO_PROTOTYPE, SIG_OK};
let address = Address::new_const(0x1000);
assert!(sig_result(SIG_OK, address, None, "").is_ok());
assert!(let Err(_) = sig_result(SIG_NO_PROTOTYPE, address, None, ""));
assert!(let Err(_) = sig_result(SIG_ARG_RANGE, address, Some((3, 2)), ""));
assert!(let Err(_) = sig_result(SIG_BUILD, address, None, ""));
assert!(let Err(_) = sig_result(SIG_APPLY, address, None, "kernel said no"));
}
mod proptests {
use proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn sig_result_only_ok_succeeds(code in any::<i32>(), reason in ".*") {
let address = Address::new_const(0x1000);
let result = sig_result(code, address, Some((0, 1)), &reason);
prop_assert_eq!(result.is_ok(), code == idakit_sys::SIG_OK);
}
}
}
}