rudy_dwarf/parser/
functions.rs

1//! Function parsing combinators for DWARF debug information
2
3use super::children::for_each_child;
4use super::primitives::entry_type;
5use super::{from_fn, Parser};
6use crate::parser::combinators::all;
7use crate::parser::primitives::{is_member_tag, name};
8use crate::{Die, DwarfDb};
9
10/// Information about a function discovered in DWARF
11#[derive(Debug, Clone)]
12pub struct FunctionInfo {
13    pub name: String,
14    pub linkage_name: Option<String>,
15    pub die: Die,
16    pub parameters: Vec<ParameterInfo>,
17    pub return_type: Option<Die>,
18}
19
20/// Information about a function parameter
21#[derive(Debug, Clone)]
22pub struct ParameterInfo {
23    pub name: Option<String>,
24    pub type_die: Die,
25}
26
27pub fn function_parser() -> impl Parser<FunctionInfo> {
28    from_fn(
29        |db: &dyn DwarfDb, entry: Die| -> anyhow::Result<FunctionInfo> {
30            // Parse function components using parser combinators
31            let name = entry.name(db).unwrap_or_else(|_| "<anonymous>".to_string());
32
33            // Extract linkage name for symbol lookup
34            let linkage_name = entry.string_attr(db, crate::gimli::DW_AT_linkage_name).ok();
35
36            // Parse return type (optional)
37            let return_type = entry_type().parse(db, entry).ok();
38
39            // Parse parameters from children
40            let parameters = parameter_list_parser().parse(db, entry)?;
41
42            Ok(FunctionInfo {
43                name,
44                linkage_name,
45                die: entry,
46                parameters,
47                return_type,
48            })
49        },
50    )
51}
52
53/// Parser that attempts to parse a DIE as a function
54pub fn opt_function_parser() -> impl Parser<Option<FunctionInfo>> {
55    from_fn(
56        |db: &dyn DwarfDb, entry: Die| -> anyhow::Result<Option<FunctionInfo>> {
57            // Check if this is a function DIE
58            if entry.tag(db) != crate::gimli::DW_TAG_subprogram {
59                Ok(None)
60            } else {
61                // Parse the function using the function parser
62                let function_info = function_parser().parse(db, entry)?;
63                Ok(Some(function_info))
64            }
65        },
66    )
67}
68
69/// Parser for function parameters
70fn parameter_parser() -> impl Parser<Option<ParameterInfo>> {
71    from_fn(
72        |db: &dyn DwarfDb, entry: Die| -> anyhow::Result<Option<ParameterInfo>> {
73            // Only process formal parameter DIEs
74            if entry.tag(db) != crate::gimli::DW_TAG_formal_parameter {
75                return Ok(None);
76            }
77
78            let name = entry.name(db).ok();
79            let type_die = entry_type().parse(db, entry)?;
80
81            Ok(Some(ParameterInfo { name, type_die }))
82        },
83    )
84}
85
86/// Parser that extracts all parameters from children
87fn parameter_list_parser() -> impl Parser<Vec<ParameterInfo>> {
88    for_each_child(parameter_parser()).map(|results| results.into_iter().flatten().collect())
89}
90
91/// Parser that finds all functions among the children of a DIE
92pub fn child_functions_parser() -> impl Parser<Vec<FunctionInfo>> {
93    for_each_child(opt_function_parser()).map(|results| results.into_iter().flatten().collect())
94}
95
96/// Parser that finds impl namespace DIEs containing trait implementations
97/// by looking for {impl#N} patterns that are siblings to the target type
98pub fn impl_namespaces_in_module_parser() -> impl Parser<Vec<Die>> {
99    from_fn(
100        |db: &dyn DwarfDb, type_die: Die| -> anyhow::Result<Vec<Die>> {
101            let mut impl_namespaces = Vec::new();
102
103            // Find {impl#N} blocks that are siblings to our target type
104            // This is more targeted than searching the entire compilation unit
105            find_sibling_impl_namespaces(db, type_die, &mut impl_namespaces)?;
106
107            tracing::debug!(
108                "Found {} impl namespaces as siblings to target type",
109                impl_namespaces.len()
110            );
111            Ok(impl_namespaces)
112        },
113    )
114}
115
116/// Helper function to find {impl#N} namespaces that are in the same module as the target type
117/// Uses module indexing to find DIEs in the same namespace scope
118fn find_sibling_impl_namespaces(
119    db: &dyn DwarfDb,
120    type_die: Die,
121    impl_namespaces: &mut Vec<Die>,
122) -> anyhow::Result<()> {
123    // Get the module index for this debug file
124    let module_index = crate::modules::module_index(db, type_die.file);
125
126    // Find the module path for our target type using its DIE offset
127    let target_offset = type_die.offset();
128    let Some(module_range) = module_index.find_by_offset(db, target_offset) else {
129        tracing::debug!("No module range found for offset {target_offset:#x}");
130        return Ok(());
131    };
132
133    tracing::debug!(
134        "Target type is in module path: {:?} and DIE: {}",
135        module_range.module_path,
136        module_range.die.print(db)
137    );
138
139    // find all impl namespaces in the same module
140    let impls = for_each_child(
141        all((
142            is_member_tag(gimli::DW_TAG_namespace),
143            name()
144                .map_with_entry(|_, entry, n| {
145                    n.and_then(|n| {
146                        if n.starts_with("{impl#") && n.ends_with('}') {
147                            tracing::debug!("Found impl namespace: {n}");
148                            Some(entry)
149                        } else {
150                            None
151                        }
152                    })
153                })
154                .map_res(|entry| {
155                    entry.ok_or_else(|| anyhow::anyhow!("Expected impl namespace DIE, found None"))
156                }),
157        ))
158        .map(|(die, _)| die),
159    )
160    .parse(db, module_range.die)?;
161
162    impl_namespaces.extend(impls);
163
164    Ok(())
165}