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}