rudy_dwarf/symbols/
names.rs

1//! Various parsing of names used in DWARF files
2
3use std::fmt;
4
5use anyhow::Context;
6use rudy_parser as parser;
7use rudy_types::Layout;
8
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct ModuleName {
11    pub segments: Vec<String>,
12}
13
14#[derive(Clone)]
15pub struct TypeName {
16    /// The module this type is defined in
17    /// e.g. `alloc::string`
18    pub module: ModuleName,
19    /// The simple name of the type, e.g. `String`
20    pub name: String,
21    /// The full name of the type, including module path
22    /// e.g. `alloc::string::String`
23    pub full_name: String,
24    pub typedef: Layout,
25}
26
27impl fmt::Debug for TypeName {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{self}")
30    }
31}
32
33impl PartialEq for TypeName {
34    fn eq(&self, other: &Self) -> bool {
35        self.module == other.module && self.name == other.name
36    }
37}
38impl Eq for TypeName {}
39impl PartialOrd for TypeName {
40    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
41        Some(self.cmp(other))
42    }
43}
44
45impl Ord for TypeName {
46    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
47        self.module
48            .cmp(&other.module)
49            .then_with(|| self.name.cmp(&other.name))
50    }
51}
52
53impl std::hash::Hash for TypeName {
54    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55        self.module.hash(state);
56        self.name.hash(state);
57    }
58}
59
60impl TypeName {
61    pub fn parse(module_path: &[String], name: &str) -> anyhow::Result<Self> {
62        fn known_bad_case(path: &str) -> bool {
63            path.contains("{closure_env#") || path.contains("{impl#") || path.contains("{extern#")
64        }
65
66        // If we have a module path, prepend it to the name for parsing
67        // This allows the parser to correctly identify std types like "String" as "alloc::string::String"
68        let full_name = if module_path.is_empty() {
69            name.to_string()
70        } else {
71            format!("{}::{}", module_path.join("::"), name)
72        };
73
74        tracing::trace!(
75            "TypeName::parse - module_path: {:?}, name: {}, full_name: {}",
76            module_path,
77            name,
78            full_name
79        );
80
81        let parsed_type = parser::parse_type(&full_name).map_err(|e| {
82            if !known_bad_case(&full_name) {
83                tracing::error!("Failed to parse type name `{full_name}`: {e}");
84            }
85            anyhow::anyhow!("Failed to parse type name `{full_name}`")
86        })?;
87
88        // let type_name = parsed_type.to_string();
89        let typedef = parsed_type.as_layout();
90
91        tracing::trace!("TypeName::parse - name: {name}, typedef: {typedef:?}",);
92
93        Ok(TypeName {
94            module: ModuleName {
95                segments: module_path.to_vec(),
96            },
97            name: typedef.display_name(),
98            full_name,
99            typedef,
100        })
101    }
102}
103
104impl fmt::Display for TypeName {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{}", self.full_name)
107    }
108}
109
110#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
111pub struct RawSymbol {
112    name_bytes: Vec<u8>,
113}
114
115impl fmt::Debug for RawSymbol {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        let name_str = std::str::from_utf8(&self.name_bytes)
118            .unwrap_or("<invalid UTF-8>")
119            .to_string();
120        write!(f, "RawSymbol({name_str})")
121    }
122}
123
124impl RawSymbol {
125    pub fn new(name_bytes: Vec<u8>) -> Self {
126        Self { name_bytes }
127    }
128
129    pub fn demangle(&self) -> anyhow::Result<SymbolName> {
130        demangle_symbol(self.clone())
131    }
132}
133
134#[derive(Clone, PartialEq, Eq, Hash, salsa::Update)]
135pub struct SymbolName {
136    pub lookup_name: String,
137    pub hash: String,
138    pub module_path: Vec<String>,
139    full_path: String,
140}
141
142impl SymbolName {
143    pub fn parse(path: &str) -> anyhow::Result<Self> {
144        fn known_bad_case(path: &str) -> bool {
145            path.contains('@')
146                || path.contains("{{")
147                || path.contains("__rustc[")
148                || path.starts_with('$')
149                || path.contains("DW.ref.rust_eh_personality")
150                || path.ends_with(".o")
151                || path.ends_with(".c")
152                || path.ends_with("cgu.0")
153                || path.starts_with("compiler_builtins.")
154                || path.starts_with(|c: char| c.is_ascii_digit())
155                || path.ends_with(".0")
156                || path.ends_with(".0$tlv$init")
157        }
158
159        let (module_path, lookup_name, hash) = parser::parse_symbol(path).map_err(|e| {
160            // known bad cases we don't care about
161            if !known_bad_case(path) {
162                tracing::error!("Failed to parse symbol path `{path}`: {e}");
163            }
164            anyhow::anyhow!("Failed to parse symbol path `{path}`")
165        })?;
166
167        Ok(SymbolName {
168            full_path: path.to_string(),
169            lookup_name,
170            hash: hash.unwrap_or_default(),
171            module_path,
172        })
173    }
174}
175
176impl fmt::Debug for SymbolName {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        write!(f, "{}", self.full_path)
179    }
180}
181
182impl fmt::Display for SymbolName {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(
185            f,
186            "{}",
187            // print without the trailing hash
188            self.full_path
189                .trim_end_matches(&self.hash)
190                .trim_end_matches("::")
191        )
192    }
193}
194
195impl SymbolName {
196    pub fn matches_name_and_module(&self, name: &str, module: &[String]) -> bool {
197        self.lookup_name == name && self.module_path.ends_with(module)
198    }
199}
200
201impl PartialOrd for SymbolName {
202    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
203        Some(self.cmp(other))
204    }
205}
206
207impl Ord for SymbolName {
208    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
209        self.lookup_name
210            .cmp(&other.lookup_name)
211            .then_with(|| self.module_path.cmp(&other.module_path))
212            .then_with(|| self.full_path.cmp(&other.full_path))
213    }
214}
215
216fn demangle_symbol(symbol: RawSymbol) -> anyhow::Result<SymbolName> {
217    let name_str = std::str::from_utf8(&symbol.name_bytes)
218        .context("Failed to convert symbol bytes to string")?;
219    let name_str = if name_str.starts_with("__Z") {
220        // Strip the extra leading `_` if it exists
221        // this is a macos trait
222        &name_str[1..]
223    } else {
224        name_str
225    };
226    let demangled = rustc_demangle::try_demangle(name_str)
227        .map_err(|_| anyhow::anyhow!("could not demangle symbol as Rust symbol"))?;
228    SymbolName::parse(&demangled.to_string()).context("Failed to parse demangled symbol")
229}