1use super::DwarfAnalyzer;
2use crate::semantics::{strip_type_aliases, VariableReadPlan};
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct TypeLookupAmbiguity {
8 pub type_name: String,
9 pub module_paths: Vec<PathBuf>,
10}
11
12impl fmt::Display for TypeLookupAmbiguity {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 let modules = self
15 .module_paths
16 .iter()
17 .map(|path| path.display().to_string())
18 .collect::<Vec<_>>()
19 .join(", ");
20 write!(
21 f,
22 "type '{}' is ambiguous across loaded modules: {}",
23 self.type_name, modules
24 )
25 }
26}
27
28impl std::error::Error for TypeLookupAmbiguity {}
29
30impl DwarfAnalyzer {
31 pub fn resolve_builtin_type_spec(type_spec: &str) -> Option<crate::TypeInfo> {
32 resolve_type_spec_with(type_spec, |_| None)
33 }
34
35 pub fn resolve_type_spec_in_module<P: AsRef<Path>>(
36 &self,
37 module_path: P,
38 type_spec: &str,
39 ) -> Option<crate::TypeInfo> {
40 let module_path = module_path.as_ref().to_path_buf();
41 resolve_type_spec_with(type_spec, |name| {
42 self.resolve_named_type_in_module(&module_path, name)
43 .or_else(|| self.resolve_named_type(name))
44 })
45 }
46
47 pub fn try_resolve_type_spec_in_module<P: AsRef<Path>>(
48 &self,
49 module_path: P,
50 type_spec: &str,
51 ) -> std::result::Result<Option<crate::TypeInfo>, TypeLookupAmbiguity> {
52 let module_path = module_path.as_ref().to_path_buf();
53 try_resolve_type_spec_with(type_spec, |name| {
54 if let Some(ty) = self.resolve_named_type_in_module(&module_path, name) {
55 return Ok(Some(ty));
56 }
57 self.resolve_unique_named_type_outside_module(&module_path, name)
58 })
59 }
60
61 pub fn resolve_type_spec(&self, type_spec: &str) -> Option<crate::TypeInfo> {
62 resolve_type_spec_with(type_spec, |name| self.resolve_named_type(name))
63 }
64
65 pub fn try_resolve_type_spec(
66 &self,
67 type_spec: &str,
68 ) -> std::result::Result<Option<crate::TypeInfo>, TypeLookupAmbiguity> {
69 try_resolve_type_spec_with(type_spec, |name| self.resolve_unique_named_type(name))
70 }
71
72 fn resolve_type_shallow_by_name_in_module_with_tags<P: AsRef<Path>>(
73 &self,
74 module_path: P,
75 name: &str,
76 tags: &[gimli::DwTag],
77 ) -> Option<crate::TypeInfo> {
78 let path_buf = module_path.as_ref().to_path_buf();
79 self.modules
80 .get(&path_buf)
81 .and_then(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
82 }
83
84 fn resolve_type_shallow_by_name_with_tags(
85 &self,
86 name: &str,
87 tags: &[gimli::DwTag],
88 ) -> Option<crate::TypeInfo> {
89 self.modules
90 .values()
91 .find_map(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
92 }
93
94 fn resolve_named_type_in_module(
95 &self,
96 module_path: &Path,
97 name: &str,
98 ) -> Option<crate::TypeInfo> {
99 let tags = [
100 gimli::constants::DW_TAG_structure_type,
101 gimli::constants::DW_TAG_class_type,
102 gimli::constants::DW_TAG_union_type,
103 gimli::constants::DW_TAG_enumeration_type,
104 ];
105 self.resolve_type_shallow_by_name_in_module_with_tags(module_path, name, &tags)
106 }
107
108 fn resolve_named_type(&self, name: &str) -> Option<crate::TypeInfo> {
109 let tags = [
110 gimli::constants::DW_TAG_structure_type,
111 gimli::constants::DW_TAG_class_type,
112 gimli::constants::DW_TAG_union_type,
113 gimli::constants::DW_TAG_enumeration_type,
114 ];
115 self.resolve_type_shallow_by_name_with_tags(name, &tags)
116 }
117
118 fn resolve_unique_named_type_outside_module(
119 &self,
120 module_path: &Path,
121 name: &str,
122 ) -> std::result::Result<Option<crate::TypeInfo>, TypeLookupAmbiguity> {
123 self.resolve_unique_named_type_with_filter(name, |candidate| candidate != module_path)
124 }
125
126 fn resolve_unique_named_type(
127 &self,
128 name: &str,
129 ) -> std::result::Result<Option<crate::TypeInfo>, TypeLookupAmbiguity> {
130 self.resolve_unique_named_type_with_filter(name, |_| true)
131 }
132
133 fn resolve_unique_named_type_with_filter(
134 &self,
135 name: &str,
136 include_module: impl Fn(&Path) -> bool,
137 ) -> std::result::Result<Option<crate::TypeInfo>, TypeLookupAmbiguity> {
138 let tags = [
139 gimli::constants::DW_TAG_structure_type,
140 gimli::constants::DW_TAG_class_type,
141 gimli::constants::DW_TAG_union_type,
142 gimli::constants::DW_TAG_enumeration_type,
143 ];
144 let mut matches = self
145 .modules
146 .iter()
147 .filter(|(path, _)| include_module(path.as_path()))
148 .filter_map(|(path, module_data)| {
149 module_data
150 .resolve_type_shallow_by_name_with_tags(name, &tags)
151 .map(|ty| (path.clone(), ty))
152 })
153 .collect::<Vec<_>>();
154
155 if matches.len() > 1 {
156 let mut module_paths = matches
157 .iter()
158 .map(|(path, _)| path.clone())
159 .collect::<Vec<_>>();
160 module_paths.sort();
161 return Err(TypeLookupAmbiguity {
162 type_name: name.to_string(),
163 module_paths,
164 });
165 }
166
167 Ok(matches.pop().map(|(_, ty)| ty))
168 }
169
170 pub(super) fn complete_unknown_pointer_target_type(
171 &self,
172 module_path: &Path,
173 plan: &mut VariableReadPlan,
174 pointer_type_name: &str,
175 ) {
176 let Some(dwarf_type) = plan.dwarf_type.clone() else {
177 return;
178 };
179
180 let (unknown_name, pointer_size) = match dwarf_type {
181 crate::TypeInfo::UnknownType { name } => (name, None),
182 crate::TypeInfo::PointerType { target_type, size } => {
183 let crate::TypeInfo::UnknownType { name } = *target_type else {
184 return;
185 };
186 (name, Some(size))
187 }
188 _ => return,
189 };
190
191 let mut candidate_names = Vec::new();
192 if !unknown_name.is_empty() && unknown_name != "void" {
193 candidate_names.push(unknown_name);
194 }
195 if candidate_names.is_empty() {
196 if let Some(index) = pointer_type_name.find('*') {
197 let mut base = pointer_type_name[..index].trim().to_string();
198 for prefix in [
199 "const ",
200 "volatile ",
201 "restrict ",
202 "struct ",
203 "class ",
204 "union ",
205 ] {
206 if base.starts_with(prefix) {
207 base = base[prefix.len()..].trim().to_string();
208 }
209 }
210 if !base.is_empty() && base != "void" {
211 candidate_names.push(base);
212 }
213 }
214 }
215
216 for candidate in candidate_names {
217 let Some(upgraded) = self.resolve_shallow_named_pointer_target(module_path, &candidate)
218 else {
219 continue;
220 };
221
222 let upgraded = Self::named_type(candidate, upgraded);
223 plan.dwarf_type = Some(if let Some(size) = pointer_size {
224 crate::TypeInfo::PointerType {
225 target_type: Box::new(upgraded),
226 size,
227 }
228 } else {
229 upgraded
230 });
231 if let Some(dwarf_type) = plan.dwarf_type.as_ref() {
232 plan.type_name = dwarf_type.type_name();
233 }
234 return;
235 }
236 }
237
238 pub fn complete_shallow_unknown_aggregate_type_in_module<P: AsRef<Path>>(
239 &self,
240 module_path: P,
241 ty: crate::TypeInfo,
242 ) -> crate::TypeInfo {
243 self.complete_shallow_unknown_aggregate_type_impl(Some(module_path.as_ref()), ty)
244 }
245
246 pub fn complete_shallow_unknown_aggregate_type(&self, ty: crate::TypeInfo) -> crate::TypeInfo {
247 self.complete_shallow_unknown_aggregate_type_impl(None, ty)
248 }
249
250 fn complete_shallow_unknown_aggregate_type_impl(
251 &self,
252 module_path: Option<&Path>,
253 ty: crate::TypeInfo,
254 ) -> crate::TypeInfo {
255 let candidate_name = match strip_type_aliases(&ty) {
256 crate::TypeInfo::UnknownType { name } => Some(name.clone()),
257 _ => None,
258 };
259 let Some(candidate_name) = candidate_name else {
260 return ty;
261 };
262 let Some(resolved) =
263 self.resolve_shallow_unknown_aggregate_name(module_path, &candidate_name)
264 else {
265 return ty;
266 };
267
268 match ty {
269 crate::TypeInfo::TypedefType { name, .. } => crate::TypeInfo::TypedefType {
270 name,
271 underlying_type: Box::new(resolved),
272 },
273 crate::TypeInfo::QualifiedType {
274 qualifier,
275 underlying_type,
276 } => {
277 crate::TypeInfo::QualifiedType {
278 qualifier,
279 underlying_type: Box::new(self.complete_shallow_unknown_aggregate_type_impl(
280 module_path,
281 *underlying_type,
282 )),
283 }
284 }
285 _ => resolved,
286 }
287 }
288
289 fn resolve_shallow_unknown_aggregate_name(
290 &self,
291 module_path: Option<&Path>,
292 name: &str,
293 ) -> Option<crate::TypeInfo> {
294 let mut candidates = Vec::new();
295 let mut push_candidate = |candidate: &str| {
296 let candidate = candidate.trim();
297 if !candidate.is_empty() && candidate != "void" {
298 candidates.push(candidate.to_string());
299 }
300 };
301
302 push_candidate(name);
303 for prefix in [
304 "const ",
305 "volatile ",
306 "restrict ",
307 "struct ",
308 "class ",
309 "union ",
310 ] {
311 if let Some(stripped) = name.strip_prefix(prefix) {
312 push_candidate(stripped);
313 }
314 }
315
316 for candidate in candidates {
317 if let Some(module_path) = module_path {
318 let resolved = self
319 .resolve_struct_type_shallow_by_name_in_module(module_path, &candidate)
320 .or_else(|| {
321 self.resolve_union_type_shallow_by_name_in_module(module_path, &candidate)
322 });
323 if let Some(resolved) = resolved.filter(|ty| ty.size() > 0) {
324 return Some(resolved);
325 }
326 continue;
327 }
328
329 let resolved = self
330 .resolve_struct_type_shallow_by_name(&candidate)
331 .or_else(|| self.resolve_union_type_shallow_by_name(&candidate));
332 if let Some(resolved) = resolved.filter(|ty| ty.size() > 0) {
333 return Some(resolved);
334 }
335 }
336
337 None
338 }
339
340 fn named_type(name: String, ty: crate::TypeInfo) -> crate::TypeInfo {
341 match ty {
342 crate::TypeInfo::StructType { .. }
343 | crate::TypeInfo::UnionType { .. }
344 | crate::TypeInfo::EnumType { .. } => crate::TypeInfo::TypedefType {
345 name,
346 underlying_type: Box::new(ty),
347 },
348 _ => ty,
349 }
350 }
351
352 fn resolve_shallow_named_pointer_target(
353 &self,
354 module_path: &Path,
355 name: &str,
356 ) -> Option<crate::TypeInfo> {
357 [
358 self.resolve_struct_type_shallow_by_name(name),
359 self.resolve_struct_type_shallow_by_name_in_module(module_path, name),
360 self.resolve_union_type_shallow_by_name(name),
361 self.resolve_union_type_shallow_by_name_in_module(module_path, name),
362 self.resolve_enum_type_shallow_by_name(name),
363 self.resolve_enum_type_shallow_by_name_in_module(module_path, name),
364 ]
365 .into_iter()
366 .flatten()
367 .find(|ty| ty.size() > 0)
368 }
369
370 pub fn resolve_struct_type_shallow_by_name_in_module<P: AsRef<Path>>(
372 &self,
373 module_path: P,
374 name: &str,
375 ) -> Option<crate::TypeInfo> {
376 self.resolve_type_shallow_by_name_in_module_with_tags(
377 module_path,
378 name,
379 &[
380 gimli::constants::DW_TAG_structure_type,
381 gimli::constants::DW_TAG_class_type,
382 ],
383 )
384 }
385
386 pub fn resolve_struct_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
388 self.resolve_type_shallow_by_name_with_tags(
389 name,
390 &[
391 gimli::constants::DW_TAG_structure_type,
392 gimli::constants::DW_TAG_class_type,
393 ],
394 )
395 }
396
397 pub fn resolve_union_type_shallow_by_name_in_module<P: AsRef<Path>>(
399 &self,
400 module_path: P,
401 name: &str,
402 ) -> Option<crate::TypeInfo> {
403 self.resolve_type_shallow_by_name_in_module_with_tags(
404 module_path,
405 name,
406 &[gimli::constants::DW_TAG_union_type],
407 )
408 }
409
410 pub fn resolve_union_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
412 self.resolve_type_shallow_by_name_with_tags(name, &[gimli::constants::DW_TAG_union_type])
413 }
414
415 pub fn resolve_enum_type_shallow_by_name_in_module<P: AsRef<Path>>(
417 &self,
418 module_path: P,
419 name: &str,
420 ) -> Option<crate::TypeInfo> {
421 self.resolve_type_shallow_by_name_in_module_with_tags(
422 module_path,
423 name,
424 &[gimli::constants::DW_TAG_enumeration_type],
425 )
426 }
427
428 pub fn resolve_enum_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
430 self.resolve_type_shallow_by_name_with_tags(
431 name,
432 &[gimli::constants::DW_TAG_enumeration_type],
433 )
434 }
435}
436
437fn resolve_type_spec_with<F>(type_spec: &str, mut resolve_named: F) -> Option<crate::TypeInfo>
438where
439 F: FnMut(&str) -> Option<crate::TypeInfo>,
440{
441 try_resolve_type_spec_with(type_spec, |name| {
442 Ok::<_, std::convert::Infallible>(resolve_named(name))
443 })
444 .ok()
445 .flatten()
446}
447
448fn try_resolve_type_spec_with<F, E>(
449 type_spec: &str,
450 mut resolve_named: F,
451) -> std::result::Result<Option<crate::TypeInfo>, E>
452where
453 F: FnMut(&str) -> std::result::Result<Option<crate::TypeInfo>, E>,
454{
455 let mut spec = type_spec.trim();
458 if spec.is_empty() {
459 return Ok(None);
460 }
461
462 let mut arrays = Vec::new();
463 while let Some((base, count)) = take_array_suffix(spec) {
464 arrays.push(count);
465 spec = base.trim_end();
466 }
467
468 let mut pointer_count = 0usize;
469 while let Some(base) = spec.strip_suffix('*') {
470 pointer_count += 1;
471 spec = base.trim_end();
472 }
473
474 let (qualifiers, base_spec) = strip_leading_qualifiers(spec);
475 let Some(mut ty) = try_resolve_base_type_spec(base_spec, &mut resolve_named)? else {
476 return Ok(None);
477 };
478
479 for qualifier in qualifiers.into_iter().rev() {
480 ty = crate::TypeInfo::QualifiedType {
481 qualifier,
482 underlying_type: Box::new(ty),
483 };
484 }
485
486 for _ in 0..pointer_count {
487 ty = crate::TypeInfo::PointerType {
488 target_type: Box::new(ty),
489 size: 8,
490 };
491 }
492
493 for count in arrays.into_iter().rev() {
494 let element_size = ty.size();
495 let total_size = count.and_then(|count| element_size.checked_mul(count));
496 ty = crate::TypeInfo::ArrayType {
497 element_type: Box::new(ty),
498 element_count: count,
499 total_size,
500 };
501 }
502
503 Ok(Some(ty))
504}
505
506fn take_array_suffix(spec: &str) -> Option<(&str, Option<u64>)> {
507 let spec = spec.trim_end();
508 if !spec.ends_with(']') {
509 return None;
510 }
511 let open = spec.rfind('[')?;
512 let inside = spec[open + 1..spec.len() - 1].trim();
513 let count = if inside.is_empty() {
514 None
515 } else {
516 Some(inside.parse::<u64>().ok()?)
517 };
518 Some((&spec[..open], count))
519}
520
521fn strip_leading_qualifiers(mut spec: &str) -> (Vec<crate::TypeQualifier>, &str) {
522 let mut qualifiers = Vec::new();
523 loop {
524 let trimmed = spec.trim_start();
525 if let Some(rest) = trimmed.strip_prefix("const ") {
526 qualifiers.push(crate::TypeQualifier::Const);
527 spec = rest;
528 } else if let Some(rest) = trimmed.strip_prefix("volatile ") {
529 qualifiers.push(crate::TypeQualifier::Volatile);
530 spec = rest;
531 } else if let Some(rest) = trimmed.strip_prefix("restrict ") {
532 qualifiers.push(crate::TypeQualifier::Restrict);
533 spec = rest;
534 } else {
535 return (qualifiers, trimmed);
536 }
537 }
538}
539
540fn try_resolve_base_type_spec<F, E>(
541 spec: &str,
542 resolve_named: &mut F,
543) -> std::result::Result<Option<crate::TypeInfo>, E>
544where
545 F: FnMut(&str) -> std::result::Result<Option<crate::TypeInfo>, E>,
546{
547 let spec = spec.trim();
548 if let Some(ty) = builtin_type_spec(spec) {
549 return Ok(Some(ty));
550 }
551
552 for prefix in ["struct ", "class ", "union ", "enum "] {
553 if let Some(name) = spec.strip_prefix(prefix) {
554 return resolve_named(name.trim());
555 }
556 }
557
558 resolve_named(spec)
559}
560
561fn builtin_type_spec(spec: &str) -> Option<crate::TypeInfo> {
562 let normalized = spec
563 .split_whitespace()
564 .collect::<Vec<_>>()
565 .join(" ")
566 .to_ascii_lowercase();
567 let (name, size, encoding) = match normalized.as_str() {
568 "void" => {
569 return Some(crate::TypeInfo::UnknownType {
570 name: "void".to_string(),
571 })
572 }
573 "bool" | "_bool" => ("bool", 1, gimli::constants::DW_ATE_boolean.0 as u16),
574 "char" | "signed char" | "i8" | "int8_t" | "__s8" => {
575 ("i8", 1, gimli::constants::DW_ATE_signed_char.0 as u16)
576 }
577 "unsigned char" | "u8" | "uint8_t" | "__u8" | "byte" => {
578 ("u8", 1, gimli::constants::DW_ATE_unsigned_char.0 as u16)
579 }
580 "short" | "short int" | "signed short" | "signed short int" | "i16" | "int16_t"
581 | "__s16" => ("i16", 2, gimli::constants::DW_ATE_signed.0 as u16),
582 "unsigned short" | "unsigned short int" | "u16" | "uint16_t" | "__u16" => {
583 ("u16", 2, gimli::constants::DW_ATE_unsigned.0 as u16)
584 }
585 "int" | "signed" | "signed int" | "i32" | "int32_t" | "__s32" => {
586 ("i32", 4, gimli::constants::DW_ATE_signed.0 as u16)
587 }
588 "unsigned" | "unsigned int" | "u32" | "uint32_t" | "__u32" => {
589 ("u32", 4, gimli::constants::DW_ATE_unsigned.0 as u16)
590 }
591 "long"
592 | "long int"
593 | "signed long"
594 | "signed long int"
595 | "long long"
596 | "long long int"
597 | "signed long long"
598 | "signed long long int"
599 | "i64"
600 | "int64_t"
601 | "__s64"
602 | "ssize_t" => ("i64", 8, gimli::constants::DW_ATE_signed.0 as u16),
603 "unsigned long"
604 | "unsigned long int"
605 | "unsigned long long"
606 | "unsigned long long int"
607 | "u64"
608 | "uint64_t"
609 | "__u64"
610 | "size_t" => ("u64", 8, gimli::constants::DW_ATE_unsigned.0 as u16),
611 "float" | "f32" => ("f32", 4, gimli::constants::DW_ATE_float.0 as u16),
612 "double" | "f64" => ("f64", 8, gimli::constants::DW_ATE_float.0 as u16),
613 "long double" => ("long double", 16, gimli::constants::DW_ATE_float.0 as u16),
614 _ => return None,
615 };
616
617 Some(crate::TypeInfo::BaseType {
618 name: name.to_string(),
619 size,
620 encoding,
621 })
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627
628 #[test]
629 fn resolves_builtin_pointer_and_array_specs() {
630 let ty = DwarfAnalyzer::resolve_builtin_type_spec("const unsigned int *[4]")
631 .expect("type should resolve");
632 let crate::TypeInfo::ArrayType {
633 element_type,
634 element_count,
635 total_size,
636 } = ty
637 else {
638 panic!("expected array type");
639 };
640 assert_eq!(element_count, Some(4));
641 assert_eq!(total_size, Some(32));
642 let crate::TypeInfo::PointerType { target_type, size } = *element_type else {
643 panic!("expected pointer element");
644 };
645 assert_eq!(size, 8);
646 assert!(matches!(
647 *target_type,
648 crate::TypeInfo::QualifiedType {
649 qualifier: crate::TypeQualifier::Const,
650 ..
651 }
652 ));
653 }
654
655 #[test]
656 fn resolves_builtin_c_integer_aliases() {
657 let ty = DwarfAnalyzer::resolve_builtin_type_spec("uint64_t").expect("type should resolve");
658 assert_eq!(ty.size(), 8);
659 assert!(ty.is_unsigned_int());
660 }
661}