ghostscope_protocol/
format_printer.rs

1//! Format printer for complex print instructions
2//!
3//! Converts PrintComplexVariable/PrintComplexFormat payloads into formatted text in user space.
4
5use crate::trace_context::TraceContext;
6use crate::trace_event::VariableStatus;
7use crate::type_info::TypeInfo;
8
9// Removed legacy simple variable wrapper; use complex paths only.
10
11/// A parsed complex variable from PrintComplexVariable instruction data
12#[derive(Debug, Clone)]
13pub struct ParsedComplexVariable {
14    pub var_name_index: u16,
15    pub type_index: u16,
16    pub access_path: String,
17    pub status: u8, // 0 OK; non-zero means error payload in data
18    pub data: Vec<u8>,
19}
20
21/// Format printer for converting PrintComplexFormat data to formatted strings
22pub struct FormatPrinter;
23
24impl FormatPrinter {
25    /// Format printer for converting PrintComplexFormat data to formatted strings
26    pub fn format_complex_print_data(
27        format_string_index: u16,
28        complex_variables: &[ParsedComplexVariable],
29        trace_context: &TraceContext,
30    ) -> String {
31        // Get the format string from the trace context
32        let format_string = match trace_context.get_string(format_string_index) {
33            Some(s) => s,
34            None => {
35                return format!("<INVALID_FORMAT_INDEX_{format_string_index}>");
36            }
37        };
38
39        // Apply formatting using raw variables to support extended specifiers
40        Self::apply_format_with_specs(format_string, complex_variables, trace_context)
41    }
42
43    /// Simple placeholder applier for tests that don't use complex variables
44    #[cfg(test)]
45    fn apply_format_strings(format_string: &str, formatted_values: &[String]) -> String {
46        let mut result = String::new();
47        let mut chars = format_string.chars().peekable();
48        let mut var_index = 0;
49
50        while let Some(ch) = chars.next() {
51            match ch {
52                '{' => {
53                    if chars.peek() == Some(&'{') {
54                        chars.next();
55                        result.push('{');
56                    } else {
57                        // Skip to closing '}' and substitute
58                        let mut found = false;
59                        for c in chars.by_ref() {
60                            if c == '}' {
61                                found = true;
62                                break;
63                            }
64                        }
65                        if found {
66                            if var_index < formatted_values.len() {
67                                result.push_str(&formatted_values[var_index]);
68                                var_index += 1;
69                            } else {
70                                result.push_str("<MISSING_ARG>");
71                            }
72                        } else {
73                            result.push_str("<MALFORMED_PLACEHOLDER>");
74                        }
75                    }
76                }
77                '}' => {
78                    if chars.peek() == Some(&'}') {
79                        chars.next();
80                        result.push('}');
81                    } else {
82                        result.push('}');
83                    }
84                }
85                _ => result.push(ch),
86            }
87        }
88        result
89    }
90
91    /// Apply formatting with extended specifiers {:x}/{:X}/{:p}/{:s}, and optional
92    /// length suffix .N / .* / .name$.
93    fn apply_format_with_specs(
94        format_string: &str,
95        vars: &[ParsedComplexVariable],
96        trace_context: &TraceContext,
97    ) -> String {
98        let mut result = String::new();
99        let mut chars = format_string.chars().peekable();
100        let mut var_index: usize = 0;
101
102        while let Some(ch) = chars.next() {
103            match ch {
104                '{' => {
105                    if chars.peek() == Some(&'{') {
106                        chars.next();
107                        result.push('{');
108                    } else {
109                        let mut found = false;
110                        let mut content = String::new();
111                        for c in chars.by_ref() {
112                            if c == '}' {
113                                found = true;
114                                break;
115                            }
116                            content.push(c);
117                        }
118                        if !found {
119                            result.push_str("<MALFORMED_PLACEHOLDER>");
120                            continue;
121                        }
122
123                        if content.is_empty() {
124                            // default {}
125                            if var_index < vars.len() {
126                                let v = &vars[var_index];
127                                let s = Self::format_complex_variable_with_status(
128                                    v.var_name_index,
129                                    v.type_index,
130                                    &v.access_path,
131                                    &v.data,
132                                    v.status,
133                                    trace_context,
134                                );
135                                let value_part = s.split(" = ").last().unwrap_or(&s);
136                                result.push_str(value_part);
137                                var_index += 1;
138                            } else {
139                                result.push_str("<MISSING_ARG>");
140                            }
141                            continue;
142                        }
143
144                        if !content.starts_with(':') {
145                            result.push_str("<INVALID_SPEC>");
146                            continue;
147                        }
148                        let tail = &content[1..];
149                        let mut it = tail.chars();
150                        let conv = it.next().unwrap_or(' ');
151                        let rest: String = it.collect();
152
153                        // (removed) helper to get bytes of current arg; we now surface errors explicitly
154
155                        enum Len {
156                            None,
157                            Static(usize),
158                            Star,
159                            Capture,
160                        }
161                        // helper: parse static length supporting decimal/0x.. /0o.. /0b..
162                        fn parse_static_len(spec: &str) -> Option<usize> {
163                            if spec.chars().all(|c| c.is_ascii_digit()) {
164                                return spec.parse::<usize>().ok();
165                            }
166                            if let Some(hex) = spec.strip_prefix("0x") {
167                                if !hex.is_empty() && hex.chars().all(|c| c.is_ascii_hexdigit()) {
168                                    return usize::from_str_radix(hex, 16).ok();
169                                }
170                            }
171                            if let Some(oct) = spec.strip_prefix("0o") {
172                                if !oct.is_empty() && oct.chars().all(|c| matches!(c, '0'..='7')) {
173                                    return usize::from_str_radix(oct, 8).ok();
174                                }
175                            }
176                            if let Some(bin) = spec.strip_prefix("0b") {
177                                if !bin.is_empty() && bin.chars().all(|c| matches!(c, '0' | '1')) {
178                                    return usize::from_str_radix(bin, 2).ok();
179                                }
180                            }
181                            None
182                        }
183
184                        let lenspec = if rest.is_empty() {
185                            Len::None
186                        } else if let Some(r) = rest.strip_prefix('.') {
187                            if r == "*" {
188                                Len::Star
189                            } else if r.ends_with('$') {
190                                Len::Capture
191                            } else if let Some(n) = parse_static_len(r) {
192                                Len::Static(n)
193                            } else {
194                                Len::None
195                            }
196                        } else {
197                            Len::None
198                        };
199
200                        // helper: parse signed length from 8-byte little endian, clamp to >=0
201                        fn parse_len_usize(lenb: &[u8]) -> usize {
202                            if lenb.len() >= 8 {
203                                let arr = [
204                                    lenb[0], lenb[1], lenb[2], lenb[3], lenb[4], lenb[5], lenb[6],
205                                    lenb[7],
206                                ];
207                                let v = i64::from_le_bytes(arr);
208                                if v <= 0 {
209                                    0
210                                } else {
211                                    v as usize
212                                }
213                            } else {
214                                0
215                            }
216                        }
217
218                        // helper: format error value for a var when status != Ok/ZeroLength
219                        let err_value_part = |idx: usize| -> Option<String> {
220                            if idx >= vars.len() {
221                                return None;
222                            }
223                            let v = &vars[idx];
224                            if v.status == VariableStatus::Ok as u8
225                                || v.status == VariableStatus::ZeroLength as u8
226                            {
227                                None
228                            } else {
229                                let s = Self::format_complex_variable_with_status(
230                                    v.var_name_index,
231                                    v.type_index,
232                                    &v.access_path,
233                                    &v.data,
234                                    v.status,
235                                    trace_context,
236                                );
237                                Some(s.split(" = ").last().unwrap_or(&s).to_string())
238                            }
239                        };
240
241                        match conv {
242                            'x' | 'X' => {
243                                match lenspec {
244                                    Len::Star => {
245                                        if var_index + 1 >= vars.len() {
246                                            result.push_str("<MISSING_ARG>");
247                                        } else if let Some(err) = err_value_part(var_index) {
248                                            // surface error from length argument
249                                            result.push_str(&err);
250                                            var_index += 2;
251                                            continue;
252                                        } else if let Some(err) = err_value_part(var_index + 1) {
253                                            // surface error from value argument
254                                            result.push_str(&err);
255                                            var_index += 2;
256                                            continue;
257                                        } else {
258                                            // both Ok or ZeroLength
259                                            let lenb = vars[var_index].data.as_slice();
260                                            let n = parse_len_usize(lenb);
261                                            let v = &vars[var_index + 1];
262                                            let full = v.data.as_slice();
263                                            let take =
264                                                if v.status == VariableStatus::ZeroLength as u8 {
265                                                    0
266                                                } else {
267                                                    std::cmp::min(n, full.len())
268                                                };
269                                            let b = &full[..take];
270                                            let s = b
271                                                .iter()
272                                                .map(|vv| {
273                                                    if conv == 'x' {
274                                                        format!("{vv:02x}")
275                                                    } else {
276                                                        format!("{vv:02X}")
277                                                    }
278                                                })
279                                                .collect::<Vec<_>>()
280                                                .join(" ");
281                                            result.push_str(&s);
282                                            var_index += 2;
283                                            continue;
284                                        }
285                                        // when missing one of the args, don't advance to avoid misalignment
286                                    }
287                                    Len::Static(n) => {
288                                        if var_index >= vars.len() {
289                                            result.push_str("<MISSING_ARG>");
290                                        } else if let Some(err) = err_value_part(var_index) {
291                                            result.push_str(&err);
292                                            var_index += 1;
293                                            continue;
294                                        } else {
295                                            let v = &vars[var_index];
296                                            let full = v.data.as_slice();
297                                            let take =
298                                                if v.status == VariableStatus::ZeroLength as u8 {
299                                                    0
300                                                } else {
301                                                    std::cmp::min(n, full.len())
302                                                };
303                                            let b = &full[..take];
304                                            let s = b
305                                                .iter()
306                                                .map(|vv| {
307                                                    if conv == 'x' {
308                                                        format!("{vv:02x}")
309                                                    } else {
310                                                        format!("{vv:02X}")
311                                                    }
312                                                })
313                                                .collect::<Vec<_>>()
314                                                .join(" ");
315                                            result.push_str(&s);
316                                            var_index += 1;
317                                            continue;
318                                        }
319                                    }
320                                    Len::Capture => {
321                                        if var_index + 1 >= vars.len() {
322                                            result.push_str("<MISSING_ARG>");
323                                        } else if let Some(err) = err_value_part(var_index) {
324                                            result.push_str(&err);
325                                            var_index += 2;
326                                            continue;
327                                        } else if let Some(err) = err_value_part(var_index + 1) {
328                                            result.push_str(&err);
329                                            var_index += 2;
330                                            continue;
331                                        } else {
332                                            let lenb = vars[var_index].data.as_slice();
333                                            let n = parse_len_usize(lenb);
334                                            let v = &vars[var_index + 1];
335                                            let full = v.data.as_slice();
336                                            let take =
337                                                if v.status == VariableStatus::ZeroLength as u8 {
338                                                    0
339                                                } else {
340                                                    std::cmp::min(n, full.len())
341                                                };
342                                            let b = &full[..take];
343                                            let s = b
344                                                .iter()
345                                                .map(|vv| {
346                                                    if conv == 'x' {
347                                                        format!("{vv:02x}")
348                                                    } else {
349                                                        format!("{vv:02X}")
350                                                    }
351                                                })
352                                                .collect::<Vec<_>>()
353                                                .join(" ");
354                                            result.push_str(&s);
355                                            var_index += 2;
356                                            continue;
357                                        }
358                                        // when missing one of the args, don't advance
359                                    }
360                                    Len::None => {
361                                        if var_index >= vars.len() {
362                                            result.push_str("<MISSING_ARG>");
363                                        } else if let Some(err) = err_value_part(var_index) {
364                                            result.push_str(&err);
365                                            var_index += 1;
366                                            continue;
367                                        } else {
368                                            let v = &vars[var_index];
369                                            let b = if v.status == VariableStatus::ZeroLength as u8
370                                            {
371                                                &[][..]
372                                            } else {
373                                                v.data.as_slice()
374                                            };
375                                            let s = b
376                                                .iter()
377                                                .map(|vv| {
378                                                    if conv == 'x' {
379                                                        format!("{vv:02x}")
380                                                    } else {
381                                                        format!("{vv:02X}")
382                                                    }
383                                                })
384                                                .collect::<Vec<_>>()
385                                                .join(" ");
386                                            result.push_str(&s);
387                                            var_index += 1;
388                                            continue;
389                                        }
390                                    }
391                                }
392                            }
393                            's' => {
394                                let mut render_bytes = |b: &[u8]| {
395                                    let mut out = String::new();
396                                    for &c in b.iter() {
397                                        if c == 0 {
398                                            break;
399                                        }
400                                        if (0x20..=0x7e).contains(&c) {
401                                            out.push(c as char);
402                                        } else {
403                                            out.push_str(&format!("\\x{c:02x}"));
404                                        }
405                                    }
406                                    result.push_str(&out);
407                                };
408
409                                match lenspec {
410                                    Len::Star => {
411                                        if var_index + 1 >= vars.len() {
412                                            result.push_str("<MISSING_ARG>");
413                                        } else if let Some(err) = err_value_part(var_index) {
414                                            result.push_str(&err);
415                                            var_index += 2;
416                                            continue;
417                                        } else if let Some(err) = err_value_part(var_index + 1) {
418                                            result.push_str(&err);
419                                            var_index += 2;
420                                            continue;
421                                        } else {
422                                            let lenb = vars[var_index].data.as_slice();
423                                            let n = parse_len_usize(lenb);
424                                            let v = &vars[var_index + 1];
425                                            let full = v.data.as_slice();
426                                            let take =
427                                                if v.status == VariableStatus::ZeroLength as u8 {
428                                                    0
429                                                } else {
430                                                    std::cmp::min(n, full.len())
431                                                };
432                                            render_bytes(&full[..take]);
433                                            var_index += 2;
434                                            continue;
435                                        }
436                                    }
437                                    Len::Static(n) => {
438                                        if var_index >= vars.len() {
439                                            result.push_str("<MISSING_ARG>");
440                                        } else if let Some(err) = err_value_part(var_index) {
441                                            result.push_str(&err);
442                                            var_index += 1;
443                                            continue;
444                                        } else {
445                                            let v = &vars[var_index];
446                                            let full = v.data.as_slice();
447                                            let take =
448                                                if v.status == VariableStatus::ZeroLength as u8 {
449                                                    0
450                                                } else {
451                                                    std::cmp::min(n, full.len())
452                                                };
453                                            render_bytes(&full[..take]);
454                                            var_index += 1;
455                                            continue;
456                                        }
457                                    }
458                                    Len::Capture => {
459                                        if var_index + 1 >= vars.len() {
460                                            result.push_str("<MISSING_ARG>");
461                                        } else if let Some(err) = err_value_part(var_index) {
462                                            result.push_str(&err);
463                                            var_index += 2;
464                                            continue;
465                                        } else if let Some(err) = err_value_part(var_index + 1) {
466                                            result.push_str(&err);
467                                            var_index += 2;
468                                            continue;
469                                        } else {
470                                            let lenb = vars[var_index].data.as_slice();
471                                            let n = parse_len_usize(lenb);
472                                            let v = &vars[var_index + 1];
473                                            let full = v.data.as_slice();
474                                            let take =
475                                                if v.status == VariableStatus::ZeroLength as u8 {
476                                                    0
477                                                } else {
478                                                    std::cmp::min(n, full.len())
479                                                };
480                                            render_bytes(&full[..take]);
481                                            var_index += 2;
482                                            continue;
483                                        }
484                                    }
485                                    Len::None => {
486                                        if var_index >= vars.len() {
487                                            result.push_str("<MISSING_ARG>");
488                                        } else if let Some(err) = err_value_part(var_index) {
489                                            result.push_str(&err);
490                                            var_index += 1;
491                                            continue;
492                                        } else {
493                                            let v = &vars[var_index];
494                                            let b = if v.status == VariableStatus::ZeroLength as u8
495                                            {
496                                                &[][..]
497                                            } else {
498                                                v.data.as_slice()
499                                            };
500                                            render_bytes(b);
501                                            var_index += 1;
502                                            continue;
503                                        }
504                                    }
505                                }
506                            }
507                            'p' => {
508                                if var_index >= vars.len() {
509                                    result.push_str("<MISSING_ARG>");
510                                } else if let Some(err) = err_value_part(var_index) {
511                                    result.push_str(&err);
512                                    var_index += 1;
513                                    continue;
514                                } else {
515                                    let b = vars[var_index].data.as_slice();
516                                    if b.len() >= 8 {
517                                        let addr = u64::from_le_bytes([
518                                            b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
519                                        ]);
520                                        result.push_str(&format!("0x{addr:x}"));
521                                    } else {
522                                        result.push_str("<INVALID_POINTER>");
523                                    }
524                                    var_index += 1;
525                                    continue;
526                                }
527                            }
528                            _ => {
529                                // fallback to default formatting
530                                if var_index < vars.len() {
531                                    let v = &vars[var_index];
532                                    let s = Self::format_complex_variable_with_status(
533                                        v.var_name_index,
534                                        v.type_index,
535                                        &v.access_path,
536                                        &v.data,
537                                        v.status,
538                                        trace_context,
539                                    );
540                                    let value_part = s.split(" = ").last().unwrap_or(&s);
541                                    result.push_str(value_part);
542                                    var_index += 1;
543                                } else {
544                                    result.push_str("<MISSING_ARG>");
545                                }
546                            }
547                        }
548                    }
549                }
550                '}' => {
551                    if chars.peek() == Some(&'}') {
552                        chars.next();
553                        result.push('}');
554                    } else {
555                        result.push('}');
556                    }
557                }
558                _ => result.push(ch),
559            }
560        }
561        result
562    }
563
564    /// Format a complex variable with full DWARF type information
565    pub fn format_complex_variable(
566        var_name_index: u16,
567        type_index: u16,
568        access_path: &str,
569        data: &[u8],
570        trace_context: &TraceContext,
571    ) -> String {
572        let var_name = trace_context
573            .get_variable_name(var_name_index)
574            .unwrap_or("<INVALID_VAR_NAME>");
575
576        let type_info = match trace_context.get_type(type_index) {
577            Some(t) => t,
578            None => return format!("<INVALID_TYPE_INDEX_{type_index}>: {var_name}"),
579        };
580
581        let formatted_data = Self::format_data_with_type_info(data, type_info);
582
583        if access_path.is_empty() {
584            format!("{var_name} = {formatted_data}")
585        } else {
586            format!("{var_name}.{access_path} = {formatted_data}")
587        }
588    }
589
590    /// Status-aware complex variable formatting
591    pub fn format_complex_variable_with_status(
592        var_name_index: u16,
593        type_index: u16,
594        access_path: &str,
595        data: &[u8],
596        status: u8,
597        trace_context: &TraceContext,
598    ) -> String {
599        let var_name = trace_context
600            .get_variable_name(var_name_index)
601            .unwrap_or("<INVALID_VAR_NAME>");
602        let type_info = match trace_context.get_type(type_index) {
603            Some(t) => t,
604            None => return format!("<INVALID_TYPE_INDEX_{type_index}>: {var_name}"),
605        };
606
607        // OK path delegates to existing formatter
608        if status == VariableStatus::Ok as u8 {
609            return Self::format_complex_variable(
610                var_name_index,
611                type_index,
612                access_path,
613                data,
614                trace_context,
615            );
616        }
617
618        // Build error prefix based on status and optional payload (errno:i32 + addr:u64)
619        let (errno, addr) = if data.len() >= 12 {
620            let errno = i32::from_le_bytes([data[0], data[1], data[2], data[3]]);
621            let addr = u64::from_le_bytes([
622                data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
623            ]);
624            (Some(errno), Some(addr))
625        } else {
626            (None, None)
627        };
628
629        let type_suffix = type_info.type_name();
630        let err_text = match status {
631            s if s == VariableStatus::NullDeref as u8 => {
632                format!("<error: null pointer dereference> ({type_suffix}*)")
633            }
634            s if s == VariableStatus::ReadError as u8 => match (errno, addr) {
635                (Some(e), Some(a)) => {
636                    format!("<read_user failed errno={e} at 0x{a:x}> ({type_suffix}*)")
637                }
638                _ => format!("<read_user failed> ({type_suffix}*)"),
639            },
640            s if s == VariableStatus::AccessError as u8 => {
641                format!("<address compute failed> ({type_suffix}*)")
642            }
643            s if s == VariableStatus::OffsetsUnavailable as u8 => {
644                format!("<proc offsets unavailable> ({type_suffix}*)")
645            }
646            s if s == VariableStatus::Truncated as u8 => format!("<truncated> ({type_suffix}*)"),
647            s if s == VariableStatus::ZeroLength as u8 => format!("<len<=0> ({type_suffix})"),
648            _ => format!("<error status={status}> ({type_suffix}*)"),
649        };
650
651        if access_path.is_empty() {
652            format!("{var_name} = {err_text}")
653        } else {
654            format!("{var_name}.{access_path} = {err_text}")
655        }
656    }
657
658    /// Format data using full DWARF type information
659    pub fn format_data_with_type_info(data: &[u8], type_info: &TypeInfo) -> String {
660        // Relax display limits: increase max depth to print more nested content.
661        Self::format_data_with_type_info_impl(data, type_info, 0, 32)
662    }
663
664    /// Internal implementation with depth control for recursion
665    fn format_data_with_type_info_impl(
666        data: &[u8],
667        type_info: &TypeInfo,
668        current_depth: usize,
669        max_depth: usize,
670    ) -> String {
671        if current_depth > max_depth {
672            return "<MAX_DEPTH_EXCEEDED>".to_string();
673        }
674
675        match type_info {
676            TypeInfo::BaseType { size, encoding, .. } => {
677                Self::format_base_type_data(data, *size, *encoding)
678            }
679            TypeInfo::BitfieldType {
680                underlying_type,
681                bit_offset,
682                bit_size,
683            } => {
684                let u_size = underlying_type.size() as usize;
685                if data.len() < u_size || *bit_size == 0 {
686                    return "<INVALID_BITFIELD>".to_string();
687                }
688                let val =
689                    Self::extract_bits_le(&data[..u_size], *bit_offset as u32, *bit_size as u32);
690                Self::format_bitfield_value(val, underlying_type, *bit_size as u32)
691            }
692            TypeInfo::PointerType { target_type, .. } => {
693                if data.len() < 8 {
694                    "<INVALID_POINTER>".to_string()
695                } else {
696                    let addr = u64::from_le_bytes([
697                        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
698                    ]);
699                    let ty = format!("{}*", target_type.type_name());
700                    if addr == 0 {
701                        format!("NULL ({ty})")
702                    } else {
703                        format!("0x{addr:x} ({ty})")
704                    }
705                }
706            }
707            TypeInfo::ArrayType {
708                element_type,
709                element_count,
710                ..
711            } => {
712                // Special-case: char arrays -> print as string
713                if Self::is_char_byte_type(element_type) {
714                    return Self::format_char_array_as_string(data, element_count);
715                }
716                let elem_size = element_type.size() as usize;
717                if elem_size == 0 {
718                    return "<ZERO_SIZE_ELEMENT>".to_string();
719                }
720
721                let count = element_count.unwrap_or(data.len() as u64 / elem_size as u64);
722                let actual_count = std::cmp::min(count, data.len() as u64 / elem_size as u64);
723
724                if actual_count == 0 {
725                    return "[]".to_string();
726                }
727
728                let mut result = String::from("[");
729                for i in 0..actual_count {
730                    if i > 0 {
731                        result.push_str(", ");
732                    }
733
734                    let start = i as usize * elem_size;
735                    let end = std::cmp::min(start + elem_size, data.len());
736                    let elem_data = &data[start..end];
737
738                    let formatted_elem = Self::format_data_with_type_info_impl(
739                        elem_data,
740                        element_type,
741                        current_depth + 1,
742                        max_depth,
743                    );
744                    result.push_str(&formatted_elem);
745                }
746                result.push(']');
747                result
748            }
749            TypeInfo::StructType { name, members, .. } => {
750                // Allow deeper nested structures now; cutoff managed by max_depth param
751                if current_depth > max_depth {
752                    return format!("<STRUCT_{name}>");
753                }
754
755                let mut result = format!("{name} {{ ");
756                let mut first = true;
757
758                for member in members.iter() {
759                    if !first {
760                        result.push_str(", ");
761                    }
762                    first = false;
763
764                    let offset = member.offset as usize;
765                    // Prefer explicit BitfieldType on member_type, else use legacy member.bit_* fields
766                    if let TypeInfo::BitfieldType {
767                        underlying_type,
768                        bit_offset,
769                        bit_size,
770                    } = &member.member_type
771                    {
772                        let u_size = underlying_type.size() as usize;
773                        if offset + u_size <= data.len() && *bit_size > 0 && *bit_size <= 64 {
774                            let raw = &data[offset..offset + u_size];
775                            let val_u64 =
776                                Self::extract_bits_le(raw, *bit_offset as u32, *bit_size as u32);
777                            let formatted_value = Self::format_bitfield_value(
778                                val_u64,
779                                underlying_type,
780                                *bit_size as u32,
781                            );
782                            result.push_str(&format!("{}: {}", member.name, formatted_value));
783                        } else {
784                            result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
785                        }
786                    } else if let (Some(bit_size), maybe_bit_offset) =
787                        (member.bit_size, member.bit_offset)
788                    {
789                        // Handle bitfield member formatting (up to 64 bits)
790                        let bit_size = bit_size as u32;
791                        let bit_offset = maybe_bit_offset.unwrap_or(0) as u32;
792                        let bytes_needed = (bit_offset + bit_size).div_ceil(8) as usize;
793                        if offset + bytes_needed <= data.len() && bit_size > 0 && bit_size <= 64 {
794                            let raw = &data[offset..offset + bytes_needed];
795                            let val_u64 = Self::extract_bits_le(raw, bit_offset, bit_size);
796                            let formatted_value =
797                                Self::format_bitfield_value(val_u64, &member.member_type, bit_size);
798                            result.push_str(&format!("{}: {}", member.name, formatted_value));
799                        } else {
800                            result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
801                        }
802                    } else {
803                        let member_size = member.member_type.size() as usize;
804                        if offset + member_size <= data.len() {
805                            let member_data = &data[offset..offset + member_size];
806                            let formatted_value = Self::format_data_with_type_info_impl(
807                                member_data,
808                                &member.member_type,
809                                current_depth + 1,
810                                max_depth,
811                            );
812                            result.push_str(&format!("{}: {}", member.name, formatted_value));
813                        } else {
814                            result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
815                        }
816                    }
817                }
818
819                // No explicit elision; show all available members
820                result.push_str(" }");
821                result
822            }
823            TypeInfo::UnionType { name, members, .. } => {
824                if members.is_empty() {
825                    format!("union {name} {{}}")
826                } else {
827                    // For unions, show the first member interpretation
828                    let first_member = &members[0];
829                    let member_size = first_member.member_type.size() as usize;
830                    let member_data = if member_size <= data.len() {
831                        &data[..member_size]
832                    } else {
833                        data
834                    };
835
836                    let formatted_value = Self::format_data_with_type_info_impl(
837                        member_data,
838                        &first_member.member_type,
839                        current_depth + 1,
840                        max_depth,
841                    );
842                    format!(
843                        "union {} {{ {} = {} }}",
844                        name, first_member.name, formatted_value
845                    )
846                }
847            }
848            TypeInfo::EnumType {
849                name,
850                base_type,
851                variants,
852                ..
853            } => {
854                let base_value = Self::format_data_with_type_info_impl(
855                    data,
856                    base_type,
857                    current_depth + 1,
858                    max_depth,
859                );
860
861                // Try to find matching enum variant and print both type::variant and numeric value
862                if let Ok(int_val) = base_value.parse::<i64>() {
863                    for variant in variants {
864                        if variant.value == int_val {
865                            return format!("{}::{}({})", name, variant.name, base_value);
866                        }
867                    }
868                }
869
870                // No variant matched; still print type name with raw value
871                format!("{name}({base_value})")
872            }
873            TypeInfo::TypedefType {
874                name,
875                underlying_type,
876                ..
877            } => {
878                // Reuse aggregate formatters by substituting display name
879                match &**underlying_type {
880                    TypeInfo::StructType { size, members, .. } => {
881                        let alias_struct = TypeInfo::StructType {
882                            name: name.clone(),
883                            size: *size,
884                            members: members.clone(),
885                        };
886                        Self::format_data_with_type_info_impl(
887                            data,
888                            &alias_struct,
889                            current_depth,
890                            max_depth,
891                        )
892                    }
893                    TypeInfo::UnionType { size, members, .. } => {
894                        let alias_union = TypeInfo::UnionType {
895                            name: name.clone(),
896                            size: *size,
897                            members: members.clone(),
898                        };
899                        Self::format_data_with_type_info_impl(
900                            data,
901                            &alias_union,
902                            current_depth,
903                            max_depth,
904                        )
905                    }
906                    _ => {
907                        let underlying_formatted = Self::format_data_with_type_info_impl(
908                            data,
909                            underlying_type,
910                            current_depth,
911                            max_depth,
912                        );
913                        format!("{name}({underlying_formatted})")
914                    }
915                }
916            }
917            TypeInfo::QualifiedType {
918                underlying_type, ..
919            } => Self::format_data_with_type_info_impl(
920                data,
921                underlying_type,
922                current_depth,
923                max_depth,
924            ),
925            TypeInfo::FunctionType { .. } => {
926                if data.len() >= 8 {
927                    let addr = u64::from_le_bytes([
928                        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
929                    ]);
930                    format!("<FUNCTION@0x{addr:x}>")
931                } else {
932                    "<INVALID_FUNCTION_POINTER>".to_string()
933                }
934            }
935            TypeInfo::UnknownType { name } => {
936                format!("<UNKNOWN_TYPE_{name}_{}_BYTES>", data.len())
937            }
938            TypeInfo::OptimizedOut { .. } => "<optimized out>".to_string(),
939        }
940    }
941
942    /// Format base type data using DWARF encoding information
943    fn format_base_type_data(data: &[u8], size: u64, encoding: u16) -> String {
944        if encoding == gimli::constants::DW_ATE_boolean.0 as u16 {
945            if data.is_empty() {
946                "<EMPTY_BOOL>".to_string()
947            } else {
948                (data[0] != 0).to_string()
949            }
950        } else if encoding == gimli::constants::DW_ATE_float.0 as u16 {
951            match size {
952                4 => {
953                    if data.len() >= 4 {
954                        let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
955                        f32::from_le_bytes(bytes).to_string()
956                    } else {
957                        "<INVALID_F32>".to_string()
958                    }
959                }
960                8 => {
961                    if data.len() >= 8 {
962                        let bytes: [u8; 8] = [
963                            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
964                        ];
965                        f64::from_le_bytes(bytes).to_string()
966                    } else {
967                        "<INVALID_F64>".to_string()
968                    }
969                }
970                _ => format!("<UNSUPPORTED_FLOAT_SIZE_{size}>"),
971            }
972        } else if encoding == gimli::constants::DW_ATE_signed.0 as u16
973            || encoding == gimli::constants::DW_ATE_signed_char.0 as u16
974        {
975            match size {
976                1 => {
977                    if !data.is_empty() {
978                        (data[0] as i8).to_string()
979                    } else {
980                        "<EMPTY_I8>".to_string()
981                    }
982                }
983                2 => {
984                    if data.len() >= 2 {
985                        let bytes: [u8; 2] = [data[0], data[1]];
986                        i16::from_le_bytes(bytes).to_string()
987                    } else {
988                        "<INVALID_I16>".to_string()
989                    }
990                }
991                4 => {
992                    if data.len() >= 4 {
993                        let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
994                        i32::from_le_bytes(bytes).to_string()
995                    } else {
996                        "<INVALID_I32>".to_string()
997                    }
998                }
999                8 => {
1000                    if data.len() >= 8 {
1001                        let bytes: [u8; 8] = [
1002                            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1003                        ];
1004                        i64::from_le_bytes(bytes).to_string()
1005                    } else {
1006                        "<INVALID_I64>".to_string()
1007                    }
1008                }
1009                _ => format!("<UNSUPPORTED_SIGNED_SIZE_{size}>"),
1010            }
1011        } else if encoding == gimli::constants::DW_ATE_unsigned.0 as u16
1012            || encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16
1013        {
1014            match size {
1015                1 => {
1016                    if !data.is_empty() {
1017                        data[0].to_string()
1018                    } else {
1019                        "<EMPTY_U8>".to_string()
1020                    }
1021                }
1022                2 => {
1023                    if data.len() >= 2 {
1024                        let bytes: [u8; 2] = [data[0], data[1]];
1025                        u16::from_le_bytes(bytes).to_string()
1026                    } else {
1027                        "<INVALID_U16>".to_string()
1028                    }
1029                }
1030                4 => {
1031                    if data.len() >= 4 {
1032                        let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
1033                        u32::from_le_bytes(bytes).to_string()
1034                    } else {
1035                        "<INVALID_U32>".to_string()
1036                    }
1037                }
1038                8 => {
1039                    if data.len() >= 8 {
1040                        let bytes: [u8; 8] = [
1041                            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1042                        ];
1043                        u64::from_le_bytes(bytes).to_string()
1044                    } else {
1045                        "<INVALID_U64>".to_string()
1046                    }
1047                }
1048                _ => format!("<UNSUPPORTED_UNSIGNED_SIZE_{size}>"),
1049            }
1050        } else {
1051            // Handle char-like 1-byte integers as characters (signed or unsigned)
1052            if (encoding == gimli::constants::DW_ATE_signed_char.0 as u16
1053                || encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16)
1054                && size == 1
1055            {
1056                if !data.is_empty() {
1057                    if data[0] >= 32 && data[0] <= 126 {
1058                        format!("'{}'", data[0] as char)
1059                    } else {
1060                        format!("'\\x{:02x}'", data[0])
1061                    }
1062                } else {
1063                    "<EMPTY_CHAR>".to_string()
1064                }
1065            } else {
1066                // Fallback for unknown encodings
1067                format!("<UNKNOWN_ENCODING_{encoding}_SIZE_{size}_BYTES>")
1068            }
1069        }
1070    }
1071
1072    /// Determine if a type is a single-byte character type (signed/unsigned char)
1073    fn is_char_byte_type(t: &TypeInfo) -> bool {
1074        match t {
1075            TypeInfo::BaseType { size, encoding, .. } => {
1076                *size == 1
1077                    && (*encoding == gimli::constants::DW_ATE_signed_char.0 as u16
1078                        || *encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16
1079                        || *encoding == gimli::constants::DW_ATE_unsigned.0 as u16
1080                        || *encoding == gimli::constants::DW_ATE_signed.0 as u16)
1081            }
1082            TypeInfo::TypedefType {
1083                underlying_type, ..
1084            }
1085            | TypeInfo::QualifiedType {
1086                underlying_type, ..
1087            } => Self::is_char_byte_type(underlying_type),
1088            _ => false,
1089        }
1090    }
1091
1092    /// Format a char array as a UTF-8-ish escaped string (best-effort)
1093    fn format_char_array_as_string(data: &[u8], element_count: &Option<u64>) -> String {
1094        let max_len = element_count.map(|c| c as usize).unwrap_or(data.len());
1095        let mut s = String::new();
1096        s.push('"');
1097        let mut i = 0usize;
1098        while i < data.len() && i < max_len {
1099            let b = data[i];
1100            if b == 0 {
1101                break; // C-string termination
1102            }
1103            match b {
1104                b'"' => s.push_str("\\\""),
1105                b'\\' => s.push_str("\\\\"),
1106                0x20..=0x7E => s.push(b as char),
1107                _ => s.push_str(&format!("\\x{b:02x}")),
1108            }
1109            i += 1;
1110        }
1111        s.push('"');
1112        s
1113    }
1114
1115    /// Extract bits from a little-endian byte slice, starting at bit_offset, with length bit_size (<=64)
1116    fn extract_bits_le(raw: &[u8], bit_offset: u32, bit_size: u32) -> u64 {
1117        // Assemble up to 8 bytes into a u64 (little-endian)
1118        let mut word: u64 = 0;
1119        let take = std::cmp::min(8, raw.len());
1120        for (i, byte) in raw.iter().take(take).enumerate() {
1121            word |= (*byte as u64) << (8 * i);
1122        }
1123        let shifted = word >> bit_offset;
1124        let mask: u64 = if bit_size == 64 {
1125            u64::MAX
1126        } else {
1127            (1u64 << bit_size) - 1
1128        };
1129        shifted & mask
1130    }
1131
1132    /// Format bitfield value according to the member's TypeInfo (basic support)
1133    fn format_bitfield_value(val: u64, ty: &TypeInfo, bit_size: u32) -> String {
1134        // Bool by encoding
1135        if let TypeInfo::BaseType { encoding, .. } = ty {
1136            if *encoding == gimli::constants::DW_ATE_boolean.0 as u16 {
1137                return if val != 0 {
1138                    "true".to_string()
1139                } else {
1140                    "false".to_string()
1141                };
1142            }
1143        }
1144
1145        // Enum mapping
1146        if let TypeInfo::EnumType { variants, .. } = ty {
1147            let sval = val as i64; // interpret as non-negative; signed variants must match exact value
1148            for v in variants {
1149                if v.value == sval {
1150                    return v.name.clone();
1151                }
1152            }
1153        }
1154
1155        // Signed extension if base type is signed
1156        let is_signed = ty.is_signed_int();
1157        if is_signed && bit_size > 0 && bit_size <= 64 {
1158            let sign_bit = 1u64 << (bit_size - 1);
1159            let signed_val: i64 = if (val & sign_bit) != 0 {
1160                // negative value, sign-extend
1161                let ext_mask = (!0u64) << bit_size;
1162                (val | ext_mask) as i64
1163            } else {
1164                val as i64
1165            };
1166            return signed_val.to_string();
1167        }
1168
1169        // Default: unsigned decimal
1170        val.to_string()
1171    }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176    use super::*;
1177
1178    #[test]
1179    fn test_apply_format_basic() {
1180        let fmt = "pid: {}, name: {}";
1181        let rendered: Vec<String> = vec!["42".to_string(), "hello".to_string()];
1182        let result = FormatPrinter::apply_format_strings(fmt, &rendered);
1183        assert_eq!(result, "pid: 42, name: hello");
1184    }
1185
1186    #[test]
1187    fn test_apply_format_escape_sequences() {
1188        let rendered: Vec<String> = vec!["123".to_string()];
1189        let result =
1190            FormatPrinter::apply_format_strings("use {{}} for braces, value: {}", &rendered);
1191        assert_eq!(result, "use {} for braces, value: 123");
1192    }
1193
1194    #[test]
1195    fn test_missing_arguments() {
1196        let result = FormatPrinter::apply_format_strings("need arg: {}", &[]);
1197        assert_eq!(result, "need arg: <MISSING_ARG>");
1198    }
1199
1200    #[test]
1201    fn test_format_print_data_with_trace_context() {
1202        let mut trace_context = TraceContext::new();
1203        let format_index = trace_context.add_string("Hello {}, you are {} years old!".to_string());
1204        let rendered: Vec<String> = vec!["Alice".to_string(), "25".to_string()];
1205        let fmt = trace_context.get_string(format_index).unwrap();
1206        let result = FormatPrinter::apply_format_strings(fmt, &rendered);
1207        assert_eq!(result, "Hello Alice, you are 25 years old!");
1208    }
1209
1210    #[test]
1211    fn test_format_complex_variable_struct() {
1212        use crate::type_info::{StructMember, TypeInfo};
1213
1214        let mut trace_context = TraceContext::new();
1215        let var_name_idx = trace_context.add_variable_name("person".to_string());
1216
1217        let person_type = TypeInfo::StructType {
1218            name: "Person".to_string(),
1219            size: 36,
1220            members: vec![
1221                StructMember {
1222                    name: "age".to_string(),
1223                    member_type: TypeInfo::BaseType {
1224                        name: "int".to_string(),
1225                        size: 4,
1226                        encoding: gimli::constants::DW_ATE_signed.0 as u16,
1227                    },
1228                    offset: 0,
1229                    bit_offset: None,
1230                    bit_size: None,
1231                },
1232                StructMember {
1233                    name: "id".to_string(),
1234                    member_type: TypeInfo::BaseType {
1235                        name: "long".to_string(),
1236                        size: 8,
1237                        encoding: gimli::constants::DW_ATE_signed.0 as u16,
1238                    },
1239                    offset: 4,
1240                    bit_offset: None,
1241                    bit_size: None,
1242                },
1243            ],
1244        };
1245
1246        let type_idx = trace_context.add_type(person_type);
1247
1248        // Data: age=25 (4 bytes) + id=12345 (8 bytes)
1249        let data = vec![
1250            25, 0, 0, 0, // age = 25
1251            57, 48, 0, 0, 0, 0, 0, 0, // id = 12345
1252        ];
1253
1254        let result = FormatPrinter::format_complex_variable(
1255            var_name_idx,
1256            type_idx,
1257            "",
1258            &data,
1259            &trace_context,
1260        );
1261
1262        assert!(result.contains("person = Person"));
1263        assert!(result.contains("age: 25"));
1264        assert!(result.contains("id: 12345"));
1265    }
1266
1267    #[test]
1268    fn test_complex_format_char_array() {
1269        use crate::type_info::TypeInfo;
1270
1271        let mut trace_context = TraceContext::new();
1272        let var_name_idx = trace_context.add_variable_name("name".to_string());
1273        // Define char array type: char name[16]
1274        let char_type = TypeInfo::BaseType {
1275            name: "char".to_string(),
1276            size: 1,
1277            encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1278        };
1279        let arr_type = TypeInfo::ArrayType {
1280            element_type: Box::new(char_type),
1281            element_count: Some(16),
1282            total_size: Some(16),
1283        };
1284        let type_idx = trace_context.add_type(arr_type);
1285
1286        // Data buffer with "Alice\0" and padding
1287        let mut data = b"Alice\0".to_vec();
1288        data.resize(16, 0u8);
1289
1290        let fmt_idx = trace_context.add_string("{}".to_string());
1291        let complex_vars = vec![ParsedComplexVariable {
1292            var_name_index: var_name_idx,
1293            type_index: type_idx,
1294            access_path: String::new(),
1295            status: 0,
1296            data,
1297        }];
1298
1299        let result =
1300            FormatPrinter::format_complex_print_data(fmt_idx, &complex_vars, &trace_context);
1301        assert_eq!(result, "\"Alice\"");
1302    }
1303
1304    #[test]
1305    fn test_format_data_with_type_info_array() {
1306        let array_type = TypeInfo::ArrayType {
1307            element_type: Box::new(TypeInfo::BaseType {
1308                name: "int".to_string(),
1309                size: 4,
1310                encoding: gimli::constants::DW_ATE_signed.0 as u16,
1311            }),
1312            element_count: Some(3),
1313            total_size: Some(12),
1314        };
1315
1316        let data = vec![
1317            1, 0, 0, 0, // 1
1318            2, 0, 0, 0, // 2
1319            3, 0, 0, 0, // 3
1320        ];
1321
1322        let result = FormatPrinter::format_data_with_type_info(&data, &array_type);
1323        assert_eq!(result, "[1, 2, 3]");
1324    }
1325
1326    #[test]
1327    fn test_bitfield_value_signed_and_unsigned() {
1328        use crate::type_info::TypeInfo;
1329
1330        // Unsigned 3-bit at bit 0 from a u32 container
1331        let u32_type = TypeInfo::BaseType {
1332            name: "unsigned int".to_string(),
1333            size: 4,
1334            encoding: gimli::constants::DW_ATE_unsigned.0 as u16,
1335        };
1336        let bf_unsigned = TypeInfo::BitfieldType {
1337            underlying_type: Box::new(u32_type.clone()),
1338            bit_offset: 0,
1339            bit_size: 3,
1340        };
1341        let data = [0b0000_0101u8, 0, 0, 0]; // value = 5
1342        let res = FormatPrinter::format_data_with_type_info(&data, &bf_unsigned);
1343        assert_eq!(res, "5");
1344
1345        // Signed 3-bit at bit 0 from an i32 container (0b111 -> -1)
1346        let i32_type = TypeInfo::BaseType {
1347            name: "int".to_string(),
1348            size: 4,
1349            encoding: gimli::constants::DW_ATE_signed.0 as u16,
1350        };
1351        let bf_signed = TypeInfo::BitfieldType {
1352            underlying_type: Box::new(i32_type),
1353            bit_offset: 0,
1354            bit_size: 3,
1355        };
1356        let data_neg1 = [0b0000_0111u8, 0, 0, 0];
1357        let res2 = FormatPrinter::format_data_with_type_info(&data_neg1, &bf_signed);
1358        assert_eq!(res2, "-1");
1359
1360        // Boolean 1-bit at bit 0 from a bool underlying type
1361        let bool_type = TypeInfo::BaseType {
1362            name: "bool".to_string(),
1363            size: 1,
1364            encoding: gimli::constants::DW_ATE_boolean.0 as u16,
1365        };
1366        let bf_bool = TypeInfo::BitfieldType {
1367            underlying_type: Box::new(bool_type),
1368            bit_offset: 0,
1369            bit_size: 1,
1370        };
1371        let data_true = [0x01u8];
1372        let res3 = FormatPrinter::format_data_with_type_info(&data_true, &bf_bool);
1373        assert_eq!(res3, "true");
1374    }
1375
1376    #[test]
1377    fn test_struct_with_bitfields() {
1378        use crate::type_info::{StructMember, TypeInfo};
1379
1380        // Define a struct S with two bitfields in a 32-bit storage at offset 0
1381        let u32_type = TypeInfo::BaseType {
1382            name: "unsigned int".to_string(),
1383            size: 4,
1384            encoding: gimli::constants::DW_ATE_unsigned.0 as u16,
1385        };
1386
1387        let s_type = TypeInfo::StructType {
1388            name: "S".to_string(),
1389            size: 4,
1390            members: vec![
1391                StructMember {
1392                    name: "active".to_string(),
1393                    member_type: TypeInfo::BitfieldType {
1394                        underlying_type: Box::new(u32_type.clone()),
1395                        bit_offset: 0,
1396                        bit_size: 1,
1397                    },
1398                    offset: 0,
1399                    bit_offset: Some(0),
1400                    bit_size: Some(1),
1401                },
1402                StructMember {
1403                    name: "flags".to_string(),
1404                    member_type: TypeInfo::BitfieldType {
1405                        underlying_type: Box::new(u32_type.clone()),
1406                        bit_offset: 1,
1407                        bit_size: 3,
1408                    },
1409                    offset: 0,
1410                    bit_offset: Some(1),
1411                    bit_size: Some(3),
1412                },
1413            ],
1414        };
1415
1416        // Value layout: bit0=1 (active), bits1..3=0b011 (flags=3)
1417        let data = [0b0000_0111u8, 0, 0, 0];
1418        let res = FormatPrinter::format_data_with_type_info(&data, &s_type);
1419        assert!(res.contains("S {"));
1420        assert!(res.contains("active: 1"));
1421        assert!(res.contains("flags: 3"));
1422    }
1423
1424    #[test]
1425    fn test_ext_hex_preserves_null_deref_error() {
1426        let mut trace_context = TraceContext::new();
1427        let fmt_idx = trace_context.add_string("{:x.16}".to_string());
1428
1429        // Array<u8,16> as the value type
1430        let arr_type = TypeInfo::ArrayType {
1431            element_type: Box::new(TypeInfo::BaseType {
1432                name: "u8".to_string(),
1433                size: 1,
1434                encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1435            }),
1436            element_count: Some(16),
1437            total_size: Some(16),
1438        };
1439        let type_idx = trace_context.add_type(arr_type);
1440        let var_name_idx = trace_context.add_variable_name("buf".to_string());
1441
1442        let vars = vec![ParsedComplexVariable {
1443            var_name_index: var_name_idx,
1444            type_index: type_idx,
1445            access_path: String::new(),
1446            status: VariableStatus::NullDeref as u8,
1447            data: vec![],
1448        }];
1449
1450        let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1451        assert!(
1452            out.contains("null pointer dereference"),
1453            "unexpected output: {out}"
1454        );
1455        assert!(
1456            !out.contains("<MISSING_ARG>"),
1457            "should not hide error: {out}"
1458        );
1459    }
1460
1461    #[test]
1462    fn test_ext_s_preserves_read_error_errno_addr() {
1463        let mut trace_context = TraceContext::new();
1464        let fmt_idx = trace_context.add_string("{:s.16}".to_string());
1465
1466        // Array<u8,16>
1467        let arr_type = TypeInfo::ArrayType {
1468            element_type: Box::new(TypeInfo::BaseType {
1469                name: "u8".to_string(),
1470                size: 1,
1471                encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1472            }),
1473            element_count: Some(16),
1474            total_size: Some(16),
1475        };
1476        let type_idx = trace_context.add_type(arr_type);
1477        let var_name_idx = trace_context.add_variable_name("buf".to_string());
1478
1479        // Encode errno:i32 + addr:u64 into data
1480        let errno: i32 = -14; // EFAULT-like
1481        let addr: u64 = 0x1234_5678_9abc_def0;
1482        let mut data = Vec::new();
1483        data.extend_from_slice(&errno.to_le_bytes());
1484        data.extend_from_slice(&addr.to_le_bytes());
1485
1486        let vars = vec![ParsedComplexVariable {
1487            var_name_index: var_name_idx,
1488            type_index: type_idx,
1489            access_path: String::new(),
1490            status: VariableStatus::ReadError as u8,
1491            data,
1492        }];
1493
1494        let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1495        assert!(
1496            out.contains("read_user failed errno=-14"),
1497            "unexpected: {out}"
1498        );
1499        assert!(out.contains("0x123456789abcdef0"), "missing addr: {out}");
1500    }
1501
1502    #[test]
1503    fn test_ext_p_preserves_offsets_unavailable() {
1504        let mut trace_context = TraceContext::new();
1505        let fmt_idx = trace_context.add_string("P={:p}".to_string());
1506
1507        let ptr_type = TypeInfo::PointerType {
1508            target_type: Box::new(TypeInfo::BaseType {
1509                name: "u8".to_string(),
1510                size: 1,
1511                encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1512            }),
1513            size: 8,
1514        };
1515        let type_idx = trace_context.add_type(ptr_type);
1516        let var_name_idx = trace_context.add_variable_name("ptr".to_string());
1517
1518        let vars = vec![ParsedComplexVariable {
1519            var_name_index: var_name_idx,
1520            type_index: type_idx,
1521            access_path: String::new(),
1522            status: VariableStatus::OffsetsUnavailable as u8,
1523            data: vec![],
1524        }];
1525
1526        let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1527        assert!(out.starts_with("P="), "prefix lost: {out}");
1528        assert!(
1529            out.contains("proc offsets unavailable"),
1530            "unexpected: {out}"
1531        );
1532    }
1533
1534    #[test]
1535    fn test_ext_star_len_error_precedence() {
1536        let mut trace_context = TraceContext::new();
1537        let fmt_idx = trace_context.add_string("S={:x.*}".to_string());
1538
1539        // length argument (will surface its error), use base type for simplicity
1540        let len_type = TypeInfo::BaseType {
1541            name: "i64".to_string(),
1542            size: 8,
1543            encoding: gimli::constants::DW_ATE_signed.0 as u16,
1544        };
1545        let len_ty_idx = trace_context.add_type(len_type);
1546        let len_name_idx = trace_context.add_variable_name("len".to_string());
1547
1548        // value argument (OK)
1549        let arr_type = TypeInfo::ArrayType {
1550            element_type: Box::new(TypeInfo::BaseType {
1551                name: "u8".to_string(),
1552                size: 1,
1553                encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1554            }),
1555            element_count: Some(16),
1556            total_size: Some(16),
1557        };
1558        let val_ty_idx = trace_context.add_type(arr_type);
1559        let val_name_idx = trace_context.add_variable_name("buf".to_string());
1560        let val_data: Vec<u8> = (0u8..16).collect();
1561
1562        let vars = vec![
1563            ParsedComplexVariable {
1564                var_name_index: len_name_idx,
1565                type_index: len_ty_idx,
1566                access_path: String::new(),
1567                status: VariableStatus::NullDeref as u8,
1568                data: vec![],
1569            },
1570            ParsedComplexVariable {
1571                var_name_index: val_name_idx,
1572                type_index: val_ty_idx,
1573                access_path: String::new(),
1574                status: VariableStatus::Ok as u8,
1575                data: val_data,
1576            },
1577        ];
1578
1579        let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1580        assert!(out.starts_with("S="), "prefix lost: {out}");
1581        assert!(
1582            out.contains("null pointer"),
1583            "should surface len arg error: {out}"
1584        );
1585        // should not print hex bytes when length errored out
1586        assert!(
1587            !out.contains("00 01 02 03"),
1588            "should not render bytes: {out}"
1589        );
1590    }
1591}