Skip to content

Commit 745f929

Browse files
authored
Merge pull request rust-lang#5229 from moabo3li/locals-projected-names-and-types-handling
[Priroda] Render projected locals with source-shaped values
2 parents e80409a + 77b975c commit 745f929

16 files changed

Lines changed: 493 additions & 43 deletions

src/tools/miri/priroda/src/main.rs

Lines changed: 178 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ use std::path::PathBuf;
2323

2424
use miri::Immediate::Uninit;
2525
use miri::{interpret, *};
26-
use rustc_abi::Size;
26+
use rustc_abi::{FIRST_VARIANT, FieldIdx, Size};
2727
use rustc_driver::Compilation;
2828
use rustc_hir::attrs::CrateType;
29+
use rustc_hir::def::CtorKind;
2930
use rustc_interface::interface;
3031
use rustc_middle::mir::interpret::AllocId;
3132
use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment};
32-
use rustc_middle::ty::{TyCtxt, TyKind};
33+
use rustc_middle::ty::{self, TyCtxt, TyKind};
3334
use rustc_session::EarlyDiagCtxt;
3435
use rustc_session::config::ErrorOutputType;
3536
use rustc_span::source_map::SourceMap;
@@ -505,6 +506,179 @@ impl<'tcx> PrirodaContext<'tcx> {
505506
interp_ok(format!("[{}]", rendered.join(" ")))
506507
}
507508

