use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
use crate::{DemangleConfig, DemangleError};
use crate::{
dem::{demangle_custom_name, demangle_method_qualifier},
dem_arg::{demangle_argument, DemangledArg},
dem_arg_list::{demangle_argument_list, demangle_argument_list_impl, ArgVec},
dem_namespace::demangle_namespaces,
dem_template::{demangle_template, demangle_template_with_return_type},
remainer::Remaining,
str_cutter::StrCutter,
};
pub fn demangle<'s>(sym: &'s str, config: &DemangleConfig) -> Result<String, DemangleError<'s>> {
if !sym.is_ascii() {
Err(DemangleError::NonAscii)
} else {
let cplus_marker = sym.chars().find(|x| *x == '.').unwrap_or('$');
demangle_impl(sym, config, cplus_marker, true)
}
}
fn demangle_impl<'s>(
sym: &'s str,
config: &DemangleConfig,
cplus_marker: char,
allow_global_sym_keyed: bool,
) -> Result<String, DemangleError<'s>> {
if let Some(s) = sym.c_strip_prefix_3chars('_', cplus_marker, '_') {
demangle_destructor(config, s)
} else if let Some(s) = sym.strip_prefix("__") {
demangle_special(config, s, sym)
} else if let Some(s) =
sym.c_cond_and_strip_prefix_and_char(allow_global_sym_keyed, "_GLOBAL_", cplus_marker)
{
demangle_global_sym_keyed(config, s, cplus_marker, sym)
} else {
demangle_impl_failables(sym, config, cplus_marker)
}
}
fn demangle_impl_failables<'s>(
sym: &'s str,
config: &DemangleConfig,
cplus_marker: char,
) -> Result<String, DemangleError<'s>> {
let leading_error = None;
let leading_error = if let Some((sym_name, the_rest, c)) = sym
.c_split2_r_starts_with("__", |c| {
matches!(c, 'F' | '1'..='9' | 'C' | 't' | 'H' | 'Q')
}) {
match c {
'F' => match demangle_free_function(config, sym_name, &the_rest[1..]) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
},
'1'..='9' | 'C' | 't' => match demangle_method(config, sym_name, the_rest) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
},
'H' => match demangle_templated_function(config, sym_name, &the_rest[1..]) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
},
'Q' => match demangle_namespaced_function(config, sym_name, &the_rest[1..]) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
},
_ => unreachable!(),
}
} else {
None
};
let leading_error = if let Some(sym) = sym.strip_prefix("_vt") {
match demangle_virtual_table(config, sym, cplus_marker) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
}
} else {
leading_error
};
let leading_error = if let Some((s, name)) = sym.c_split2_char(cplus_marker) {
match demangle_namespaced_global(config, s, name) {
Ok(d) => return Ok(d),
Err(e) => leading_error.or(Some(e)),
}
} else {
leading_error
};
Err(leading_error.unwrap_or(DemangleError::NotMangled))
}
fn demangle_destructor<'s>(
config: &DemangleConfig,
s: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let (r, namespace, typ) = if let Some(s) = s.strip_prefix('t') {
let (r, template, typ) =
demangle_template(config, s, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Cow::from(template), Cow::from(typ))
} else if let Some(s) = s.strip_prefix('Q') {
let (r, namespaces, trailing_namespace) =
demangle_namespaces(config, s, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Cow::from(namespaces), Cow::from(trailing_namespace))
} else {
let Remaining { r, d: class_name } =
demangle_custom_name(s, DemangleError::InvalidClassNameOnDestructor)?;
(r, Cow::from(class_name), Cow::from(class_name))
};
if r.is_empty() {
Ok(format!("{namespace}::~{typ}(void)"))
} else {
Err(DemangleError::TrailingDataOnDestructor(r))
}
}
fn demangle_special<'s>(
config: &DemangleConfig,
s: &'s str,
full_sym: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let c = s
.chars()
.next()
.ok_or(DemangleError::RanOutWhileDemanglingSpecial)?;
let (remaining, class_name, method_name, suffix) = if matches!(c, '1'..='9') {
let Remaining { r, d: class_name } =
demangle_custom_name(s, DemangleError::InvalidClassNameOnConstructor)?;
(r, Some(Cow::from(class_name)), Cow::from(class_name), "")
} else if let Some(remaining) = s.strip_prefix("tf") {
return demangle_type_info_function(config, remaining);
} else if let Some(remaining) = s.strip_prefix("ti") {
return demangle_type_info_node(config, remaining);
} else if let Some(remaining) = s.strip_prefix('t') {
let (remaining, template, typ) = demangle_template(
config,
remaining,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
(remaining, Some(Cow::from(template)), Cow::from(typ), "")
} else if let Some(q_less) = s.strip_prefix('Q') {
let (remaining, namespaces, trailing_namespace) = demangle_namespaces(
config,
q_less,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
(
remaining,
Some(Cow::from(namespaces)),
Cow::from(trailing_namespace),
"",
)
} else {
let end_index = s.find("__").ok_or(DemangleError::InvalidSpecialMethod(s))?;
let op = &s[..end_index];
let remaining = &s[end_index + 2..];
let method_name = match op {
"nw" => Cow::from("operator new"),
"dl" => Cow::from("operator delete"),
"vn" => Cow::from("operator new []"),
"vd" => Cow::from("operator delete []"),
"eq" => Cow::from("operator=="),
"ne" => Cow::from("operator!="),
"lt" => Cow::from("operator<"),
"gt" => Cow::from("operator>"),
"le" => Cow::from("operator<="),
"ge" => Cow::from("operator>="),
"as" => Cow::from("operator="),
"apl" => Cow::from("operator+="),
"ami" => Cow::from("operator-="),
"aml" => Cow::from("operator*="),
"adv" => Cow::from("operator/="),
"amd" => Cow::from("operator%="),
"aer" => Cow::from("operator^="),
"aad" => Cow::from("operator&="),
"aor" => Cow::from("operator|="),
"als" => Cow::from("operator<<="),
"ars" => Cow::from("operator>>="),
"er" => Cow::from("operator^"),
"ad" => Cow::from("operator&"),
"or" => Cow::from("operator|"),
"ls" => Cow::from("operator<<"),
"rs" => Cow::from("operator>>"),
"co" => Cow::from("operator~"),
"pp" => Cow::from("operator++"),
"mm" => Cow::from("operator--"),
"aa" => Cow::from("operator&&"),
"oo" => Cow::from("operator||"),
"nt" => Cow::from("operator!"),
"vc" => Cow::from("operator[]"),
"rf" => Cow::from("operator->"),
"rm" => Cow::from("operator->*"),
"pl" => Cow::from("operator+"),
"mi" => Cow::from("operator-"),
"ml" => Cow::from("operator*"),
"dv" => Cow::from("operator/"),
"md" => Cow::from("operator%"),
"cl" => Cow::from("operator()"),
"cm" => Cow::from("operator, "),
_ => {
if let Some(cast) = op.strip_prefix("op") {
let (remaining, DemangledArg::Plain(typ, array_qualifiers)) =
demangle_argument(
config,
cast,
&ArgVec::new(config, None),
&ArgVec::new(config, None),
allow_array_fixup,
)?
else {
return Err(DemangleError::UnrecognizedSpecialMethod(op));
};
if !remaining.is_empty() {
return Err(DemangleError::MalformedCastOperatorOverload(remaining));
}
Cow::from(format!("operator {typ}{array_qualifiers}"))
} else {
return {
if let Some((func_name, args)) = full_sym.c_split2("__F") {
demangle_free_function(config, func_name, args)
} else if let Some((incomplete_method_name, class_and_args, _c)) =
s.c_split2_r_starts_with("__", |c| matches!(c, '1'..='9' | 'C' | 't'))
{
let method_name = &full_sym[..incomplete_method_name.len() + 2];
demangle_method(config, method_name, class_and_args)
} else if let Some((func_name, s)) = full_sym.c_split2("__H") {
demangle_templated_function(config, func_name, s)
} else {
Err(DemangleError::UnrecognizedSpecialMethod(op))
}
};
}
}
};
if let Some(remaining) = remaining.strip_prefix('F') {
(remaining, None, method_name, "")
} else {
let Remaining {
r: remaining,
d: suffix,
} = demangle_method_qualifier(remaining);
let (remaining, namespaces) = if let Some(q_less) = remaining.strip_prefix('Q') {
let (remaining, namespaces, _trailing_namespace) = demangle_namespaces(
config,
q_less,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
(remaining, Cow::from(namespaces))
} else if let Some(r) = remaining.strip_prefix('t') {
let (remaining, template, _typ) =
demangle_template(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
(remaining, Cow::from(template))
} else {
let Remaining { r, d: class_name } =
demangle_custom_name(remaining, DemangleError::InvalidClassNameOnOperator)?
.d_as_cow();
(r, class_name)
};
(remaining, Some(namespaces), method_name, suffix)
}
};
let argument_list = if remaining.is_empty() {
"void"
} else {
&demangle_argument_list(
config,
remaining,
class_name.as_deref(),
&ArgVec::new(config, None),
allow_array_fixup,
)?
};
let out = if let Some(class_name) = class_name {
format!("{class_name}::{method_name}({argument_list}){suffix}")
} else {
format!("{method_name}({argument_list}){suffix}")
};
Ok(out)
}
fn demangle_free_function<'s>(
config: &DemangleConfig,
func_name: &'s str,
args: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let argument_list = demangle_argument_list(
config,
args,
None,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
Ok(format!("{func_name}({argument_list})"))
}
fn demangle_method<'s>(
config: &DemangleConfig,
method_name: &'s str,
class_and_args: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let Remaining {
r: remaining,
d: suffix,
} = demangle_method_qualifier(class_and_args);
let (remaining, namespace) = if let Some(templated) = remaining.strip_prefix('t') {
let (remaining, template, _typ) = demangle_template(
config,
templated,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
(remaining, Cow::from(template))
} else if let Some(q_less) = remaining.strip_prefix('Q') {
let (remaining, namespaces, _trailing_namespace) = demangle_namespaces(
config,
q_less,
&ArgVec::new(config, None),
allow_array_fixup,
)?;
(remaining, Cow::from(namespaces))
} else {
let Remaining { r, d: class_name } =
demangle_custom_name(remaining, DemangleError::InvalidClassNameOnMethod)?.d_as_cow();
(r, class_name)
};
let argument_list = if remaining.is_empty() {
"void"
} else {
&demangle_argument_list(
config,
remaining,
Some(&namespace),
&ArgVec::new(config, None),
allow_array_fixup,
)?
};
Ok(format!(
"{namespace}::{method_name}({argument_list}){suffix}"
))
}
fn demangle_templated_function<'s>(
config: &DemangleConfig,
func_name: &'s str,
s: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let (remaining, template_args, typ) =
demangle_template_with_return_type(config, s, allow_array_fixup)?;
let allow_array_fixup = false;
let Remaining {
r: remaining,
d: suffix,
} = demangle_method_qualifier(remaining);
let (remaining, typ) = if let Some(typ) = typ {
(remaining, Some(typ))
} else if remaining.starts_with(|c| matches!(c, '1'..='9')) {
let Remaining { r, d: namespace } = demangle_custom_name(
remaining,
DemangleError::InvalidNamespaceOnTemplatedFunction,
)?
.d_as_cow();
(r, Some(namespace))
} else if let Some(r) = remaining.strip_prefix('t') {
let (r, template, _typ) =
demangle_template(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Some(Cow::from(template)))
} else if let Some(r) = remaining.strip_prefix('Q') {
let (r, namespaces, _trailing_namespace) =
demangle_namespaces(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Some(Cow::from(namespaces)))
} else {
(remaining, None)
};
let (remaining, specialization_namespace) = if let Some(r) = remaining.strip_prefix('_') {
let (r, DemangledArg::Plain(specialization_namespace, array_qualifiers)) =
demangle_argument(
config,
r,
&ArgVec::new(config, typ.as_deref()),
&template_args,
allow_array_fixup,
)?
else {
return Err(DemangleError::MalformedTemplatedSpecializationInvalidNamespace(r));
};
(r, Some((specialization_namespace, array_qualifiers)))
} else {
(remaining, None)
};
let (remaining, argument_list) = demangle_argument_list_impl(
config,
remaining,
typ.as_deref(),
&template_args,
false,
allow_array_fixup,
)?;
let (specialization_namespace, return_type, array_qualifiers) =
if let Some(r) = remaining.strip_prefix('_') {
let (r, DemangledArg::Plain(ret_type, array_qualifiers)) = demangle_argument(
config,
r,
&ArgVec::new(config, typ.as_deref()),
&template_args,
allow_array_fixup,
)?
else {
return Err(DemangleError::MalformedTemplateWithReturnTypeMissingReturnType(r));
};
if !r.is_empty() {
return Err(
DemangleError::TrailingDataAfterReturnTypeOfMalformedTemplateWithReturnType(r),
);
}
(specialization_namespace, ret_type, array_qualifiers)
} else if let Some((actual_return_type, array_qualifiers)) = specialization_namespace {
if !remaining.is_empty() {
return Err(
DemangleError::TrailingDataAfterReturnTypeOfTemplatedSpecialization(remaining),
);
}
(None, actual_return_type, array_qualifiers)
} else {
return Err(DemangleError::MalformedTemplateWithReturnTypeMissingReturnType(remaining));
};
let template_args = template_args.join();
let formated_template_args = if template_args.ends_with('>') {
format!("<{} >", template_args)
} else {
format!("<{}>", template_args)
};
let argument_list = argument_list.join();
let mut out = return_type;
if let Some((specialization_namespace, _array_qualifiers)) = specialization_namespace {
out.push(' ');
out.push_str(&specialization_namespace);
}
if let Some(array_qualifiers) = array_qualifiers.as_option() {
if config.fix_array_in_return_position {
out.push_str(" (");
out.push_str(&array_qualifiers.inner_post_qualifiers);
} else {
out.push_str(&array_qualifiers.to_string());
out.push(' ');
}
} else {
out.push(' ');
}
if let Some(typ) = typ {
out.push_str(&typ);
out.push_str("::");
}
out.push_str(func_name);
out.push_str(&formated_template_args);
out.push('(');
out.push_str(&argument_list);
out.push(')');
out.push_str(suffix);
if let Some(array_qualifiers) = array_qualifiers.as_option() {
if config.fix_array_in_return_position {
out.push(')');
out.push_str(&array_qualifiers.arrays);
}
}
Ok(out)
}
fn demangle_namespaced_function<'s>(
config: &DemangleConfig,
func_name: &'s str,
s: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let (remaining, namespaces, _trailing_namespace) =
demangle_namespaces(config, s, &ArgVec::new(config, None), allow_array_fixup)?;
let argument_list = if remaining.is_empty() {
"void"
} else {
&demangle_argument_list(
config,
remaining,
Some(&namespaces),
&ArgVec::new(config, None),
allow_array_fixup,
)?
};
let out = format!("{namespaces}::{func_name}({argument_list})");
Ok(out)
}
fn demangle_type_info_function<'s>(
config: &DemangleConfig,
s: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
if let (remaining, DemangledArg::Plain(demangled_type, array_qualifiers)) = demangle_argument(
config,
s,
&ArgVec::new(config, None),
&ArgVec::new(config, None),
allow_array_fixup,
)? {
if remaining.is_empty() {
Ok(format!(
"{demangled_type}{array_qualifiers} type_info function"
))
} else {
Err(DemangleError::TrailingDataOnTypeInfoFunction(remaining))
}
} else {
Err(DemangleError::InvalidTypeOnTypeInfoFunction(s))
}
}
fn demangle_type_info_node<'s>(
config: &DemangleConfig,
s: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
if let (remaining, DemangledArg::Plain(demangled_type, array_qualifiers)) = demangle_argument(
config,
s,
&ArgVec::new(config, None),
&ArgVec::new(config, None),
allow_array_fixup,
)? {
if remaining.is_empty() {
Ok(format!("{demangled_type}{array_qualifiers} type_info node"))
} else {
Err(DemangleError::TrailingDataOnTypeInfoNode(remaining))
}
} else {
Err(DemangleError::InvalidTypeOnTypeInfoNode(s))
}
}
fn demangle_virtual_table<'s>(
config: &DemangleConfig,
s: &'s str,
cplus_marker: char,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let mut remaining = s;
let mut stuff = Vec::new();
while !remaining.is_empty() {
remaining = remaining
.strip_prefix(cplus_marker)
.ok_or(DemangleError::VTableMissingDollarSeparator(remaining))?;
remaining = if let Some(r) = remaining.strip_prefix('t') {
let (r, template, _typ) =
demangle_template(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
stuff.push(Cow::from(template));
r
} else if let Some(r) = remaining.strip_prefix('Q') {
let (r, namespaces, _trailing_namespace) =
demangle_namespaces(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
stuff.push(Cow::from(namespaces));
r
} else {
let Remaining { r, d: class_name } =
demangle_custom_name(remaining, DemangleError::InvalidClassNameOnVirtualTable)?
.d_as_cow();
stuff.push(class_name);
r
};
}
Ok(format!("{} virtual table", stuff.join("::")))
}
fn demangle_namespaced_global<'s>(
config: &DemangleConfig,
s: &'s str,
name: &'s str,
) -> Result<String, DemangleError<'s>> {
let allow_array_fixup = true;
let Some(remaining) = s.strip_prefix('_') else {
return Err(DemangleError::InvalidNamespacedGlobal(s, name));
};
let (r, space) = if let Some(r) = remaining.strip_prefix('t') {
let (r, template, _typ) =
demangle_template(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Cow::from(template))
} else if let Some(r) = remaining.strip_prefix('Q') {
let (r, namespaces, _trailing_namespace) =
demangle_namespaces(config, r, &ArgVec::new(config, None), allow_array_fixup)?;
(r, Cow::from(namespaces))
} else {
let Remaining { r, d: class_name } =
demangle_custom_name(remaining, DemangleError::InvalidNamespaceOnNamespacedGlobal)?
.d_as_cow();
(r, class_name)
};
if !r.is_empty() {
return Err(DemangleError::TrailingDataOnNamespacedGlobal(r));
}
Ok(format!("{space}::{name}"))
}
fn demangle_global_sym_keyed<'s>(
config: &DemangleConfig,
s: &'s str,
cplus_marker: char,
full_sym: &'s str,
) -> Result<String, DemangleError<'s>> {
let (remaining, which, is_constructor) = if let Some(r) = s.strip_prefix("I") {
(r, "constructors", true)
} else if let Some(r) = s.strip_prefix("D") {
(r, "destructors", false)
} else if let Some(r) = s.strip_prefix("F") {
if config.demangle_global_keyed_frames {
(r, "frames", false)
} else {
return demangle_impl(full_sym, config, cplus_marker, false);
}
} else {
return Err(DemangleError::InvalidGlobalSymKeyed(s));
};
let Some(remaining) = remaining.strip_prefix(cplus_marker) else {
return Err(DemangleError::InvalidGlobalSymKeyed(s));
};
let demangled_sym = demangle_impl(remaining, config, cplus_marker, false);
if !config.fix_namespaced_global_constructor_bug
&& is_constructor
&& remaining.starts_with("__Q")
{
return demangled_sym;
}
let actual_sym = demangled_sym
.map(Cow::from)
.unwrap_or_else(|_| Cow::from(remaining));
Ok(format!("global {which} keyed to {actual_sym}"))
}