rudy_dwarf/parser/
enums.rs

1//! Enum parser implementation using combinators
2
3use anyhow::Result;
4use rudy_types::{
5    CEnumLayout, CEnumVariant, Discriminant, DiscriminantType, EnumLayout, EnumVariantLayout,
6    Layout, PrimitiveLayout,
7};
8
9use crate::{
10    parser::{
11        children::{for_each_child, parse_children},
12        combinators::all,
13        primitives::{
14            attr, data_offset, entry_type, is_member_tag, member, member_by_tag, optional_attr,
15            resolve_type_shallow, IsMember,
16        },
17        Parser,
18    },
19    types::resolve_entry_type_shallow,
20    Die, DwarfDb,
21};
22
23/// Parser for discriminant information
24///
25/// Should be called on a `DW_TAG_variant_part` DIE to extract the discriminant type and offset.
26pub fn enum_discriminant() -> impl Parser<Discriminant> {
27    struct DiscriminantParser;
28
29    impl Parser<Discriminant> for DiscriminantParser {
30        fn parse(&self, db: &dyn DwarfDb, variants_entry: Die) -> Result<Discriminant> {
31            let (discr_die, offset) = optional_attr::<Die>(gimli::DW_AT_discr)
32                .and(optional_attr::<usize>(gimli::DW_AT_data_member_location))
33                .parse(db, variants_entry)?;
34
35            if let Some(discr) = discr_die {
36                // We have an explicit discriminant - resolve its type
37                let discriminant_type = resolve_entry_type_shallow(db, discr)?;
38                let ty = match discriminant_type.layout.as_ref() {
39                    rudy_types::Layout::Primitive(rudy_types::PrimitiveLayout::Int(i)) => {
40                        DiscriminantType::Int(*i)
41                    }
42                    rudy_types::Layout::Primitive(rudy_types::PrimitiveLayout::UnsignedInt(u)) => {
43                        DiscriminantType::UnsignedInt(*u)
44                    }
45                    _ => {
46                        tracing::warn!(
47                            "discriminant type is not an integer: {discriminant_type:?} {}",
48                            discr.location(db)
49                        );
50                        DiscriminantType::Implicit
51                    }
52                };
53                Ok(Discriminant {
54                    ty,
55                    offset: offset.unwrap_or(0),
56                })
57            } else {
58                // No explicit discriminant, so we assume it's implicit
59                Ok(Discriminant {
60                    ty: DiscriminantType::Implicit,
61                    offset: 0,
62                })
63            }
64        }
65    }
66
67    DiscriminantParser
68}
69
70/// Parser for enum variants
71pub fn enum_variant() -> impl Parser<EnumVariantLayout<Die>> {
72    // 0x000002f5:           DW_TAG_variant
73    //                         DW_AT_discr_value       (0x00)
74
75    // 0x000002f7:             DW_TAG_member
76    //                         DW_AT_name    ("None")
77    //                         DW_AT_type    (0x00000312 "core::option::Option<u32>::None<u32>")
78    //                         DW_AT_alignment       (4)
79    //                         DW_AT_data_member_location    (0x00)
80
81    // check we're in a variant DIE
82    is_member_tag(gimli::DW_TAG_variant)
83        .then(
84            // we _may_ have a discriminant value
85            optional_attr::<i128>(gimli::DW_AT_discr_value).and(
86                // and we must have a member
87                member_by_tag(gimli::DW_TAG_member).then(
88                    // which has a name, offset, and type
89                    all((
90                        attr::<String>(gimli::DW_AT_name),
91                        data_offset(),
92                        entry_type().then(resolve_type_shallow()),
93                    )),
94                ),
95            ),
96        )
97        .map(|(discriminant, (name, offset, layout))| {
98            // Generally it seems like the variants should have offset 0, and we get the
99            // "real" offsets from variant layouts themselves. But we need to verify this
100            debug_assert_eq!(offset, 0, "enum variants should not have offsets");
101            EnumVariantLayout {
102                name,
103                discriminant,
104                layout,
105            }
106        })
107}
108
109pub struct EnumNamedTupleVariant<T> {
110    variant_name: String,
111    parser: T,
112}
113
114/// Specialized version of `named_enum_variant` that parses a named enum variant
115/// and then proceeds to parse it as a tuple variant, applying a parser to each field
116///
117/// Should be used with `parse_children` on a `DW_TAG_variant_part` DIE.
118pub fn enum_named_tuple_variant<T>(variant_name: &str, parser: T) -> EnumNamedTupleVariant<T> {
119    let variant_name = variant_name.to_string();
120
121    EnumNamedTupleVariant {
122        variant_name,
123        parser,
124    }
125}
126
127pub(super) struct PartiallyParsedEnumVariant {
128    pub discriminant: Option<usize>,
129    pub offset: usize,
130    pub layout: Die,
131}
132
133/// Parses an enum variant by a specific name.
134/// Should be used with `parse_children` on a `DW_TAG_variant_part` DIE.
135///
136///   DW_TAG_variant_part
137///     DW_AT_discr       (0x00002440)
138///     DW_TAG_member
139///       DW_AT_type      (0x00004f86 "u32")
140///       DW_AT_data_member_location      (0x00)
141/// --> DW_TAG_variant
142///       DW_AT_discr_value       (0x00)
143///       DW_TAG_member
144///         DW_AT_name    ("None")
145///         DW_AT_type    (0x00002464 "core::option::Option<i32>::None<i32>")
146///         DW_AT_data_member_location    (0x00)
147/// --> DW_TAG_variant
148///       DW_AT_discr_value       (0x01)
149///       DW_TAG_member
150///         DW_AT_name    ("Some")
151///         DW_AT_type    (0x00002476 "core::option::Option<i32>::Some<i32>")
152///         DW_AT_data_member_location    (0x00)
153pub(super) fn named_enum_variant(variant_name: &str) -> impl Parser<PartiallyParsedEnumVariant> {
154    is_member_tag(gimli::DW_TAG_variant).then(
155        optional_attr::<usize>(gimli::DW_AT_discr)
156            .and(member(variant_name).then(all((data_offset(), entry_type()))))
157            .map(
158                |(discriminant, (offset, layout))| PartiallyParsedEnumVariant {
159                    discriminant,
160                    offset,
161                    layout,
162                },
163            ),
164    )
165}
166
167macro_rules! impl_parse_enum_named_tuple_variant_for_tuples {
168    (
169        $($P:ident, $T:ident, $idx:tt),*
170    ) => {
171        impl<'db, $($T, $P,)*> Parser< (Option<usize>, ($((usize, $T),)*))> for EnumNamedTupleVariant<($($P,)*)>
172        where
173            $($P: Parser< $T>),*
174        {
175            fn parse(&self, db: &dyn DwarfDb, entry: Die) -> anyhow::Result<(Option<usize>, ($((usize, $T),)*))> {
176                // --> DW_TAG_variant
177                //       DW_AT_discr_value       (0x00)
178                //       DW_TAG_member
179                //         DW_AT_name    ("None")
180                //         DW_AT_type    (0x00002464 "core::option::Option<i32>::None<i32>")
181                //         DW_AT_data_member_location    (0x00)
182
183
184                // we first need to resolve to the DW_TAG_variant -> DW_TAG_member with a matching name
185                // and get the type entry for that member
186
187                // e.g. in the case of "Some", we should get back
188                // PartiallyParsedEnumVariant { name: "Some", discriminant: Some(1), offset: 0, layout: 0x00002476 }
189                let variant = named_enum_variant(&self.variant_name).parse(db, entry)?;
190
191                debug_assert_eq!(variant.offset, 0, "enum variants should not have offsets");
192
193                // next, e.g. in the case of "Some", we get a structure type for the enum variant
194                // 0x00002476:         DW_TAG_structure_type
195                //                       DW_AT_name        ("Some")
196                //                       DW_AT_byte_size   (0x08)
197                //                       DW_AT_accessibility       (DW_ACCESS_public)
198                //                       DW_AT_alignment   (4)
199
200                // 0x0000247e:           DW_TAG_template_type_parameter
201                //                         DW_AT_type      (0x000036f7 "i32")
202                //                         DW_AT_name      ("T")
203
204                // 0x00002487:           DW_TAG_member
205                //                         DW_AT_name      ("__0")
206                //                         DW_AT_type      (0x000036f7 "i32")
207                //                         DW_AT_alignment (4)
208                //                         DW_AT_data_member_location      (0x04)
209                //                         DW_AT_accessibility     (DW_ACCESS_public)
210
211                // parses each subfield as a member called `__0`, `__1`, etc.
212                let field_parser = (
213                    $(
214                        IsMember { expected_name: format!("__{}", $idx) }
215                            .then(all((
216                                data_offset(),
217                                entry_type().then(&self.parser.$idx)
218                            ))),
219                    )*
220                );
221
222                parse_children(field_parser).map(|fields| {
223                    // If we have a discriminant, return it, otherwise None
224                    let discriminant = variant.discriminant;
225                    (discriminant, fields)
226                }).parse(db, variant.layout)
227            }
228        }
229    };
230}
231
232impl_parse_enum_named_tuple_variant_for_tuples!();
233impl_parse_enum_named_tuple_variant_for_tuples!(P0, T0, 0);
234impl_parse_enum_named_tuple_variant_for_tuples!(P0, T0, 0, P1, T1, 1);
235impl_parse_enum_named_tuple_variant_for_tuples!(P0, T0, 0, P1, T1, 1, P2, T2, 2);
236impl_parse_enum_named_tuple_variant_for_tuples!(P0, T0, 0, P1, T1, 1, P2, T2, 2, P3, T3, 3);
237impl_parse_enum_named_tuple_variant_for_tuples!(
238    P0, T0, 0, P1, T1, 1, P2, T2, 2, P3, T3, 3, P4, T4, 4
239);
240impl_parse_enum_named_tuple_variant_for_tuples!(
241    P0, T0, 0, P1, T1, 1, P2, T2, 2, P3, T3, 3, P4, T4, 4, P5, T5, 5
242);
243impl_parse_enum_named_tuple_variant_for_tuples!(
244    P0, T0, 0, P1, T1, 1, P2, T2, 2, P3, T3, 3, P4, T4, 4, P5, T5, 5, P6, T6, 6
245);
246impl_parse_enum_named_tuple_variant_for_tuples!(
247    P0, T0, 0, P1, T1, 1, P2, T2, 2, P3, T3, 3, P4, T4, 4, P5, T5, 5, P6, T6, 6, P7, T7, 7
248);
249
250/// Parser for enum types
251///
252/// Reference: https://github.com/rust-lang/rust/blob/3b97f1308ff72016a4aaa93fbe6d09d4d6427815/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs
253pub fn enum_def() -> impl Parser<EnumLayout<Die>> {
254    EnumParser
255}
256
257pub struct EnumParser;
258
259impl Parser<EnumLayout<Die>> for EnumParser {
260    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<EnumLayout<Die>> {
261        tracing::debug!("resolving enum type: {}", entry.print(db));
262
263        // Get the variant part
264        let (name, size, variants_entry) = all((
265            attr::<String>(gimli::DW_AT_name),
266            attr::<usize>(gimli::DW_AT_byte_size),
267            member_by_tag(gimli::DW_TAG_variant_part),
268        ))
269        .parse(db, entry)?;
270
271        // Parse discriminant info and variants
272        let discriminant = enum_discriminant().parse(db, variants_entry)?;
273
274        // Parse all variants
275        let variants = for_each_child(enum_variant()).parse(db, variants_entry)?;
276
277        Ok(EnumLayout {
278            name,
279            variants,
280            size,
281            discriminant,
282        })
283    }
284}
285
286/// Parser for C-style enum variants (DW_TAG_enumerator)
287pub fn c_enum_variant() -> impl Parser<CEnumVariant> {
288    all((
289        attr::<String>(gimli::DW_AT_name),
290        attr::<i128>(gimli::DW_AT_const_value),
291    ))
292    .map(|(name, value)| CEnumVariant { name, value })
293}
294
295/// Parser for C-style enumeration types (DW_TAG_enumeration_type)
296pub fn c_enum_def() -> impl Parser<CEnumLayout> {
297    struct CEnumParser;
298
299    impl Parser<CEnumLayout> for CEnumParser {
300        fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<CEnumLayout> {
301            tracing::debug!("resolving C-style enum type: {}", entry.print(db));
302
303            // Parse name, size, and underlying type
304            let (name, size, underlying_type) = all((
305                attr::<String>(gimli::DW_AT_name),
306                attr::<usize>(gimli::DW_AT_byte_size),
307                entry_type()
308                    .then(resolve_type_shallow())
309                    .map_res(|ty| match ty.layout.as_ref() {
310                        Layout::Primitive(PrimitiveLayout::Int(i)) => Ok(DiscriminantType::Int(*i)),
311                        Layout::Primitive(PrimitiveLayout::UnsignedInt(u)) => {
312                            Ok(DiscriminantType::UnsignedInt(*u))
313                        }
314                        _ => Err(anyhow::anyhow!(
315                            "C enum underlying type must be integer, got: {:?}",
316                            ty
317                        )),
318                    }),
319            ))
320            .parse(db, entry)?;
321
322            // Parse all enumerator variants
323            let variants = for_each_child(c_enum_variant()).parse(db, entry)?;
324
325            Ok(CEnumLayout {
326                name,
327                discriminant_type: underlying_type,
328                variants,
329                size,
330            })
331        }
332    }
333
334    CEnumParser
335}