summaryrefslogtreecommitdiff
path: root/plugins/workflow_objc/src/activities/util.rs
diff options
context:
space:
mode:
authorMark Rowe <mark@vector35.com>2026-03-23 19:34:55 -0700
committerMark Rowe <mark@vector35.com>2026-03-30 09:19:20 -0700
commitefbde23abcf1f38d98a096044e2256c5619f9004 (patch)
tree2c5d910ef3c2215431e3e112779ab2f203de7d84 /plugins/workflow_objc/src/activities/util.rs
parentdca922496f96d18f9060b1f7a349ecb5c1c1a9a1 (diff)
[ObjC] Propagate types from objc_alloc_init and friends
Diffstat (limited to 'plugins/workflow_objc/src/activities/util.rs')
-rw-r--r--plugins/workflow_objc/src/activities/util.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/plugins/workflow_objc/src/activities/util.rs b/plugins/workflow_objc/src/activities/util.rs
new file mode 100644
index 00000000..3ac7df7c
--- /dev/null
+++ b/plugins/workflow_objc/src/activities/util.rs
@@ -0,0 +1,140 @@
+use binaryninja::{
+ binary_view::{BinaryView, BinaryViewExt as _},
+ confidence::Conf,
+ function::Function,
+ medium_level_il::{
+ operation::{Constant, LiftedCallSsa, LiftedLoadSsa},
+ MediumLevelILFunction, MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind,
+ },
+ rc::Ref,
+ types::Type,
+ variable::{RegisterValueType, SSAVariable},
+};
+use bstr::BStr;
+
+#[allow(clippy::struct_field_names)]
+pub struct Call<'a> {
+ pub instr: &'a MediumLevelILLiftedInstruction,
+ pub call: &'a LiftedCallSsa,
+ pub target: Ref<Function>,
+}
+
+/// Returns a `Call` if `instr` is a call or tail call to a function whose name appears in `function_names`
+pub fn match_call_to_function_named<'a>(
+ instr: &'a MediumLevelILLiftedInstruction,
+ view: &'a BinaryView,
+ function_names: &'a [&[u8]],
+) -> Option<Call<'a>> {
+ let (MediumLevelILLiftedInstructionKind::TailcallSsa(ref call)
+ | MediumLevelILLiftedInstructionKind::CallSsa(ref call)) = instr.kind
+ else {
+ return None;
+ };
+
+ let MediumLevelILLiftedInstructionKind::ConstPtr(Constant {
+ constant: call_target,
+ }) = call.dest.kind
+ else {
+ return None;
+ };
+
+ let target_function = view.function_at(&instr.function.function().platform(), call_target)?;
+ let function_name = target_function.symbol().full_name();
+ if !function_names.contains(&function_name.to_bytes()) {
+ return None;
+ }
+
+ Some(Call {
+ instr,
+ call,
+ target: target_function,
+ })
+}
+
+fn ssa_variable_value_or_load_of_constant_pointer(
+ function: &MediumLevelILFunction,
+ var: &SSAVariable,
+) -> Option<u64> {
+ let value = function.ssa_variable_value(var);
+ match value.state {
+ RegisterValueType::ConstantPointerValue => return Some(value.value as u64),
+ RegisterValueType::UndeterminedValue => {}
+ _ => return None,
+ }
+
+ let def = function.ssa_variable_definition(var)?;
+ let MediumLevelILLiftedInstructionKind::SetVarSsa(set_var) = def.lift().kind else {
+ return None;
+ };
+
+ let MediumLevelILLiftedInstructionKind::LoadSsa(LiftedLoadSsa { src, .. }) = set_var.src.kind
+ else {
+ return None;
+ };
+
+ match src.kind {
+ MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant),
+ _ => None,
+ }
+}
+
+/// If `instr` is a constant pointer or is a variable whose value is loaded from a constant pointer,
+/// return that pointer address.
+pub fn match_constant_pointer_or_load_of_constant_pointer(
+ instr: &MediumLevelILLiftedInstruction,
+) -> Option<u64> {
+ match instr.kind {
+ MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant),
+ MediumLevelILLiftedInstructionKind::VarSsa(var) => {
+ ssa_variable_value_or_load_of_constant_pointer(&instr.function, &var.src)
+ }
+ _ => None,
+ }
+}
+
+pub fn class_name_from_symbol_name(symbol_name: &BStr) -> Option<&BStr> {
+ // The symbol name for the `objc_class_t` can have different names depending
+ // on factors such as being local or external, and whether the reference
+ // is from the shared cache or a standalone Mach-O file.
+ Some(if symbol_name.starts_with(b"cls_") {
+ &symbol_name[4..]
+ } else if symbol_name.starts_with(b"clsRef_") {
+ &symbol_name[7..]
+ } else if symbol_name.starts_with(b"_OBJC_CLASS_$_") {
+ &symbol_name[14..]
+ } else {
+ return None;
+ })
+}
+
+/// Adjust the return type of the call represented by `call`.
+pub fn adjust_return_type_of_call(call: &Call<'_>, return_type: &Type, confidence: u8) {
+ let function = call.instr.function.function();
+
+ // We're changing only the return type, so preserve other aspects of any existing call type adjustment.
+ let target_function_type = if let Some(existing_call_type_adjustment) =
+ function.call_type_adjustment(call.instr.address, None)
+ {
+ existing_call_type_adjustment.contents
+ } else {
+ call.target.function_type()
+ };
+
+ // There's nothing to do if the return type is already correct
+ if let Some(conf) = target_function_type.return_value() {
+ if &*conf.contents == return_type {
+ return;
+ }
+ }
+
+ let adjusted_call_type = target_function_type
+ .to_builder()
+ .set_child_type(return_type)
+ .finalize();
+
+ function.set_auto_call_type_adjustment(
+ call.instr.address,
+ Conf::new(&*adjusted_call_type, confidence),
+ None,
+ );
+}