binaryninja/types/
parser.rs

1#![allow(unused)]
2use binaryninjacore_sys::*;
3use std::ffi::{c_char, c_void};
4use std::fmt::Debug;
5use std::ptr::NonNull;
6
7use crate::platform::Platform;
8use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
9use crate::string::{raw_to_string, BnString, IntoCStr};
10use crate::types::{QualifiedName, QualifiedNameAndType, Type, TypeContainer};
11
12pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity;
13pub type TypeParserOption = BNTypeParserOption;
14
15/// Register a custom parser with the API
16pub fn register_type_parser<T: TypeParser>(
17    name: &str,
18    parser: T,
19) -> (&'static mut T, CoreTypeParser) {
20    let parser = Box::leak(Box::new(parser));
21    let mut callback = BNTypeParserCallbacks {
22        context: parser as *mut _ as *mut c_void,
23        getOptionText: Some(cb_get_option_text::<T>),
24        preprocessSource: Some(cb_preprocess_source::<T>),
25        parseTypesFromSource: Some(cb_parse_types_from_source::<T>),
26        parseTypeString: Some(cb_parse_type_string::<T>),
27        freeString: Some(cb_free_string),
28        freeResult: Some(cb_free_result),
29        freeErrorList: Some(cb_free_error_list),
30    };
31    let name = name.to_cstr();
32    let result = unsafe { BNRegisterTypeParser(name.as_ptr(), &mut callback) };
33    let core = unsafe { CoreTypeParser::from_raw(NonNull::new(result).unwrap()) };
34    (parser, core)
35}
36
37#[repr(transparent)]
38pub struct CoreTypeParser {
39    pub(crate) handle: NonNull<BNTypeParser>,
40}
41
42impl CoreTypeParser {
43    pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeParser>) -> Self {
44        Self { handle }
45    }
46
47    pub fn parsers() -> Array<CoreTypeParser> {
48        let mut count = 0;
49        let result = unsafe { BNGetTypeParserList(&mut count) };
50        unsafe { Array::new(result, count, ()) }
51    }
52
53    pub fn parser_by_name(name: &str) -> Option<CoreTypeParser> {
54        let name_raw = name.to_cstr();
55        let result = unsafe { BNGetTypeParserByName(name_raw.as_ptr()) };
56        NonNull::new(result).map(|x| unsafe { Self::from_raw(x) })
57    }
58
59    pub fn name(&self) -> String {
60        let result = unsafe { BNGetTypeParserName(self.handle.as_ptr()) };
61        assert!(!result.is_null());
62        unsafe { BnString::into_string(result) }
63    }
64}
65
66impl TypeParser for CoreTypeParser {
67    fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option<String> {
68        let mut output = std::ptr::null_mut();
69        let value_ptr = std::ptr::null_mut();
70        let result = unsafe {
71            BNGetTypeParserOptionText(self.handle.as_ptr(), option, value_ptr, &mut output)
72        };
73        result.then(|| {
74            assert!(!output.is_null());
75            unsafe { BnString::into_string(value_ptr) }
76        })
77    }
78
79    fn preprocess_source(
80        &self,
81        source: &str,
82        file_name: &str,
83        platform: &Platform,
84        existing_types: &TypeContainer,
85        options: &[String],
86        include_dirs: &[String],
87    ) -> Result<String, Vec<TypeParserError>> {
88        let source_cstr = BnString::new(source);
89        let file_name_cstr = BnString::new(file_name);
90        let mut result = std::ptr::null_mut();
91        let mut errors = std::ptr::null_mut();
92        let mut error_count = 0;
93        let success = unsafe {
94            BNTypeParserPreprocessSource(
95                self.handle.as_ptr(),
96                source_cstr.as_ptr(),
97                file_name_cstr.as_ptr(),
98                platform.handle,
99                existing_types.handle.as_ptr(),
100                options.as_ptr() as *const *const c_char,
101                options.len(),
102                include_dirs.as_ptr() as *const *const c_char,
103                include_dirs.len(),
104                &mut result,
105                &mut errors,
106                &mut error_count,
107            )
108        };
109        if success {
110            assert!(!result.is_null());
111            let bn_result = unsafe { BnString::into_string(result) };
112            Ok(bn_result)
113        } else {
114            let errors: Array<TypeParserError> = unsafe { Array::new(errors, error_count, ()) };
115            Err(errors.to_vec())
116        }
117    }
118
119    fn parse_types_from_source(
120        &self,
121        source: &str,
122        file_name: &str,
123        platform: &Platform,
124        existing_types: &TypeContainer,
125        options: &[String],
126        include_dirs: &[String],
127        auto_type_source: &str,
128    ) -> Result<TypeParserResult, Vec<TypeParserError>> {
129        let source_cstr = BnString::new(source);
130        let file_name_cstr = BnString::new(file_name);
131        let auto_type_source = BnString::new(auto_type_source);
132        let mut raw_result = BNTypeParserResult::default();
133        let mut errors = std::ptr::null_mut();
134        let mut error_count = 0;
135        let success = unsafe {
136            BNTypeParserParseTypesFromSource(
137                self.handle.as_ptr(),
138                source_cstr.as_ptr(),
139                file_name_cstr.as_ptr(),
140                platform.handle,
141                existing_types.handle.as_ptr(),
142                options.as_ptr() as *const *const c_char,
143                options.len(),
144                include_dirs.as_ptr() as *const *const c_char,
145                include_dirs.len(),
146                auto_type_source.as_ptr(),
147                &mut raw_result,
148                &mut errors,
149                &mut error_count,
150            )
151        };
152        if success {
153            let result = TypeParserResult::from_raw(&raw_result);
154            // NOTE: This is safe because the core allocated the TypeParserResult
155            TypeParserResult::free_raw(raw_result);
156            Ok(result)
157        } else {
158            let errors: Array<TypeParserError> = unsafe { Array::new(errors, error_count, ()) };
159            Err(errors.to_vec())
160        }
161    }
162
163    fn parse_type_string(
164        &self,
165        source: &str,
166        platform: &Platform,
167        existing_types: &TypeContainer,
168    ) -> Result<QualifiedNameAndType, Vec<TypeParserError>> {
169        let source_cstr = BnString::new(source);
170        let mut output = BNQualifiedNameAndType::default();
171        let mut errors = std::ptr::null_mut();
172        let mut error_count = 0;
173        let result = unsafe {
174            BNTypeParserParseTypeString(
175                self.handle.as_ptr(),
176                source_cstr.as_ptr(),
177                platform.handle,
178                existing_types.handle.as_ptr(),
179                &mut output,
180                &mut errors,
181                &mut error_count,
182            )
183        };
184        if result {
185            Ok(QualifiedNameAndType::from_owned_raw(output))
186        } else {
187            let errors: Array<TypeParserError> = unsafe { Array::new(errors, error_count, ()) };
188            Err(errors.to_vec())
189        }
190    }
191}
192
193impl Default for CoreTypeParser {
194    fn default() -> Self {
195        // TODO: This should return a ref
196        unsafe { Self::from_raw(NonNull::new(BNGetDefaultTypeParser()).unwrap()) }
197    }
198}
199
200// TODO: Impl this on platform.
201pub trait TypeParser {
202    /// Get the string representation of an option for passing to parse_type_*.
203    /// Returns a string representing the option if the parser supports it,
204    /// otherwise None
205    ///
206    /// * `option` - Option type
207    /// * `value` - Option value
208    fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option<String>;
209
210    /// Preprocess a block of source, returning the source that would be parsed
211    ///
212    /// * `source` - Source code to process
213    /// * `file_name` - Name of the file containing the source (does not need to exist on disk)
214    /// * `platform` - Platform to assume the source is relevant to
215    /// * `existing_types` - Optional collection of all existing types to use for parsing context
216    /// * `options` - Optional string arguments to pass as options, e.g. command line arguments
217    /// * `include_dirs` - Optional list of directories to include in the header search path
218    fn preprocess_source(
219        &self,
220        source: &str,
221        file_name: &str,
222        platform: &Platform,
223        existing_types: &TypeContainer,
224        options: &[String],
225        include_dirs: &[String],
226    ) -> Result<String, Vec<TypeParserError>>;
227
228    /// Parse an entire block of source into types, variables, and functions
229    ///
230    /// * `source` - Source code to parse
231    /// * `file_name` - Name of the file containing the source (optional: exists on disk)
232    /// * `platform` - Platform to assume the types are relevant to
233    /// * `existing_types` - Optional container of all existing types to use for parsing context
234    /// * `options` - Optional string arguments to pass as options, e.g. command line arguments
235    /// * `include_dirs` - Optional list of directories to include in the header search path
236    /// * `auto_type_source` - Optional source of types if used for automatically generated types
237    fn parse_types_from_source(
238        &self,
239        source: &str,
240        file_name: &str,
241        platform: &Platform,
242        existing_types: &TypeContainer,
243        options: &[String],
244        include_dirs: &[String],
245        auto_type_source: &str,
246    ) -> Result<TypeParserResult, Vec<TypeParserError>>;
247
248    /// Parse a single type and name from a string containing their definition.
249    ///
250    /// * `source` - Source code to parse
251    /// * `platform` - Platform to assume the types are relevant to
252    /// * `existing_types` - Optional container of all existing types to use for parsing context
253    fn parse_type_string(
254        &self,
255        source: &str,
256        platform: &Platform,
257        existing_types: &TypeContainer,
258    ) -> Result<QualifiedNameAndType, Vec<TypeParserError>>;
259}
260
261impl CoreArrayProvider for CoreTypeParser {
262    type Raw = *mut BNTypeParser;
263    type Context = ();
264    type Wrapped<'a> = Self;
265}
266
267unsafe impl CoreArrayProviderInner for CoreTypeParser {
268    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
269        BNFreeTypeParserList(raw)
270    }
271
272    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
273        // TODO: Because handle is a NonNull we should prob make Self::Raw that as well...
274        let handle = NonNull::new(*raw).unwrap();
275        CoreTypeParser::from_raw(handle)
276    }
277}
278
279#[derive(Clone, Debug, Eq, PartialEq)]
280pub struct TypeParserError {
281    pub severity: TypeParserErrorSeverity,
282    pub message: String,
283    pub file_name: String,
284    pub line: u64,
285    pub column: u64,
286}
287
288impl TypeParserError {
289    pub(crate) fn from_raw(value: &BNTypeParserError) -> Self {
290        Self {
291            severity: value.severity,
292            message: raw_to_string(value.message).unwrap(),
293            file_name: raw_to_string(value.fileName).unwrap(),
294            line: value.line,
295            column: value.column,
296        }
297    }
298
299    pub(crate) fn from_owned_raw(value: BNTypeParserError) -> Self {
300        let owned = Self::from_raw(&value);
301        Self::free_raw(value);
302        owned
303    }
304
305    pub(crate) fn into_raw(value: Self) -> BNTypeParserError {
306        BNTypeParserError {
307            severity: value.severity,
308            message: BnString::into_raw(BnString::new(value.message)),
309            fileName: BnString::into_raw(BnString::new(value.file_name)),
310            line: value.line,
311            column: value.column,
312        }
313    }
314
315    pub(crate) fn free_raw(value: BNTypeParserError) {
316        unsafe { BnString::free_raw(value.message) };
317        unsafe { BnString::free_raw(value.fileName) };
318    }
319
320    pub fn new(
321        severity: TypeParserErrorSeverity,
322        message: String,
323        file_name: String,
324        line: u64,
325        column: u64,
326    ) -> Self {
327        Self {
328            severity,
329            message,
330            file_name,
331            line,
332            column,
333        }
334    }
335}
336
337impl CoreArrayProvider for TypeParserError {
338    type Raw = BNTypeParserError;
339    type Context = ();
340    type Wrapped<'a> = Self;
341}
342
343unsafe impl CoreArrayProviderInner for TypeParserError {
344    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
345        unsafe { BNFreeTypeParserErrors(raw, count) }
346    }
347
348    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
349        Self::from_raw(raw)
350    }
351}
352
353#[derive(Debug, Eq, PartialEq, Default)]
354pub struct TypeParserResult {
355    pub types: Vec<ParsedType>,
356    pub variables: Vec<ParsedType>,
357    pub functions: Vec<ParsedType>,
358}
359
360impl TypeParserResult {
361    pub(crate) fn from_raw(value: &BNTypeParserResult) -> Self {
362        let raw_types = unsafe { std::slice::from_raw_parts(value.types, value.typeCount) };
363        let types = raw_types.iter().map(ParsedType::from_raw).collect();
364        let raw_variables =
365            unsafe { std::slice::from_raw_parts(value.variables, value.variableCount) };
366        let variables = raw_variables.iter().map(ParsedType::from_raw).collect();
367        let raw_functions =
368            unsafe { std::slice::from_raw_parts(value.functions, value.functionCount) };
369        let functions = raw_functions.iter().map(ParsedType::from_raw).collect();
370        TypeParserResult {
371            types,
372            variables,
373            functions,
374        }
375    }
376
377    /// Return a rust allocated type parser result, free using [`Self::free_owned_raw`].
378    ///
379    /// Under no circumstance should you call [`Self::free_raw`] on the returned result.
380    pub(crate) fn into_raw(value: Self) -> BNTypeParserResult {
381        let boxed_raw_types: Box<[BNParsedType]> = value
382            .types
383            .into_iter()
384            // NOTE: Freed with [`Self::free_owned_raw`].
385            .map(ParsedType::into_raw)
386            .collect();
387        let boxed_raw_variables: Box<[BNParsedType]> = value
388            .variables
389            .into_iter()
390            // NOTE: Freed with [`Self::free_owned_raw`].
391            .map(ParsedType::into_raw)
392            .collect();
393        let boxed_raw_functions: Box<[BNParsedType]> = value
394            .functions
395            .into_iter()
396            // NOTE: Freed with [`Self::free_owned_raw`].
397            .map(ParsedType::into_raw)
398            .collect();
399        BNTypeParserResult {
400            typeCount: boxed_raw_types.len(),
401            // NOTE: Freed with [`Self::free_owned_raw`].
402            types: Box::leak(boxed_raw_types).as_mut_ptr(),
403            variableCount: boxed_raw_variables.len(),
404            // NOTE: Freed with [`Self::free_owned_raw`].
405            variables: Box::leak(boxed_raw_variables).as_mut_ptr(),
406            functionCount: boxed_raw_functions.len(),
407            // NOTE: Freed with [`Self::free_owned_raw`].
408            functions: Box::leak(boxed_raw_functions).as_mut_ptr(),
409        }
410    }
411
412    pub(crate) fn free_raw(mut value: BNTypeParserResult) {
413        // SAFETY: `value` must be a properly initialized BNTypeParserResult.
414        // SAFETY: `value` must be core allocated.
415        unsafe { BNFreeTypeParserResult(&mut value) };
416    }
417
418    pub(crate) fn free_owned_raw(value: BNTypeParserResult) {
419        let raw_types = std::ptr::slice_from_raw_parts_mut(value.types, value.typeCount);
420        // Free the rust allocated types list
421        let boxed_types = unsafe { Box::from_raw(raw_types) };
422        for parsed_type in boxed_types {
423            ParsedType::free_raw(parsed_type);
424        }
425        let raw_variables =
426            std::ptr::slice_from_raw_parts_mut(value.variables, value.variableCount);
427        // Free the rust allocated variables list
428        let boxed_variables = unsafe { Box::from_raw(raw_variables) };
429        for parsed_type in boxed_variables {
430            ParsedType::free_raw(parsed_type);
431        }
432        let raw_functions =
433            std::ptr::slice_from_raw_parts_mut(value.functions, value.functionCount);
434        // Free the rust allocated functions list
435        let boxed_functions = unsafe { Box::from_raw(raw_functions) };
436        for parsed_type in boxed_functions {
437            ParsedType::free_raw(parsed_type);
438        }
439    }
440}
441
442#[derive(Debug, Clone, Eq, PartialEq)]
443pub struct ParsedType {
444    pub name: QualifiedName,
445    pub ty: Ref<Type>,
446    pub user: bool,
447}
448
449impl ParsedType {
450    pub(crate) fn from_raw(value: &BNParsedType) -> Self {
451        Self {
452            name: QualifiedName::from_raw(&value.name),
453            ty: unsafe { Type::from_raw(value.type_).to_owned() },
454            user: value.isUser,
455        }
456    }
457
458    pub(crate) fn from_owned_raw(value: BNParsedType) -> Self {
459        let owned = Self::from_raw(&value);
460        Self::free_raw(value);
461        owned
462    }
463
464    pub(crate) fn into_raw(value: Self) -> BNParsedType {
465        BNParsedType {
466            name: QualifiedName::into_raw(value.name),
467            type_: unsafe { Ref::into_raw(value.ty) }.handle,
468            isUser: value.user,
469        }
470    }
471
472    pub(crate) fn free_raw(value: BNParsedType) {
473        QualifiedName::free_raw(value.name);
474        let _ = unsafe { Type::ref_from_raw(value.type_) };
475    }
476
477    pub fn new(name: QualifiedName, ty: Ref<Type>, user: bool) -> Self {
478        Self { name, ty, user }
479    }
480}
481
482impl CoreArrayProvider for ParsedType {
483    type Raw = BNParsedType;
484    type Context = ();
485    type Wrapped<'b> = Self;
486}
487
488unsafe impl CoreArrayProviderInner for ParsedType {
489    unsafe fn free(_raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
490        // Expected to be freed with BNFreeTypeParserResult
491        // TODO ^ because of the above, we should not provide an array provider for this
492    }
493
494    unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, _context: &'b Self::Context) -> Self::Wrapped<'b> {
495        ParsedType::from_raw(raw)
496    }
497}
498
499unsafe extern "C" fn cb_get_option_text<T: TypeParser>(
500    ctxt: *mut ::std::os::raw::c_void,
501    option: BNTypeParserOption,
502    value: *const c_char,
503    result: *mut *mut c_char,
504) -> bool {
505    let ctxt: &mut T = &mut *(ctxt as *mut T);
506    if let Some(inner_result) = ctxt.get_option_text(option, &raw_to_string(value).unwrap()) {
507        let bn_inner_result = BnString::new(inner_result);
508        // NOTE: Dropped by `cb_free_string`
509        *result = BnString::into_raw(bn_inner_result);
510        true
511    } else {
512        *result = std::ptr::null_mut();
513        false
514    }
515}
516
517unsafe extern "C" fn cb_preprocess_source<T: TypeParser>(
518    ctxt: *mut c_void,
519    source: *const c_char,
520    file_name: *const c_char,
521    platform: *mut BNPlatform,
522    existing_types: *mut BNTypeContainer,
523    options: *const *const c_char,
524    option_count: usize,
525    include_dirs: *const *const c_char,
526    include_dir_count: usize,
527    result: *mut *mut c_char,
528    errors: *mut *mut BNTypeParserError,
529    error_count: *mut usize,
530) -> bool {
531    let ctxt: &mut T = &mut *(ctxt as *mut T);
532    let platform = Platform { handle: platform };
533    let existing_types_ptr = NonNull::new(existing_types).unwrap();
534    let existing_types = TypeContainer::from_raw(existing_types_ptr);
535    let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) };
536    let options: Vec<_> = options_raw
537        .iter()
538        .filter_map(|&r| raw_to_string(r))
539        .collect();
540    let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) };
541    let includes: Vec<_> = includes_raw
542        .iter()
543        .filter_map(|&r| raw_to_string(r))
544        .collect();
545    match ctxt.preprocess_source(
546        &raw_to_string(source).unwrap(),
547        &raw_to_string(file_name).unwrap(),
548        &platform,
549        &existing_types,
550        &options,
551        &includes,
552    ) {
553        Ok(inner_result) => {
554            let bn_inner_result = BnString::new(inner_result);
555            // NOTE: Dropped by `cb_free_string`
556            *result = BnString::into_raw(bn_inner_result);
557            *errors = std::ptr::null_mut();
558            *error_count = 0;
559            true
560        }
561        Err(inner_errors) => {
562            *result = std::ptr::null_mut();
563            *error_count = inner_errors.len();
564            // NOTE: Leaking errors here, dropped by `cb_free_error_list`.
565            let inner_errors: Box<[_]> = inner_errors
566                .into_iter()
567                .map(TypeParserError::into_raw)
568                .collect();
569            // NOTE: Dropped by `cb_free_error_list`
570            *errors = Box::leak(inner_errors).as_mut_ptr();
571            false
572        }
573    }
574}
575
576unsafe extern "C" fn cb_parse_types_from_source<T: TypeParser>(
577    ctxt: *mut c_void,
578    source: *const c_char,
579    file_name: *const c_char,
580    platform: *mut BNPlatform,
581    existing_types: *mut BNTypeContainer,
582    options: *const *const c_char,
583    option_count: usize,
584    include_dirs: *const *const c_char,
585    include_dir_count: usize,
586    auto_type_source: *const c_char,
587    result: *mut BNTypeParserResult,
588    errors: *mut *mut BNTypeParserError,
589    error_count: *mut usize,
590) -> bool {
591    let ctxt: &mut T = &mut *(ctxt as *mut T);
592    let platform = Platform { handle: platform };
593    let existing_types_ptr = NonNull::new(existing_types).unwrap();
594    let existing_types = TypeContainer::from_raw(existing_types_ptr);
595    let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) };
596    let options: Vec<_> = options_raw
597        .iter()
598        .filter_map(|&r| raw_to_string(r))
599        .collect();
600    let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) };
601    let includes: Vec<_> = includes_raw
602        .iter()
603        .filter_map(|&r| raw_to_string(r))
604        .collect();
605    match ctxt.parse_types_from_source(
606        &raw_to_string(source).unwrap(),
607        &raw_to_string(file_name).unwrap(),
608        &platform,
609        &existing_types,
610        &options,
611        &includes,
612        &raw_to_string(auto_type_source).unwrap(),
613    ) {
614        Ok(type_parser_result) => {
615            *result = TypeParserResult::into_raw(type_parser_result);
616            *errors = std::ptr::null_mut();
617            *error_count = 0;
618            true
619        }
620        Err(inner_errors) => {
621            *error_count = inner_errors.len();
622            let inner_errors: Box<[_]> = inner_errors
623                .into_iter()
624                .map(TypeParserError::into_raw)
625                .collect();
626            *result = Default::default();
627            // NOTE: Dropped by cb_free_error_list
628            *errors = Box::leak(inner_errors).as_mut_ptr();
629            false
630        }
631    }
632}
633
634unsafe extern "C" fn cb_parse_type_string<T: TypeParser>(
635    ctxt: *mut c_void,
636    source: *const c_char,
637    platform: *mut BNPlatform,
638    existing_types: *mut BNTypeContainer,
639    result: *mut BNQualifiedNameAndType,
640    errors: *mut *mut BNTypeParserError,
641    error_count: *mut usize,
642) -> bool {
643    let ctxt: &mut T = &mut *(ctxt as *mut T);
644    let platform = Platform { handle: platform };
645    let existing_types_ptr = NonNull::new(existing_types).unwrap();
646    let existing_types = TypeContainer::from_raw(existing_types_ptr);
647    match ctxt.parse_type_string(&raw_to_string(source).unwrap(), &platform, &existing_types) {
648        Ok(inner_result) => {
649            *result = QualifiedNameAndType::into_raw(inner_result);
650            *errors = std::ptr::null_mut();
651            *error_count = 0;
652            true
653        }
654        Err(inner_errors) => {
655            *error_count = inner_errors.len();
656            let inner_errors: Box<[_]> = inner_errors
657                .into_iter()
658                .map(TypeParserError::into_raw)
659                .collect();
660            *result = Default::default();
661            // NOTE: Dropped by cb_free_error_list
662            *errors = Box::leak(inner_errors).as_mut_ptr();
663            false
664        }
665    }
666}
667
668unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) {
669    // SAFETY: The returned string is just BnString
670    BnString::free_raw(string);
671}
672
673unsafe extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNTypeParserResult) {
674    TypeParserResult::free_owned_raw(*result);
675}
676
677unsafe extern "C" fn cb_free_error_list(
678    _ctxt: *mut c_void,
679    errors: *mut BNTypeParserError,
680    error_count: usize,
681) {
682    let errors = std::ptr::slice_from_raw_parts_mut(errors, error_count);
683    let boxed_errors = Box::from_raw(errors);
684    for error in boxed_errors {
685        TypeParserError::free_raw(error);
686    }
687}