Skip to main content

ghostscope_dwarf/analyzer/
type_lookup.rs

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    /// Resolve struct/class by name (shallow) in a specific module using only indexes
371    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    /// Resolve struct/class by name (shallow) across modules (first match)
387    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    /// Resolve union by name (shallow) in a specific module
398    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    /// Resolve union by name (shallow) across modules (first match)
411    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    /// Resolve enum by name (shallow) in a specific module
416    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    /// Resolve enum by name (shallow) across modules (first match)
429    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    // TODO: This parser intentionally accepts only C/C++-style type specs for now.
456    // Add an explicit TypeSpec/parser layer before extending this to Rust or other languages.
457    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}