509+
/// Render an evaluated operand using Rust-source-shaped containers with raw leaves.
510+
///
511+
/// The operand is produced from live interpreter state, usually via `local_to_op`
512+
/// for a whole MIR local or `eval_place_to_op` for a projected debug-info place.
513+
///
514+
/// This intentionally does not call user `Debug` / `Display`, and it does not
515+
/// try to make every scalar leaf pretty yet. Unsupported cases and leaf values
516+
/// fall back to `render_op`, preserving the old raw byte/provenance renderer.
517+
///
518+
/// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers,
519+
/// chars, raw pointers/references) once the source-shaped container output is
520+
/// stable enough to stop depending on byte dumps for every field.
521+
///
522+
/// FIXME: decide how much dereferencing belongs in this renderer. References
523+
/// currently stay as raw pointer leaves; following them may belong in the
524+
/// existing `follow` command instead of automatic local rendering.
525+
fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String {
526+
self.render_source_shaped_op_inner(op, 0)
527+
}
528+
529+
/// Recursive worker for `render_source_shaped_op`.
530+
///
531+
/// The depth limit keeps cyclic/reference-heavy values from making debugger
532+
/// output explode once more container kinds are added. At the limit, the raw
533+
/// renderer remains the ground truth.
534+
///
535+
/// FIXME: replace this fixed recursion limit with a value-size/output-budget
536+
/// policy so large acyclic values and deeply nested values degrade more
537+
/// predictably.
538+
fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String {
539+
const MAX_SOURCE_SHAPE_DEPTH: usize = 8;
540+
541+
if depth >= MAX_SOURCE_SHAPE_DEPTH {
542+
return self.render_op(op);
543+
}
544+
545+
match op.layout.ty.kind() {
546+
// Empty enums have no active variant to format. Unions do not record
547+
// which field is currently active, so choosing one would be misleading.
548+
//
549+
// FIXME: support unions only with an explicit user-selected field or
550+
// another source of active-field information. Guessing from layout
551+
// bytes would make debugger output look more certain than it is.
552+
ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op),
553+
554+
ty::Adt(def, _) => {
555+
// Enums need their runtime discriminant and a downcasted layout
556+
// view before fields can be projected. Structs use their sole
557+
// variant directly. Keep the display name tied to the same choice.
558+
let (variant_idx, down, name) = if def.is_enum() {
559+
let variant_idx = match self.ecx.read_discriminant(&op).discard_err() {
560+
Some(variant_idx) => variant_idx,
561+
// FIXME: expose this as an explicit render error when
562+
// Priroda grows structured value states. Falling back to
563+
// bytes keeps today's UI usable but hides why the enum
564+
// could not be source-shaped.
565+
None => return self.render_op(op),
566+
};
567+
let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() {
568+
Some(down) => down,
569+
// FIXME: distinguish invalid/uninitialized discriminants
570+
// from projection bugs in the rendered output once locals
571+
// can carry structured diagnostics.
572+
None => return self.render_op(op),
573+
};
574+
let variant_def = &def.variants()[variant_idx];
575+
(
576+
variant_idx,
577+
down,
578+
format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name),
579+
)
580+
} else {
581+
let variant_idx = FIRST_VARIANT;
582+
let variant_def = &def.variants()[variant_idx];
583+
(variant_idx, op.clone(), variant_def.name.to_string())
584+
};
585+
586+
let variant_def = &def.variants()[variant_idx];
587+
588+
let mut fields = Vec::with_capacity(variant_def.fields.len());
589+
for i in 0..variant_def.fields.len() {
590+
let field_idx = FieldIdx::from_usize(i);
591+
// `project_field` avoids manual offset math and works for both
592+
// immediate and memory-backed operands through `Projectable`.
593+
let field_op = match self.ecx.project_field(&down, field_idx).discard_err() {
594+
Some(field_op) => field_op,
595+
// FIXME: preserve the successfully rendered fields and
596+
// mark only this field as unavailable once the value model
597+
// can represent partial render failures.
598+
None => return self.render_op(op),
599+
};
600+
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1));
601+
}
602+
603+
// Match Rust constructor spelling:
604+
// - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit`
605+
// - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()`
606+
// - `None`: braced structs/variants, including the empty `{}` case
607+
match variant_def.ctor_kind() {
608+
Some(CtorKind::Const) => name,
609+
Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")),
610+
None if fields.is_empty() => format!("{name} {{}}"),
611+
None => {
612+
let fields = variant_def
613+
.fields
614+
.iter()
615+
.zip(fields)
616+
.map(|(field_def, value)| format!("{}: {value}", field_def.name))
617+
.collect::<Vec<_>>()
618+
.join(", ");
619+
format!("{name} {{ {fields} }}")
620+
}
621+
}
622+
}
623+
624+
ty::Tuple(args) => {
625+
let mut fields = Vec::with_capacity(args.len());
626+
for i in 0..args.len() {
627+
// Tuples have no field names in source, so preserve their
628+
// source field order and render children positionally.
629+
let field_op =
630+
match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() {
631+
Some(field_op) => field_op,
632+
// FIXME: render tuple fields independently so one
633+
// projection failure does not throw away the whole
634+
// source-shaped tuple.
635+
None => return self.render_op(op),
636+
};
637+
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1));
638+
}
639+
640+
if fields.len() == 1 {
641+
format!("({},)", fields[0])
642+
} else {
643+
format!("({})", fields.join(", "))
644+
}
645+
}
646+
647+
ty::Array(_, _) | ty::Slice(_) => {
648+
// `project_array_fields` uses the dynamic length for slices. That
649+
// avoids the classic mistake of treating slice layout as a fixed
650+
// zero-length array.
651+
let mut iter = match self.ecx.project_array_fields(&op).discard_err() {
652+
Some(iter) => iter,
653+
// FIXME: when slice metadata is invalid, show that as a slice
654+
// length problem instead of silently falling back to raw bytes.
655+
None => return self.render_op(op),
656+
};
657+
658+
let mut fields = Vec::new();
659+
// FIXME: add an output budget/truncation policy before rendering
660+
// very large arrays or slices in full.
661+
loop {
662+
match iter.next(&self.ecx).discard_err() {
663+
Some(Some((_idx, field_op))) =>
664+
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)),
665+
Some(None) => break,
666+
// FIXME: keep already-rendered elements and mark the
667+
// failed index once partial render errors are supported.
668+
None => return self.render_op(op),
669+
}
670+
}
671+
672+
format!("[{}]", fields.join(", "))
673+
}
674+
675+
// FIXME: consider source-shaped special cases for strings, closures,
676+
// generators/coroutines, trait objects, and SIMD/vector-like types.
677+
// Until then these stay on the raw renderer path.
678+
_ => self.render_op(op),
679+
}
680+
}
681+
508682
/// Render an evaluated operand using the same raw representation for
509683
/// whole locals and projected MIR places.
510684
fn render_op(&self, op: OpTy<'tcx>) -> String {
@@ -613,7 +787,7 @@ impl<'tcx> PrirodaContext<'tcx> {
613787
.ecx
614788
.local_to_op(local, None)
615789
.expect("this error can only occur in CTFE on generic code");
616-
local_desc.value = self.render_op(op);
790+
local_desc.value = self.render_source_shaped_op(op);
617791
}
618792
};
619793

