1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::project::Project;
/// Enum used for the `demangling` option in `Config`.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Demangling {
/// Don't try to demangle
NoDemangling,
/// Try to demangle using the C++ demangler (suitable for `Project`s containing C++ code).
/// Names that fail to demangle will simply be printed as-is.
CPP,
/// Try to demangle using the Rust demangler (suitable for `Project`s containing Rust code).
/// Names that fail to demangle will simply be printed as-is.
Rust,
}
impl Demangling {
/// Attempts to demangle the given function name, as appropriate based on the
/// `Demangling` setting.
//
// (takes `self` by value because `self` is `Copy`)
pub fn maybe_demangle(self, funcname: &str) -> String {
match self {
Demangling::NoDemangling => funcname.to_owned(),
Demangling::CPP => cpp_demangle_or_id(funcname),
Demangling::Rust => rust_demangle_or_id(funcname),
}
}
/// Guesses an appropriate `Demangling` for the given `Project`.
pub fn autodetect(proj: &Project) -> Self {
// our autodetection is pretty unsophisticated right now,
// but something is better than nothing
// if any file in the `Project` comes from a source file
// ending in `.rs`, then use Rust demangling.
// Empirically, bitcode generated by `rustc` may have a "source
// filename" ending in `cgu.0` instead (for example, our test file
// `panic.rs` is compiled to a bitcode file with a "source filename"
// of `panic.3a1fbbbh-cgu.0`), so also check for filenames ending in
// `u.0`. (False positives aren't the end of the world, because any
// symbols that aren't actually valid Rust symbols will be passed
// through the demangler unchanged.)
// TODO figure out a tighter test here, hopefully we can avoid both
// false positives and false negatives.
if proj
.module_source_file_names()
.any(|name| name.ends_with(".rs") || name.ends_with("u.0"))
{
return Demangling::Rust;
}
// otherwise, if any file in the `Project` comes from a source
// file ending in `.cpp`, then use C++ demangling
if proj
.module_source_file_names()
.any(|name| name.ends_with(".cpp"))
{
return Demangling::CPP;
}
// otherwise give up and don't try to demangle
Demangling::NoDemangling
}
}
/// Helper function to demangle function names with the C++ demangler.
///
/// Returns `Some` if successfully demangled, or `None` if any error occurs
/// (for instance, if `funcname` isn't a valid C++ mangled name)
pub(crate) fn try_cpp_demangle(funcname: &str) -> Option<String> {
let opts = cpp_demangle::DemangleOptions { no_params: true };
cpp_demangle::Symbol::new(funcname)
.ok()
.and_then(|sym| sym.demangle(&opts).ok())
}
/// Like `try_cpp_demangle()`, but just returns the input string unmodified in
/// the case of any error, rather than returning `None`.
pub(crate) fn cpp_demangle_or_id(funcname: &str) -> String {
try_cpp_demangle(funcname).unwrap_or_else(|| funcname.to_owned())
}
/// Helper function to demangle function names with the Rust demangler.
///
/// Returns `Some` if successfully demangled, or `None` if any error occurs
/// (for instance, if `funcname` isn't a valid Rust mangled name)
pub(crate) fn try_rust_demangle(funcname: &str) -> Option<String> {
rustc_demangle::try_demangle(funcname)
.ok()
.map(|demangled| format!("{:#}", demangled))
}
/// Like `try_rust_demangle()`, but just returns the input string unmodified in
/// the case of any error, rather than returning `None`.
pub(crate) fn rust_demangle_or_id(funcname: &str) -> String {
format!("{:#}", rustc_demangle::demangle(funcname))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn autodetect() -> Result<(), String> {
// A `Project` from a single C file
let c_proj = Project::from_bc_path("tests/bcfiles/basic.bc")?;
assert_eq!(Demangling::autodetect(&c_proj), Demangling::NoDemangling);
// A `Project` from a single C++ file
let cpp_proj = Project::from_bc_path("tests/bcfiles/throwcatch.bc")?;
assert_eq!(Demangling::autodetect(&cpp_proj), Demangling::CPP);
// A `Project` from a single Rust file
let rust_proj = Project::from_bc_path("tests/bcfiles/panic.bc")?;
assert_eq!(Demangling::autodetect(&rust_proj), Demangling::Rust);
// A `Project` containing multiple C files
let c_proj = Project::from_bc_paths(&[
"tests/bcfiles/basic.bc",
"tests/bcfiles/call.bc",
"tests/bcfiles/globals.bc",
"tests/bcfiles/simd.bc",
])?;
assert_eq!(Demangling::autodetect(&c_proj), Demangling::NoDemangling);
// A `Project` containing both C and Rust files
let c_rust_proj = Project::from_bc_paths(&[
"tests/bcfiles/basic.bc",
"tests/bcfiles/call.bc",
"tests/bcfiles/panic.bc",
])?;
assert_eq!(Demangling::autodetect(&c_rust_proj), Demangling::Rust);
// A `Project` containing both C and C++ files
let c_cpp_proj = Project::from_bc_paths(&[
"tests/bcfiles/basic.bc",
"tests/bcfiles/call.bc",
"tests/bcfiles/throwcatch.bc",
])?;
assert_eq!(Demangling::autodetect(&c_cpp_proj), Demangling::CPP);
Ok(())
}
}