ghostscope_dwarf/parser/detailed_parser.rs
1//! Detailed DWARF parser for on-demand traversal and variable resolution
2//!
3//! This module handles detailed parsing of DWARF tree structures, including:
4//! - Tree traversal for variable collection
5//! - Variable and parameter DIE parsing
6//! - Scope-aware variable resolution
7
8use crate::{
9 binary::DwarfReader,
10 core::{attr_u64, EvaluationResult, Result},
11 dwarf_expr::{errors as expr_errors, modes::DwarfExprMode},
12 index::{CfiIndex, FunctionBlocks},
13 parser::ExpressionEvaluator,
14 semantics::{resolve_name_with_origins, resolve_type_ref_in_same_unit_with_origins},
15 TypeInfo,
16};
17use gimli::Reader;
18// Alias gimli constants to upper-case identifiers to satisfy naming lints without allow attributes
19use gimli::constants::{
20 DW_AT_byte_size as DW_AT_BYTE_SIZE, DW_AT_encoding as DW_AT_ENCODING, DW_AT_name as DW_AT_NAME,
21 DW_AT_type as DW_AT_TYPE, DW_TAG_array_type as DW_TAG_ARRAY_TYPE,
22 DW_TAG_base_type as DW_TAG_BASE_TYPE, DW_TAG_class_type as DW_TAG_CLASS_TYPE,
23 DW_TAG_const_type as DW_TAG_CONST_TYPE, DW_TAG_enumeration_type as DW_TAG_ENUMERATION_TYPE,
24 DW_TAG_pointer_type as DW_TAG_POINTER_TYPE, DW_TAG_restrict_type as DW_TAG_RESTRICT_TYPE,
25 DW_TAG_structure_type as DW_TAG_STRUCTURE_TYPE,
26 DW_TAG_subroutine_type as DW_TAG_SUBROUTINE_TYPE, DW_TAG_typedef as DW_TAG_TYPEDEF,
27 DW_TAG_union_type as DW_TAG_UNION_TYPE, DW_TAG_volatile_type as DW_TAG_VOLATILE_TYPE,
28};
29use std::collections::HashSet;
30// no tracing imports needed here
31
32/// Variable with complete information including EvaluationResult
33#[derive(Debug, Clone)]
34pub struct VariableWithEvaluation {
35 pub name: String,
36 pub type_name: String,
37 pub dwarf_type: Option<TypeInfo>,
38 pub evaluation_result: EvaluationResult,
39 pub scope_depth: usize,
40 pub is_parameter: bool,
41 pub is_artificial: bool,
42}
43
44// Removed full traversal request/context types in shallow mode
45
46/// Detailed DWARF parser for tree traversal and variable collection
47#[derive(Debug)]
48pub struct DetailedParser {}
49
50impl DetailedParser {
51 /// Create new detailed parser
52 pub fn new() -> Self {
53 Self {}
54 }
55
56 /// Attach a cross-CU type name index for faster completion
57 pub fn set_type_name_index(&mut self, _index: std::sync::Arc<crate::index::TypeNameIndex>) {}
58
59 // Full type resolution intentionally removed; only shallow type resolution is supported.
60
61 /// Shallow type resolution (no recursive member expansion)
62 /// Returns minimal TypeInfo with name/size where possible.
63 pub fn resolve_type_shallow_at_offset(
64 dwarf: &gimli::Dwarf<DwarfReader>,
65 unit: &gimli::Unit<DwarfReader>,
66 mut type_offset: gimli::UnitOffset,
67 ) -> Option<TypeInfo> {
68 let mut visited = std::collections::HashSet::new();
69 // Strip typedef/qualifiers chain but keep last typedef name if it's the canonical alias
70 let mut alias_name: Option<String> = None;
71
72 let mut step = 0usize;
73 const MAX_STEPS: usize = 64;
74 loop {
75 if step >= MAX_STEPS || !visited.insert(type_offset) {
76 return Some(TypeInfo::UnknownType {
77 name: "<depth_limit>".to_string(),
78 });
79 }
80 step += 1;
81 let entry = unit.entry(type_offset).ok()?;
82 let tag = entry.tag();
83 // Utility to read attr string name
84 let mut entry_name: Option<String> = None;
85 if let Some(a) = entry.attr(DW_AT_NAME) {
86 if let Ok(s) = dwarf.attr_string(unit, a.value()) {
87 if let Ok(s_str) = s.to_string_lossy() {
88 entry_name = Some(s_str.into_owned());
89 }
90 }
91 }
92 match tag {
93 DW_TAG_TYPEDEF => {
94 if alias_name.is_none() {
95 alias_name = entry_name.clone();
96 }
97 if let Some(off) =
98 resolve_type_ref_in_same_unit_with_origins(dwarf, &entry, unit).ok()?
99 {
100 type_offset = off;
101 continue;
102 }
103 return Some(TypeInfo::TypedefType {
104 name: alias_name.unwrap_or_else(|| {
105 entry_name.unwrap_or_else(|| "<anon_typedef>".to_string())
106 }),
107 underlying_type: Box::new(TypeInfo::UnknownType {
108 name: "<unknown>".to_string(),
109 }),
110 });
111 }
112 DW_TAG_CONST_TYPE | DW_TAG_VOLATILE_TYPE | DW_TAG_RESTRICT_TYPE => {
113 if let Some(off) =
114 resolve_type_ref_in_same_unit_with_origins(dwarf, &entry, unit).ok()?
115 {
116 type_offset = off;
117 continue;
118 }
119 return Some(TypeInfo::QualifiedType {
120 qualifier: crate::TypeQualifier::Const,
121 underlying_type: Box::new(TypeInfo::UnknownType {
122 name: "<unknown>".to_string(),
123 }),
124 });
125 }
126 DW_TAG_POINTER_TYPE => {
127 let mut byte_size: u64 = 8;
128 // Default to unknown pointee until we find a concrete base/aggregate
129 let mut target: TypeInfo = TypeInfo::UnknownType {
130 name: "void".to_string(),
131 };
132 if let Some(a) = entry.attr(DW_AT_BYTE_SIZE) {
133 if let gimli::AttributeValue::Udata(sz) = a.value() {
134 byte_size = sz;
135 }
136 }
137 // Very shallow pointee name resolution: unwrap typedef/qualifiers only, without recursion
138 let mut pointee_name: Option<String> = None;
139 if let Some(mut toff) =
140 resolve_type_ref_in_same_unit_with_origins(dwarf, &entry, unit).ok()?
141 {
142 // Unwrap up to a small bound to avoid deep recursion/stack blowups on real-world code
143 for _ in 0..8 {
144 if let Ok(tentry) = unit.entry(toff) {
145 match tentry.tag() {
146 DW_TAG_TYPEDEF | DW_TAG_CONST_TYPE | DW_TAG_VOLATILE_TYPE
147 | DW_TAG_RESTRICT_TYPE => {
148 // Capture typedef name as a fallback pointee name
149 if tentry.tag() == DW_TAG_TYPEDEF {
150 if let Some(na) = tentry.attr(DW_AT_NAME) {
151 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
152 if pointee_name.is_none() {
153 if let Ok(s_str) = s.to_string_lossy() {
154 pointee_name = Some(s_str.into_owned());
155 }
156 }
157 }
158 }
159 }
160 if let Some(next) =
161 resolve_type_ref_in_same_unit_with_origins(
162 dwarf, &tentry, unit,
163 )
164 .ok()?
165 {
166 toff = next;
167 continue;
168 }
169 // No further type; bail
170 }
171 DW_TAG_BASE_TYPE => {
172 // Construct BaseType with size+encoding
173 let mut byte_size = 0u64;
174 let mut encoding = gimli::constants::DW_ATE_unsigned;
175 if let Some(a) = tentry.attr(DW_AT_BYTE_SIZE) {
176 if let gimli::AttributeValue::Udata(sz) = a.value() {
177 byte_size = sz;
178 }
179 }
180 if let Some(a) = tentry.attr(DW_AT_ENCODING) {
181 if let gimli::AttributeValue::Encoding(enc) = a.value()
182 {
183 encoding = enc;
184 }
185 }
186 let name = if let Some(na) = tentry.attr(DW_AT_NAME) {
187 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
188 s.to_string_lossy()
189 .ok()
190 .map(|c| c.into_owned())
191 .unwrap_or_else(|| "<base>".into())
192 } else {
193 "<base>".into()
194 }
195 } else {
196 "<base>".into()
197 };
198 pointee_name = Some(name.clone());
199 target = TypeInfo::BaseType {
200 name,
201 size: byte_size,
202 encoding: encoding.0 as u16,
203 };
204 }
205 DW_TAG_STRUCTURE_TYPE
206 | DW_TAG_CLASS_TYPE
207 | DW_TAG_UNION_TYPE
208 | DW_TAG_ENUMERATION_TYPE => {
209 // Do NOT recursively resolve aggregates here to avoid cycles on self-referential types.
210 // Only record the name; deref-time upgrade will use analyzer's shallow index safely.
211 if let Some(na) = tentry.attr(DW_AT_NAME) {
212 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
213 if let Ok(s_str) = s.to_string_lossy() {
214 let n = s_str.into_owned();
215 pointee_name = Some(n.clone());
216 // keep target as UnknownType{name} to avoid deep recursion here
217 if matches!(
218 target,
219 TypeInfo::UnknownType { .. }
220 ) {
221 target = TypeInfo::UnknownType { name: n };
222 }
223 }
224 }
225 }
226 }
227 _ => {}
228 }
229 break;
230 } else {
231 break;
232 }
233 }
234 }
235 // If only a name was found without concrete target, carry it as UnknownType
236 if let Some(n) = pointee_name {
237 if matches!(target, TypeInfo::UnknownType { .. }) {
238 target = TypeInfo::UnknownType { name: n };
239 }
240 }
241 return Some(TypeInfo::PointerType {
242 target_type: Box::new(target),
243 size: byte_size,
244 });
245 }
246 DW_TAG_BASE_TYPE => {
247 let name = entry_name.unwrap_or_else(|| "<base>".to_string());
248 let mut byte_size = 0u64;
249 let mut encoding = gimli::constants::DW_ATE_unsigned;
250 for a in entry.attrs() {
251 match a.name() {
252 DW_AT_BYTE_SIZE => {
253 if let gimli::AttributeValue::Udata(sz) = a.value() {
254 byte_size = sz;
255 }
256 }
257 DW_AT_ENCODING => {
258 if let gimli::AttributeValue::Encoding(enc) = a.value() {
259 encoding = enc;
260 }
261 }
262 _ => {}
263 }
264 }
265 return Some(TypeInfo::BaseType {
266 name,
267 size: byte_size,
268 encoding: encoding.0 as u16,
269 });
270 }
271 DW_TAG_STRUCTURE_TYPE | DW_TAG_CLASS_TYPE => {
272 let name = alias_name.clone().unwrap_or_else(|| {
273 entry_name.unwrap_or_else(|| "<anon_struct>".to_string())
274 });
275 let mut byte_size = 0u64;
276 if let Some(a) = entry.attr(DW_AT_BYTE_SIZE) {
277 if let gimli::AttributeValue::Udata(sz) = a.value() {
278 byte_size = sz;
279 }
280 }
281 // Collect only direct member DIEs
282 let mut members: Vec<crate::StructMember> = Vec::new();
283 if let Ok(mut tree) = unit.entries_tree(Some(entry.offset())) {
284 if let Ok(root) = tree.root() {
285 let mut children = root.children();
286 while let Ok(Some(child)) = children.next() {
287 let ce = child.entry();
288 if ce.tag() == gimli::DW_TAG_member {
289 // member name
290 let mut m_name = String::new();
291 if let Some(na) = ce.attr(DW_AT_NAME) {
292 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
293 if let Ok(s_str) = s.to_string_lossy() {
294 m_name = s_str.into_owned();
295 }
296 }
297 }
298 // member type (shallow)
299 let mut m_type = TypeInfo::UnknownType {
300 name: "unknown".to_string(),
301 };
302 if let Some(gimli::AttributeValue::UnitRef(toff)) =
303 ce.attr_value(DW_AT_TYPE)
304 {
305 if let Some(ti) =
306 Self::resolve_type_shallow_at_offset(dwarf, unit, toff)
307 {
308 m_type = ti;
309 }
310 }
311 // member offset (simple evaluation)
312 let mut m_offset: u64 = 0;
313 if let Some(ml) = ce.attr(gimli::DW_AT_data_member_location) {
314 match ml.value() {
315 gimli::AttributeValue::Exprloc(expr) => {
316 if let Some(v) = expr_errors::downgrade_optional_to_none(
317 DwarfExprMode::ConstOffset,
318 crate::dwarf_expr::const_eval::eval_const_offset(
319 &expr,
320 unit.encoding(),
321 ),
322 "shallow member type display",
323 ) {
324 m_offset = v;
325 }
326 }
327 value => {
328 if let Some(v) = attr_u64(value) {
329 m_offset = v;
330 }
331 }
332 }
333 }
334 // bit offsets/sizes (optional)
335 let mut bit_offset: Option<u8> = None;
336 let mut raw_bit_offset: Option<u64> = None;
337 let mut has_data_bit_offset = false;
338 let mut bit_size: Option<u8> = None;
339 if let Some(bo) = ce.attr(gimli::DW_AT_bit_offset) {
340 if let Some(v) = attr_u64(bo.value()) {
341 raw_bit_offset = Some(v);
342 }
343 }
344 if let Some(bs) = ce.attr(gimli::DW_AT_data_bit_offset) {
345 if let Some(v) = attr_u64(bs.value()) {
346 has_data_bit_offset = true;
347 bit_offset = u8::try_from(v % 8).ok();
348 m_offset = v / 8;
349 }
350 }
351 if let Some(bsz) = ce.attr(gimli::DW_AT_bit_size) {
352 if let Some(v) = attr_u64(bsz.value()) {
353 bit_size = u8::try_from(v).ok();
354 }
355 }
356 // DW_AT_bit_offset (legacy) is typically counted from the MSB
357 // of the storage unit; convert to little-endian LSB offset.
358 // When DW_AT_data_bit_offset is present we already have LSB-based
359 // layout and should not convert again.
360 if !has_data_bit_offset {
361 if let (Some(raw_bo), Some(bs)) = (raw_bit_offset, bit_size)
362 {
363 let storage_bits = m_type.size().saturating_mul(8);
364 let bs_u64 = bs as u64;
365 if storage_bits > 0 && raw_bo + bs_u64 <= storage_bits {
366 let le_off = storage_bits - raw_bo - bs_u64;
367 bit_offset = u8::try_from(le_off).ok();
368 } else {
369 bit_offset = u8::try_from(raw_bo).ok();
370 }
371 } else if let Some(raw_bo) = raw_bit_offset {
372 bit_offset = u8::try_from(raw_bo).ok();
373 }
374 }
375 if m_name.is_empty() {
376 m_name = format!("member_{}", members.len());
377 }
378 // Wrap bitfield member type into BitfieldType for standalone printing
379 let member_type = if let Some(bs) = bit_size {
380 let bo = bit_offset.unwrap_or(0);
381 TypeInfo::BitfieldType {
382 underlying_type: Box::new(m_type),
383 bit_offset: bo,
384 bit_size: bs,
385 }
386 } else {
387 m_type
388 };
389 members.push(crate::StructMember {
390 name: m_name,
391 member_type,
392 offset: m_offset,
393 bit_offset,
394 bit_size,
395 });
396 }
397 }
398 }
399 }
400 // Post-process: infer array total_size/element_count when missing (from next member offset or struct size)
401 if !members.is_empty() {
402 // Pre-build sorted offsets
403 let mut offsets: Vec<u64> = members.iter().map(|m| m.offset).collect();
404 offsets.sort_unstable();
405 offsets.dedup();
406
407 for m in &mut members {
408 // Only for array members with missing size info
409 if let TypeInfo::ArrayType {
410 element_type,
411 element_count,
412 total_size,
413 } = &m.member_type
414 {
415 if element_count.is_none() && total_size.is_none() {
416 let cur_off = m.offset;
417 let next_off = offsets
418 .iter()
419 .cloned()
420 .filter(|&o| o > cur_off)
421 .min()
422 .unwrap_or(byte_size);
423 let avail = next_off.saturating_sub(cur_off);
424 if avail > 0 {
425 let elem_sz = element_type.size();
426 let mut new_count: Option<u64> = None;
427 if elem_sz > 0 && avail % elem_sz == 0 {
428 new_count = Some(avail / elem_sz);
429 }
430 m.member_type = TypeInfo::ArrayType {
431 element_type: element_type.clone(),
432 element_count: new_count,
433 total_size: Some(avail),
434 };
435 }
436 }
437 }
438 }
439 }
440 return Some(TypeInfo::StructType {
441 name,
442 size: byte_size,
443 members,
444 });
445 }
446 DW_TAG_UNION_TYPE => {
447 let name = alias_name.clone().unwrap_or_else(|| {
448 entry_name.unwrap_or_else(|| "<anon_union>".to_string())
449 });
450 let mut byte_size = 0u64;
451 if let Some(a) = entry.attr(DW_AT_BYTE_SIZE) {
452 if let gimli::AttributeValue::Udata(sz) = a.value() {
453 byte_size = sz;
454 }
455 }
456 let mut members: Vec<crate::StructMember> = Vec::new();
457 if let Ok(mut tree) = unit.entries_tree(Some(entry.offset())) {
458 if let Ok(root) = tree.root() {
459 let mut children = root.children();
460 while let Ok(Some(child)) = children.next() {
461 let ce = child.entry();
462 if ce.tag() == gimli::DW_TAG_member {
463 let mut m_name = String::new();
464 if let Some(na) = ce.attr(gimli::DW_AT_name) {
465 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
466 if let Ok(s_str) = s.to_string_lossy() {
467 m_name = s_str.into_owned();
468 }
469 }
470 }
471 let mut m_type = TypeInfo::UnknownType {
472 name: "unknown".to_string(),
473 };
474 if let Some(gimli::AttributeValue::UnitRef(toff)) =
475 ce.attr_value(DW_AT_TYPE)
476 {
477 if let Some(ti) =
478 Self::resolve_type_shallow_at_offset(dwarf, unit, toff)
479 {
480 m_type = ti;
481 }
482 }
483 if m_name.is_empty() {
484 m_name = format!("member_{}", members.len());
485 }
486 members.push(crate::StructMember {
487 name: m_name,
488 member_type: m_type,
489 offset: 0,
490 bit_offset: None,
491 bit_size: None,
492 });
493 }
494 }
495 }
496 }
497 return Some(TypeInfo::UnionType {
498 name,
499 size: byte_size,
500 members,
501 });
502 }
503 DW_TAG_ENUMERATION_TYPE => {
504 let name = alias_name
505 .clone()
506 .unwrap_or_else(|| entry_name.unwrap_or_else(|| "<anon_enum>".to_string()));
507 // Parse base type and size
508 let mut byte_size = 0u64;
509 if let Some(a) = entry.attr(DW_AT_BYTE_SIZE) {
510 if let gimli::AttributeValue::Udata(sz) = a.value() {
511 byte_size = sz;
512 }
513 }
514 // Default base type as signed int; size from byte_size or 4
515 let mut base_type: TypeInfo = TypeInfo::BaseType {
516 name: "int".to_string(),
517 size: if byte_size > 0 { byte_size } else { 4 },
518 encoding: gimli::constants::DW_ATE_signed.0 as u16,
519 };
520 // If DW_AT_type refers to a base type, resolve it shallowly
521 if let Some(gimli::AttributeValue::UnitRef(toff)) = entry.attr_value(DW_AT_TYPE)
522 {
523 if let Some(ti) = Self::resolve_type_shallow_at_offset(dwarf, unit, toff) {
524 // Accept only base/qualified/typedef chain base type as enum underlying type
525 base_type = ti;
526 // If enum size missing, use underlying base type size
527 let bs = base_type.size();
528 if byte_size == 0 && bs > 0 {
529 byte_size = bs;
530 }
531 }
532 }
533 // Collect enum variants (one level)
534 let mut variants: Vec<crate::EnumVariant> = Vec::new();
535 if let Ok(mut tree) = unit.entries_tree(Some(entry.offset())) {
536 if let Ok(root) = tree.root() {
537 let mut children = root.children();
538 while let Ok(Some(child)) = children.next() {
539 let ce = child.entry();
540 if ce.tag() == gimli::DW_TAG_enumerator {
541 let mut v_name = String::new();
542 if let Some(na) = ce.attr(gimli::DW_AT_name) {
543 if let Ok(s) = dwarf.attr_string(unit, na.value()) {
544 if let Ok(s_str) = s.to_string_lossy() {
545 v_name = s_str.into_owned();
546 }
547 }
548 }
549 let mut v_val: i64 = 0;
550 if let Some(cv) = ce.attr(gimli::DW_AT_const_value) {
551 let signed = match &base_type {
552 TypeInfo::BaseType { encoding, .. } => {
553 *encoding
554 == gimli::constants::DW_ATE_signed.0 as u16
555 || *encoding
556 == gimli::constants::DW_ATE_signed_char.0
557 as u16
558 }
559 TypeInfo::TypedefType {
560 underlying_type, ..
561 }
562 | TypeInfo::QualifiedType {
563 underlying_type, ..
564 } => {
565 matches!(
566 &**underlying_type,
567 TypeInfo::BaseType { encoding, .. }
568 if *encoding == gimli::constants::DW_ATE_signed.0 as u16
569 || *encoding
570 == gimli::constants::DW_ATE_signed_char.0 as u16
571 )
572 }
573 _ => true,
574 };
575 v_val = match cv.value() {
576 gimli::AttributeValue::Udata(u) => u as i64,
577 gimli::AttributeValue::Sdata(s) => s,
578 gimli::AttributeValue::Data1(b) => {
579 let u = b as u64;
580 if signed && (u & 0x80) != 0 {
581 (u as i8) as i64
582 } else {
583 u as i64
584 }
585 }
586 gimli::AttributeValue::Data2(u) => {
587 let u = u as u64;
588 if signed && (u & 0x8000) != 0 {
589 (u as i16) as i64
590 } else {
591 u as i64
592 }
593 }
594 gimli::AttributeValue::Data4(u) => {
595 let u = u as u64;
596 if signed && (u & 0x8000_0000) != 0 {
597 (u as i32) as i64
598 } else {
599 u as i64
600 }
601 }
602 gimli::AttributeValue::Data8(u) => u as i64,
603 _ => v_val,
604 };
605 }
606 if v_name.is_empty() {
607 v_name = format!("variant_{}", variants.len());
608 }
609 variants.push(crate::EnumVariant {
610 name: v_name,
611 value: v_val,
612 });
613 }
614 }
615 }
616 }
617 return Some(TypeInfo::EnumType {
618 name,
619 size: byte_size,
620 base_type: Box::new(base_type),
621 variants,
622 });
623 }
624 DW_TAG_ARRAY_TYPE => {
625 // element_type shallow + total_size if available + subrange element_count (one step deeper)
626 let mut elem_type: Option<TypeInfo> = None;
627 if let Some(gimli::AttributeValue::UnitRef(eoff)) = entry.attr_value(DW_AT_TYPE)
628 {
629 elem_type = Self::resolve_type_shallow_at_offset(dwarf, unit, eoff);
630 }
631 let element_type = Box::new(elem_type.unwrap_or(TypeInfo::UnknownType {
632 name: "<elem>".to_string(),
633 }));
634 let mut total_size: Option<u64> = None;
635 if let Some(a) = entry.attr(DW_AT_BYTE_SIZE) {
636 if let gimli::AttributeValue::Udata(sz) = a.value() {
637 total_size = Some(sz);
638 }
639 }
640 // Optional: subrange child yields count/upper_bound
641 let mut element_count: Option<u64> = None;
642 if let Ok(mut tree) = unit.entries_tree(Some(entry.offset())) {
643 if let Ok(root) = tree.root() {
644 let mut children = root.children();
645 while let Ok(Some(child)) = children.next() {
646 let ce = child.entry();
647 if ce.tag() == gimli::DW_TAG_subrange_type {
648 // Prefer DW_AT_count; fallback to upper_bound (+1)
649 if let Some(cv) = ce.attr(gimli::DW_AT_count) {
650 match cv.value() {
651 gimli::AttributeValue::Udata(u) => {
652 element_count = Some(u);
653 }
654 gimli::AttributeValue::Sdata(s) => {
655 if s >= 0 {
656 element_count = Some(s as u64);
657 }
658 }
659 gimli::AttributeValue::Data1(b) => {
660 element_count = Some(b as u64);
661 }
662 gimli::AttributeValue::Data2(u) => {
663 element_count = Some(u as u64);
664 }
665 gimli::AttributeValue::Data4(u) => {
666 element_count = Some(u as u64);
667 }
668 gimli::AttributeValue::Data8(u) => {
669 element_count = Some(u);
670 }
671 _ => {}
672 }
673 }
674 if element_count.is_none() {
675 if let Some(ub) = ce.attr(gimli::DW_AT_upper_bound) {
676 let ub_v: Option<i64> = match ub.value() {
677 gimli::AttributeValue::Udata(u) => Some(u as i64),
678 gimli::AttributeValue::Sdata(s) => Some(s),
679 gimli::AttributeValue::Data1(b) => Some(b as i64),
680 gimli::AttributeValue::Data2(u) => Some(u as i64),
681 gimli::AttributeValue::Data4(u) => Some(u as i64),
682 gimli::AttributeValue::Data8(u) => Some(u as i64),
683 _ => None,
684 };
685 if let Some(ub_i) = ub_v {
686 if ub_i >= 0 {
687 element_count = Some((ub_i as u64) + 1);
688 }
689 }
690 }
691 }
692 // Stop at first subrange
693 if element_count.is_some() {
694 break;
695 }
696 }
697 }
698 }
699 }
700 // If total_size absent but count + elem size known, compute it
701 if total_size.is_none() {
702 let es = element_type.size();
703 if let Some(cnt) = element_count {
704 if es > 0 {
705 total_size = Some(es * cnt);
706 }
707 }
708 }
709 return Some(TypeInfo::ArrayType {
710 element_type,
711 element_count,
712 total_size,
713 });
714 }
715 DW_TAG_SUBROUTINE_TYPE => {
716 return Some(TypeInfo::FunctionType {
717 return_type: None,
718 parameters: Vec::new(),
719 });
720 }
721 _ => {
722 // Fallback: return alias name or entry name
723 let nm = alias_name
724 .or(entry_name)
725 .unwrap_or_else(|| "<unknown>".to_string());
726 return Some(TypeInfo::UnknownType { name: nm });
727 }
728 }
729 }
730 }
731
732 // Full variable collection and traversal helpers removed in shallow-only mode
733
734 // parse_variable_entry wrapper removed; use parse_variable_entry_with_mode
735
736 /// Parse a variable and optionally skip full DWARF type resolution
737 #[allow(clippy::too_many_arguments)]
738 pub fn parse_variable_entry_with_mode(
739 &self,
740 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
741 unit: &gimli::Unit<DwarfReader>,
742 dwarf: &gimli::Dwarf<DwarfReader>,
743 address: u64,
744 get_cfa: Option<&dyn Fn(u64) -> Result<Option<crate::core::CfaResult>>>,
745 function_context: Option<&FunctionBlocks>,
746 cfi_index: Option<&CfiIndex>,
747 scope_depth: usize,
748 ) -> Result<Option<VariableWithEvaluation>> {
749 // No traversal context retained in shallow mode
750 // Resolve basic
751 let mut visited = std::collections::HashSet::new();
752 let Some(name) = Self::resolve_name_with_origins(entry, unit, dwarf, &mut visited)? else {
753 return Ok(None);
754 };
755 let is_parameter = entry.tag() == gimli::constants::DW_TAG_formal_parameter;
756 let type_name = Self::resolve_type_name(entry, unit, dwarf)?;
757 let evaluation_result = self.parse_location(
758 entry,
759 unit,
760 dwarf,
761 address,
762 get_cfa,
763 function_context,
764 cfi_index,
765 )?;
766 // Full type resolution disabled in shallow mode
767 let dwarf_type = None;
768 Ok(Some(VariableWithEvaluation {
769 name,
770 type_name,
771 dwarf_type,
772 evaluation_result,
773 scope_depth,
774 is_parameter,
775 is_artificial: false,
776 }))
777 }
778
779 /// Resolve type name for a variable or type DIE.
780 ///
781 /// This function follows DW_AT_type chains (pointer/const/array/typedef) and
782 /// includes a recursion guard to break true cycles (e.g., typedef A->B->A).
783 fn resolve_type_name(
784 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
785 unit: &gimli::Unit<DwarfReader>,
786 dwarf: &gimli::Dwarf<DwarfReader>,
787 ) -> Result<String> {
788 let mut visited_types: HashSet<gimli::UnitOffset> = HashSet::new();
789 Self::resolve_type_name_rec(entry, unit, dwarf, &mut visited_types)
790 }
791
792 fn resolve_type_name_rec(
793 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
794 unit: &gimli::Unit<DwarfReader>,
795 dwarf: &gimli::Dwarf<DwarfReader>,
796 visited: &mut HashSet<gimli::UnitOffset>,
797 ) -> Result<String> {
798 // Follow DW_AT_type if present
799 let Some(type_off) = Self::resolve_type_ref(entry, unit, dwarf)? else {
800 // As a fallback, try to use the entry's own name if any
801 let mut name_visited = HashSet::new();
802 if let Some(n) = Self::resolve_name_with_origins(entry, unit, dwarf, &mut name_visited)?
803 {
804 return Ok(n);
805 }
806 return Ok("unknown".to_string());
807 };
808
809 // Recursion guard: if we've seen this type offset already, break the cycle
810 if !visited.insert(type_off) {
811 return Ok("<recursive>".to_string());
812 }
813
814 let mut tree = unit.entries_tree(Some(type_off))?;
815 let type_node = tree.root()?;
816 let type_entry = type_node.entry();
817
818 // If this DIE has a name, prefer it directly
819 let mut name_visited = HashSet::new();
820 if let Some(name) =
821 Self::resolve_name_with_origins(type_entry, unit, dwarf, &mut name_visited)?
822 {
823 return Ok(name);
824 }
825
826 // Handle wrapper/indirection DIEs by following their DW_AT_type
827 match type_entry.tag() {
828 gimli::constants::DW_TAG_pointer_type => {
829 let pointee = Self::resolve_type_name_rec(type_entry, unit, dwarf, visited)?;
830 Ok(format!("{pointee}*"))
831 }
832 gimli::constants::DW_TAG_const_type => {
833 let base = Self::resolve_type_name_rec(type_entry, unit, dwarf, visited)?;
834 Ok(format!("const {base}"))
835 }
836 gimli::constants::DW_TAG_array_type => {
837 let elem = Self::resolve_type_name_rec(type_entry, unit, dwarf, visited)?;
838 Ok(format!("{elem}[]"))
839 }
840 gimli::constants::DW_TAG_typedef => {
841 // Use typedef's own name if present; otherwise follow underlying type
842 let mut tvisited = HashSet::new();
843 if let Some(tname) =
844 Self::resolve_name_with_origins(type_entry, unit, dwarf, &mut tvisited)?
845 {
846 Ok(tname)
847 } else {
848 Self::resolve_type_name_rec(type_entry, unit, dwarf, visited)
849 }
850 }
851 // Fallback: stringify the DWARF tag
852 other => Ok(format!("{other:?}")),
853 }
854 }
855
856 /// Parse location attribute
857 #[allow(clippy::too_many_arguments)]
858 pub fn parse_location(
859 &self,
860 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
861 unit: &gimli::Unit<DwarfReader>,
862 dwarf: &gimli::Dwarf<DwarfReader>,
863 address: u64,
864 get_cfa: Option<&dyn Fn(u64) -> Result<Option<crate::core::CfaResult>>>,
865 function_context: Option<&FunctionBlocks>,
866 cfi_index: Option<&CfiIndex>,
867 ) -> Result<EvaluationResult> {
868 // Use ExpressionEvaluator for unified logic
869 ExpressionEvaluator::evaluate_location(
870 entry,
871 unit,
872 dwarf,
873 address,
874 get_cfa,
875 function_context,
876 cfi_index,
877 )
878 }
879
880 // extract_name removed; call resolve_name_with_origins directly when needed
881
882 fn resolve_name_with_origins(
883 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
884 unit: &gimli::Unit<DwarfReader>,
885 dwarf: &gimli::Dwarf<DwarfReader>,
886 _visited: &mut HashSet<gimli::UnitOffset>,
887 ) -> Result<Option<String>> {
888 Ok(resolve_name_with_origins(dwarf, unit, entry)?)
889 }
890
891 fn resolve_type_ref(
892 entry: &gimli::DebuggingInformationEntry<DwarfReader>,
893 unit: &gimli::Unit<DwarfReader>,
894 dwarf: &gimli::Dwarf<DwarfReader>,
895 ) -> Result<Option<gimli::UnitOffset>> {
896 resolve_type_ref_in_same_unit_with_origins(dwarf, entry, unit)
897 }
898
899 // resolve_flag_with_origins and entry_pc_matches removed with variable traversal helpers
900}
901
902impl Default for DetailedParser {
903 fn default() -> Self {
904 Self::new()
905 }
906}