binaryninja/
demangle.rs

1// Copyright 2022-2026 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Interfaces for demangling and simplifying mangled names in binaries.
16
17use binaryninjacore_sys::*;
18use std::ffi::{c_char, c_void};
19use std::ptr;
20
21use crate::architecture::{ArchitectureExt, CoreArchitecture};
22use crate::binary_view::BinaryView;
23use crate::platform::Platform;
24use crate::qualified_name::QualifiedName;
25use crate::rc::*;
26use crate::string::{raw_to_string, BnString, IntoCStr};
27use crate::types::Type;
28
29pub type Result<R> = std::result::Result<R, ()>;
30
31fn msvc_demangler() -> *mut BNDemangler {
32    unsafe { BNGetMSVCDemangler() }
33}
34
35fn gnu3_demangler() -> *mut BNDemangler {
36    unsafe { BNGetGNU3Demangler() }
37}
38
39fn llvm_demangler() -> *mut BNDemangler {
40    unsafe { BNGetLLVMDemangler() }
41}
42
43/// Platform, view, and simplification options used by demangler APIs.
44#[derive(Clone, Debug)]
45pub struct DemanglerConfig {
46    pub platform: Option<Ref<Platform>>,
47    pub view: Option<Ref<BinaryView>>,
48    pub simplify_templates: bool,
49}
50
51impl Default for DemanglerConfig {
52    fn default() -> Self {
53        let config = unsafe { BNGetDefaultDemanglerConfig() };
54        Self::from_api_object(&config)
55    }
56}
57
58impl DemanglerConfig {
59    pub fn for_platform(platform: &Platform, simplify_templates: bool) -> Self {
60        let config =
61            unsafe { BNGetDemanglerConfigForPlatform(platform.handle, simplify_templates) };
62        Self::from_api_object(&config)
63    }
64
65    pub fn for_binary_view(view: &BinaryView) -> Self {
66        let config = unsafe { BNGetDemanglerConfigForBinaryView(view.handle) };
67        Self::from_api_object(&config)
68    }
69
70    pub(crate) fn from_api_object(config: &BNDemanglerConfig) -> Self {
71        let platform = match config.platform.is_null() {
72            true => None,
73            false => {
74                Some(unsafe { Platform::ref_from_raw(BNNewPlatformReference(config.platform)) })
75            }
76        };
77        let view = match config.view.is_null() {
78            true => None,
79            false => Some(unsafe { BinaryView::ref_from_raw(BNNewViewReference(config.view)) }),
80        };
81        Self {
82            platform,
83            view,
84            simplify_templates: config.simplifyTemplates,
85        }
86    }
87
88    pub fn get_platform(&self) -> Ref<Platform> {
89        self.platform
90            .clone()
91            .or_else(|| Self::default().platform)
92            .expect("default demangler config has no platform")
93    }
94
95    pub(crate) fn to_api_object(&self) -> BNDemanglerConfig {
96        let platform = self.get_platform();
97        BNDemanglerConfig {
98            platform: platform.handle,
99            view: self
100                .view
101                .as_ref()
102                .map(|view| view.handle)
103                .unwrap_or(ptr::null_mut()),
104            simplifyTemplates: self.simplify_templates,
105        }
106    }
107}
108
109/// Demangled name and optional type recovered from a mangled name.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct DemanglerResult {
112    pub name: QualifiedName,
113    pub ty: Option<Ref<Type>>,
114}
115
116/// Compatibility alias matching the Python API name.
117pub type DemangleResult = DemanglerResult;
118
119impl DemanglerResult {
120    pub fn new(name: impl Into<QualifiedName>, ty: Option<Ref<Type>>) -> Self {
121        Self {
122            name: name.into(),
123            ty,
124        }
125    }
126
127    pub(crate) fn from_api_object(result: &BNDemanglerResult) -> Self {
128        let ty = match result.type_.is_null() {
129            true => None,
130            // The source result remains independently owned and is freed by the caller.
131            false => Some(unsafe { Type::ref_from_raw(BNNewTypeReference(result.type_)) }),
132        };
133        Self {
134            name: QualifiedName::from_raw(&result.name),
135            ty,
136        }
137    }
138
139    pub(crate) fn from_api_object_and_free(result: &mut BNDemanglerResult) -> Self {
140        let demangler_result = Self::from_api_object(result);
141        unsafe { BNFreeDemanglerResult(result) };
142        demangler_result
143    }
144
145    pub(crate) fn to_api_object(&self) -> BNDemanglerResult {
146        BNDemanglerResult {
147            name: QualifiedName::into_raw(self.name.clone()),
148            type_: self
149                .ty
150                .as_ref()
151                .map(|ty| unsafe { BNNewTypeReference(ty.handle) })
152                .unwrap_or(ptr::null_mut()),
153        }
154    }
155
156    pub fn into_tuple(self) -> (QualifiedName, Option<Ref<Type>>) {
157        (self.name, self.ty)
158    }
159}
160
161impl From<(QualifiedName, Option<Ref<Type>>)> for DemanglerResult {
162    fn from(value: (QualifiedName, Option<Ref<Type>>)) -> Self {
163        Self {
164            name: value.0,
165            ty: value.1,
166        }
167    }
168}
169
170impl From<DemanglerResult> for (QualifiedName, Option<Ref<Type>>) {
171    fn from(value: DemanglerResult) -> Self {
172        value.into_tuple()
173    }
174}
175
176fn collect_demangler_result(res: bool, result: &mut BNDemanglerResult) -> Option<DemanglerResult> {
177    match res {
178        true => Some(DemanglerResult::from_api_object_and_free(result)),
179        false => None,
180    }
181}
182
183fn demangle_with_demangler(
184    demangler: *mut BNDemangler,
185    mangled_name: &str,
186    config: &DemanglerConfig,
187) -> Option<DemanglerResult> {
188    if demangler.is_null() {
189        return None;
190    }
191
192    let mangled_name = mangled_name.to_cstr();
193    let api_config = config.to_api_object();
194    let mut result = BNDemanglerResult::default();
195    let res = unsafe {
196        BNDemangleWithDemangler(demangler, mangled_name.as_ptr(), &api_config, &mut result)
197    };
198    collect_demangler_result(res, &mut result)
199}
200
201pub fn demangle_any(mangled_name: &str, config: &DemanglerConfig) -> Option<DemanglerResult> {
202    let mangled_name = mangled_name.to_cstr();
203    let api_config = config.to_api_object();
204    let mut result = BNDemanglerResult::default();
205    let res = unsafe { BNDemangle(mangled_name.as_ptr(), &api_config, &mut result) };
206    collect_demangler_result(res, &mut result)
207}
208
209pub fn demangle_generic(
210    arch: &CoreArchitecture,
211    mangled_name: &str,
212    view: Option<&BinaryView>,
213    simplify: bool,
214) -> Option<DemanglerResult> {
215    let mut config = match view {
216        Some(view) => DemanglerConfig::for_binary_view(view),
217        None => match arch.standalone_platform() {
218            Some(platform) => DemanglerConfig::for_platform(&platform, simplify),
219            None => DemanglerConfig::default(),
220        },
221    };
222    config.simplify_templates = simplify;
223    demangle_any(mangled_name, &config)
224}
225
226pub fn demangle_llvm(mangled_name: &str, simplify: bool) -> Option<DemanglerResult> {
227    let config = DemanglerConfig {
228        simplify_templates: simplify,
229        ..DemanglerConfig::default()
230    };
231    demangle_llvm_with_config(mangled_name, &config)
232}
233
234pub fn demangle_llvm_with_config(
235    mangled_name: &str,
236    config: &DemanglerConfig,
237) -> Option<DemanglerResult> {
238    demangle_with_demangler(llvm_demangler(), mangled_name, config)
239}
240
241pub fn demangle_gnu3(
242    arch: &CoreArchitecture,
243    mangled_name: &str,
244    simplify: bool,
245) -> Option<DemanglerResult> {
246    let mut config = arch
247        .standalone_platform()
248        .map(|platform| DemanglerConfig::for_platform(&platform, simplify))
249        .unwrap_or_default();
250    config.simplify_templates = simplify;
251    demangle_gnu3_with_config(mangled_name, &config)
252}
253
254pub fn demangle_gnu3_with_config(
255    mangled_name: &str,
256    config: &DemanglerConfig,
257) -> Option<DemanglerResult> {
258    demangle_with_demangler(gnu3_demangler(), mangled_name, config)
259}
260
261pub fn demangle_ms(
262    arch: &CoreArchitecture,
263    mangled_name: &str,
264    simplify: bool,
265) -> Option<DemanglerResult> {
266    let mut config = arch
267        .standalone_platform()
268        .map(|platform| DemanglerConfig::for_platform(&platform, simplify))
269        .unwrap_or_default();
270    config.simplify_templates = simplify;
271    demangle_ms_with_config(mangled_name, &config)
272}
273
274pub fn demangle_ms_with_config(
275    mangled_name: &str,
276    config: &DemanglerConfig,
277) -> Option<DemanglerResult> {
278    demangle_with_demangler(msvc_demangler(), mangled_name, config)
279}
280
281pub fn simplify_demangled_template_name(name: impl Into<QualifiedName>) -> QualifiedName {
282    let name = name.into();
283    let raw_name = QualifiedName::into_raw(name.clone());
284    let mut result = BNQualifiedName::default();
285    let ok = unsafe { BNSimplifyDemangledTemplateName(&raw_name, &mut result) };
286    QualifiedName::free_raw(raw_name);
287    if ok {
288        QualifiedName::from_owned_raw(result)
289    } else {
290        name
291    }
292}
293
294#[derive(PartialEq, Eq, Hash)]
295pub struct Demangler {
296    pub(crate) handle: *mut BNDemangler,
297}
298
299impl Demangler {
300    pub(crate) unsafe fn from_raw(handle: *mut BNDemangler) -> Self {
301        debug_assert!(!handle.is_null());
302        Self { handle }
303    }
304
305    pub fn list() -> Array<Self> {
306        let mut count: usize = 0;
307        let demanglers = unsafe { BNGetDemanglerList(&mut count) };
308        unsafe { Array::<Demangler>::new(demanglers, count, ()) }
309    }
310
311    pub fn is_mangled_string(&self, name: &str) -> bool {
312        let bytes = name.to_cstr();
313        unsafe { BNIsDemanglerMangledName(self.handle, bytes.as_ref().as_ptr() as *const _) }
314    }
315
316    pub fn demangle(&self, name: &str, config: &DemanglerConfig) -> Option<DemanglerResult> {
317        demangle_with_demangler(self.handle, name, config)
318    }
319
320    pub fn name(&self) -> String {
321        unsafe { BnString::into_string(BNGetDemanglerName(self.handle)) }
322    }
323
324    pub fn from_name(name: &str) -> Option<Self> {
325        let name_bytes = name.to_cstr();
326        let demangler = unsafe { BNGetDemanglerByName(name_bytes.as_ref().as_ptr() as *const _) };
327        if demangler.is_null() {
328            None
329        } else {
330            Some(unsafe { Demangler::from_raw(demangler) })
331        }
332    }
333
334    pub fn register<C: CustomDemangler>(name: &str, demangler: C) -> bool {
335        extern "C" fn cb_is_mangled_string<C>(ctxt: *mut c_void, name: *const c_char) -> bool
336        where
337            C: CustomDemangler,
338        {
339            ffi_wrap!("CustomDemangler::cb_is_mangled_string", unsafe {
340                let cmd = &*(ctxt as *const C);
341                let Some(name) = raw_to_string(name) else {
342                    return false;
343                };
344                cmd.is_mangled_string(&name)
345            })
346        }
347        extern "C" fn cb_demangle<C>(
348            ctxt: *mut c_void,
349            name: *const c_char,
350            config: *const BNDemanglerConfig,
351            result: *mut BNDemanglerResult,
352        ) -> bool
353        where
354            C: CustomDemangler,
355        {
356            ffi_wrap!("CustomDemangler::cb_demangle", unsafe {
357                if result.is_null() {
358                    return false;
359                }
360
361                let cmd = &*(ctxt as *const C);
362                let Some(name) = raw_to_string(name) else {
363                    return false;
364                };
365                let config = match config.as_ref() {
366                    Some(config) => DemanglerConfig::from_api_object(config),
367                    None => DemanglerConfig::default(),
368                };
369
370                match cmd.demangle(&name, &config) {
371                    Some(demangle_result) => {
372                        *result = demangle_result.to_api_object();
373                        true
374                    }
375                    None => false,
376                }
377            })
378        }
379
380        extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNDemanglerResult) {
381            ffi_wrap!("CustomDemangler::cb_free_result", unsafe {
382                if !result.is_null() {
383                    BNFreeDemanglerResult(result);
384                }
385            })
386        }
387
388        let name = name.to_cstr();
389        let name_ptr = name.as_ptr();
390        let ctxt = Box::into_raw(Box::new(demangler));
391
392        let callbacks = Box::into_raw(Box::new(BNDemanglerCallbacks {
393            context: ctxt as *mut c_void,
394            isMangledString: Some(cb_is_mangled_string::<C>),
395            demangle: Some(cb_demangle::<C>),
396            freeResult: Some(cb_free_result),
397        }));
398
399        let handle = unsafe { BNRegisterDemangler(name_ptr, callbacks) };
400        if handle.is_null() {
401            unsafe {
402                drop(Box::from_raw(ctxt));
403                drop(Box::from_raw(callbacks));
404            }
405            false
406        } else {
407            true
408        }
409    }
410
411    pub fn demangle_any(name: &str, config: &DemanglerConfig) -> Option<DemanglerResult> {
412        demangle_any(name, config)
413    }
414
415    pub fn promote(demangler: &Demangler) -> bool {
416        if demangler.handle.is_null() {
417            return false;
418        }
419        unsafe { BNPromoteDemangler(demangler.handle) }
420    }
421}
422
423unsafe impl Sync for Demangler {}
424
425unsafe impl Send for Demangler {}
426
427impl CoreArrayProvider for Demangler {
428    type Raw = *mut BNDemangler;
429    type Context = ();
430    type Wrapped<'a> = Demangler;
431}
432
433unsafe impl CoreArrayProviderInner for Demangler {
434    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
435        BNFreeDemanglerList(raw);
436    }
437
438    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
439        Demangler::from_raw(*raw)
440    }
441}
442
443pub trait CustomDemangler: 'static + Sync {
444    fn is_mangled_string(&self, name: &str) -> bool;
445
446    fn demangle(&self, name: &str, config: &DemanglerConfig) -> Option<DemanglerResult>;
447}