@@ -682,7 +856,7 @@ impl<'tcx> PrirodaContext<'tcx> {
682856
let value = self
683857
.ecx
684858
.eval_place_to_op(*place, None)
685-
.map(|op| self.render_op(op))
859+
.map(|op| self.render_source_shaped_op(op))
686860
.unwrap_or_else(|err| {
687861
format!("<error: {}>", interpret::format_interp_error(err))
688862
});

src/tools/miri/priroda/tests/ui/locals_access_field.stdout

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
(priroda) Hit breakpoint
33
{MANIFEST_DIR}/tests/ui/locals_access_field.rs:14
44
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
5-
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
5+
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: ExtraSlice { _slice: [{ALLOC_PTR} 00 00 00 00 00 00 00 00], _extra: [00 00 00 00] }
66
Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
77
Name: _extra, Id: _3, Ty: u32, Value: 0_u32
88
(priroda) Id: _0, Ty: (), Value: <uninit>
9-
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
9+
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: ExtraSlice { _slice: [{ALLOC_PTR} 00 00 00 00 00 00 00 00], _extra: [00 00 00 00] }
1010
(priroda) Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
1111
(priroda) Id: _3, Ty: u32, Value: 0_u32
1212
(priroda) no local for this id

src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@
77
(priroda) Hit breakpoint
88
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:31
99
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
10-
Name: <none>, Id: _1, Ty: (u32, i32), Value: (0x00000005, 0x00000006): (u32, i32)
10+
Name: <none>, Id: _1, Ty: (u32, i32), Value: (5_u32, 6_i32)
1111
Name: first, Id: _1.0, Ty: u32, Value: 5_u32
1212
Name: second, Id: _1.1, Ty: i32, Value: 6_i32
1313
(priroda) Hit breakpoint
1414
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:45
1515
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
16-
Name: <none>, Id: _1, Ty: S, Value: {transmute(0x40a00000): S}
16+
Name: <none>, Id: _1, Ty: S, Value: S { x: 5f32 }
1717
Name: x, Id: _1.0, Ty: f32, Value: 5f32
1818
(priroda) Hit breakpoint
1919
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55
2020
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
21-
Name: <none>, Id: _1, Ty: std::option::Option<i32>, Value: [01 00 00 00 05 00 00 00]
21+
Name: <none>, Id: _1, Ty: std::option::Option<i32>, Value: Option::Some([05 00 00 00])
2222
Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: [05 00 00 00]
2323
(priroda) Hit breakpoint
2424
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66
2525
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
26-
Name: <none>, Id: _1, Ty: std::option::Option<&i32>, Value: [{ALLOC_PTR}]
26+
Name: <none>, Id: _1, Ty: std::option::Option<&i32>, Value: Option::Some([{ALLOC_PTR}])
2727
Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: [{ALLOC_PTR}]
2828
Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: [05 00 00 00]
2929
(priroda) Allocation alloc2+0: [05 00 00 00]

src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
(priroda) Hit breakpoint
55
{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8
66
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
7-
Name: slice, Id: _1, Ty: [u8], Value: [01 02 03]
7+
Name: slice, Id: _1, Ty: [u8], Value: [[01], [02], [03]]
88
Name: <none>, Id: _2, Ty: &[u8], Value: <dead>
99
Name: <none>, Id: _3, Ty: &[u8], Value: <dead>
10-
(priroda) Id: _1, Ty: [u8], Value: [01 02 03]
10+
(priroda) Id: _1, Ty: [u8], Value: [[01], [02], [03]]
1111
(priroda) Hit breakpoint
1212
{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12
1313
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>

src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,37 @@
22
(priroda) Hit breakpoint
33
{MANIFEST_DIR}/tests/ui/locals_pointer_rendering.rs:59
44
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
5-
Name: target, Id: _1, Ty: [u8; 2], Value: [0a 14]
6-
Name: pointer_at_offset0, Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}]
5+
Name: target, Id: _1, Ty: [u8; 2], Value: [[0a], [14]]
6+
Name: pointer_at_offset0, Id: _2, Ty: PointerAtOffset0<'_>, Value: PointerAtOffset0 { ptr: [{ALLOC_PTR}] }
77
Name: <none>, Id: _3, Ty: &u8, Value: <dead>
88
Name: <none>, Id: _4, Ty: &u8, Value: <dead>
99
Name: <none>, Id: _5, Ty: usize, Value: <dead>
1010
Name: <none>, Id: _6, Ty: bool, Value: true
11-
Name: pointer_after_bytes, Id: _7, Ty: PointerAfterBytes<'_>, Value: [01 02 03 __ __ __ __ __ {ALLOC_PTR}]
11+
Name: pointer_after_bytes, Id: _7, Ty: PointerAfterBytes<'_>, Value: PointerAfterBytes { bytes: [[01], [02], [03]], ptr: [{ALLOC_PTR}] }
1212
Name: <none>, Id: _8, Ty: [u8; 3], Value: <dead>
1313
Name: <none>, Id: _9, Ty: &u8, Value: <dead>
1414
Name: <none>, Id: _10, Ty: &u8, Value: <dead>
1515
Name: <none>, Id: _11, Ty: usize, Value: <dead>
1616
Name: <none>, Id: _12, Ty: bool, Value: true
17-
Name: pointer_at_end, Id: _13, Ty: PointerAtEnd<'_>, Value: [04 __ __ __ __ __ __ __ {ALLOC_PTR}]
17+
Name: pointer_at_end, Id: _13, Ty: PointerAtEnd<'_>, Value: PointerAtEnd { byte: [04], ptr: [{ALLOC_PTR}] }
1818
Name: <none>, Id: _14, Ty: &u8, Value: <dead>
1919
Name: <none>, Id: _15, Ty: &u8, Value: <dead>
2020
Name: <none>, Id: _16, Ty: usize, Value: <dead>
2121
Name: <none>, Id: _17, Ty: bool, Value: true
22-
Name: uninit_around_pointer, Id: _18, Ty: UninitAroundPointer<'_>, Value: [__ __ __ __ __ __ __ __ {ALLOC_PTR} __ __ __ __ __ __ __ __]
22+
Name: uninit_around_pointer, Id: _18, Ty: UninitAroundPointer<'_>, Value: UninitAroundPointer { before: [__ __], ptr: [{ALLOC_PTR}], after: [__ __] }
2323
Name: <none>, Id: _19, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: <dead>
2424
Name: <none>, Id: _20, Ty: &u8, Value: <dead>
2525
Name: <none>, Id: _21, Ty: &u8, Value: <dead>
2626
Name: <none>, Id: _22, Ty: usize, Value: <dead>
2727
Name: <none>, Id: _23, Ty: bool, Value: true
2828
Name: <none>, Id: _24, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: <dead>
29-
Name: integer_and_pointer, Id: _25, Ty: IntegerAndPointer<'_>, Value: [11 22 33 44 __ __ __ __ {ALLOC_PTR}]
29+
Name: integer_and_pointer, Id: _25, Ty: IntegerAndPointer<'_>, Value: IntegerAndPointer { integer: [11 22 33 44], ptr: [{ALLOC_PTR}] }
3030
Name: <none>, Id: _26, Ty: &u8, Value: <dead>
3131
Name: <none>, Id: _27, Ty: &u8, Value: <dead>
3232
Name: <none>, Id: _28, Ty: usize, Value: <dead>
3333
Name: <none>, Id: _29, Ty: bool, Value: true
3434
Name: fixed_addr_ptr, Id: _30, Ty: *const u8, Value: [0x1234[wildcard]]
35-
Name: short_pointer_bytes, Id: _31, Ty: [u8; 1], Value: [34]
35+
Name: short_pointer_bytes, Id: _31, Ty: [u8; 1], Value: [[34]]
3636
Name: bytes, Id: _32, Ty: std::mem::MaybeUninit<[u8; 1]>, Value: <dead>
3737
Name: <none>, Id: _33, Ty: (), Value: <dead>
3838
Name: <none>, Id: _34, Ty: *const u8, Value: <dead>
@@ -52,5 +52,5 @@ Name: <none>, Id: _47, Ty: &UninitAroundPointer<'_>, Value: <dead>
5252
Name: <none>, Id: _48, Ty: &IntegerAndPointer<'_>, Value: <dead>
5353
Name: <none>, Id: _49, Ty: &*const u8, Value: <dead>
5454
Name: <none>, Id: _50, Ty: &[u8; 1], Value: <dead>
55-
(priroda) Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}]
55+
(priroda) Id: _2, Ty: PointerAtOffset0<'_>, Value: PointerAtOffset0 { ptr: [{ALLOC_PTR}] }
5656
(priroda) quitting

