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