binaryninja/
line_formatter.rs

1use std::ffi::c_void;
2use std::ptr::NonNull;
3
4use binaryninjacore_sys::*;
5
6use crate::disassembly::DisassemblyTextLine;
7use crate::high_level_il::HighLevelILFunction;
8use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
9use crate::string::{raw_to_string, BnString, IntoCStr};
10
11/// Register a [`LineFormatter`] with the API.
12pub fn register_line_formatter<C: LineFormatter>(name: &str, formatter: C) -> CoreLineFormatter {
13    let custom = Box::leak(Box::new(formatter));
14    let mut callbacks = BNCustomLineFormatter {
15        context: custom as *mut C as *mut c_void,
16        formatLines: Some(cb_format_lines::<C>),
17        freeLines: Some(cb_free_lines),
18    };
19    let name = name.to_cstr();
20    let handle = unsafe { BNRegisterLineFormatter(name.as_ptr(), &mut callbacks) };
21    CoreLineFormatter::from_raw(NonNull::new(handle).unwrap())
22}
23
24pub trait LineFormatter: Sized {
25    fn format_lines(
26        &self,
27        lines: &[DisassemblyTextLine],
28        settings: &LineFormatterSettings,
29    ) -> Vec<DisassemblyTextLine>;
30}
31
32#[repr(transparent)]
33pub struct CoreLineFormatter {
34    pub(crate) handle: NonNull<BNLineFormatter>,
35}
36
37impl CoreLineFormatter {
38    pub fn from_raw(handle: NonNull<BNLineFormatter>) -> Self {
39        Self { handle }
40    }
41
42    /// Get the default [`CoreLineFormatter`] if available, because the user might have disabled it.
43    pub fn default_if_available() -> Option<Self> {
44        Some(unsafe { Self::from_raw(NonNull::new(BNGetDefaultLineFormatter())?) })
45    }
46
47    pub fn all() -> Array<CoreLineFormatter> {
48        let mut count = 0;
49        let result = unsafe { BNGetLineFormatterList(&mut count) };
50        unsafe { Array::new(result, count, ()) }
51    }
52
53    pub fn from_name(name: &str) -> Option<CoreLineFormatter> {
54        let name_raw = name.to_cstr();
55        let result = unsafe { BNGetLineFormatterByName(name_raw.as_ptr()) };
56        NonNull::new(result).map(Self::from_raw)
57    }
58
59    pub fn name(&self) -> BnString {
60        unsafe { BnString::from_raw(BNGetLineFormatterName(self.handle.as_ptr())) }
61    }
62}
63
64impl CoreArrayProvider for CoreLineFormatter {
65    type Raw = *mut BNLineFormatter;
66    type Context = ();
67    type Wrapped<'a> = Self;
68}
69
70unsafe impl CoreArrayProviderInner for CoreLineFormatter {
71    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
72        BNFreeLineFormatterList(raw)
73    }
74
75    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
76        // TODO: Because handle is a NonNull we should prob make Self::Raw that as well...
77        let handle = NonNull::new(*raw).unwrap();
78        CoreLineFormatter::from_raw(handle)
79    }
80}
81
82#[derive(Clone, Debug)]
83pub struct LineFormatterSettings {
84    pub high_level_il: Option<Ref<HighLevelILFunction>>,
85    pub desired_line_len: usize,
86    pub min_content_len: usize,
87    pub tab_width: usize,
88    pub lang_name: String,
89    pub comment_start: String,
90    pub comment_end: String,
91    pub annotation_start: String,
92    pub annotation_end: String,
93}
94
95impl LineFormatterSettings {
96    pub(crate) fn from_raw(value: &BNLineFormatterSettings) -> Self {
97        Self {
98            high_level_il: match value.highLevelIL.is_null() {
99                false => Some(
100                    unsafe { HighLevelILFunction::from_raw(value.highLevelIL, false) }.to_owned(),
101                ),
102                true => None,
103            },
104            desired_line_len: value.desiredLineLength,
105            min_content_len: value.minimumContentLength,
106            tab_width: value.tabWidth,
107            lang_name: raw_to_string(value.languageName as *mut _).unwrap(),
108            comment_start: raw_to_string(value.commentStartString as *mut _).unwrap(),
109            comment_end: raw_to_string(value.commentEndString as *mut _).unwrap(),
110            annotation_start: raw_to_string(value.annotationStartString as *mut _).unwrap(),
111            annotation_end: raw_to_string(value.annotationEndString as *mut _).unwrap(),
112        }
113    }
114
115    #[allow(unused)]
116    pub(crate) fn from_owned_raw(value: BNLineFormatterSettings) -> Self {
117        let owned = Self::from_raw(&value);
118        Self::free_raw(value);
119        owned
120    }
121
122    #[allow(unused)]
123    pub(crate) fn free_raw(value: BNLineFormatterSettings) {
124        let _ = unsafe { HighLevelILFunction::ref_from_raw(value.highLevelIL, false) };
125        let _ = unsafe { BnString::from_raw(value.languageName as *mut _) };
126        let _ = unsafe { BnString::from_raw(value.commentStartString as *mut _) };
127        let _ = unsafe { BnString::from_raw(value.commentEndString as *mut _) };
128        let _ = unsafe { BnString::from_raw(value.annotationStartString as *mut _) };
129        let _ = unsafe { BnString::from_raw(value.annotationEndString as *mut _) };
130    }
131}
132
133unsafe extern "C" fn cb_format_lines<C: LineFormatter>(
134    ctxt: *mut c_void,
135    in_lines: *mut BNDisassemblyTextLine,
136    in_count: usize,
137    raw_settings: *const BNLineFormatterSettings,
138    out_count: *mut usize,
139) -> *mut BNDisassemblyTextLine {
140    // NOTE dropped by line_formatter_free_lines_ffi
141    let ctxt = ctxt as *mut C;
142    let lines_slice = core::slice::from_raw_parts(in_lines, in_count);
143    let lines: Vec<_> = lines_slice
144        .iter()
145        .map(DisassemblyTextLine::from_raw)
146        .collect();
147    let settings = LineFormatterSettings::from_raw(&*raw_settings);
148    let result = (*ctxt).format_lines(&lines, &settings);
149    *out_count = result.len();
150    let result: Box<[BNDisassemblyTextLine]> = result
151        .into_iter()
152        .map(DisassemblyTextLine::into_raw)
153        .collect();
154    Box::leak(result).as_mut_ptr()
155}
156
157unsafe extern "C" fn cb_free_lines(
158    _ctxt: *mut c_void,
159    raw_lines: *mut BNDisassemblyTextLine,
160    count: usize,
161) {
162    let lines: Box<[BNDisassemblyTextLine]> =
163        Box::from_raw(core::slice::from_raw_parts_mut(raw_lines, count));
164    for line in lines {
165        DisassemblyTextLine::free_raw(line);
166    }
167}