Skip to main content

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, CString};
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};
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 = CString::new(mangled_name).ok()?;
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 = CString::new(mangled_name).ok()?;
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 Ok(bytes) = CString::new(name) else {
313            return false;
314        };
315        unsafe { BNIsDemanglerMangledName(self.handle, bytes.as_ref().as_ptr() as *const _) }
316    }
317
318    pub fn demangle(&self, name: &str, config: &DemanglerConfig) -> Option<DemanglerResult> {
319        demangle_with_demangler(self.handle, name, config)
320    }
321
322    pub fn name(&self) -> String {
323        unsafe { BnString::into_string(BNGetDemanglerName(self.handle)) }
324    }
325
326    pub fn from_name(name: &str) -> Option<Self> {
327        let name_bytes = CString::new(name).ok()?;
328        let demangler = unsafe { BNGetDemanglerByName(name_bytes.as_ref().as_ptr() as *const _) };
329        if demangler.is_null() {
330            None
331        } else {
332            Some(unsafe { Demangler::from_raw(demangler) })
333        }
334    }
335
336    pub fn register<C: CustomDemangler>(name: &str, demangler: C) -> bool {
337        let Ok(name) = CString::new(name) else {
338            return false;
339        };
340
341        extern "C" fn cb_is_mangled_string<C>(ctxt: *mut c_void, name: *const c_char) -> bool
342        where
343            C: CustomDemangler,
344        {
345            ffi_wrap!("CustomDemangler::cb_is_mangled_string", unsafe {
346                let cmd = &*(ctxt as *const C);
347                let Some(name) = raw_to_string(name) else {
348                    return false;
349                };
350                cmd.is_mangled_string(&name)
351            })
352        }
353        extern "C" fn cb_demangle<C>(
354            ctxt: *mut c_void,
355            name: *const c_char,
356            config: *const BNDemanglerConfig,
357            result: *mut BNDemanglerResult,
358        ) -> bool
359        where
360            C: CustomDemangler,
361        {
362            ffi_wrap!("CustomDemangler::cb_demangle", unsafe {
363                if result.is_null() {
364                    return false;
365                }
366
367                let cmd = &*(ctxt as *const C);
368                let Some(name) = raw_to_string(name) else {
369                    return false;
370                };
371                let config = match config.as_ref() {
372                    Some(config) => DemanglerConfig::from_api_object(config),
373                    None => DemanglerConfig::default(),
374                };
375
376                match cmd.demangle(&name, &config) {
377                    Some(demangle_result) => {
378                        *result = demangle_result.to_api_object();
379                        true
380                    }
381                    None => false,
382                }
383            })
384        }
385
386        extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNDemanglerResult) {
387            ffi_wrap!("CustomDemangler::cb_free_result", unsafe {
388                if !result.is_null() {
389                    BNFreeDemanglerResult(result);
390                }
391            })
392        }
393
394        let name_ptr = name.as_ptr();
395        let ctxt = Box::into_raw(Box::new(demangler));
396
397        let callbacks = Box::into_raw(Box::new(BNDemanglerCallbacks {
398            context: ctxt as *mut c_void,
399            isMangledString: Some(cb_is_mangled_string::<C>),
400            demangle: Some(cb_demangle::<C>),
401            freeResult: Some(cb_free_result),
402        }));
403
404        let handle = unsafe { BNRegisterDemangler(name_ptr, callbacks) };
405        if handle.is_null() {
406            unsafe {
407                drop(Box::from_raw(ctxt));
408                drop(Box::from_raw(callbacks));
409            }
410            false
411        } else {
412            true
413        }
414    }
415
416    pub fn demangle_any(name: &str, config: &DemanglerConfig) -> Option<DemanglerResult> {
417        demangle_any(name, config)
418    }
419
420    pub fn promote(demangler: &Demangler) -> bool {
421        if demangler.handle.is_null() {
422            return false;
423        }
424        unsafe { BNPromoteDemangler(demangler.handle) }
425    }
426}
427
428unsafe impl Sync for Demangler {}
429
430unsafe impl Send for Demangler {}
431
432impl CoreArrayProvider for Demangler {
433    type Raw = *mut BNDemangler;
434    type Context = ();
435    type Wrapped<'a> = Demangler;
436}
437
438unsafe impl CoreArrayProviderInner for Demangler {
439    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
440        BNFreeDemanglerList(raw);
441    }
442
443    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
444        Demangler::from_raw(*raw)
445    }
446}
447
448pub trait CustomDemangler: 'static + Sync {
449    fn is_mangled_string(&self, name: &str) -> bool;
450
451    fn demangle(&self, name: &str, config: &DemanglerConfig) -> Option<DemanglerResult>;
452}