From 4d8feb4921cde4b70bb6db4ccd525a69205dee7f Mon Sep 17 00:00:00 2001 From: rbran Date: Thu, 10 Apr 2025 12:58:27 +0000 Subject: [Rust] Implement `LanguageRepresentation` and `LineFormatter` --- rust/src/function.rs | 37 ++ rust/src/high_level_il.rs | 1 + rust/src/high_level_il/function.rs | 7 +- rust/src/high_level_il/token_emitter.rs | 400 ++++++++++++++++++++ rust/src/language_representation.rs | 635 ++++++++++++++++++++++++++++++++ rust/src/lib.rs | 2 + rust/src/line_formatter.rs | 172 +++++++++ rust/src/render_layer.rs | 4 +- rust/src/type_parser.rs | 2 +- rust/src/type_printer.rs | 2 +- 10 files changed, 1257 insertions(+), 5 deletions(-) create mode 100644 rust/src/high_level_il/token_emitter.rs create mode 100644 rust/src/language_representation.rs create mode 100644 rust/src/line_formatter.rs (limited to 'rust/src') diff --git a/rust/src/function.rs b/rust/src/function.rs index c1c29873..edde15a7 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -40,6 +40,7 @@ pub use binaryninjacore_sys::BNHighlightStandardColor as HighlightStandardColor; use crate::architecture::RegisterId; use crate::confidence::Conf; use crate::high_level_il::HighLevelILFunction; +use crate::language_representation::CoreLanguageRepresentationFunction; use crate::low_level_il::{LiftedILFunction, RegularLowLevelILFunction}; use crate::medium_level_il::MediumLevelILFunction; use crate::variable::{ @@ -476,6 +477,42 @@ impl Function { } } + /// Get the language representation of the function. + /// + /// * `language` - The language representation, ex. "Pseudo C". + pub fn language_representation( + &self, + language: S, + ) -> Option> { + let lang_name = language.into_bytes_with_nul(); + let repr = unsafe { + BNGetFunctionLanguageRepresentation( + self.handle, + lang_name.as_ref().as_ptr() as *const c_char, + ) + }; + NonNull::new(repr) + .map(|handle| unsafe { CoreLanguageRepresentationFunction::ref_from_raw(handle) }) + } + + /// Get the language representation of the function, if available. + /// + /// * `language` - The language representation, ex. "Pseudo C". + pub fn language_representation_if_available( + &self, + language: S, + ) -> Option> { + let lang_name = language.into_bytes_with_nul(); + let repr = unsafe { + BNGetFunctionLanguageRepresentationIfAvailable( + self.handle, + lang_name.as_ref().as_ptr() as *const c_char, + ) + }; + NonNull::new(repr) + .map(|handle| unsafe { CoreLanguageRepresentationFunction::ref_from_raw(handle) }) + } + pub fn high_level_il(&self, full_ast: bool) -> Result, ()> { unsafe { let hlil_ptr = BNGetFunctionHighLevelIL(self.handle); diff --git a/rust/src/high_level_il.rs b/rust/src/high_level_il.rs index adb44a7e..8d02720d 100644 --- a/rust/src/high_level_il.rs +++ b/rust/src/high_level_il.rs @@ -4,6 +4,7 @@ mod function; mod instruction; mod lift; pub mod operation; +pub mod token_emitter; pub use self::block::*; pub use self::function::*; diff --git a/rust/src/high_level_il/function.rs b/rust/src/high_level_il/function.rs index 0813ad04..a65a244c 100644 --- a/rust/src/high_level_il/function.rs +++ b/rust/src/high_level_il/function.rs @@ -15,12 +15,17 @@ pub struct HighLevelILFunction { } impl HighLevelILFunction { + pub(crate) unsafe fn from_raw(handle: *mut BNHighLevelILFunction, full_ast: bool) -> Self { + debug_assert!(!handle.is_null()); + Self { handle, full_ast } + } + pub(crate) unsafe fn ref_from_raw( handle: *mut BNHighLevelILFunction, full_ast: bool, ) -> Ref { debug_assert!(!handle.is_null()); - Self { handle, full_ast }.to_owned() + Ref::new(Self { handle, full_ast }) } pub fn instruction_from_index( diff --git a/rust/src/high_level_il/token_emitter.rs b/rust/src/high_level_il/token_emitter.rs new file mode 100644 index 00000000..2f1236a9 --- /dev/null +++ b/rust/src/high_level_il/token_emitter.rs @@ -0,0 +1,400 @@ +use std::ptr::NonNull; + +use binaryninjacore_sys::*; + +use crate::disassembly::{ + DisassemblySettings, DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContext, + InstructionTextTokenType, +}; +use crate::high_level_il::HighLevelILFunction; +use crate::language_representation::{OperatorPrecedence, SymbolDisplayResult, SymbolDisplayType}; +use crate::rc::{Array, Ref, RefCountable}; +use crate::variable::Variable; + +pub type ScopeType = BNScopeType; +pub type TokenEmitterExpr = BNTokenEmitterExpr; +pub type BraceRequirement = BNBraceRequirement; + +#[derive(PartialEq, Eq, Hash)] +pub struct HighLevelILTokenEmitter { + handle: NonNull, +} + +impl HighLevelILTokenEmitter { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + /// Returns the list of [`InstructionTextToken`] on the current line. + pub fn current_tokens(&self) -> Array { + let mut count = 0; + let array = + unsafe { BNHighLevelILTokenEmitterGetCurrentTokens(self.handle.as_ptr(), &mut count) }; + unsafe { Array::new(array, count, ()) } + } + + /// Returns the list of [`DisassemblyTextLine`] in the output. + pub fn lines(&self) -> Array { + let mut count = 0; + let array = unsafe { BNHighLevelILTokenEmitterGetLines(self.handle.as_ptr(), &mut count) }; + unsafe { Array::new(array, count, ()) } + } + + pub fn prepend_collapse_blank_indicator(&self) { + unsafe { BNHighLevelILTokenPrependCollapseBlankIndicator(self.handle.as_ptr()) }; + } + + pub fn prepend_collapse_indicator(&self, context: InstructionTextTokenContext, hash: u64) { + unsafe { + BNHighLevelILTokenPrependCollapseIndicator(self.handle.as_ptr(), context.into(), hash) + }; + } + + pub fn has_collapsable_regions(&self) -> bool { + unsafe { BNHighLevelILTokenEmitterHasCollapsableRegions(self.handle.as_ptr()) } + } + + pub fn set_has_collapsable_regions(&self, state: bool) { + unsafe { BNHighLevelILTokenEmitterSetHasCollapsableRegions(self.handle.as_ptr(), state) }; + } + + pub fn append(&self, token: InstructionTextToken) { + let mut raw_token = InstructionTextToken::into_raw(token); + unsafe { BNHighLevelILTokenEmitterAppend(self.handle.as_ptr(), &mut raw_token) }; + InstructionTextToken::free_raw(raw_token); + } + + /// Starts a new line in the output. + pub fn init_line(&self) { + unsafe { BNHighLevelILTokenEmitterInitLine(self.handle.as_ptr()) }; + } + + // TODO: Difference from `init_line`? + /// Starts a new line in the output. + pub fn new_line(&self) { + unsafe { BNHighLevelILTokenEmitterNewLine(self.handle.as_ptr()) }; + } + + /// Increases the indentation level by one. + pub fn increase_indent(&self) { + unsafe { BNHighLevelILTokenEmitterIncreaseIndent(self.handle.as_ptr()) }; + } + + /// Decreases the indentation level by one. + pub fn decrease_indent(&self) { + unsafe { BNHighLevelILTokenEmitterDecreaseIndent(self.handle.as_ptr()) }; + } + + /// Indicates that visual separation of scopes is desirable at the current position. + /// + /// By default, this will insert a blank line, but this can be configured by the user. + pub fn scope_separator(&self) { + unsafe { BNHighLevelILTokenEmitterScopeSeparator(self.handle.as_ptr()) }; + } + + /// Begins a new scope. Insertion of newlines and braces will be handled using the current settings. + pub fn begin_scope(&self, ty: ScopeType) { + unsafe { BNHighLevelILTokenEmitterBeginScope(self.handle.as_ptr(), ty) }; + } + + /// Ends the current scope. + /// + /// The type `ty` should be equal to what was passed to [`HighLevelILTokenEmitter::begin_scope`]. + pub fn end_scope(&self, ty: ScopeType) { + unsafe { BNHighLevelILTokenEmitterEndScope(self.handle.as_ptr(), ty) }; + } + + /// Continues the previous scope with a new associated scope. This is most commonly used for else statements. + /// + /// If `force_same_line` is true, the continuation will always be placed on the same line as the previous scope. + pub fn scope_continuation(&self, force_same_line: bool) { + unsafe { + BNHighLevelILTokenEmitterScopeContinuation(self.handle.as_ptr(), force_same_line) + }; + } + + /// Finalizes the previous scope, indicating that there are no more associated scopes. + pub fn finalize_scope(&self) { + unsafe { BNHighLevelILTokenEmitterFinalizeScope(self.handle.as_ptr()) }; + } + + /// Forces there to be no indentation for the next line. + pub fn no_indent_for_this_line(&self) { + unsafe { BNHighLevelILTokenEmitterNoIndentForThisLine(self.handle.as_ptr()) }; + } + + /// Begins a region of tokens that always have zero confidence. + pub fn begin_force_zero_confidence(&self) { + unsafe { BNHighLevelILTokenEmitterBeginForceZeroConfidence(self.handle.as_ptr()) }; + } + + /// Ends a region of tokens that always have zero confidence. + pub fn end_force_zero_confidence(&self) { + unsafe { BNHighLevelILTokenEmitterEndForceZeroConfidence(self.handle.as_ptr()) }; + } + + /// Sets the current expression. Returning the [`CurrentTokenEmitterExpr`] which when dropped + /// will restore the previously active [`TokenEmitterExpr`]. + pub fn set_current_expr(&self, expr: TokenEmitterExpr) -> CurrentTokenEmitterExpr { + let previous_expr = + unsafe { BNHighLevelILTokenEmitterSetCurrentExpr(self.handle.as_ptr(), expr) }; + CurrentTokenEmitterExpr::new(self.to_owned(), expr, previous_expr) + } + + fn restore_current_expr(&self, expr: TokenEmitterExpr) { + unsafe { BNHighLevelILTokenEmitterRestoreCurrentExpr(self.handle.as_ptr(), expr) }; + } + + /// Finalizes the outputted lines. + pub fn finalize(&self) { + unsafe { BNHighLevelILTokenEmitterFinalize(self.handle.as_ptr()) }; + } + + /// Appends `(`. + pub fn append_open_paren(&self) { + unsafe { BNHighLevelILTokenEmitterAppendOpenParen(self.handle.as_ptr()) }; + } + + /// Appends `)`. + pub fn append_close_paren(&self) { + unsafe { BNHighLevelILTokenEmitterAppendCloseParen(self.handle.as_ptr()) }; + } + + /// Appends `[`. + pub fn append_open_bracket(&self) { + unsafe { BNHighLevelILTokenEmitterAppendOpenBracket(self.handle.as_ptr()) }; + } + + /// Appends `]`. + pub fn append_close_bracket(&self) { + unsafe { BNHighLevelILTokenEmitterAppendCloseBracket(self.handle.as_ptr()) }; + } + + /// Appends `{`. + pub fn append_open_brace(&self) { + unsafe { BNHighLevelILTokenEmitterAppendOpenBrace(self.handle.as_ptr()) }; + } + + /// Appends `}`. + pub fn append_close_brace(&self) { + unsafe { BNHighLevelILTokenEmitterAppendCloseBrace(self.handle.as_ptr()) }; + } + + /// Appends `;`. + pub fn append_semicolon(&self) { + unsafe { BNHighLevelILTokenEmitterAppendSemicolon(self.handle.as_ptr()) }; + } + + /// Sets the requirement for insertion of braces around scopes in the output. + pub fn set_brace_requirement(&self, required: BraceRequirement) { + unsafe { BNHighLevelILTokenEmitterSetBraceRequirement(self.handle.as_ptr(), required) }; + } + + /// Sets whether cases within switch statements should always have braces around them. + pub fn set_braces_around_switch_cases(&self, braces: bool) { + unsafe { + BNHighLevelILTokenEmitterSetBracesAroundSwitchCases(self.handle.as_ptr(), braces) + }; + } + + /// Sets whether braces should default to being on the same line as the statement that begins the scope. + /// + /// If the user has explicitly set a preference, this setting will be ignored and the user's preference will be used instead. + pub fn set_default_braces_on_same_line(&self, same_line: bool) { + unsafe { + BNHighLevelILTokenEmitterSetDefaultBracesOnSameLine(self.handle.as_ptr(), same_line) + }; + } + + /// Sets whether omitting braces around single-line scopes is allowed. + pub fn set_simple_scope_allowed(&self, allowed: bool) { + unsafe { BNHighLevelILTokenEmitterSetSimpleScopeAllowed(self.handle.as_ptr(), allowed) }; + } + + pub fn brace_requirement(&self) -> BraceRequirement { + unsafe { BNHighLevelILTokenEmitterGetBraceRequirement(self.handle.as_ptr()) } + } + + pub fn has_braces_around_switch_cases(&self) -> bool { + unsafe { BNHighLevelILTokenEmitterHasBracesAroundSwitchCases(self.handle.as_ptr()) } + } + + pub fn default_braces_on_same_line(&self) -> bool { + unsafe { BNHighLevelILTokenEmitterGetDefaultBracesOnSameLine(self.handle.as_ptr()) } + } + + pub fn is_simple_scope_allowed(&self) -> bool { + unsafe { BNHighLevelILTokenEmitterIsSimpleScopeAllowed(self.handle.as_ptr()) } + } + + /// Appends a size token for the given size in the High Level IL syntax. + pub fn append_size_token(&self, size: usize, ty: InstructionTextTokenType) { + unsafe { BNAddHighLevelILSizeToken(size, ty, self.handle.as_ptr()) } + } + + /// Appends a floating point size token for the given size in the High Level IL syntax. + pub fn append_float_size_token(&self, size: usize, ty: InstructionTextTokenType) { + unsafe { BNAddHighLevelILFloatSizeToken(size, ty, self.handle.as_ptr()) } + } + + /// Appends tokens for access to a variable. + pub fn append_var_text_token( + &self, + func: &HighLevelILFunction, + var: Variable, + expr_index: usize, + size: usize, + ) { + unsafe { + BNAddHighLevelILVarTextToken( + func.handle, + &BNVariable::from(var), + self.handle.as_ptr(), + expr_index, + size, + ) + } + } + + /// Appends tokens for a constant integer value. + pub fn append_integer_text_token( + &self, + func: &HighLevelILFunction, + expr_index: usize, + val: i64, + size: usize, + ) { + unsafe { + BNAddHighLevelILIntegerTextToken( + func.handle, + expr_index, + val, + size, + self.handle.as_ptr(), + ) + } + } + + /// Appends tokens for accessing an array by constant index. + pub fn append_array_index_token( + &self, + func: &HighLevelILFunction, + expr_index: usize, + val: i64, + size: usize, + address: Option, + ) { + unsafe { + BNAddHighLevelILArrayIndexToken( + func.handle, + expr_index, + val, + size, + self.handle.as_ptr(), + address.unwrap_or(0), + ) + } + } + + /// Appends tokens for displaying a constant pointer value. + /// + /// If `allow_short_string` is true, then a string will be shown even if it is "short". + pub fn append_pointer_text_token( + &self, + func: &HighLevelILFunction, + expr_index: usize, + val: i64, + settings: &DisassemblySettings, + symbol_display: SymbolDisplayType, + precedence: OperatorPrecedence, + allow_short_string: bool, + ) -> SymbolDisplayResult { + unsafe { + BNAddHighLevelILPointerTextToken( + func.handle, + expr_index, + val, + self.handle.as_ptr(), + settings.handle, + symbol_display, + precedence, + allow_short_string, + ) + } + } + + /// Appends tokens for a constant value. + pub fn append_constant_text_token( + &self, + func: &HighLevelILFunction, + expr_index: usize, + val: i64, + size: usize, + settings: &DisassemblySettings, + precedence: OperatorPrecedence, + ) { + unsafe { + BNAddHighLevelILConstantTextToken( + func.handle, + expr_index, + val, + size, + self.handle.as_ptr(), + settings.handle, + precedence, + ) + } + } +} + +unsafe impl Send for HighLevelILTokenEmitter {} +unsafe impl Sync for HighLevelILTokenEmitter {} + +unsafe impl RefCountable for HighLevelILTokenEmitter { + unsafe fn inc_ref(handle: &Self) -> Ref { + let handle = BNNewHighLevelILTokenEmitterReference(handle.handle.as_ptr()); + let handle = NonNull::new(handle).unwrap(); + Ref::new(HighLevelILTokenEmitter { handle }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeHighLevelILTokenEmitter(handle.handle.as_ptr()) + } +} + +impl ToOwned for HighLevelILTokenEmitter { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +/// Manages the currently active [`TokenEmitterExpr`] for the given [`HighLevelILTokenEmitter`]. +/// +/// When this object is destroyed, the previously active [`TokenEmitterExpr`] will become active again. +pub struct CurrentTokenEmitterExpr { + pub emitter: Ref, + pub expr: TokenEmitterExpr, + pub previous_expr: TokenEmitterExpr, +} + +impl CurrentTokenEmitterExpr { + pub fn new( + emitter: Ref, + expr: TokenEmitterExpr, + previous_expr: TokenEmitterExpr, + ) -> Self { + Self { + emitter, + expr, + previous_expr, + } + } +} + +impl Drop for CurrentTokenEmitterExpr { + fn drop(&mut self) { + self.emitter.restore_current_expr(self.previous_expr); + } +} diff --git a/rust/src/language_representation.rs b/rust/src/language_representation.rs new file mode 100644 index 00000000..50e7b710 --- /dev/null +++ b/rust/src/language_representation.rs @@ -0,0 +1,635 @@ +use std::ffi::{c_char, c_void}; +use std::mem::MaybeUninit; +use std::ptr::NonNull; + +use binaryninjacore_sys::*; + +use crate::architecture::{Architecture, CoreArchitecture}; +use crate::basic_block::{BasicBlock, BlockContext}; +use crate::binary_view::BinaryView; +use crate::disassembly::{DisassemblySettings, DisassemblyTextLine}; +use crate::function::{Function, HighlightColor}; +use crate::high_level_il::token_emitter::HighLevelILTokenEmitter; +use crate::high_level_il::{HighLevelILFunction, HighLevelInstructionIndex}; +use crate::line_formatter::CoreLineFormatter; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; +use crate::string::{BnStrCompatible, BnString}; +use crate::type_parser::CoreTypeParser; +use crate::type_printer::CoreTypePrinter; + +pub type InstructionTextTokenContext = BNInstructionTextTokenContext; +pub type ScopeType = BNScopeType; +pub type BraceRequirement = BNBraceRequirement; +pub type SymbolDisplayType = BNSymbolDisplayType; +pub type OperatorPrecedence = BNOperatorPrecedence; +pub type SymbolDisplayResult = BNSymbolDisplayResult; + +pub fn register_language_representation_function_type< + C: LanguageRepresentationFunctionType, + F: FnOnce(CoreLanguageRepresentationFunctionType) -> C, + B: BnStrCompatible, +>( + creator: F, + name: B, +) -> CoreLanguageRepresentationFunctionType { + let custom = Box::leak(Box::new(MaybeUninit::uninit())); + let mut callbacks = BNCustomLanguageRepresentationFunctionType { + context: custom as *mut MaybeUninit as *mut c_void, + create: Some(cb_create::), + isValid: Some(cb_is_valid::), + getTypePrinter: Some(cb_get_type_printer::), + getTypeParser: Some(cb_get_type_parser::), + getLineFormatter: Some(cb_get_line_formatter::), + getFunctionTypeTokens: Some(cb_get_function_type_tokens::), + freeLines: Some(cb_free_lines), + }; + let name = name.into_bytes_with_nul(); + let core = unsafe { + BNRegisterLanguageRepresentationFunctionType( + name.as_ref().as_ptr() as *const c_char, + &mut callbacks, + ) + }; + let core = + unsafe { CoreLanguageRepresentationFunctionType::from_raw(NonNull::new(core).unwrap()) }; + custom.write(creator(core)); + core +} + +pub trait LanguageRepresentationFunction: Send + Sync { + fn on_token_emitter_init(&self, tokens: &HighLevelILTokenEmitter); + + fn expr_text( + &self, + il: &HighLevelILFunction, + expr_index: HighLevelInstructionIndex, + tokens: &HighLevelILTokenEmitter, + settings: &DisassemblySettings, + as_full_ast: bool, + precedence: OperatorPrecedence, + statement: bool, + ); + + fn begin_lines( + &self, + il: &HighLevelILFunction, + expr_index: HighLevelInstructionIndex, + tokens: &HighLevelILTokenEmitter, + ); + + fn end_lines( + &self, + il: &HighLevelILFunction, + expr_index: HighLevelInstructionIndex, + tokens: &HighLevelILTokenEmitter, + ); + + fn comment_start_string(&self) -> &str; + + fn comment_end_string(&self) -> &str; + + fn annotation_start_string(&self) -> &str; + + fn annotation_end_string(&self) -> &str; +} + +pub trait LanguageRepresentationFunctionType: Send + Sync { + fn create( + &self, + arch: &CoreArchitecture, + owner: &Function, + high_level_il: &HighLevelILFunction, + ) -> Ref; + + fn is_valid(&self, view: &BinaryView) -> bool; + + fn type_printer(&self) -> Option { + None + } + + fn type_parser(&self) -> Option { + None + } + + fn line_formatter(&self) -> Option { + None + } + + fn function_type_tokens( + &self, + func: &Function, + settings: &DisassemblySettings, + ) -> Vec; +} + +// NOTE static, it never gets freed, so we can clone/copy it +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct CoreLanguageRepresentationFunctionType { + handle: NonNull, +} + +impl CoreLanguageRepresentationFunctionType { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub(crate) fn as_raw(&self) -> *mut BNLanguageRepresentationFunctionType { + self.handle.as_ptr() + } + + pub fn from_name(name: S) -> Option { + let name = name.into_bytes_with_nul(); + let result = unsafe { + BNGetLanguageRepresentationFunctionTypeByName(name.as_ref().as_ptr() as *const c_char) + }; + NonNull::new(result).map(|handle| unsafe { Self::from_raw(handle) }) + } + + pub fn all() -> Array { + let mut count = 0; + let result = unsafe { BNGetLanguageRepresentationFunctionTypeList(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn tokens( + &self, + func: &Function, + settings: &DisassemblySettings, + ) -> Array { + let mut count = 0; + let result = unsafe { + BNGetLanguageRepresentationFunctionTypeFunctionTypeTokens( + self.handle.as_ptr(), + func.handle, + settings.handle, + &mut count, + ) + }; + unsafe { Array::new(result, count, ()) } + } + + pub fn name(&self) -> BnString { + unsafe { + BnString::from_raw(BNGetLanguageRepresentationFunctionTypeName( + self.handle.as_ptr(), + )) + } + } + + pub fn create(&self, func: &Function) -> Ref { + let repr_func = unsafe { + BNCreateLanguageRepresentationFunction( + self.handle.as_ptr(), + func.arch().handle, + func.handle, + match func.high_level_il(false) { + Ok(hlil) => hlil.handle, + Err(_) => std::ptr::null_mut(), + }, + ) + }; + + unsafe { + CoreLanguageRepresentationFunction::ref_from_raw(NonNull::new(repr_func).unwrap()) + } + } + + pub fn is_valid(&self, view: &BinaryView) -> bool { + unsafe { BNIsLanguageRepresentationFunctionTypeValid(self.handle.as_ptr(), view.handle) } + } + + pub fn printer(&self) -> CoreTypePrinter { + let type_printer = + unsafe { BNGetLanguageRepresentationFunctionTypePrinter(self.handle.as_ptr()) }; + unsafe { CoreTypePrinter::from_raw(NonNull::new(type_printer).unwrap()) } + } + + pub fn parser(&self) -> CoreTypeParser { + let type_parser = + unsafe { BNGetLanguageRepresentationFunctionTypeParser(self.handle.as_ptr()) }; + unsafe { CoreTypeParser::from_raw(NonNull::new(type_parser).unwrap()) } + } + + pub fn line_formatter(&self) -> CoreLineFormatter { + let formatter = + unsafe { BNGetLanguageRepresentationFunctionTypeLineFormatter(self.handle.as_ptr()) }; + CoreLineFormatter::from_raw(NonNull::new(formatter).unwrap()) + } +} + +impl CoreArrayProvider for CoreLanguageRepresentationFunctionType { + type Raw = *mut BNLanguageRepresentationFunctionType; + type Context = (); + type Wrapped<'a> = &'a CoreLanguageRepresentationFunctionType; +} + +unsafe impl CoreArrayProviderInner for CoreLanguageRepresentationFunctionType { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeLanguageRepresentationFunctionTypeList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // SAFETY: CoreLanguageRepresentationFunctionType and BNCoreLanguageRepresentationFunctionType + // transparent + std::mem::transmute::< + &*mut BNLanguageRepresentationFunctionType, + &CoreLanguageRepresentationFunctionType, + >(raw) + } +} + +pub struct CoreLanguageRepresentationFunction { + handle: NonNull, +} + +impl CoreLanguageRepresentationFunction { + pub(crate) unsafe fn ref_from_raw( + handle: NonNull, + ) -> Ref { + unsafe { Ref::new(Self { handle }) } + } + + pub fn new( + repr_type: &CoreLanguageRepresentationFunctionType, + repr_context: C, + arch: &A, + func: &Function, + high_level_il: &HighLevelILFunction, + ) -> Ref { + let core_arch: &CoreArchitecture = arch.as_ref(); + let context: &mut C = Box::leak(Box::new(repr_context)); + let mut callbacks = BNCustomLanguageRepresentationFunction { + context: context as *mut C as *mut c_void, + freeObject: Some(cb_free_object::), + externalRefTaken: Some(cb_external_ref_taken::), + externalRefReleased: Some(cb_external_ref_released::), + initTokenEmitter: Some(cb_init_token_emitter::), + getExprText: Some(cb_get_expr_text::), + beginLines: Some(cb_begin_lines::), + endLines: Some(cb_end_lines::), + getCommentStartString: Some(cb_get_comment_start_string::), + getCommentEndString: Some(cb_get_comment_end_string::), + getAnnotationStartString: Some(cb_get_annotation_start_string::), + getAnnotationEndString: Some(cb_get_annotation_end_string::), + }; + let handle = unsafe { + BNCreateCustomLanguageRepresentationFunction( + repr_type.as_raw(), + core_arch.handle, + func.handle, + high_level_il.handle, + &mut callbacks, + ) + }; + unsafe { Self::ref_from_raw(NonNull::new(handle).unwrap()) } + } + + pub fn expr_text( + &self, + il: &HighLevelILFunction, + expr_index: HighLevelInstructionIndex, + settings: &DisassemblySettings, + as_full_ast: bool, + precedence: OperatorPrecedence, + statement: bool, + ) -> Array { + let mut count = 0; + let result = unsafe { + BNGetLanguageRepresentationFunctionExprText( + self.handle.as_ptr(), + il.handle, + expr_index.0, + settings.handle, + as_full_ast, + precedence, + statement, + &mut count, + ) + }; + unsafe { Array::new(result, count, ()) } + } + + pub fn linear_lines( + &self, + il: &HighLevelILFunction, + expr_index: HighLevelInstructionIndex, + settings: &DisassemblySettings, + as_full_ast: bool, + ) -> Array { + let mut count = 0; + let result = unsafe { + BNGetLanguageRepresentationFunctionLinearLines( + self.handle.as_ptr(), + il.handle, + expr_index.0, + settings.handle, + as_full_ast, + &mut count, + ) + }; + unsafe { Array::new(result, count, ()) } + } + + pub fn block_lines( + &self, + block: &BasicBlock, + settings: &DisassemblySettings, + ) -> Array { + let mut count = 0; + let result = unsafe { + BNGetLanguageRepresentationFunctionBlockLines( + self.handle.as_ptr(), + block.handle, + settings.handle, + &mut count, + ) + }; + unsafe { Array::new(result, count, ()) } + } + + pub fn highlight(&self, block: &BasicBlock) -> HighlightColor { + let result = unsafe { + BNGetLanguageRepresentationFunctionHighlight(self.handle.as_ptr(), block.handle) + }; + result.into() + } + + pub fn get_type(&self) -> CoreLanguageRepresentationFunctionType { + let repr_type = unsafe { BNGetLanguageRepresentationType(self.handle.as_ptr()) }; + unsafe { + CoreLanguageRepresentationFunctionType::from_raw(NonNull::new(repr_type).unwrap()) + } + } + + pub fn arch(&self) -> CoreArchitecture { + let arch = unsafe { BNGetLanguageRepresentationArchitecture(self.handle.as_ptr()) }; + unsafe { CoreArchitecture::from_raw(arch) } + } + + pub fn owner_function(&self) -> Ref { + let func = unsafe { BNGetLanguageRepresentationOwnerFunction(self.handle.as_ptr()) }; + unsafe { Function::ref_from_raw(func) } + } + + pub fn hlil(&self) -> Ref { + let hlil = unsafe { BNGetLanguageRepresentationILFunction(self.handle.as_ptr()) }; + unsafe { HighLevelILFunction::ref_from_raw(hlil, false) } + } + + pub fn comment_start_string(&self) -> BnString { + unsafe { + BnString::from_raw(BNGetLanguageRepresentationFunctionCommentStartString( + self.handle.as_ptr(), + )) + } + } + + pub fn comment_end_string(&self) -> BnString { + unsafe { + BnString::from_raw(BNGetLanguageRepresentationFunctionCommentEndString( + self.handle.as_ptr(), + )) + } + } + + pub fn annotation_start_string(&self) -> BnString { + unsafe { + BnString::from_raw(BNGetLanguageRepresentationFunctionAnnotationStartString( + self.handle.as_ptr(), + )) + } + } + + pub fn annotation_end_string(&self) -> BnString { + unsafe { + BnString::from_raw(BNGetLanguageRepresentationFunctionAnnotationEndString( + self.handle.as_ptr(), + )) + } + } +} + +unsafe impl RefCountable for CoreLanguageRepresentationFunction { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw( + NonNull::new(BNNewLanguageRepresentationFunctionReference( + handle.handle.as_ptr(), + )) + .unwrap(), + ) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeLanguageRepresentationFunction(handle.handle.as_ptr()) + } +} + +impl ToOwned for CoreLanguageRepresentationFunction { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { ::inc_ref(self) } + } +} + +unsafe extern "C" fn cb_create( + ctxt: *mut c_void, + arch: *mut BNArchitecture, + owner: *mut BNFunction, + high_level_il: *mut BNHighLevelILFunction, +) -> *mut BNLanguageRepresentationFunction { + let ctxt = ctxt as *mut C; + let arch = CoreArchitecture::from_raw(arch); + let owner = Function::from_raw(owner); + let high_level_il = HighLevelILFunction { + full_ast: false, + handle: high_level_il, + }; + let result = (*ctxt).create(&arch, &owner, &high_level_il); + Ref::into_raw(result).handle.as_ptr() +} + +unsafe extern "C" fn cb_is_valid( + ctxt: *mut c_void, + view: *mut BNBinaryView, +) -> bool { + let ctxt = ctxt as *mut C; + let view = BinaryView::from_raw(view); + (*ctxt).is_valid(&view) +} + +unsafe extern "C" fn cb_get_type_printer( + ctxt: *mut c_void, +) -> *mut BNTypePrinter { + let ctxt = ctxt as *mut C; + match (*ctxt).type_printer() { + None => std::ptr::null_mut(), + Some(printer) => printer.handle.as_ptr(), + } +} + +unsafe extern "C" fn cb_get_type_parser( + ctxt: *mut c_void, +) -> *mut BNTypeParser { + let ctxt = ctxt as *mut C; + match (*ctxt).type_parser() { + None => std::ptr::null_mut(), + Some(parser) => parser.handle.as_ptr(), + } +} + +unsafe extern "C" fn cb_get_line_formatter( + ctxt: *mut c_void, +) -> *mut BNLineFormatter { + let ctxt = ctxt as *mut C; + match (*ctxt).line_formatter() { + None => std::ptr::null_mut(), + Some(formatter) => formatter.handle.as_ptr(), + } +} + +unsafe extern "C" fn cb_get_function_type_tokens( + ctxt: *mut c_void, + func: *mut BNFunction, + settings: *mut BNDisassemblySettings, + count: *mut usize, +) -> *mut BNDisassemblyTextLine { + let ctxt = ctxt as *mut C; + let func = Function::from_raw(func); + let settings = DisassemblySettings { handle: settings }; + let result = (*ctxt).function_type_tokens(&func, &settings); + *count = result.len(); + let result: Box<[BNDisassemblyTextLine]> = result + .into_iter() + .map(DisassemblyTextLine::into_raw) + .collect(); + // NOTE freed by function_type_free_lines_ffi + Box::leak(result).as_mut_ptr() +} + +unsafe extern "C" fn cb_free_lines( + _ctxt: *mut c_void, + lines: *mut BNDisassemblyTextLine, + count: usize, +) { + let lines: Box<[BNDisassemblyTextLine]> = + Box::from_raw(core::slice::from_raw_parts_mut(lines, count)); + for line in lines { + DisassemblyTextLine::free_raw(line); + } +} + +unsafe extern "C" fn cb_free_object(ctxt: *mut c_void) { + let ctxt = ctxt as *mut C; + drop(Box::from_raw(ctxt)) +} + +unsafe extern "C" fn cb_external_ref_taken(_ctxt: *mut c_void) { + // TODO Make an Arc? conflict with free? +} + +unsafe extern "C" fn cb_external_ref_released( + _ctxt: *mut c_void, +) { + // TODO Make an Arc? conflict with free? +} + +unsafe extern "C" fn cb_init_token_emitter( + ctxt: *mut c_void, + tokens: *mut BNHighLevelILTokenEmitter, +) { + let ctxt = ctxt as *mut C; + let tokens = HighLevelILTokenEmitter::from_raw(NonNull::new(tokens).unwrap()); + (*ctxt).on_token_emitter_init(&tokens) +} + +unsafe extern "C" fn cb_get_expr_text( + ctxt: *mut c_void, + il: *mut BNHighLevelILFunction, + expr_index: usize, + tokens: *mut BNHighLevelILTokenEmitter, + settings: *mut BNDisassemblySettings, + as_full_ast: bool, + precedence: BNOperatorPrecedence, + statement: bool, +) { + let ctxt = ctxt as *mut C; + let il = HighLevelILFunction { + full_ast: as_full_ast, + handle: il, + }; + let tokens = HighLevelILTokenEmitter::from_raw(NonNull::new(tokens).unwrap()); + let settings = DisassemblySettings { handle: settings }; + (*ctxt).expr_text( + &il, + expr_index.into(), + &tokens, + &settings, + as_full_ast, + precedence, + statement, + ); +} + +unsafe extern "C" fn cb_begin_lines( + ctxt: *mut c_void, + il: *mut BNHighLevelILFunction, + expr_index: usize, + tokens: *mut BNHighLevelILTokenEmitter, +) { + let ctxt = ctxt as *mut C; + let il = HighLevelILFunction { + full_ast: false, + handle: il, + }; + let tokens = HighLevelILTokenEmitter::from_raw(NonNull::new(tokens).unwrap()); + (*ctxt).begin_lines(&il, expr_index.into(), &tokens) +} + +unsafe extern "C" fn cb_end_lines( + ctxt: *mut c_void, + il: *mut BNHighLevelILFunction, + expr_index: usize, + tokens: *mut BNHighLevelILTokenEmitter, +) { + let ctxt = ctxt as *mut C; + let il = HighLevelILFunction { + full_ast: false, + handle: il, + }; + let tokens = HighLevelILTokenEmitter::from_raw(NonNull::new(tokens).unwrap()); + (*ctxt).end_lines(&il, expr_index.into(), &tokens) +} + +unsafe extern "C" fn cb_get_comment_start_string( + ctxt: *mut c_void, +) -> *mut c_char { + let ctxt = ctxt as *mut C; + let result = (*ctxt).comment_start_string(); + BnString::into_raw(BnString::new(result)) +} + +unsafe extern "C" fn cb_get_comment_end_string( + ctxt: *mut c_void, +) -> *mut c_char { + let ctxt = ctxt as *mut C; + let result = (*ctxt).comment_end_string(); + BnString::into_raw(BnString::new(result)) +} + +unsafe extern "C" fn cb_get_annotation_start_string( + ctxt: *mut c_void, +) -> *mut c_char { + let ctxt = ctxt as *mut C; + let result = (*ctxt).annotation_start_string(); + BnString::into_raw(BnString::new(result)) +} + +unsafe extern "C" fn cb_get_annotation_end_string( + ctxt: *mut c_void, +) -> *mut c_char { + let ctxt = ctxt as *mut C; + let result = (*ctxt).annotation_end_string(); + BnString::into_raw(BnString::new(result)) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7056e529..1b02da0d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -57,6 +57,8 @@ pub mod function_recognizer; pub mod headless; pub mod high_level_il; pub mod interaction; +pub mod language_representation; +pub mod line_formatter; pub mod linear_view; pub mod logger; pub mod low_level_il; diff --git a/rust/src/line_formatter.rs b/rust/src/line_formatter.rs new file mode 100644 index 00000000..5dc07c5c --- /dev/null +++ b/rust/src/line_formatter.rs @@ -0,0 +1,172 @@ +use std::ffi::{c_char, c_void}; +use std::ptr::NonNull; + +use binaryninjacore_sys::*; + +use crate::disassembly::DisassemblyTextLine; +use crate::high_level_il::HighLevelILFunction; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{raw_to_string, BnStrCompatible, BnString}; + +/// Register a [`LineFormatter`] with the API. +pub fn register_line_formatter( + name: B, + formatter: C, +) -> CoreLineFormatter { + let custom = Box::leak(Box::new(formatter)); + let mut callbacks = BNCustomLineFormatter { + context: custom as *mut C as *mut c_void, + formatLines: Some(cb_format_lines::), + freeLines: Some(cb_free_lines), + }; + let name = name.into_bytes_with_nul(); + let handle = + unsafe { BNRegisterLineFormatter(name.as_ref().as_ptr() as *const c_char, &mut callbacks) }; + CoreLineFormatter::from_raw(NonNull::new(handle).unwrap()) +} + +pub trait LineFormatter: Sized { + fn format_lines( + &self, + lines: &[DisassemblyTextLine], + settings: &LineFormatterSettings, + ) -> Vec; +} + +#[repr(transparent)] +pub struct CoreLineFormatter { + pub(crate) handle: NonNull, +} + +impl CoreLineFormatter { + pub fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + /// Get the default [`CoreLineFormatter`] if available, because the user might have disabled it. + pub fn default_if_available() -> Option { + Some(unsafe { Self::from_raw(NonNull::new(BNGetDefaultLineFormatter())?) }) + } + + pub fn all() -> Array { + let mut count = 0; + let result = unsafe { BNGetLineFormatterList(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn from_name(name: S) -> Option { + let name_raw = name.into_bytes_with_nul(); + let result = + unsafe { BNGetLineFormatterByName(name_raw.as_ref().as_ptr() as *const c_char) }; + NonNull::new(result).map(Self::from_raw) + } + + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetLineFormatterName(self.handle.as_ptr())) } + } +} + +impl CoreArrayProvider for CoreLineFormatter { + type Raw = *mut BNLineFormatter; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreLineFormatter { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeLineFormatterList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreLineFormatter::from_raw(handle) + } +} + +#[derive(Clone, Debug)] +pub struct LineFormatterSettings { + pub high_level_il: Option>, + pub desired_line_len: usize, + pub min_content_len: usize, + pub tab_width: usize, + pub lang_name: String, + pub comment_start: String, + pub comment_end: String, + pub annotation_start: String, + pub annotation_end: String, +} + +impl LineFormatterSettings { + pub(crate) fn from_raw(value: &BNLineFormatterSettings) -> Self { + Self { + high_level_il: match value.highLevelIL.is_null() { + false => Some( + unsafe { HighLevelILFunction::from_raw(value.highLevelIL, false) }.to_owned(), + ), + true => None, + }, + desired_line_len: value.desiredLineLength, + min_content_len: value.minimumContentLength, + tab_width: value.tabWidth, + lang_name: raw_to_string(value.languageName as *mut _).unwrap(), + comment_start: raw_to_string(value.commentStartString as *mut _).unwrap(), + comment_end: raw_to_string(value.commentEndString as *mut _).unwrap(), + annotation_start: raw_to_string(value.annotationStartString as *mut _).unwrap(), + annotation_end: raw_to_string(value.annotationEndString as *mut _).unwrap(), + } + } + + #[allow(unused)] + pub(crate) fn from_owned_raw(value: BNLineFormatterSettings) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + #[allow(unused)] + pub(crate) fn free_raw(value: BNLineFormatterSettings) { + let _ = unsafe { HighLevelILFunction::ref_from_raw(value.highLevelIL, false) }; + let _ = unsafe { BnString::from_raw(value.languageName as *mut _) }; + let _ = unsafe { BnString::from_raw(value.commentStartString as *mut _) }; + let _ = unsafe { BnString::from_raw(value.commentEndString as *mut _) }; + let _ = unsafe { BnString::from_raw(value.annotationStartString as *mut _) }; + let _ = unsafe { BnString::from_raw(value.annotationEndString as *mut _) }; + } +} + +unsafe extern "C" fn cb_format_lines( + ctxt: *mut c_void, + in_lines: *mut BNDisassemblyTextLine, + in_count: usize, + raw_settings: *const BNLineFormatterSettings, + out_count: *mut usize, +) -> *mut BNDisassemblyTextLine { + // NOTE dropped by line_formatter_free_lines_ffi + let ctxt = ctxt as *mut C; + let lines_slice = core::slice::from_raw_parts(in_lines, in_count); + let lines: Vec<_> = lines_slice + .iter() + .map(DisassemblyTextLine::from_raw) + .collect(); + let settings = LineFormatterSettings::from_raw(&*raw_settings); + let result = (*ctxt).format_lines(&lines, &settings); + *out_count = result.len(); + let result: Box<[BNDisassemblyTextLine]> = result + .into_iter() + .map(DisassemblyTextLine::into_raw) + .collect(); + Box::leak(result).as_mut_ptr() +} + +unsafe extern "C" fn cb_free_lines( + _ctxt: *mut c_void, + raw_lines: *mut BNDisassemblyTextLine, + count: usize, +) { + let lines: Box<[BNDisassemblyTextLine]> = + Box::from_raw(core::slice::from_raw_parts_mut(raw_lines, count)); + for line in lines { + DisassemblyTextLine::free_raw(line); + } +} diff --git a/rust/src/render_layer.rs b/rust/src/render_layer.rs index 08553552..181294d4 100644 --- a/rust/src/render_layer.rs +++ b/rust/src/render_layer.rs @@ -293,13 +293,13 @@ impl CoreRenderLayer { Self { handle } } - pub fn render_layers() -> Array { + pub fn all() -> Array { let mut count = 0; let result = unsafe { BNGetRenderLayerList(&mut count) }; unsafe { Array::new(result, count, ()) } } - pub fn render_layer_by_name(name: &str) -> Option { + pub fn from_name(name: &str) -> Option { let name_raw = name.to_cstr(); let result = unsafe { BNGetRenderLayerByName(name_raw.as_ptr()) }; NonNull::new(result).map(Self::from_raw) diff --git a/rust/src/type_parser.rs b/rust/src/type_parser.rs index 0ce46a50..13985c44 100644 --- a/rust/src/type_parser.rs +++ b/rust/src/type_parser.rs @@ -37,7 +37,7 @@ pub fn register_type_parser( #[repr(transparent)] pub struct CoreTypeParser { - handle: NonNull, + pub(crate) handle: NonNull, } impl CoreTypeParser { diff --git a/rust/src/type_printer.rs b/rust/src/type_printer.rs index c9bec609..a7495f1c 100644 --- a/rust/src/type_printer.rs +++ b/rust/src/type_printer.rs @@ -42,7 +42,7 @@ pub fn register_type_printer( #[repr(transparent)] pub struct CoreTypePrinter { - handle: NonNull, + pub(crate) handle: NonNull, } impl CoreTypePrinter { -- cgit v1.3.1