diff --git a/rust/src/mlil/function.rs b/rust/src/mlil/function.rs index 3a3ce5e84..d9e133138 100644 --- a/rust/src/mlil/function.rs +++ b/rust/src/mlil/function.rs @@ -5,10 +5,14 @@ use binaryninjacore_sys::*; use crate::architecture::CoreArchitecture; use crate::basicblock::BasicBlock; +use crate::disassembly::DisassemblySettings; +use crate::flowgraph::FlowGraph; use crate::function::{Function, Location}; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; use crate::string::BnStrCompatible; -use crate::types::{Conf, PossibleValueSet, Type, UserVariableValues, Variable}; +use crate::types::{ + Conf, PossibleValueSet, RegisterValue, SSAVariable, Type, UserVariableValues, Variable, +}; use super::{MediumLevelILBlock, MediumLevelILInstruction, MediumLevelILLiftedInstruction}; @@ -371,6 +375,207 @@ impl MediumLevelILFunction { assert!(!refs.is_null()); unsafe { Array::new(refs, count, self.to_owned()) } } + + /// Current IL Address + pub fn current_address(&self) -> u64 { + unsafe { BNMediumLevelILGetCurrentAddress(self.handle) } + } + + /// Set the current IL Address + pub fn set_current_address(&self, value: u64, arch: Option) { + let arch = arch + .map(|x| x.0) + .unwrap_or_else(|| self.get_function().arch().0); + unsafe { BNMediumLevelILSetCurrentAddress(self.handle, arch, value) } + } + + /// Returns the BasicBlock at the given MLIL `instruction`. + pub fn basic_block_containing( + &self, + instruction: &MediumLevelILInstruction, + ) -> Option> { + let index = instruction.index; + let block = unsafe { BNGetMediumLevelILBasicBlockForInstruction(self.handle, index) }; + (!block.is_null()).then(|| unsafe { + BasicBlock::from_raw( + block, + MediumLevelILBlock { + function: self.to_owned(), + }, + ) + }) + } + /// ends the function and computes the list of basic blocks. + pub fn finalize(&self) { + unsafe { BNFinalizeMediumLevelILFunction(self.handle) } + } + + /// Generate SSA form given the current MLIL + /// + /// * `analyze_conditionals` - whether or not to analyze conditionals + /// * `handle_aliases` - whether or not to handle aliases + /// * `known_not_aliases` - optional list of variables known to be not aliased + /// * `known_aliases` - optional list of variables known to be aliased + pub fn generate_ssa_form( + &self, + analyze_conditionals: bool, + handle_aliases: bool, + known_not_aliases: impl IntoIterator, + known_aliases: impl IntoIterator, + ) { + let mut known_not_aliases: Box<[_]> = + known_not_aliases.into_iter().map(|x| x.raw()).collect(); + let mut known_aliases: Box<[_]> = known_aliases.into_iter().map(|x| x.raw()).collect(); + let (known_not_aliases_ptr, known_not_aliases_len) = if known_not_aliases.is_empty() { + (core::ptr::null_mut(), 0) + } else { + (known_not_aliases.as_mut_ptr(), known_not_aliases.len()) + }; + let (known_aliases_ptr, known_aliases_len) = if known_not_aliases.is_empty() { + (core::ptr::null_mut(), 0) + } else { + (known_aliases.as_mut_ptr(), known_aliases.len()) + }; + unsafe { + BNGenerateMediumLevelILSSAForm( + self.handle, + analyze_conditionals, + handle_aliases, + known_not_aliases_ptr, + known_not_aliases_len, + known_aliases_ptr, + known_aliases_len, + ) + } + } + + /// Gets the instruction that contains the given SSA variable's definition. + /// + /// Since SSA variables can only be defined once, this will return the single instruction where that occurs. + /// For SSA variable version 0s, which don't have definitions, this will return None instead. + pub fn ssa_variable_definition(&self, var: SSAVariable) -> Option { + let result = unsafe { + BNGetMediumLevelILSSAVarDefinition(self.handle, &var.variable.raw(), var.version) + }; + (result < self.instruction_count()) + .then(|| MediumLevelILInstruction::new(self.to_owned(), result)) + } + + pub fn ssa_memory_definition(&self, version: usize) -> Option { + let result = unsafe { BNGetMediumLevelILSSAMemoryDefinition(self.handle, version) }; + (result < self.instruction_count()) + .then(|| MediumLevelILInstruction::new(self.to_owned(), result)) + } + + ///Gets all the instructions that use the given SSA variable. + pub fn ssa_variable_uses(&self, ssa_var: SSAVariable) -> Array { + let mut count = 0; + let uses = unsafe { + BNGetMediumLevelILSSAVarUses( + self.handle, + &ssa_var.variable.raw(), + ssa_var.version, + &mut count, + ) + }; + assert!(!uses.is_null()); + unsafe { Array::new(uses, count, self.to_owned()) } + } + + pub fn ssa_memory_uses(&self, version: usize) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILSSAMemoryUses(self.handle, version, &mut count) }; + assert!(!uses.is_null()); + unsafe { Array::new(uses, count, self.to_owned()) } + } + + /// determines if `ssa_var` is live at any point in the function + pub fn is_ssa_variable_live(&self, ssa_var: SSAVariable) -> bool { + unsafe { + BNIsMediumLevelILSSAVarLive(self.handle, &ssa_var.variable.raw(), ssa_var.version) + } + } + + pub fn variable_definitions(&self, variable: Variable) -> Array { + let mut count = 0; + let defs = unsafe { + BNGetMediumLevelILVariableDefinitions(self.handle, &variable.raw(), &mut count) + }; + unsafe { Array::new(defs, count, self.to_owned()) } + } + + pub fn variable_uses(&self, variable: Variable) -> Array { + let mut count = 0; + let uses = + unsafe { BNGetMediumLevelILVariableUses(self.handle, &variable.raw(), &mut count) }; + unsafe { Array::new(uses, count, self.to_owned()) } + } + + /// Computes the list of instructions for which `var` is live. + /// If `include_last_use` is false, the last use of the variable will not be included in the + /// list (this allows for easier computation of overlaps in liveness between two variables). + /// If the variable is never used, this function will return an empty list. + /// + /// `var` - the variable to query + /// `include_last_use` - whether to include the last use of the variable in the list of instructions + pub fn live_instruction_for_variable( + &self, + variable: Variable, + include_last_user: bool, + ) -> Array { + let mut count = 0; + let uses = unsafe { + BNGetMediumLevelILLiveInstructionsForVariable( + self.handle, + &variable.raw(), + include_last_user, + &mut count, + ) + }; + unsafe { Array::new(uses, count, self.to_owned()) } + } + + pub fn ssa_variable_value(&self, ssa_var: SSAVariable) -> RegisterValue { + unsafe { + BNGetMediumLevelILSSAVarValue(self.handle, &ssa_var.variable.raw(), ssa_var.version) + } + .into() + } + + pub fn create_graph(&self, settings: Option) -> FlowGraph { + let settings = settings.map(|x| x.handle).unwrap_or(core::ptr::null_mut()); + let graph = unsafe { BNCreateMediumLevelILFunctionGraph(self.handle, settings) }; + unsafe { FlowGraph::from_raw(graph) } + } + + /// This gets just the MLIL variables - you may be interested in the union + /// of [MediumLevelIlFunction::aliased_variables] and + /// [crate::function::Function::parameter_variables] for all the + /// variables used in the function + pub fn variables(&self) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) }; + unsafe { Array::new(uses, count, ()) } + } + + /// This returns a list of Variables that are taken reference to and used + /// elsewhere. You may also wish to consider [MediumLevelIlFunction::variables] + /// and [crate::function::Function::parameter_variables] + pub fn aliased_variables(&self) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILAliasedVariables(self.handle, &mut count) }; + unsafe { Array::new(uses, count, ()) } + } + + /// This gets just the MLIL SSA variables - you may be interested in the + /// union of [MediumLevelIlFunction::aliased_variables] and + /// [crate::function::Function::parameter_variables] for all the + /// variables used in the function. + pub fn ssa_variables(&self) -> Array> { + let mut count = 0; + let vars = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) }; + unsafe { Array::new(vars, count, self.to_owned()) } + } } impl ToOwned for MediumLevelILFunction { diff --git a/rust/src/mlil/instruction.rs b/rust/src/mlil/instruction.rs index cb7628853..bcaf3f9da 100644 --- a/rust/src/mlil/instruction.rs +++ b/rust/src/mlil/instruction.rs @@ -1,12 +1,11 @@ -use binaryninjacore_sys::BNGetDefaultIndexForMediumLevelILVariableDefinition; -use binaryninjacore_sys::BNGetMediumLevelILByIndex; -use binaryninjacore_sys::BNMediumLevelILInstruction; -use binaryninjacore_sys::BNMediumLevelILOperation; +use binaryninjacore_sys::*; +use crate::disassembly::InstructionTextToken; use crate::operand_iter::OperandIter; -use crate::rc::Ref; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; use crate::types::{ - ConstantData, ILIntrinsic, RegisterValue, RegisterValueType, SSAVariable, Variable, + Conf, ConstantData, DataFlowQueryOption, ILBranchDependence, ILIntrinsic, PossibleValueSet, + RegisterValue, RegisterValueType, SSAVariable, Type, Variable, }; use super::lift::*; @@ -1034,20 +1033,386 @@ impl MediumLevelILInstruction { } } + pub fn tokens(&self) -> Array { + let mut count = 0; + let mut tokens = core::ptr::null_mut(); + assert!(unsafe { + BNGetMediumLevelILExprText( + self.function.handle, + self.function.get_function().arch().0, + self.index, + &mut tokens, + &mut count, + core::ptr::null_mut(), + ) + }); + unsafe { Array::new(tokens, count, ()) } + } + + /// Value of expression if constant or a known value + pub fn value(&self) -> RegisterValue { + unsafe { BNGetMediumLevelILExprValue(self.function.handle, self.index) }.into() + } + + /// Possible values of expression using path-sensitive static data flow analysis + pub fn possible_values(&self, options: Option<&[DataFlowQueryOption]>) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleExprValues( + self.function.handle, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_ssa_variable_values( + &self, + ssa_var: SSAVariable, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleSSAVarValues( + self.function.handle, + &ssa_var.variable.raw(), + ssa_var.version, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + /// return the variable version used at this instruction + pub fn ssa_variable_version(&self, var: Variable) -> SSAVariable { + let version = unsafe { + BNGetMediumLevelILSSAVarVersionAtILInstruction( + self.function.handle, + &var.raw(), + self.index, + ) + }; + SSAVariable::new(var, version) + } + + /// Set of branching instructions that must take the true or false path to reach this instruction + pub fn branch_dependence(&self) -> Array { + let mut count = 0; + let deps = unsafe { + BNGetAllMediumLevelILBranchDependence(self.function.handle, self.index, &mut count) + }; + assert!(!deps.is_null()); + unsafe { Array::new(deps, count, self.function.clone()) } + } + + pub fn branch_dependence_at(&self, instruction: MediumLevelILInstruction) -> BranchDependence { + let deps = unsafe { + BNGetMediumLevelILBranchDependence(self.function.handle, self.index, instruction.index) + }; + BranchDependence { + instruction, + dependence: deps, + } + } + + /// Version of active memory contents in SSA form for this instruction + pub fn ssa_memory_version(&self) -> usize { + unsafe { + BNGetMediumLevelILSSAMemoryVersionAtILInstruction(self.function.handle, self.index) + } + } + + /// Type of expression + pub fn expr_type(&self) -> Option>> { + let result = unsafe { BNGetMediumLevelILExprType(self.function.handle, self.index) }; + (!result.type_.is_null()).then(|| { + Conf::new( + unsafe { Type::ref_from_raw(result.type_) }, + result.confidence, + ) + }) + } + + /// Set type of expression + /// + /// This API is only meant for workflows or for debugging purposes, since the changes they make are not persistent + /// and get lost after a database save and reload. To make persistent changes to the analysis, one should use other + /// APIs to, for example, change the type of variables. The analysis will then propagate the type of the variable + /// and update the type of related expressions. + pub fn set_expr_type<'a, T: Into>>(&self, value: T) { + let type_: Conf<&'a Type> = value.into(); + let mut type_raw: BNTypeWithConfidence = BNTypeWithConfidence { + type_: type_.contents.handle, + confidence: type_.confidence, + }; + unsafe { BNSetMediumLevelILExprType(self.function.handle, self.index, &mut type_raw) } + } + + pub fn variable_for_register(&self, reg_id: u32) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForRegisterAtInstruction( + self.function.handle, + reg_id, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn variable_for_flag(&self, flag_id: u32) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForFlagAtInstruction( + self.function.handle, + flag_id, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn variable_for_stack_location(&self, offset: i64) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForStackLocationAtInstruction( + self.function.handle, + offset, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn register_value(&self, reg_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILRegisterValueAtInstruction(self.function.handle, reg_id, self.index) + } + .into() + } + + pub fn register_value_after(&self, reg_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILRegisterValueAfterInstruction( + self.function.handle, + reg_id, + self.index, + ) + } + .into() + } + + pub fn possible_register_values( + &self, + reg_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleRegisterValuesAtInstruction( + self.function.handle, + reg_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_register_values_after( + &self, + reg_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleRegisterValuesAfterInstruction( + self.function.handle, + reg_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn flag_value(&self, flag_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILFlagValueAtInstruction(self.function.handle, flag_id, self.index) + } + .into() + } + + pub fn flag_value_after(&self, flag_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILFlagValueAfterInstruction(self.function.handle, flag_id, self.index) + } + .into() + } + + pub fn possible_flag_values( + &self, + flag_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleFlagValuesAtInstruction( + self.function.handle, + flag_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_flag_values_after( + &self, + flag_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleFlagValuesAfterInstruction( + self.function.handle, + flag_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn stack_contents(&self, offset: i64, size: usize) -> RegisterValue { + unsafe { + BNGetMediumLevelILStackContentsAtInstruction( + self.function.handle, + offset, + size, + self.index, + ) + } + .into() + } + + pub fn stack_contents_after(&self, offset: i64, size: usize) -> RegisterValue { + unsafe { + BNGetMediumLevelILStackContentsAfterInstruction( + self.function.handle, + offset, + size, + self.index, + ) + } + .into() + } + + pub fn possible_stack_contents( + &self, + offset: i64, + size: usize, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleStackContentsAtInstruction( + self.function.handle, + offset, + size, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_stack_contents_after( + &self, + offset: i64, + size: usize, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleStackContentsAfterInstruction( + self.function.handle, + offset, + size, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + /// Gets the unique variable for a definition instruction. This unique variable can be passed /// to [crate::function::Function::split_variable] to split a variable at a definition. The given `var` is the /// assigned variable to query. /// /// * `var` - variable to query - pub fn get_split_var_for_definition(&self, var: &Variable) -> Variable { - let new_index = unsafe { + pub fn split_var_for_definition(&self, var: Variable) -> Variable { + let index = unsafe { BNGetDefaultIndexForMediumLevelILVariableDefinition( self.function.handle, &var.raw(), self.index, ) }; - Variable::new(var.t, new_index, var.storage) + Variable::new(var.t, index, var.storage) + } + + /// alias for [MediumLevelILInstruction::split_var_for_definition] + #[inline] + pub fn get_split_var_for_definition(&self, var: &Variable) -> Variable { + self.split_var_for_definition(*var) } fn lift_operand(&self, expr_idx: usize) -> Box { @@ -1123,6 +1488,22 @@ impl MediumLevelILInstruction { } } +impl CoreArrayProvider for MediumLevelILInstruction { + type Raw = usize; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for MediumLevelILInstruction { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeILInstructionList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + context.instruction_from_idx(*raw) + } +} + fn get_float(value: u64, size: usize) -> f64 { match size { 4 => f32::from_bits(value as u32) as f64, @@ -1176,3 +1557,28 @@ fn get_call_params_ssa( assert_eq!(op.operation, BNMediumLevelILOperation::MLIL_CALL_PARAM_SSA); OperandIter::new(function, op.operands[2] as usize, op.operands[1] as usize).exprs() } + +/// Conditional branching instruction and an expected conditional result +pub struct BranchDependence { + pub instruction: MediumLevelILInstruction, + pub dependence: ILBranchDependence, +} + +impl CoreArrayProvider for BranchDependence { + type Raw = BNILBranchInstructionAndDependence; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for BranchDependence { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + unsafe { BNFreeILBranchDependenceList(raw) }; + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self { + instruction: MediumLevelILInstruction::new(context.clone(), raw.branch), + dependence: raw.dependence, + } + } +} diff --git a/rust/src/types.rs b/rust/src/types.rs index 1a5918012..5bcc2529a 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -24,6 +24,7 @@ use crate::{ callingconvention::CallingConvention, filemetadata::FileMetadata, function::Function, + mlil::MediumLevelILFunction, rc::*, string::{raw_to_string, BnStrCompatible, BnString}, symbol::Symbol, @@ -51,6 +52,8 @@ pub type TypeClass = BNTypeClass; pub type NamedTypeReferenceClass = BNNamedTypeReferenceClass; pub type MemberAccess = BNMemberAccess; pub type MemberScope = BNMemberScope; +pub type ILBranchDependence = BNILBranchDependence; +pub type DataFlowQueryOption = BNDataFlowQueryOption; //////////////// // Confidence @@ -1444,6 +1447,41 @@ impl SSAVariable { } } +impl CoreArrayProvider for SSAVariable { + type Raw = usize; + type Context = Variable; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for SSAVariable { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeILInstructionList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + SSAVariable::new(*context, *raw) + } +} + +impl CoreArrayProvider for Array { + type Raw = BNVariable; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for Array { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeVariableList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + let mut count = 0; + let versions = + unsafe { BNGetMediumLevelILVariableSSAVersions(context.handle, raw, &mut count) }; + Array::new(versions, count, Variable::from_raw(*raw)) + } +} + /////////////// // NamedVariable