src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
(priroda) Hit breakpoint
33
{MANIFEST_DIR}/tests/ui/locals_projected_mplace_size.rs:36
44
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
5-
Name: <none>, Id: _1, Ty: Envelope, Value: [aa __ __ __ 22 11 33 __ 77 66 55 44 88 __ __ __ 00 ff ee dd cc bb aa 99 34 12 __ __ __ __ __ __]
5+
Name: <none>, Id: _1, Ty: Envelope, Value: Envelope { prefix: [aa], target: Payload { a: [22 11], b: [33], c: [77 66 55 44], d: [88] }, trailer: [00 ff ee dd cc bb aa 99], checksum: [34 12] }
66
Name: prefix, Id: _1.0, Ty: u8, Value: [aa]
7-
Name: target, Id: _1.1, Ty: Payload, Value: [22 11 33 __ 77 66 55 44 88 __ __ __]
7+
Name: target, Id: _1.1, Ty: Payload, Value: Payload { a: [22 11], b: [33], c: [77 66 55 44], d: [88] }
88
Name: trailer, Id: _1.2, Ty: u64, Value: [00 ff ee dd cc bb aa 99]
99
Name: checksum, Id: _1.3, Ty: u16, Value: [34 12]
1010
(priroda) quitting

0 commit comments

Comments
 (0)