From 3ea68fb6eee930d7030704842e201c64fdf2ad5c Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Fri, 10 Jul 2026 06:26:16 +0300 Subject: [PATCH 01/80] [Priroda] Render indirect local values Use `local_to_op` and the operand storage shape to render initialized immediate locals through Miri's Immediate display implementation. Keep mplace-backed locals as `` for now, preserve explicit dead/uninit states, and expose interpreter errors in `LocalDesc`.value. Add value-shape and metadata-sized local fixtures for the next indirect rendering slices. --- src/tools/miri/priroda/src/main.rs | 29 +++++++------- .../tests/ui/locals_access_field.stdout | 8 ++-- .../tests/ui/locals_corpus_async.stdout | 6 +-- .../tests/ui/locals_in_function.stdout | 2 +- .../tests/ui/locals_mplace_metadata.rs | 26 +++++++++++++ .../tests/ui/locals_mplace_metadata.stdin | 13 +++++++ .../tests/ui/locals_mplace_metadata.stdout | 25 ++++++++++++ .../miri/priroda/tests/ui/locals_sroa.stdout | 2 +- .../ui/locals_struct_field_fragment.stdout | 2 +- .../priroda/tests/ui/locals_value_shapes.rs | 30 +++++++++++++++ .../tests/ui/locals_value_shapes.stdin | 12 ++++++ .../tests/ui/locals_value_shapes.stdout | 38 +++++++++++++++++++ 12 files changed, 168 insertions(+), 25 deletions(-) create mode 100644 src/tools/miri/priroda/tests/ui/locals_mplace_metadata.rs create mode 100644 src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdin create mode 100644 src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout create mode 100644 src/tools/miri/priroda/tests/ui/locals_value_shapes.rs create mode 100644 src/tools/miri/priroda/tests/ui/locals_value_shapes.stdin create mode 100644 src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 98fcb937db9e7..6d9142c280875 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -19,7 +19,7 @@ use std::io::{self, Write}; use std::path::PathBuf; use miri::Immediate::{Scalar, ScalarPair, Uninit}; -use miri::*; +use miri::{interpret, *}; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; use rustc_interface::interface; @@ -486,22 +486,21 @@ impl<'tcx> PrirodaContext<'tcx> { None => { local_desc.value = "".to_string(); } - Some(Either::Left(_)) => { - local_desc.value = "".to_string(); - } - Some(Either::Right(imm)) => { - match imm { - Scalar(_) => { - local_desc.value = "".to_string(); - } - ScalarPair(_, _) => { - local_desc.value = "".to_string(); + Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), + + Some(Either::Left(_) | Either::Right(_)) => { + match self.ecx.local_to_op(local, None).report_err() { + Ok(op) => { + local_desc.value = match op.as_mplace_or_imm() { + Either::Right(imm) => format!("{imm}"), + Either::Left(_) => "".to_string(), + }; } - - Uninit => { - local_desc.value = "".to_string(); + Err(err) => { + local_desc.value = + format!("", interpret::format_interp_error(err)); } - }; + } } }; diff --git a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout index 5aeff083cc9d7..1fb02e657e9e9 100644 --- a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout @@ -3,12 +3,12 @@ {MANIFEST_DIR}/tests/ui/locals_access_field.rs:14 (priroda) Name: , Id: _0, Ty: (), Value: Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: -Name: _slice, Id: _2, Ty: &[u8], Value: -Name: _extra, Id: _3, Ty: u32, Value: +Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] +Name: _extra, Id: _3, Ty: u32, Value: 0_u32 (priroda) Id: _0, Ty: (), Value: (priroda) Id: _1, Ty: ExtraSlice<'_>, Value: -(priroda) Id: _2, Ty: &[u8], Value: -(priroda) Id: _3, Ty: u32, Value: +(priroda) Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] +(priroda) Id: _3, Ty: u32, Value: 0_u32 (priroda) no local for this id (priroda) no local for this id (priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index 03e00f88e488d..a2b94c788abad 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -7,13 +7,13 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:31 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: (u32, i32), Value: +Name: , Id: _1, Ty: (u32, i32), Value: (0x00000005, 0x00000006): (u32, i32) Name: first, Id: _1.0, Ty: u32, Value: Name: second, Id: _1.1, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:45 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: S, Value: +Name: , Id: _1, Ty: S, Value: {transmute(0x40a00000): S} Name: x, Id: _1.0, Ty: f32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55 @@ -29,7 +29,7 @@ Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:20 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: &mut std::option::Option, Value: +Name: , Id: _1, Ty: &mut std::option::Option, Value: {&_: &mut std::option::Option} Name: foo, Id: _1.* as variant#1.0, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:76 diff --git a/src/tools/miri/priroda/tests/ui/locals_in_function.stdout b/src/tools/miri/priroda/tests/ui/locals_in_function.stdout index f88cc173d3611..5161a3397092e 100644 --- a/src/tools/miri/priroda/tests/ui/locals_in_function.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_in_function.stdout @@ -2,7 +2,7 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_in_function.rs:5 (priroda) Name: , Id: _0, Ty: (), Value: -Name: x, Id: _1, Ty: i32, Value: +Name: x, Id: _1, Ty: i32, Value: 1_i32 Name: y, Id: _2, Ty: bool, Value: Name: , Id: _3, Ty: (i32, bool), Value: Name: , Id: _4, Ty: i32, Value: diff --git a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.rs b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.rs new file mode 100644 index 0000000000000..b031f73328f74 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.rs @@ -0,0 +1,26 @@ +#![feature(unsized_fn_params)] +#![allow(incomplete_features, internal_features, unused_variables)] + +trait Marker {} + +impl Marker for (i32, i32) {} + +fn slice_param(slice: [u8]) { + std::hint::black_box(&slice); +} + +fn str_param(text: str) { + std::hint::black_box(&text); +} + +fn dyn_param(value: dyn Marker) { + std::hint::black_box(&value); +} + +fn main() { + let slice: Box<[u8]> = Box::new([1_u8, 2, 3]); + slice_param(*slice); + str_param(*String::from("abc").into_boxed_str()); + let value: Box = Box::new((1_i32, 2_i32)); + dyn_param(*value); +} diff --git a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdin b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdin new file mode 100644 index 0000000000000..86a2cfcd68a3f --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdin @@ -0,0 +1,13 @@ +break tests/ui/locals_mplace_metadata.rs:8 +break tests/ui/locals_mplace_metadata.rs:12 +break tests/ui/locals_mplace_metadata.rs:16 +continue +locals +print 1 +continue +locals +print 1 +continue +locals +print 1 +quit diff --git a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout new file mode 100644 index 0000000000000..dcd9e0fb5508d --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout @@ -0,0 +1,25 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8 +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12 +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:16 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: slice, Id: _1, Ty: [u8], Value: +Name: , Id: _2, Ty: &[u8], Value: +Name: , Id: _3, Ty: &[u8], Value: +(priroda) Id: _1, Ty: [u8], Value: +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: text, Id: _1, Ty: str, Value: +Name: , Id: _2, Ty: &str, Value: +Name: , Id: _3, Ty: &str, Value: +(priroda) Id: _1, Ty: str, Value: +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:16 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: value, Id: _1, Ty: dyn Marker, Value: +Name: , Id: _2, Ty: &dyn Marker, Value: +Name: , Id: _3, Ty: &dyn Marker, Value: +(priroda) Id: _1, Ty: dyn Marker, Value: +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_sroa.stdout b/src/tools/miri/priroda/tests/ui/locals_sroa.stdout index 7dcffd8b3c722..b5bb004f56cf0 100644 --- a/src/tools/miri/priroda/tests/ui/locals_sroa.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_sroa.stdout @@ -2,7 +2,7 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_sroa.rs:13 (priroda) Name: , Id: _0, Ty: (), Value: -Name: s, Id: _1, Ty: &[u8], Value: +Name: s, Id: _1, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<324>, 0x0000000000000002): &[u8] Name: , Id: _2, Ty: ExtraSlice<'_>, Value: Name: , Id: _3, Ty: &[u8], Value: Name: , Id: _4, Ty: u32, Value: diff --git a/src/tools/miri/priroda/tests/ui/locals_struct_field_fragment.stdout b/src/tools/miri/priroda/tests/ui/locals_struct_field_fragment.stdout index ffc3d24354e4b..cc717bfb97fe2 100644 --- a/src/tools/miri/priroda/tests/ui/locals_struct_field_fragment.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_struct_field_fragment.stdout @@ -5,6 +5,6 @@ Name: , Id: _1, Ty: Outer, Value: Name: , Id: _2, Ty: Inner, Value: Name: , Id: _3, Ty: Inner, Value: -Name: x.inner.value, Id: _4, Ty: u8, Value: +Name: x.inner.value, Id: _4, Ty: u8, Value: 7_u8 Name: , Id: _5, Ty: u8, Value: (priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.rs b/src/tools/miri/priroda/tests/ui/locals_value_shapes.rs new file mode 100644 index 0000000000000..b6f00bd1e08cc --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.rs @@ -0,0 +1,30 @@ +#![allow(dead_code, unused_variables)] + +struct Aggregate { + byte: u8, + word: u16, +} + +fn main() { + // Edge case for `None` from `as_mplace_or_imm`: moved/drop temporaries become dead. + let dead_box = Box::new(0x11_u8); + let consumed = dead_box; + drop(consumed); + + let pointed_box = Box::new(0x11_u8); + let pointer_box = &pointed_box; + + // Immediate::Scalar. + let scalar = 0x2a_u8; + let pointed = 0x33_u8; + // Immediate::Scalar with pointer provenance. + let scalar_pointer = &pointed; + // Immediate::ScalarPair. + let scalar_pair = &[10_u8, 20_u8][..]; + // Either::Left mplace/indirect storage. + let mplace = Aggregate { byte: scalar, word: 0x1234 }; + // Immediate::Uninit. + let uninit_scalar: u32; + + std::hint::black_box((scalar, scalar_pointer, scalar_pair.len(), &mplace)); +} diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdin b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdin new file mode 100644 index 0000000000000..fbd5687a45754 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdin @@ -0,0 +1,12 @@ +break tests/ui/locals_value_shapes.rs:29 +continue +locals +print 0 +print 4 +print 5 +print 7 +print 8 +print 13 +print 15 +print 999 +quit diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout new file mode 100644 index 0000000000000..53dd04295e4f3 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout @@ -0,0 +1,38 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_value_shapes.rs:29 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_value_shapes.rs:29 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: +Name: consumed, Id: _2, Ty: std::boxed::Box, Value: +Name: , Id: _3, Ty: (), Value: +Name: , Id: _4, Ty: std::boxed::Box, Value: +Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: +Name: pointer_box, Id: _6, Ty: &std::boxed::Box, Value: {&_: &std::boxed::Box} +Name: scalar, Id: _7, Ty: u8, Value: 42_u8 +Name: pointed, Id: _8, Ty: u8, Value: +Name: scalar_pointer, Id: _9, Ty: &u8, Value: {&_: &u8} +Name: scalar_pair, Id: _10, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<489>, 0x0000000000000002): &[u8] +Name: , Id: _11, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<488>, 0x0000000000000002): &[u8] +Name: , Id: _12, Ty: &[u8; 2], Value: +Name: , Id: _13, Ty: [u8; 2], Value: +Name: , Id: _14, Ty: std::ops::RangeFull, Value: +Name: mplace, Id: _15, Ty: Aggregate, Value: +Name: , Id: _16, Ty: u8, Value: +Name: uninit_scalar, Id: _17, Ty: u32, Value: +Name: , Id: _18, Ty: (u8, &u8, usize, &Aggregate), Value: +Name: , Id: _19, Ty: (u8, &u8, usize, &Aggregate), Value: +Name: , Id: _20, Ty: u8, Value: +Name: , Id: _21, Ty: &u8, Value: +Name: , Id: _22, Ty: usize, Value: +Name: , Id: _23, Ty: &[u8], Value: +Name: , Id: _24, Ty: &Aggregate, Value: +Name: , Id: _25, Ty: &[u8; 2], Value: {&_: &[u8; 2]} +(priroda) Id: _0, Ty: (), Value: +(priroda) Id: _4, Ty: std::boxed::Box, Value: +(priroda) Id: _5, Ty: std::boxed::Box, Value: +(priroda) Id: _7, Ty: u8, Value: 42_u8 +(priroda) Id: _8, Ty: u8, Value: +(priroda) Id: _13, Ty: [u8; 2], Value: +(priroda) Id: _15, Ty: Aggregate, Value: +(priroda) no local for this id +(priroda) quitting From 037fbe7661d3bd9350ac02d6c94d799a8fe06f02 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Thu, 16 Jul 2026 06:43:12 +0300 Subject: [PATCH 02/80] [Priroda] Render indirect locals with allocation dumps Resolve allocation-backed mplace locals and display their allocation contents with dump_alloc. Keep unsupported provenance as and update the fixtures for the temporary diagnostic allocation output. --- src/tools/miri/priroda/src/main.rs | 9 +++- .../tests/ui/locals_access_field.stdout | 14 +++++- .../tests/ui/locals_corpus_async.stdout | 13 ++++- .../tests/ui/locals_mplace_metadata.stdout | 30 +++++++++--- .../tests/ui/locals_value_shapes.stdout | 48 +++++++++++++++---- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 6d9142c280875..47f0b9e1fee37 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -493,7 +493,14 @@ impl<'tcx> PrirodaContext<'tcx> { Ok(op) => { local_desc.value = match op.as_mplace_or_imm() { Either::Right(imm) => format!("{imm}"), - Either::Left(_) => "".to_string(), + + Either::Left(mplace_ty) => { + match mplace_ty.ptr().provenance.and_then(|p| p.get_alloc_id()) { + Some(alloc_id) => + format!("{:#?}", self.ecx.dump_alloc(alloc_id)), + None => "".to_string(), + } + } }; } Err(err) => { diff --git a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout index 1fb02e657e9e9..18c9fdd968374 100644 --- a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout @@ -2,11 +2,21 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_access_field.rs:14 (priroda) Name: , Id: _0, Ty: (), Value: -Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: +Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: alloc153 (stack variable, size: 24, align: 8) { + 0x00 │ ╾0x20af8[alloc2]<327>─╼ 00 00 00 00 00 00 00 00 │ ╾──────╼........ + 0x10 │ 00 00 00 00 __ __ __ __ │ ....░░░░ +} +alloc2 (global (static or const), size: 0, align: 1) {} + Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] Name: _extra, Id: _3, Ty: u32, Value: 0_u32 (priroda) Id: _0, Ty: (), Value: -(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: +(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: alloc153 (stack variable, size: 24, align: 8) { + 0x00 │ ╾0x20af8[alloc2]<327>─╼ 00 00 00 00 00 00 00 00 │ ╾──────╼........ + 0x10 │ 00 00 00 00 __ __ __ __ │ ....░░░░ +} +alloc2 (global (static or const), size: 0, align: 1) {} + (priroda) Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] (priroda) Id: _3, Ty: u32, Value: 0_u32 (priroda) no local for this id diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index a2b94c788abad..15b02f668ac36 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -18,12 +18,21 @@ Name: x, Id: _1.0, Ty: f32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: std::option::Option, Value: +Name: , Id: _1, Ty: std::option::Option, Value: alloc155 (stack variable, size: 8, align: 4) { + 01 00 00 00 05 00 00 00 │ ........ +} + Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: std::option::Option<&i32>, Value: +Name: , Id: _1, Ty: std::option::Option<&i32>, Value: alloc157 (stack variable, size: 8, align: 8) { + ╾0x20b3c[alloc2]<332>─╼ │ ╾──────╼ +} +alloc2 (global (static or const), size: 4, align: 4) { + 05 00 00 00 │ .... +} + Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: (priroda) Hit breakpoint diff --git a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout index dcd9e0fb5508d..fb7a9aa86f00d 100644 --- a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout @@ -4,22 +4,40 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8 (priroda) Name: , Id: _0, Ty: (), Value: -Name: slice, Id: _1, Ty: [u8], Value: +Name: slice, Id: _1, Ty: [u8], Value: alloc273 (stack variable, size: 3, align: 1) { + 01 02 03 │ ... +} + Name: , Id: _2, Ty: &[u8], Value: Name: , Id: _3, Ty: &[u8], Value: -(priroda) Id: _1, Ty: [u8], Value: +(priroda) Id: _1, Ty: [u8], Value: alloc273 (stack variable, size: 3, align: 1) { + 01 02 03 │ ... +} + (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12 (priroda) Name: , Id: _0, Ty: (), Value: -Name: text, Id: _1, Ty: str, Value: +Name: text, Id: _1, Ty: str, Value: alloc478 (stack variable, size: 3, align: 1) { + __ __ __ │ ░░░ +} + Name: , Id: _2, Ty: &str, Value: Name: , Id: _3, Ty: &str, Value: -(priroda) Id: _1, Ty: str, Value: +(priroda) Id: _1, Ty: str, Value: alloc478 (stack variable, size: 3, align: 1) { + __ __ __ │ ░░░ +} + (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:16 (priroda) Name: , Id: _0, Ty: (), Value: -Name: value, Id: _1, Ty: dyn Marker, Value: +Name: value, Id: _1, Ty: dyn Marker, Value: alloc555 (stack variable, size: 8, align: 4) { + 01 00 00 00 02 00 00 00 │ ........ +} + Name: , Id: _2, Ty: &dyn Marker, Value: Name: , Id: _3, Ty: &dyn Marker, Value: -(priroda) Id: _1, Ty: dyn Marker, Value: +(priroda) Id: _1, Ty: dyn Marker, Value: alloc555 (stack variable, size: 8, align: 4) { + 01 00 00 00 02 00 00 00 │ ........ +} + (priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout index 53dd04295e4f3..0c9b33e366883 100644 --- a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout @@ -2,21 +2,41 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_value_shapes.rs:29 (priroda) Name: , Id: _0, Ty: (), Value: -Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: -Name: consumed, Id: _2, Ty: std::boxed::Box, Value: +Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: alloc266 (stack variable, size: 8, align: 8) { + ╾─0x20b95[a236]<374>──╼ │ ╾──────╼ +} +alloc236 (deallocated) + +Name: consumed, Id: _2, Ty: std::boxed::Box, Value: alloc267 (stack variable, size: 8, align: 8) { + ╾─0x20b95[a236]<376>──╼ │ ╾──────╼ +} +alloc236 (deallocated) + Name: , Id: _3, Ty: (), Value: Name: , Id: _4, Ty: std::boxed::Box, Value: -Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: +Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: alloc319 (stack variable, size: 8, align: 8) { + ╾─0x21030[a289]<468>──╼ │ ╾──────╼ +} +alloc289 (Rust heap, size: 1, align: 1) { + 11 │ . +} + Name: pointer_box, Id: _6, Ty: &std::boxed::Box, Value: {&_: &std::boxed::Box} Name: scalar, Id: _7, Ty: u8, Value: 42_u8 -Name: pointed, Id: _8, Ty: u8, Value: +Name: pointed, Id: _8, Ty: u8, Value: alloc320 (stack variable, size: 1, align: 1) { + 33 │ 3 +} + Name: scalar_pointer, Id: _9, Ty: &u8, Value: {&_: &u8} Name: scalar_pair, Id: _10, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<489>, 0x0000000000000002): &[u8] Name: , Id: _11, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<488>, 0x0000000000000002): &[u8] Name: , Id: _12, Ty: &[u8; 2], Value: Name: , Id: _13, Ty: [u8; 2], Value: Name: , Id: _14, Ty: std::ops::RangeFull, Value: -Name: mplace, Id: _15, Ty: Aggregate, Value: +Name: mplace, Id: _15, Ty: Aggregate, Value: alloc321 (stack variable, size: 4, align: 2) { + 34 12 2a __ │ 4.*░ +} + Name: , Id: _16, Ty: u8, Value: Name: uninit_scalar, Id: _17, Ty: u32, Value: Name: , Id: _18, Ty: (u8, &u8, usize, &Aggregate), Value: @@ -29,10 +49,22 @@ Name: , Id: _24, Ty: &Aggregate, Value: Name: , Id: _25, Ty: &[u8; 2], Value: {&_: &[u8; 2]} (priroda) Id: _0, Ty: (), Value: (priroda) Id: _4, Ty: std::boxed::Box, Value: -(priroda) Id: _5, Ty: std::boxed::Box, Value: +(priroda) Id: _5, Ty: std::boxed::Box, Value: alloc319 (stack variable, size: 8, align: 8) { + ╾─0x21030[a289]<468>──╼ │ ╾──────╼ +} +alloc289 (Rust heap, size: 1, align: 1) { + 11 │ . +} + (priroda) Id: _7, Ty: u8, Value: 42_u8 -(priroda) Id: _8, Ty: u8, Value: +(priroda) Id: _8, Ty: u8, Value: alloc320 (stack variable, size: 1, align: 1) { + 33 │ 3 +} + (priroda) Id: _13, Ty: [u8; 2], Value: -(priroda) Id: _15, Ty: Aggregate, Value: +(priroda) Id: _15, Ty: Aggregate, Value: alloc321 (stack variable, size: 4, align: 2) { + 34 12 2a __ │ 4.*░ +} + (priroda) no local for this id (priroda) quitting From cc00f34968aee7d14929714cf659ef16aa32938b Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 19 Jul 2026 05:25:53 +0000 Subject: [PATCH 03/80] Prepare for merging from rust-lang/rust This updates the rust-version file to c904ba32e22c9d6fd31140b4283c5cc6c6b2fb5c. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 3dd271b8b5762..7dab7b32196a0 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -4a9d5368df6450bbd2fc8dde4508c3e5d83bb19d +c904ba32e22c9d6fd31140b4283c5cc6c6b2fb5c From 18ba44950d62939f56f3851466d5d5de8f526725 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Thu, 16 Jul 2026 11:23:26 +0300 Subject: [PATCH 04/80] [Priroda] Render mplace local byte ranges Replace the diagnostic dump_alloc output with a Priroda-owned byte renderer for mplace-backed locals. Compute the current value size with size_and_align_of_val, inspect only the mplace byte range, and render uninitialized bytes with an explicit marker. --- src/tools/miri/priroda/README.md | 28 +++++++ src/tools/miri/priroda/src/main.rs | 77 ++++++++++++++----- .../tests/ui/locals_access_field.stdout | 14 +--- .../tests/ui/locals_corpus_async.stdout | 13 +--- .../tests/ui/locals_mplace_metadata.stdout | 30 ++------ .../tests/ui/locals_value_shapes.stdout | 48 ++---------- 6 files changed, 103 insertions(+), 107 deletions(-) diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index b9d946e08e544..aa5db4cae290e 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -9,6 +9,8 @@ Current focus: - source-location output after stepping - source-location breakpoint prototype - source-local listing prototype +- runtime local state and value rendering +- range-limited byte output for indirect locals ## Setup @@ -65,8 +67,34 @@ RUSTC_BLESS=1 cargo test | `c`, `continue` | Continue until the program finishes or reaches a breakpoint. | | `b :`, `break :` | Add a source-location breakpoint. | | `l`, `locals` | List source-level locals in the current frame by name. | +| `p `, `print ` | Print one MIR local by numeric id. | | `q`, `quit` | Exit Priroda. | +## Value Output + +Immediate values use Miri's `Immediate` display representation. Indirect +locals are rendered as the bytes belonging to the current value range, not as +the entire backing allocation: + +```text +[01 02 03] +[?? ?? ??] +``` + +`??` means the byte is uninitialized. A value whose runtime size cannot be +determined is reported as ``. + +Pointer/provenance spans are planned as part of the raw byte output, using a +compact dump-like marker such as: + +```text +[ 2a 00 00 00] +``` + +Automatic pointer following is future work and should be explicit, not part of +ordinary value printing. Typed field rendering and dereference/projection-aware +printing are also future work. + EOF also exits Priroda cleanly. Example: diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 47f0b9e1fee37..c86a9832f6d75 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -18,7 +18,7 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; use std::path::PathBuf; -use miri::Immediate::{Scalar, ScalarPair, Uninit}; +use miri::Immediate::Uninit; use miri::{interpret, *}; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; @@ -399,6 +399,49 @@ impl<'tcx> PrirodaContext<'tcx> { self.build_local_descs(frame) } + fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { + let size = match self.ecx.size_and_align_of_val(mplace)? { + Some((size, _)) => size, + None => { + // Extern types cannot currently be executed as by-value locals, + // so this path cannot yet be covered by a Priroda UI fixture. + // FIXME: Add coverage once Priroda supports printing dereferenced places. + return interp_ok("".to_string()); + } + }; + + let size = size.bytes_usize(); + if size == 0 { + return interp_ok("[]".to_string()); + } + + let (alloc_id, offset, _) = + self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; + let offset = offset.bytes_usize(); + let range = offset..offset.strict_add(size); + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + + let mut rendered = Vec::with_capacity(size); + + for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { + let chunk_range = chunk.range(); + let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); + + if chunk.is_init() { + rendered.extend( + alloc + .inspect_with_uninit_and_ptr_outside_interpreter(chunk_range) + .iter() + .map(|byte| format!("{byte:02x}")), + ); + } else { + rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); + } + } + + interp_ok(format!("[{}]", rendered.join(" "))) + } + /// Render the source-side path from composite debug info, such as `.field`. fn render_source_projection( fragment: Option<&VarDebugInfoFragment<'tcx>>, @@ -489,25 +532,19 @@ impl<'tcx> PrirodaContext<'tcx> { Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), Some(Either::Left(_) | Either::Right(_)) => { - match self.ecx.local_to_op(local, None).report_err() { - Ok(op) => { - local_desc.value = match op.as_mplace_or_imm() { - Either::Right(imm) => format!("{imm}"), - - Either::Left(mplace_ty) => { - match mplace_ty.ptr().provenance.and_then(|p| p.get_alloc_id()) { - Some(alloc_id) => - format!("{:#?}", self.ecx.dump_alloc(alloc_id)), - None => "".to_string(), - } - } - }; - } - Err(err) => { - local_desc.value = - format!("", interpret::format_interp_error(err)); - } - } + let op = self + .ecx + .local_to_op(local, None) + .expect("this error can only occur in CTFE on generic code"); + local_desc.value = match op.as_mplace_or_imm() { + Either::Right(imm) => format!("{imm}"), + + Either::Left(mplace) => + match self.render_mplace_bytes(&mplace).report_err() { + Ok(bytes) => bytes, + Err(err) => format!("", interpret::format_interp_error(err)), + }, + }; } }; diff --git a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout index 18c9fdd968374..51bd1c7fa42fb 100644 --- a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout @@ -2,21 +2,11 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_access_field.rs:14 (priroda) Name: , Id: _0, Ty: (), Value: -Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: alloc153 (stack variable, size: 24, align: 8) { - 0x00 │ ╾0x20af8[alloc2]<327>─╼ 00 00 00 00 00 00 00 00 │ ╾──────╼........ - 0x10 │ 00 00 00 00 __ __ __ __ │ ....░░░░ -} -alloc2 (global (static or const), size: 0, align: 1) {} - +Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [f8 0a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] Name: _extra, Id: _3, Ty: u32, Value: 0_u32 (priroda) Id: _0, Ty: (), Value: -(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: alloc153 (stack variable, size: 24, align: 8) { - 0x00 │ ╾0x20af8[alloc2]<327>─╼ 00 00 00 00 00 00 00 00 │ ╾──────╼........ - 0x10 │ 00 00 00 00 __ __ __ __ │ ....░░░░ -} -alloc2 (global (static or const), size: 0, align: 1) {} - +(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [f8 0a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] (priroda) Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] (priroda) Id: _3, Ty: u32, Value: 0_u32 (priroda) no local for this id diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index 15b02f668ac36..2d6bda11505e7 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -18,21 +18,12 @@ Name: x, Id: _1.0, Ty: f32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: std::option::Option, Value: alloc155 (stack variable, size: 8, align: 4) { - 01 00 00 00 05 00 00 00 │ ........ -} - +Name: , Id: _1, Ty: std::option::Option, Value: [01 00 00 00 05 00 00 00] Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: std::option::Option<&i32>, Value: alloc157 (stack variable, size: 8, align: 8) { - ╾0x20b3c[alloc2]<332>─╼ │ ╾──────╼ -} -alloc2 (global (static or const), size: 4, align: 4) { - 05 00 00 00 │ .... -} - +Name: , Id: _1, Ty: std::option::Option<&i32>, Value: [3c 0b 02 00 00 00 00 00] Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: (priroda) Hit breakpoint diff --git a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout index fb7a9aa86f00d..3a92ca95a5b68 100644 --- a/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout @@ -4,40 +4,22 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8 (priroda) Name: , Id: _0, Ty: (), Value: -Name: slice, Id: _1, Ty: [u8], Value: alloc273 (stack variable, size: 3, align: 1) { - 01 02 03 │ ... -} - +Name: slice, Id: _1, Ty: [u8], Value: [01 02 03] Name: , Id: _2, Ty: &[u8], Value: Name: , Id: _3, Ty: &[u8], Value: -(priroda) Id: _1, Ty: [u8], Value: alloc273 (stack variable, size: 3, align: 1) { - 01 02 03 │ ... -} - +(priroda) Id: _1, Ty: [u8], Value: [01 02 03] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12 (priroda) Name: , Id: _0, Ty: (), Value: -Name: text, Id: _1, Ty: str, Value: alloc478 (stack variable, size: 3, align: 1) { - __ __ __ │ ░░░ -} - +Name: text, Id: _1, Ty: str, Value: [__ __ __] Name: , Id: _2, Ty: &str, Value: Name: , Id: _3, Ty: &str, Value: -(priroda) Id: _1, Ty: str, Value: alloc478 (stack variable, size: 3, align: 1) { - __ __ __ │ ░░░ -} - +(priroda) Id: _1, Ty: str, Value: [__ __ __] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:16 (priroda) Name: , Id: _0, Ty: (), Value: -Name: value, Id: _1, Ty: dyn Marker, Value: alloc555 (stack variable, size: 8, align: 4) { - 01 00 00 00 02 00 00 00 │ ........ -} - +Name: value, Id: _1, Ty: dyn Marker, Value: [01 00 00 00 02 00 00 00] Name: , Id: _2, Ty: &dyn Marker, Value: Name: , Id: _3, Ty: &dyn Marker, Value: -(priroda) Id: _1, Ty: dyn Marker, Value: alloc555 (stack variable, size: 8, align: 4) { - 01 00 00 00 02 00 00 00 │ ........ -} - +(priroda) Id: _1, Ty: dyn Marker, Value: [01 00 00 00 02 00 00 00] (priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout index 0c9b33e366883..f403756841ebb 100644 --- a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout @@ -2,41 +2,21 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_value_shapes.rs:29 (priroda) Name: , Id: _0, Ty: (), Value: -Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: alloc266 (stack variable, size: 8, align: 8) { - ╾─0x20b95[a236]<374>──╼ │ ╾──────╼ -} -alloc236 (deallocated) - -Name: consumed, Id: _2, Ty: std::boxed::Box, Value: alloc267 (stack variable, size: 8, align: 8) { - ╾─0x20b95[a236]<376>──╼ │ ╾──────╼ -} -alloc236 (deallocated) - +Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: [95 0b 02 00 00 00 00 00] +Name: consumed, Id: _2, Ty: std::boxed::Box, Value: [95 0b 02 00 00 00 00 00] Name: , Id: _3, Ty: (), Value: Name: , Id: _4, Ty: std::boxed::Box, Value: -Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: alloc319 (stack variable, size: 8, align: 8) { - ╾─0x21030[a289]<468>──╼ │ ╾──────╼ -} -alloc289 (Rust heap, size: 1, align: 1) { - 11 │ . -} - +Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: [30 10 02 00 00 00 00 00] Name: pointer_box, Id: _6, Ty: &std::boxed::Box, Value: {&_: &std::boxed::Box} Name: scalar, Id: _7, Ty: u8, Value: 42_u8 -Name: pointed, Id: _8, Ty: u8, Value: alloc320 (stack variable, size: 1, align: 1) { - 33 │ 3 -} - +Name: pointed, Id: _8, Ty: u8, Value: [33] Name: scalar_pointer, Id: _9, Ty: &u8, Value: {&_: &u8} Name: scalar_pair, Id: _10, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<489>, 0x0000000000000002): &[u8] Name: , Id: _11, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<488>, 0x0000000000000002): &[u8] Name: , Id: _12, Ty: &[u8; 2], Value: Name: , Id: _13, Ty: [u8; 2], Value: Name: , Id: _14, Ty: std::ops::RangeFull, Value: -Name: mplace, Id: _15, Ty: Aggregate, Value: alloc321 (stack variable, size: 4, align: 2) { - 34 12 2a __ │ 4.*░ -} - +Name: mplace, Id: _15, Ty: Aggregate, Value: [34 12 2a __] Name: , Id: _16, Ty: u8, Value: Name: uninit_scalar, Id: _17, Ty: u32, Value: Name: , Id: _18, Ty: (u8, &u8, usize, &Aggregate), Value: @@ -49,22 +29,10 @@ Name: , Id: _24, Ty: &Aggregate, Value: Name: , Id: _25, Ty: &[u8; 2], Value: {&_: &[u8; 2]} (priroda) Id: _0, Ty: (), Value: (priroda) Id: _4, Ty: std::boxed::Box, Value: -(priroda) Id: _5, Ty: std::boxed::Box, Value: alloc319 (stack variable, size: 8, align: 8) { - ╾─0x21030[a289]<468>──╼ │ ╾──────╼ -} -alloc289 (Rust heap, size: 1, align: 1) { - 11 │ . -} - +(priroda) Id: _5, Ty: std::boxed::Box, Value: [30 10 02 00 00 00 00 00] (priroda) Id: _7, Ty: u8, Value: 42_u8 -(priroda) Id: _8, Ty: u8, Value: alloc320 (stack variable, size: 1, align: 1) { - 33 │ 3 -} - +(priroda) Id: _8, Ty: u8, Value: [33] (priroda) Id: _13, Ty: [u8; 2], Value: -(priroda) Id: _15, Ty: Aggregate, Value: alloc321 (stack variable, size: 4, align: 2) { - 34 12 2a __ │ 4.*░ -} - +(priroda) Id: _15, Ty: Aggregate, Value: [34 12 2a __] (priroda) no local for this id (priroda) quitting From 52cc3b1ac629d9911ad95384a51629f71f2cabed Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 19 Jul 2026 04:44:53 +0300 Subject: [PATCH 05/80] [Priroda] Render provenance-aware local bytes Render complete pointer provenance while dumping indirect local values. Preserve initialized and uninitialized byte ranges, including padding and partial pointer bytes, and report unsupported unsized values explicitly. Add UI coverage for pointer positions, nonzero target offsets, uninitialized regions, and partial provenance. --- src/tools/miri/priroda/src/main.rs | 60 ++++++++++++++-- src/tools/miri/priroda/tests/cli.rs | 3 +- .../tests/ui/locals_access_field.stdout | 8 +-- .../tests/ui/locals_corpus_async.stdout | 2 +- .../tests/ui/locals_pointer_rendering.rs | 68 +++++++++++++++++++ .../tests/ui/locals_pointer_rendering.stderr | 0 .../tests/ui/locals_pointer_rendering.stdin | 5 ++ .../tests/ui/locals_pointer_rendering.stdout | 56 +++++++++++++++ .../miri/priroda/tests/ui/locals_sroa.stdout | 2 +- .../tests/ui/locals_value_shapes.stdout | 12 ++-- .../tests/ui/locals_wildcard_pointer.rs | 16 +++++ .../tests/ui/locals_wildcard_pointer.stderr | 0 .../tests/ui/locals_wildcard_pointer.stdin | 5 ++ .../tests/ui/locals_wildcard_pointer.stdout | 11 +++ 14 files changed, 228 insertions(+), 20 deletions(-) create mode 100644 src/tools/miri/priroda/tests/ui/locals_pointer_rendering.rs create mode 100644 src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stderr create mode 100644 src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdin create mode 100644 src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout create mode 100644 src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.rs create mode 100644 src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stderr create mode 100644 src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdin create mode 100644 src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdout diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index c86a9832f6d75..3ce576585403a 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -1,6 +1,7 @@ #![feature(rustc_private)] extern crate miri; +extern crate rustc_abi; extern crate rustc_codegen_ssa; extern crate rustc_data_structures; extern crate rustc_driver; @@ -16,10 +17,12 @@ extern crate rustc_type_ir; use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; +use std::ops::Range; use std::path::PathBuf; use miri::Immediate::Uninit; use miri::{interpret, *}; +use rustc_abi::Size; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; use rustc_interface::interface; @@ -399,6 +402,10 @@ impl<'tcx> PrirodaContext<'tcx> { self.build_local_descs(frame) } + /// Renders the current byte range of an indirect MIR value. + /// + /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`, + /// and complete pointer-sized provenance as pointer markers. fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { let size = match self.ecx.size_and_align_of_val(mplace)? { Some((size, _)) => size, @@ -419,21 +426,60 @@ impl<'tcx> PrirodaContext<'tcx> { self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; let offset = offset.bytes_usize(); let range = offset..offset.strict_add(size); + + self.render_alloc_bytes(alloc_id, range) + } + + /// Render a raw allocation range without requiring a typed memory place. + /// + /// This is also used by the future-facing `follow` command, where we have a + /// pointer target but do not yet know the target's type or size. + fn render_alloc_bytes( + &self, + alloc_id: AllocId, + range: Range, + ) -> InterpResult<'tcx, String> { let alloc = self.ecx.get_alloc_raw(alloc_id)?; - let mut rendered = Vec::with_capacity(size); + let mut rendered = Vec::with_capacity(range.len()); + + let ptr_size = self.ecx.tcx.data_layout.pointer_size(); for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { let chunk_range = chunk.range(); let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); if chunk.is_init() { - rendered.extend( - alloc - .inspect_with_uninit_and_ptr_outside_interpreter(chunk_range) - .iter() - .map(|byte| format!("{byte:02x}")), - ); + let ptr_size = ptr_size.bytes_usize(); + let mut cursor = chunk_range.start; + + while cursor < chunk_range.end { + // Full pointer provenance is rendered as a pointer marker. Bytewise + // provenance fragments are intentionally left as raw bytes here: they do + // not represent a complete pointer-sized value. + if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor)) + && cursor + ptr_size <= chunk_range.end + { + let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( + cursor..cursor + ptr_size, + ); + let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes) + .map_err(|err| { + miri::err_unsup_format!("invalid pointer representation: {err}") + })?; + + let offset = Size::from_bytes(offset); + rendered.push(format!("{:?}", Pointer::new(Some(prov), offset))); + + cursor += ptr_size; + } else { + let byte = alloc + .inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0]; + + rendered.push(format!("{byte:02x}")); + cursor += 1; + } + } } else { rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); } diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs index f4f9bf5d176ac..3b596fbf91f26 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/cli.rs @@ -32,11 +32,12 @@ fn main() -> Result<(), Box> { Regex::new(®ex::escape(&manifest_dir.display().to_string())).unwrap(); let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); - + let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); config.comment_defaults.base().normalize_stdout.extend([ (manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()), (miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()), (rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()), + (pointer_regex.into(), b"{ALLOC_PTR}".to_vec()), ]); // Priroda CLI tests do not currently require annotation comments in the test files diff --git a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout index 51bd1c7fa42fb..65a40a5ab584d 100644 --- a/src/tools/miri/priroda/tests/ui/locals_access_field.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_access_field.stdout @@ -2,12 +2,12 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_access_field.rs:14 (priroda) Name: , Id: _0, Ty: (), Value: -Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [f8 0a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] -Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] +Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] +Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8] Name: _extra, Id: _3, Ty: u32, Value: 0_u32 (priroda) Id: _0, Ty: (), Value: -(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [f8 0a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] -(priroda) Id: _2, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<328>, 0x0000000000000000): &[u8] +(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __] +(priroda) Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8] (priroda) Id: _3, Ty: u32, Value: 0_u32 (priroda) no local for this id (priroda) no local for this id diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index 2d6bda11505e7..5dd879d6c43fc 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -23,7 +23,7 @@ Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66 (priroda) Name: , Id: _0, Ty: (), Value: -Name: , Id: _1, Ty: std::option::Option<&i32>, Value: [3c 0b 02 00 00 00 00 00] +Name: , Id: _1, Ty: std::option::Option<&i32>, Value: [{ALLOC_PTR}] Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: (priroda) Hit breakpoint diff --git a/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.rs b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.rs new file mode 100644 index 0000000000000..63981c2080145 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.rs @@ -0,0 +1,68 @@ +//@ normalize-stderr-test: "(?s)^warning: integer-to-pointer cast.*warning: 1 warning emitted\n\n$" -> "" +use std::mem::{self, MaybeUninit}; + +#[repr(C)] +struct PointerAtOffset0<'a> { + ptr: &'a u8, +} + +#[repr(C)] +struct PointerAfterBytes<'a> { + bytes: [u8; 3], + ptr: &'a u8, +} + +#[repr(C)] +struct PointerAtEnd<'a> { + byte: u8, + ptr: &'a u8, +} + +#[repr(C)] +struct UninitAroundPointer<'a> { + before: MaybeUninit<[u8; 2]>, + ptr: &'a u8, + after: MaybeUninit<[u8; 2]>, +} + +#[repr(C)] +struct IntegerAndPointer<'a> { + integer: u32, + ptr: &'a u8, +} + +fn main() { + let target = [10u8, 20]; + let pointer_at_offset0 = PointerAtOffset0 { ptr: &target[0] }; + let pointer_after_bytes = PointerAfterBytes { bytes: [1, 2, 3], ptr: &target[0] }; + let pointer_at_end = PointerAtEnd { byte: 4, ptr: &target[0] }; + let uninit_around_pointer = UninitAroundPointer { + before: MaybeUninit::uninit(), + ptr: &target[0], + after: MaybeUninit::uninit(), + }; + let integer_and_pointer = IntegerAndPointer { integer: 0x44332211, ptr: &target[1] }; + + // Keep only fewer than pointer-sized bytes from a pointer representation. + let fixed_addr_ptr = std::ptr::with_exposed_provenance::(0x1234); + let short_pointer_bytes = unsafe { + let mut bytes = MaybeUninit::<[u8; 1]>::uninit(); + std::ptr::copy_nonoverlapping( + (&fixed_addr_ptr as *const *const u8).cast::(), + bytes.as_mut_ptr().cast::(), + mem::size_of::<[u8; 1]>(), + ); + bytes.assume_init() + }; + + // Break here so all locals are still available to the debugger. + std::hint::black_box(( + &pointer_at_offset0, + &pointer_after_bytes, + &pointer_at_end, + &uninit_around_pointer, + &integer_and_pointer, + &fixed_addr_ptr, + &short_pointer_bytes, + )); +} diff --git a/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stderr b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stderr new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdin b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdin new file mode 100644 index 0000000000000..6bd9bbf3c514f --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdin @@ -0,0 +1,5 @@ +break tests/ui/locals_pointer_rendering.rs:59 +continue +locals +print 2 +quit diff --git a/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout new file mode 100644 index 0000000000000..f26a06e16ae0c --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout @@ -0,0 +1,56 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_pointer_rendering.rs:59 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_pointer_rendering.rs:59 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: target, Id: _1, Ty: [u8; 2], Value: [0a 14] +Name: pointer_at_offset0, Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}] +Name: , Id: _3, Ty: &u8, Value: +Name: , Id: _4, Ty: &u8, Value: +Name: , Id: _5, Ty: usize, Value: +Name: , Id: _6, Ty: bool, Value: true +Name: pointer_after_bytes, Id: _7, Ty: PointerAfterBytes<'_>, Value: [01 02 03 __ __ __ __ __ {ALLOC_PTR}] +Name: , Id: _8, Ty: [u8; 3], Value: +Name: , Id: _9, Ty: &u8, Value: +Name: , Id: _10, Ty: &u8, Value: +Name: , Id: _11, Ty: usize, Value: +Name: , Id: _12, Ty: bool, Value: true +Name: pointer_at_end, Id: _13, Ty: PointerAtEnd<'_>, Value: [04 __ __ __ __ __ __ __ {ALLOC_PTR}] +Name: , Id: _14, Ty: &u8, Value: +Name: , Id: _15, Ty: &u8, Value: +Name: , Id: _16, Ty: usize, Value: +Name: , Id: _17, Ty: bool, Value: true +Name: uninit_around_pointer, Id: _18, Ty: UninitAroundPointer<'_>, Value: [__ __ __ __ __ __ __ __ {ALLOC_PTR} __ __ __ __ __ __ __ __] +Name: , Id: _19, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: +Name: , Id: _20, Ty: &u8, Value: +Name: , Id: _21, Ty: &u8, Value: +Name: , Id: _22, Ty: usize, Value: +Name: , Id: _23, Ty: bool, Value: true +Name: , Id: _24, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: +Name: integer_and_pointer, Id: _25, Ty: IntegerAndPointer<'_>, Value: [11 22 33 44 __ __ __ __ {ALLOC_PTR}] +Name: , Id: _26, Ty: &u8, Value: +Name: , Id: _27, Ty: &u8, Value: +Name: , Id: _28, Ty: usize, Value: +Name: , Id: _29, Ty: bool, Value: true +Name: fixed_addr_ptr, Id: _30, Ty: *const u8, Value: [0x1234[wildcard]] +Name: short_pointer_bytes, Id: _31, Ty: [u8; 1], Value: [34] +Name: bytes, Id: _32, Ty: std::mem::MaybeUninit<[u8; 1]>, Value: +Name: , Id: _33, Ty: (), Value: +Name: , Id: _34, Ty: *const u8, Value: +Name: , Id: _35, Ty: *const *const u8, Value: +Name: , Id: _36, Ty: &*const u8, Value: +Name: , Id: _37, Ty: *mut u8, Value: +Name: , Id: _38, Ty: *mut [u8; 1], Value: +Name: , Id: _39, Ty: &mut std::mem::MaybeUninit<[u8; 1]>, Value: +Name: , Id: _40, Ty: usize, Value: +Name: , Id: _41, Ty: std::mem::MaybeUninit<[u8; 1]>, Value: +Name: , Id: _42, Ty: (&PointerAtOffset0<'_>, &PointerAfterBytes<'_>, &PointerAtEnd<'_>, &UninitAroundPointer<'_>, &IntegerAndPointer<'_>, &*const u8, &[u8; 1]), Value: +Name: , Id: _43, Ty: (&PointerAtOffset0<'_>, &PointerAfterBytes<'_>, &PointerAtEnd<'_>, &UninitAroundPointer<'_>, &IntegerAndPointer<'_>, &*const u8, &[u8; 1]), Value: +Name: , Id: _44, Ty: &PointerAtOffset0<'_>, Value: +Name: , Id: _45, Ty: &PointerAfterBytes<'_>, Value: +Name: , Id: _46, Ty: &PointerAtEnd<'_>, Value: +Name: , Id: _47, Ty: &UninitAroundPointer<'_>, Value: +Name: , Id: _48, Ty: &IntegerAndPointer<'_>, Value: +Name: , Id: _49, Ty: &*const u8, Value: +Name: , Id: _50, Ty: &[u8; 1], Value: +(priroda) Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}] +(priroda) quitting diff --git a/src/tools/miri/priroda/tests/ui/locals_sroa.stdout b/src/tools/miri/priroda/tests/ui/locals_sroa.stdout index b5bb004f56cf0..94c44adf6ae59 100644 --- a/src/tools/miri/priroda/tests/ui/locals_sroa.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_sroa.stdout @@ -2,7 +2,7 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_sroa.rs:13 (priroda) Name: , Id: _0, Ty: (), Value: -Name: s, Id: _1, Ty: &[u8], Value: (pointer to 0x20af8[alloc2]<324>, 0x0000000000000002): &[u8] +Name: s, Id: _1, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000002): &[u8] Name: , Id: _2, Ty: ExtraSlice<'_>, Value: Name: , Id: _3, Ty: &[u8], Value: Name: , Id: _4, Ty: u32, Value: diff --git a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout index f403756841ebb..4250c443221f4 100644 --- a/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_value_shapes.stdout @@ -2,17 +2,17 @@ (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_value_shapes.rs:29 (priroda) Name: , Id: _0, Ty: (), Value: -Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: [95 0b 02 00 00 00 00 00] -Name: consumed, Id: _2, Ty: std::boxed::Box, Value: [95 0b 02 00 00 00 00 00] +Name: dead_box, Id: _1, Ty: std::boxed::Box, Value: [{ALLOC_PTR}] +Name: consumed, Id: _2, Ty: std::boxed::Box, Value: [{ALLOC_PTR}] Name: , Id: _3, Ty: (), Value: Name: , Id: _4, Ty: std::boxed::Box, Value: -Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: [30 10 02 00 00 00 00 00] +Name: pointed_box, Id: _5, Ty: std::boxed::Box, Value: [{ALLOC_PTR}] Name: pointer_box, Id: _6, Ty: &std::boxed::Box, Value: {&_: &std::boxed::Box} Name: scalar, Id: _7, Ty: u8, Value: 42_u8 Name: pointed, Id: _8, Ty: u8, Value: [33] Name: scalar_pointer, Id: _9, Ty: &u8, Value: {&_: &u8} -Name: scalar_pair, Id: _10, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<489>, 0x0000000000000002): &[u8] -Name: , Id: _11, Ty: &[u8], Value: (pointer to 0x212fa[alloc2]<488>, 0x0000000000000002): &[u8] +Name: scalar_pair, Id: _10, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000002): &[u8] +Name: , Id: _11, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000002): &[u8] Name: , Id: _12, Ty: &[u8; 2], Value: Name: , Id: _13, Ty: [u8; 2], Value: Name: , Id: _14, Ty: std::ops::RangeFull, Value: @@ -29,7 +29,7 @@ Name: , Id: _24, Ty: &Aggregate, Value: Name: , Id: _25, Ty: &[u8; 2], Value: {&_: &[u8; 2]} (priroda) Id: _0, Ty: (), Value: (priroda) Id: _4, Ty: std::boxed::Box, Value: -(priroda) Id: _5, Ty: std::boxed::Box, Value: [30 10 02 00 00 00 00 00] +(priroda) Id: _5, Ty: std::boxed::Box, Value: [{ALLOC_PTR}] (priroda) Id: _7, Ty: u8, Value: 42_u8 (priroda) Id: _8, Ty: u8, Value: [33] (priroda) Id: _13, Ty: [u8; 2], Value: diff --git a/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.rs b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.rs new file mode 100644 index 0000000000000..5a8bb13c43df8 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.rs @@ -0,0 +1,16 @@ +//@ normalize-stderr-test: "(?s)^warning: integer-to-pointer cast.*warning: 1 warning emitted\n\n$" -> "" + +#[repr(C)] +struct WildcardPointer { + ptr: *const u8, +} + +fn main() { + // The pointer is never dereferenced: this keeps the wildcard provenance + // available for the debugger renderer without requiring it to resolve. + let ptr = 0x1234usize as *const u8; + let wildcard = WildcardPointer { ptr }; + + // Keep the local alive for inspection. + std::hint::black_box(&wildcard); +} diff --git a/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stderr b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stderr new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdin b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdin new file mode 100644 index 0000000000000..9731f603d6957 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdin @@ -0,0 +1,5 @@ +break tests/ui/locals_wildcard_pointer.rs:15 +continue +locals +print 2 +quit diff --git a/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdout b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdout new file mode 100644 index 0000000000000..866c549f49a46 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_wildcard_pointer.stdout @@ -0,0 +1,11 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_wildcard_pointer.rs:15 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_wildcard_pointer.rs:15 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: ptr, Id: _1, Ty: *const u8, Value: {&_: *const u8} +Name: wildcard, Id: _2, Ty: WildcardPointer, Value: [0x1234[wildcard]] +Name: , Id: _3, Ty: *const u8, Value: +Name: , Id: _4, Ty: &WildcardPointer, Value: +Name: , Id: _5, Ty: &WildcardPointer, Value: +(priroda) Id: _2, Ty: WildcardPointer, Value: [0x1234[wildcard]] +(priroda) quitting From 0726da7124a777b50aec877a58a12992788dbc33 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 19 Jul 2026 06:40:26 +0300 Subject: [PATCH 06/80] [Priroda] Add follow command for allocation bytes Add a `follow` command that renders allocation bytes starting at a given allocation offset, reusing the provenance-aware allocation byte renderer used for indirect locals. Document the command and cover following a rendered pointer allocation in the pointer-rendering UI test. --- src/tools/miri/priroda/README.md | 1 + src/tools/miri/priroda/src/main.rs | 35 +++++++++++++++++++ .../tests/ui/locals_corpus_async.stdin | 1 + .../tests/ui/locals_corpus_async.stdout | 1 + 4 files changed, 38 insertions(+) diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index aa5db4cae290e..a8c25bf279868 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -68,6 +68,7 @@ RUSTC_BLESS=1 cargo test | `b :`, `break :` | Add a source-location breakpoint. | | `l`, `locals` | List source-level locals in the current frame by name. | | `p `, `print ` | Print one MIR local by numeric id. | +| `f `, `follow ` | Render allocation bytes from an offset, including the full allocation size. | | `q`, `quit` | Exit Priroda. | ## Value Output diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 3ce576585403a..4fae8e02d9f0a 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -17,6 +17,7 @@ extern crate rustc_type_ir; use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; +use std::num::NonZeroU64; use std::ops::Range; use std::path::PathBuf; @@ -26,6 +27,7 @@ use rustc_abi::Size; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; use rustc_interface::interface; +use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; use rustc_middle::ty::{TyCtxt, TyKind}; use rustc_session::EarlyDiagCtxt; @@ -380,10 +382,25 @@ impl<'tcx> PrirodaContext<'tcx> { DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())), DebuggerCommand::Print(local) => interp_ok(CommandResult::SingleLocal(self.get_local(local))), + DebuggerCommand::Follow(alloc_id, offset) => + self.follow_alloc(alloc_id, offset).map(CommandResult::Memory), DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), } } + fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> { + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + if offset > alloc.len() { + return Err(miri::err_unsup_format!( + "allocation offset {offset} is outside {alloc_id}" + )) + .into(); + } + + let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?; + interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}")) + } + fn get_local(&self, local: usize) -> Option { let frame = self.ecx.active_thread_stack().last()?; @@ -681,6 +698,7 @@ enum DebuggerCommand { Breakpoint(PathBuf, usize), ListLocals, Print(usize), + Follow(AllocId, usize), } enum BreakpointSetResult { @@ -694,6 +712,7 @@ enum CommandResult { BreakpointResult(BreakpointSetResult), Locals(Vec), SingleLocal(Option), + Memory(String), // FIXME: distinguish terminating the debugger session from disconnecting a // frontend and terminating the interpreted program once multiple frontends exist. TerminateSession, @@ -782,6 +801,7 @@ impl Cli { } None => println!("no local for this id"), }, + CommandResult::Memory(memory) => println!("{memory}"), CommandResult::TerminateSession => { println!("quitting"); return interp_ok(()); @@ -814,6 +834,7 @@ impl Cli { "b" | "break" => self.parse_breakpoint(args), "l" | "locals" => Some(DebuggerCommand::ListLocals), "p" | "print" => self.parse_print_local(args), + "f" | "follow" => self.parse_follow(args), _ => None, } } @@ -846,4 +867,18 @@ impl Cli { let local = input.parse().ok()?; Some(DebuggerCommand::Print(local)) } + + fn parse_follow(&self, input: &str) -> Option { + let mut parts = input.split_whitespace(); + let alloc_id = parts.next()?; + let offset = parts.next()?; + if parts.next().is_some() { + return None; + } + + let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?; + let alloc_id = AllocId(NonZeroU64::new(alloc_id)?); + let offset = offset.parse().ok()?; + Some(DebuggerCommand::Follow(alloc_id, offset)) + } } diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin index 7c686ffd5bffb..0bfe69e063043 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdin @@ -12,6 +12,7 @@ continue locals continue locals +follow 2 0 continue locals continue diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index 5dd879d6c43fc..54f10f1e97ad4 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -26,6 +26,7 @@ Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: Name: , Id: _1, Ty: std::option::Option<&i32>, Value: [{ALLOC_PTR}] Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: +(priroda) Allocation alloc2+0: [05 00 00 00] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:20 (priroda) Name: , Id: _0, Ty: (), Value: From f9c7888cf5c31fe6b4986627d7ae058aacf0a5b7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 20 Jul 2026 15:00:51 +0200 Subject: [PATCH 07/80] support `neon.tbl*` intrinsics --- src/tools/miri/src/intrinsics/aarch64.rs | 63 +++++-- .../shims/aarch64/intrinsics-aarch64-neon.rs | 170 ++++++++++++++---- 2 files changed, 185 insertions(+), 48 deletions(-) diff --git a/src/tools/miri/src/intrinsics/aarch64.rs b/src/tools/miri/src/intrinsics/aarch64.rs index aabda2a997525..626a83a65a238 100644 --- a/src/tools/miri/src/intrinsics/aarch64.rs +++ b/src/tools/miri/src/intrinsics/aarch64.rs @@ -1,5 +1,3 @@ -use std::assert_matches; - use rustc_middle::mir::BinOp; use rustc_span::Symbol; @@ -180,34 +178,77 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - // Vector table lookup: each index selects a byte from the 8 or 16-byte table, + // Vector table lookup: each index selects a byte from the table, // out-of-range -> 0. // - // Used to implement the vqtbl1 and vqtbl1q set of functions, e.g.: + // Used to implement the vtblN, vqtblN and vqtblNq set of functions, e.g.: // // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbl1_u8 + // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl1_u8 // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl1q_s8 + // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbl2_u8 + // - https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbl4q_s8 // // LLVM does not have a portable shuffle that takes non-const indices // so we need to implement this ourselves. - "neon.tbl1.v8i8" | "neon.tbl1.v16i8" => { - let [table, indices] = this.check_shim_sig_unadjusted(link_name, args)?; + _ if unprefixed_name.starts_with("neon.tbl") => { + // The table segments always have 16 elements, the index vector and output vector + // have either 8 or 16 elements. + let (table_segments, indices) = match unprefixed_name { + "neon.tbl1.v8i8" | "neon.tbl1.v16i8" => { + let [table, indices] = this.check_shim_sig_unadjusted(link_name, args)?; + let (table, len) = this.project_to_simd(table)?; + assert_eq!(len, 16); + (vec![table], indices) + } + "neon.tbl2.v8i8" | "neon.tbl2.v16i8" => { + let [table0, table1, indices] = + this.check_shim_sig_unadjusted(link_name, args)?; + let (table0, len0) = this.project_to_simd(table0)?; + let (table1, len1) = this.project_to_simd(table1)?; + assert_eq!([len0, len1], [16; 2]); + (vec![table0, table1], indices) + } + "neon.tbl3.v8i8" | "neon.tbl3.v16i8" => { + let [table0, table1, table2, indices] = + this.check_shim_sig_unadjusted(link_name, args)?; + let (table0, len0) = this.project_to_simd(table0)?; + let (table1, len1) = this.project_to_simd(table1)?; + let (table2, len2) = this.project_to_simd(table2)?; + assert_eq!([len0, len1, len2], [16; 3]); + (vec![table0, table1, table2], indices) + } + "neon.tbl4.v8i8" | "neon.tbl4.v16i8" => { + let [table0, table1, table2, table3, indices] = + this.check_shim_sig_unadjusted(link_name, args)?; + let (table0, len0) = this.project_to_simd(table0)?; + let (table1, len1) = this.project_to_simd(table1)?; + let (table2, len2) = this.project_to_simd(table2)?; + let (table3, len3) = this.project_to_simd(table3)?; + assert_eq!([len0, len1, len2, len3], [16; 4]); + (vec![table0, table1, table2, table3], indices) + } + _ => unreachable!(), + }; - let (table, table_len) = this.project_to_simd(table)?; let (indices, idx_len) = this.project_to_simd(indices)?; let (dest, dest_len) = this.project_to_simd(dest)?; - assert_matches!(table_len, 8 | 16); assert_eq!(idx_len, dest_len); for i in 0..dest_len { let idx = this.read_immediate(&this.project_index(&indices, i)?)?; - let idx_u = idx.to_scalar().to_u8()?; - let val = if u64::from(idx_u) < table_len { - let t = this.read_immediate(&this.project_index(&table, idx_u.into())?)?; + let idx = idx.to_scalar().to_u8()?; + + // The LLVM intrinsic table segments always have 16 elements. + let val = if usize::from(idx) < table_segments.len().strict_mul(16) { + let table = &table_segments[usize::from(idx.strict_div(16))]; + let lane = u64::from(idx.strict_rem(16)); + let t = this.read_immediate(&this.project_index(table, lane)?)?; t.to_scalar() } else { Scalar::from_u8(0) }; + this.write_scalar(val, &this.project_index(&dest, i)?)?; } } diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs index 7b4c5f082ea5c..1bc6e3539222d 100644 --- a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs @@ -12,7 +12,7 @@ fn main() { unsafe { test_vpmaxq_u8(); - test_tbl1_basic(); + test_tbl(); test_vpadd(); test_vpaddl(); test_vqdmulh(); @@ -46,47 +46,143 @@ unsafe fn test_vpmaxq_u8() { } #[target_feature(enable = "neon")] -fn test_tbl1_basic() { +fn test_tbl() { unsafe { - // table = 0..7 - let table: uint8x8_t = transmute::<[u8; 8], _>([0, 1, 2, 3, 4, 5, 6, 7]); - - // indices - let idx: uint8x8_t = transmute::<[u8; 8], _>([0, 1, 2, 3, 4, 5, 6, 7]); - let got = vtbl1_u8(table, idx); - let got_arr: [u8; 8] = transmute(got); - assert_eq!(got_arr, [0, 1, 2, 3, 4, 5, 6, 7]); - - // Also try different order and out-of-range indices (8, 255). - let idx2: uint8x8_t = transmute::<[u8; 8], _>([7, 8, 255, 0, 1, 2, 3, 4]); - let got2 = vtbl1_u8(table, idx2); - let got2_arr: [u8; 8] = transmute(got2); - assert_eq!(got2_arr[0], 7); - assert_eq!(got2_arr[1], 0); // out-of-range - assert_eq!(got2_arr[2], 0); // out-of-range - assert_eq!(&got2_arr[3..8], &[0, 1, 2, 3, 4][..]); + let table_arr = [0, 1, 2, 3, 4, 5, 6, 7]; + let table = vld1_u8(table_arr.as_ptr()); + + let idx_arr = [0, 1, 2, 3, 4, 5, 6, 7]; + let idx = vld1_u8(idx_arr.as_ptr()); + + let got: [u8; 8] = transmute(vtbl1_u8(table, idx)); + assert_eq!(got, [0, 1, 2, 3, 4, 5, 6, 7]); + + // Also try a different order and out-of-range indices. + let idx_arr = [7, 8, 255, 0, 1, 2, 3, 4]; + let idx = vld1_u8(idx_arr.as_ptr()); + + let got: [u8; 8] = transmute(vtbl1_u8(table, idx)); + assert_eq!(got, [7, 0, 0, 0, 1, 2, 3, 4]); } unsafe { // table = 0..15 - let table: uint8x16_t = - transmute::<[u8; 16], _>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - // indices - let idx: uint8x16_t = - transmute::<[u8; 16], _>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - let got = vqtbl1q_u8(table, idx); - let got_arr: [u8; 16] = transmute(got); - assert_eq!(got_arr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - - // Also try different order and out-of-range indices (16, 255). - let idx2: uint8x16_t = - transmute::<[u8; 16], _>([15, 16, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - let got2 = vqtbl1q_u8(table, idx2); - let got2_arr: [u8; 16] = transmute(got2); - assert_eq!(got2_arr[0], 15); - assert_eq!(got2_arr[1], 0); // out-of-range - assert_eq!(got2_arr[2], 0); // out-of-range - assert_eq!(&got2_arr[3..16], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12][..]); + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7]; + let table1_arr = [8, 9, 10, 11, 12, 13, 14, 15]; + let table0 = vld1_u8(table0_arr.as_ptr()); + let table1 = vld1_u8(table1_arr.as_ptr()); + let table = uint8x8x2_t(table0, table1); + + let idx_arr = [0, 7, 8, 15, 16, 255, 4, 12]; + let idx = vld1_u8(idx_arr.as_ptr()); + + let got: [u8; 8] = transmute(vtbl2_u8(table, idx)); + assert_eq!(got, [0, 7, 8, 15, 0, 0, 4, 12]); + } + + unsafe { + // table = 0..23 + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7]; + let table1_arr = [8, 9, 10, 11, 12, 13, 14, 15]; + let table2_arr = [16, 17, 18, 19, 20, 21, 22, 23]; + let table0 = vld1_u8(table0_arr.as_ptr()); + let table1 = vld1_u8(table1_arr.as_ptr()); + let table2 = vld1_u8(table2_arr.as_ptr()); + let table = uint8x8x3_t(table0, table1, table2); + + let idx_arr = [0, 7, 8, 15, 16, 23, 24, 255]; + let idx = vld1_u8(idx_arr.as_ptr()); + + let got: [u8; 8] = transmute(vtbl3_u8(table, idx)); + assert_eq!(got, [0, 7, 8, 15, 16, 23, 0, 0]); + } + + unsafe { + // table = 0..31 + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7]; + let table1_arr = [8, 9, 10, 11, 12, 13, 14, 15]; + let table2_arr = [16, 17, 18, 19, 20, 21, 22, 23]; + let table3_arr = [24, 25, 26, 27, 28, 29, 30, 31]; + let table0 = vld1_u8(table0_arr.as_ptr()); + let table1 = vld1_u8(table1_arr.as_ptr()); + let table2 = vld1_u8(table2_arr.as_ptr()); + let table3 = vld1_u8(table3_arr.as_ptr()); + let table = uint8x8x4_t(table0, table1, table2, table3); + + let idx_arr = [0, 8, 15, 16, 23, 24, 31, 32]; + let idx = vld1_u8(idx_arr.as_ptr()); + + let got: [u8; 8] = transmute(vtbl4_u8(table, idx)); + assert_eq!(got, [0, 8, 15, 16, 23, 24, 31, 0]); + } + + unsafe { + let table_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let table = vld1q_u8(table_arr.as_ptr()); + + let idx_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let idx = vld1q_u8(idx_arr.as_ptr()); + + let got: [u8; 16] = transmute(vqtbl1q_u8(table, idx)); + assert_eq!(got, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + + // Also try a different order and out-of-range indices. + let idx_arr = [15, 16, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + let idx = vld1q_u8(idx_arr.as_ptr()); + + let got: [u8; 16] = transmute(vqtbl1q_u8(table, idx)); + assert_eq!(got, [15, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + } + + unsafe { + // table = 0..31 + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let table1_arr = [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]; + let table0 = vld1q_u8(table0_arr.as_ptr()); + let table1 = vld1q_u8(table1_arr.as_ptr()); + let table = uint8x16x2_t(table0, table1); + + let idx_arr = [0, 15, 16, 31, 32, 255, 7, 8, 23, 24, 1, 14, 17, 30, 4, 27]; + let idx = vld1q_u8(idx_arr.as_ptr()); + + let got: [u8; 16] = transmute(vqtbl2q_u8(table, idx)); + assert_eq!(got, [0, 15, 16, 31, 0, 0, 7, 8, 23, 24, 1, 14, 17, 30, 4, 27]); + } + + unsafe { + // table = 0..47 + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let table1_arr = [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]; + let table2_arr = [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]; + let table0 = vld1q_u8(table0_arr.as_ptr()); + let table1 = vld1q_u8(table1_arr.as_ptr()); + let table2 = vld1q_u8(table2_arr.as_ptr()); + let table = uint8x16x3_t(table0, table1, table2); + + let idx_arr = [0, 15, 16, 31, 32, 47, 48, 255, 7, 23, 39, 1, 17, 33, 30, 46]; + let idx = vld1q_u8(idx_arr.as_ptr()); + + let got: [u8; 16] = transmute(vqtbl3q_u8(table, idx)); + assert_eq!(got, [0, 15, 16, 31, 32, 47, 0, 0, 7, 23, 39, 1, 17, 33, 30, 46]); + } + + unsafe { + // table = 0..63 + let table0_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let table1_arr = [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]; + let table2_arr = [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]; + let table3_arr = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; + let table0 = vld1q_u8(table0_arr.as_ptr()); + let table1 = vld1q_u8(table1_arr.as_ptr()); + let table2 = vld1q_u8(table2_arr.as_ptr()); + let table3 = vld1q_u8(table3_arr.as_ptr()); + let table = uint8x16x4_t(table0, table1, table2, table3); + + let idx_arr = [0, 15, 16, 31, 32, 47, 48, 63, 64, 255, 7, 23, 39, 55, 1, 62]; + let idx = vld1q_u8(idx_arr.as_ptr()); + + let got: [u8; 16] = transmute(vqtbl4q_u8(table, idx)); + assert_eq!(got, [0, 15, 16, 31, 32, 47, 48, 63, 0, 0, 7, 23, 39, 55, 1, 62]); } } From 763fe4d6af76efca012919161bc90a2162de14d6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 18 Jul 2026 10:09:52 +0200 Subject: [PATCH 08/80] add a riscv target to CI --- src/tools/miri/ci/ci.sh | 1 + src/tools/miri/tests/pass-dep/libc/pthread-sync.rs | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 42107adfe566a..503cc68fbc834 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -178,6 +178,7 @@ case $HOST_TARGET in # Not officially supported tier 2 MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics + MANY_SEEDS=16 TEST_TARGET=riscv64a23-unknown-linux-gnu run_tests ;; aarch64-apple-darwin) # Host diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-sync.rs b/src/tools/miri/tests/pass-dep/libc/pthread-sync.rs index a79d0656d85c4..849703d33b333 100644 --- a/src/tools/miri/tests/pass-dep/libc/pthread-sync.rs +++ b/src/tools/miri/tests/pass-dep/libc/pthread-sync.rs @@ -12,10 +12,12 @@ fn main() { test_mutex_libc_init_recursive(); test_mutex_libc_init_normal(); test_mutex_libc_init_errorcheck(); + #[cfg(target_os = "linux")] - test_mutex_libc_static_initializer_recursive(); - #[cfg(target_os = "linux")] - test_mutex_libc_static_initializer_errorcheck(); + { + test_mutex_libc_static_initializer_recursive(); + test_mutex_libc_static_initializer_errorcheck(); + } test_cond(); test_condattr(); From 810cdc03c3fea095c7c0a6af60029788ec714e2b Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Tue, 21 Jul 2026 12:06:34 +0200 Subject: [PATCH 09/80] readme: fix misspelling of `-Ctarget-feature` When using `RUSTFLAGS="-Ctarget-features=+avx512f"`, the Rust compiler complains: error: unknown codegen option: `target-features` The actual option uses the singular form. --- src/tools/miri/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index ee77ea4d64597..d674774184a22 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -162,7 +162,7 @@ endian-sensitive code. ### Controlling target features Controlling target features works similar to regular rustc invocations: -`RUSTFLAGS="-Ctarget-features=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri +`RUSTFLAGS="-Ctarget-feature=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri only supports very few AVX512 intrinsics at the moment.) `-Ctarget-cpu` also works. If target features are also relevant for doctests, you have to also set `RUSTDOCFLAGS`. From afefb4f75633c781ea754c8c01f054e8f8eb2c3d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 21 Jul 2026 09:57:02 +0200 Subject: [PATCH 10/80] bump libc --- src/tools/miri/tests/deps/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index 4691588eefb40..5039054e04990 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -253,9 +253,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b" [[package]] name = "linux-raw-sys" From 6ca51276e7fb33b59dda79e1ff4eabc053b422af Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 08:23:12 +0200 Subject: [PATCH 11/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 1af98b7cdf86932b76182a387f52f1be6f0c1be0. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 7dab7b32196a0..f6d7daa057750 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -c904ba32e22c9d6fd31140b4283c5cc6c6b2fb5c +1af98b7cdf86932b76182a387f52f1be6f0c1be0 From 94fde0cb52c1d77d77a6b1f69000dee1cf47ecbb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 08:26:11 +0200 Subject: [PATCH 12/80] Revert "disable `portable_simd` miri test that needs additional intrinsic support" This reverts commit 785e740bb5c1d9166717c93612f7b9f99fee1caf. The additional intrinsic support has meanwhile been implemented. --- src/tools/miri/tests/pass/intrinsics/portable-simd.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index e507e92624bde..ac7838247f288 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -766,11 +766,6 @@ fn simd_swizzle() { } fn simd_swizzle_dyn() { - // FIXME: needs llvm.neon.tbl2 support, see https://github.com/rust-lang/miri/pull/5219. - if cfg!(target_arch = "aarch64") { - return; - } - fn check_swizzle_dyn() { assert_eq!( Simd::::default().swizzle_dyn(Simd::::default()), From cabd1f14d6f56d490b37a2674982adb8445ab3a3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 09:11:11 +0200 Subject: [PATCH 13/80] disable swizzle_dyn test on loongarch --- src/tools/miri/tests/pass/intrinsics/portable-simd.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index ac7838247f288..a3991f78b1166 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -766,6 +766,11 @@ fn simd_swizzle() { } fn simd_swizzle_dyn() { + if cfg!(target_arch = "loongarch64") { + // We don't support the required intrinsic here. + return; + } + fn check_swizzle_dyn() { assert_eq!( Simd::::default().swizzle_dyn(Simd::::default()), From cd551c3fc03b0ae83f31e68e7bd3468d5fc3838a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 22 Jul 2026 10:07:40 -0700 Subject: [PATCH 14/80] std: Switch implementations of `thread_local!` for WASI This commit is a change for all WASI targets to use a different underlying implementation in the standard library for `thread_local!`. Previously all wasm targets, without the `atomics` feature, were funneled into the `no_threads` implementation and various fallbacks in the `thread_local` module. The upcoming `wasm32-wasip3` target, however, will actually have threads and will need different treatment. Additionally the modules that `wasm32-wasip3` needs all already work on all other WASI targets as well -- for example the `pthread_*` symbols needed to manage destructors are exposed by `wasi-libc`. The end result is that the `wasm32-wasip3` target will be "ready for threads" as soon as `wasi-libc` has support. Other targets shouldn't have any functional difference from before, too. --- library/std/src/sys/thread_local/mod.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index d48bb1c8b721e..cb954e475be1b 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -25,7 +25,7 @@ cfg_select! { any( - all(target_family = "wasm", not(target_feature = "atomics")), + all(target_family = "wasm", not(target_feature = "atomics"), not(target_os = "wasi")), target_os = "uefi", target_os = "zkvm", target_os = "trusty", @@ -54,7 +54,10 @@ cfg_select! { /// destructor for each variable. On these platforms, we keep track of the /// destructors ourselves and register (through the [`guard`] module) only a /// single callback that runs all of the destructors in the list. -#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] +#[cfg(all( + target_thread_local, + not(all(target_family = "wasm", not(target_feature = "atomics"), not(target_os = "wasi"))) +))] pub(crate) mod destructors { cfg_select! { any( @@ -93,9 +96,7 @@ pub(crate) mod guard { pub(crate) use windows::enable; } any( - all(target_family = "wasm", not( - all(target_os = "wasi", target_env = "p1", target_feature = "atomics") - )), + all(target_family = "wasm", not(target_os = "wasi")), target_os = "uefi", target_os = "zkvm", target_os = "trusty", @@ -150,7 +151,7 @@ pub(crate) mod key { ), all(not(target_thread_local), target_vendor = "apple"), target_os = "teeos", - all(target_os = "wasi", target_env = "p1", target_feature = "atomics"), + target_os = "wasi", ) => { mod racy; mod unix; From 525e1aaf891eefaa57cc8265688c9ea9706304e6 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 23 Jul 2026 05:28:39 +0000 Subject: [PATCH 15/80] Prepare for merging from rust-lang/rust This updates the rust-version file to 390279b302ca98ae270f434100ae3730531d1246. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f6d7daa057750..25a5238f538ee 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -1af98b7cdf86932b76182a387f52f1be6f0c1be0 +390279b302ca98ae270f434100ae3730531d1246 From 21b23c279f515a13640f80735bd45c50d7263092 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Thu, 23 Jul 2026 09:13:13 +0300 Subject: [PATCH 16/80] [Priroda] Test projected mplace value range rendering Add a CLI fixture covering a projected memory-backed field inside a larger aggregate. This locks in that projected values render only their own MPlace layout bytes instead of trailing bytes from the parent allocation. --- .../tests/ui/locals_projected_mplace_size.rs | 48 +++++++++++++++++++ .../ui/locals_projected_mplace_size.stdin | 4 ++ .../ui/locals_projected_mplace_size.stdout | 10 ++++ 3 files changed, 62 insertions(+) create mode 100644 src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.rs create mode 100644 src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdin create mode 100644 src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout diff --git a/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.rs b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.rs new file mode 100644 index 0000000000000..561d786890aed --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.rs @@ -0,0 +1,48 @@ +//@ compile-flags: -Zmir-opt-level=0 + +#![allow(internal_features)] +#![feature(custom_mir, core_intrinsics)] + +extern crate core; +use core::intrinsics::mir::*; + +// Regression test for projected values: each `item.` row must render +// only that field's own value/layout, and the memory-backed `item.target` row +// must not continue through the rest of the allocation backing `Envelope`. +#[repr(C)] +struct Payload { + a: u16, + b: u8, + c: u32, + d: u8, +} + +#[repr(C)] +struct Envelope { + prefix: u8, + target: Payload, + trailer: u64, + checksum: u16, +} + +#[custom_mir(dialect = "analysis", phase = "post-cleanup")] +fn projected_struct(item: Envelope) { + mir! { + debug prefix => item.prefix; + debug target => item.target; + debug trailer => item.trailer; + debug checksum => item.checksum; + { + Return() + } + } +} + +fn main() { + projected_struct(Envelope { + prefix: 0xaa, + target: Payload { a: 0x1122, b: 0x33, c: 0x44556677, d: 0x88 }, + trailer: 0x99aabbccddeeff00, + checksum: 0x1234, + }); +} diff --git a/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdin b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdin new file mode 100644 index 0000000000000..9db90a1115764 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdin @@ -0,0 +1,4 @@ +break tests/ui/locals_projected_mplace_size.rs:36 +continue +locals +quit diff --git a/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout new file mode 100644 index 0000000000000..46e84edf0d929 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout @@ -0,0 +1,10 @@ +(priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_projected_mplace_size.rs:36 +(priroda) Hit breakpoint +{MANIFEST_DIR}/tests/ui/locals_projected_mplace_size.rs:36 +(priroda) Name: , Id: _0, Ty: (), Value: +Name: , Id: _1, Ty: Envelope, Value: [aa __ __ __ 22 11 33 __ 77 66 55 44 88 __ __ __ 00 ff ee dd cc bb aa 99 34 12 __ __ __ __ __ __] +Name: prefix, Id: _1.0, Ty: u8, Value: +Name: target, Id: _1.1, Ty: Payload, Value: +Name: trailer, Id: _1.2, Ty: u64, Value: +Name: checksum, Id: _1.3, Ty: u16, Value: +(priroda) quitting From d37c84d1d30559c470b8cc0f62e1f6f53b2ba8fb Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Tue, 21 Jul 2026 10:02:19 +0300 Subject: [PATCH 17/80] [Priroda] Render projected debug-info values Keep projected MIR Places long enough to evaluate them with `eval_place_to_op`, then reuse the existing raw operand renderer for immediate and memory-backed values. This removes `` from projected locals, including fields, enum projections, references, and dereferences. Update the projected debug-info UI expectations and verify the full Priroda suite. --- src/tools/miri/priroda/src/main.rs | 37 +++++++++++++------ .../tests/ui/locals_corpus_async.stdout | 14 +++---- .../ui/locals_projected_mplace_size.stdout | 8 ++-- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index ff517079aa815..e748ae7340aa2 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -505,6 +505,20 @@ impl<'tcx> PrirodaContext<'tcx> { interp_ok(format!("[{}]", rendered.join(" "))) } + /// Render an evaluated operand using the same raw representation for + /// whole locals and projected MIR places. + fn render_op(&self, op: OpTy<'tcx>) -> String { + match op.as_mplace_or_imm() { + Either::Right(imm) => format!("{imm}"), + + Either::Left(mplace) => + match self.render_mplace_bytes(&mplace).report_err() { + Ok(bytes) => bytes, + Err(err) => format!("", interpret::format_interp_error(err)), + }, + } + } + /// Render the source-side path from composite debug info, such as `.field`. fn render_source_projection( fragment: Option<&VarDebugInfoFragment<'tcx>>, @@ -599,15 +613,7 @@ impl<'tcx> PrirodaContext<'tcx> { .ecx .local_to_op(local, None) .expect("this error can only occur in CTFE on generic code"); - local_desc.value = match op.as_mplace_or_imm() { - Either::Right(imm) => format!("{imm}"), - - Either::Left(mplace) => - match self.render_mplace_bytes(&mplace).report_err() { - Ok(bytes) => bytes, - Err(err) => format!("", interpret::format_interp_error(err)), - }, - }; + local_desc.value = self.render_op(op); } }; @@ -658,7 +664,8 @@ impl<'tcx> PrirodaContext<'tcx> { // and `_slice._extra`, not as two separate locals both named `_slice`. // Whole-place debug entries enrich the direct storage-local description. - // Projected places and constants are handled separately/deferred. + // Projected places are evaluated from their original MIR Place and use + // the same raw renderer as ordinary locals. for var_debug_info in &frame.body().var_debug_info { if let VarDebugInfoContents::Place(place) = &var_debug_info.value { if let Some(local_idx) = place.as_local() @@ -672,6 +679,13 @@ impl<'tcx> PrirodaContext<'tcx> { let storage_projection = Self::render_storage_projection(place.projection); let source_projection = Self::render_source_projection(var_debug_info.composite.as_deref()); + let value = self + .ecx + .eval_place_to_op(*place, None) + .map(|op| self.render_op(op)) + .unwrap_or_else(|err| { + format!("", interpret::format_interp_error(err)) + }); local_descs.push(LocalDesc { source_name: Some(var_debug_info.name), @@ -679,8 +693,7 @@ impl<'tcx> PrirodaContext<'tcx> { local: Some(place.local), storage_projection, ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(), - // FIXME: projection not handled yet. - value: "".to_string(), + value, }); } } diff --git a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout index 54f10f1e97ad4..6ccfbc997c590 100644 --- a/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout @@ -8,30 +8,30 @@ {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:31 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: (u32, i32), Value: (0x00000005, 0x00000006): (u32, i32) -Name: first, Id: _1.0, Ty: u32, Value: -Name: second, Id: _1.1, Ty: i32, Value: +Name: first, Id: _1.0, Ty: u32, Value: 5_u32 +Name: second, Id: _1.1, Ty: i32, Value: 6_i32 (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:45 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: S, Value: {transmute(0x40a00000): S} -Name: x, Id: _1.0, Ty: f32, Value: +Name: x, Id: _1.0, Ty: f32, Value: 5f32 (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: std::option::Option, Value: [01 00 00 00 05 00 00 00] -Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: +Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: [05 00 00 00] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: std::option::Option<&i32>, Value: [{ALLOC_PTR}] -Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: -Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: +Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: [{ALLOC_PTR}] +Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: [05 00 00 00] (priroda) Allocation alloc2+0: [05 00 00 00] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:20 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: &mut std::option::Option, Value: {&_: &mut std::option::Option} -Name: foo, Id: _1.* as variant#1.0, Ty: i32, Value: +Name: foo, Id: _1.* as variant#1.0, Ty: i32, Value: [05 00 00 00] (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:76 (priroda) Name: , Id: _0, Ty: (), Value: diff --git a/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout index 46e84edf0d929..a2111bed511aa 100644 --- a/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_projected_mplace_size.stdout @@ -3,8 +3,8 @@ {MANIFEST_DIR}/tests/ui/locals_projected_mplace_size.rs:36 (priroda) Name: , Id: _0, Ty: (), Value: Name: , Id: _1, Ty: Envelope, Value: [aa __ __ __ 22 11 33 __ 77 66 55 44 88 __ __ __ 00 ff ee dd cc bb aa 99 34 12 __ __ __ __ __ __] -Name: prefix, Id: _1.0, Ty: u8, Value: -Name: target, Id: _1.1, Ty: Payload, Value: -Name: trailer, Id: _1.2, Ty: u64, Value: -Name: checksum, Id: _1.3, Ty: u16, Value: +Name: prefix, Id: _1.0, Ty: u8, Value: [aa] +Name: target, Id: _1.1, Ty: Payload, Value: [22 11 33 __ 77 66 55 44 88 __ __ __] +Name: trailer, Id: _1.2, Ty: u64, Value: [00 ff ee dd cc bb aa 99] +Name: checksum, Id: _1.3, Ty: u16, Value: [34 12] (priroda) quitting From 1bd4fa0b6fc4c4f791e26b31d731466890bcab06 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 24 Jul 2026 12:14:01 +0800 Subject: [PATCH 18/80] Add tuple never coercion collection regression test --- ...ever-to-any-coercion-in-collection-issue-100727.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs diff --git a/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs new file mode 100644 index 0000000000000..f44c486e0f50b --- /dev/null +++ b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs @@ -0,0 +1,11 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/100727. +//! A tuple element of type `!` should be coerced using constraints from `collect`. + +//@ check-pass +//@ edition: 2021 + +#![allow(unreachable_code)] + +fn main() { + let _: Vec<(i32,)> = [(todo!(),)].into_iter().collect(); +} From bae7e21f42ac7bb04d75bd0ba37d746f35b0cc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Fri, 24 Jul 2026 00:59:09 +0800 Subject: [PATCH 19/80] Check unsafe impls on safe EIIs --- compiler/rustc_ast_lowering/src/item.rs | 5 +- .../rustc_hir/src/attrs/data_structures.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 69 +++++++++++-------- compiler/rustc_passes/src/diagnostics.rs | 12 +++- tests/ui/eii/default/auxiliary/impl1.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl1.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl2.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl3.rs | 2 +- tests/ui/eii/duplicate/auxiliary/impl4.rs | 2 +- .../eii/duplicate/dylib_default_duplicate.rs | 2 +- tests/ui/eii/safe_eii_unsafe_impl.rs | 14 ++++ tests/ui/eii/safe_eii_unsafe_impl.stderr | 10 +++ .../eii/type_checking/cross_crate_type_ok.rs | 2 +- .../eii/type_checking/cross_crate_wrong_ty.rs | 2 +- .../type_checking/cross_crate_wrong_ty.stderr | 4 +- 15 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 tests/ui/eii/safe_eii_unsafe_impl.rs create mode 100644 tests/ui/eii/safe_eii_unsafe_impl.stderr diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index caa554c4aa669..34c7137b8676d 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -155,7 +155,10 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::attrs::EiiImpl { span: self.lower_span(*span), inner_span: self.lower_span(*inner_span), - impl_marked_unsafe: self.lower_safety(*impl_safety, hir::Safety::Safe).is_unsafe(), + impl_unsafe_span: match *impl_safety { + Safety::Unsafe(span) => Some(self.lower_span(span)), + Safety::Safe(_) | Safety::Default => None, + }, is_default: *is_default, resolution, } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index db492475a3aa4..b438cdc212bc6 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -40,7 +40,7 @@ pub enum EiiImplResolution { #[derive(Copy, Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub struct EiiImpl { pub resolution: EiiImplResolution, - pub impl_marked_unsafe: bool, + pub impl_unsafe_span: Option, pub span: Span, pub inner_span: Span, pub is_default: bool, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f9b508476689e..8f835c9e502f2 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -473,7 +473,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } fn check_eii_impl(&self, impls: &[EiiImpl], target: Target) { - for EiiImpl { span, inner_span, resolution, impl_marked_unsafe, is_default: _ } in impls { + for EiiImpl { span, inner_span, resolution, impl_unsafe_span, is_default: _ } in impls { match target { Target::Fn | Target::Static => {} _ => { @@ -481,35 +481,48 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - let needs_unsafe = match resolution { - EiiImplResolution::Macro(eii_macro) => { - find_attr!(self.tcx, *eii_macro, EiiDeclaration(EiiDecl { impl_unsafe, .. }) if *impl_unsafe) - } - EiiImplResolution::Known(foreign_item_did) => { - let foreign_item_did = *foreign_item_did; - self.tcx - .externally_implementable_items(foreign_item_did.krate) - .get(&foreign_item_did) - .map(|(decl, _)| decl.impl_unsafe) - .unwrap_or(false) - } - EiiImplResolution::Error(_) => false, + let impl_unsafe = match resolution { + EiiImplResolution::Macro(eii_macro) => find_attr!( + self.tcx, + *eii_macro, + EiiDeclaration(EiiDecl { impl_unsafe, .. }) => *impl_unsafe + ), + EiiImplResolution::Known(foreign_item_did) => self + .tcx + .externally_implementable_items(foreign_item_did.krate) + .get(foreign_item_did) + .map(|(decl, _)| decl.impl_unsafe), + EiiImplResolution::Error(_) => None, + }; + let Some(needs_unsafe) = impl_unsafe else { + continue; }; - if needs_unsafe && !impl_marked_unsafe { - let name = match resolution { - EiiImplResolution::Macro(eii_macro) => self.tcx.item_name(*eii_macro), - EiiImplResolution::Known(def_id) => self.tcx.item_name(*def_id), - EiiImplResolution::Error(_) => unreachable!(), - }; - self.dcx().emit_err(diagnostics::EiiImplRequiresUnsafe { - span: *span, - name, - suggestion: diagnostics::EiiImplRequiresUnsafeSuggestion { - left: inner_span.shrink_to_lo(), - right: inner_span.shrink_to_hi(), - }, - }); + let name = match resolution { + EiiImplResolution::Macro(eii_macro) => self.tcx.item_name(*eii_macro), + EiiImplResolution::Known(def_id) => self.tcx.item_name(*def_id), + EiiImplResolution::Error(_) => unreachable!(), + }; + + match (needs_unsafe, *impl_unsafe_span) { + (true, None) => { + self.dcx().emit_err(diagnostics::EiiImplRequiresUnsafe { + span: *span, + name, + suggestion: diagnostics::EiiImplRequiresUnsafeSuggestion { + left: inner_span.shrink_to_lo(), + right: inner_span.shrink_to_hi(), + }, + }); + } + (false, Some(unsafe_span)) => { + self.dcx().emit_err(diagnostics::EiiImplCannotBeUnsafe { + impl_span: *span, + unsafe_span, + name, + }); + } + _ => {} } } } diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 34fa0b264319d..d8e33f87966c8 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -515,10 +515,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for NoMainErr { if self.add_teach_note { diag.note(msg!("if you don't know the basics of Rust, you can go look to the Rust Book to get started: https://doc.rust-lang.org/book/")); } + diag } } - pub(crate) struct DuplicateLangItem { pub local_span: Option, pub lang_item_name: Symbol, @@ -1082,6 +1082,16 @@ pub(crate) struct EiiImplRequiresUnsafeSuggestion { pub right: Span, } +#[derive(Diagnostic)] +#[diag("`{$name}` is not unsafe to implement")] +pub(crate) struct EiiImplCannotBeUnsafe { + #[primary_span] + pub impl_span: Span, + #[label("`unsafe` is not allowed here")] + pub unsafe_span: Span, + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("`#[{$name}]` {$kind} required, but not found")] pub(crate) struct EiiWithoutImpl { diff --git a/tests/ui/eii/default/auxiliary/impl1.rs b/tests/ui/eii/default/auxiliary/impl1.rs index 3510ea1eb3f27..5ed6ab639a883 100644 --- a/tests/ui/eii/default/auxiliary/impl1.rs +++ b/tests/ui/eii/default/auxiliary/impl1.rs @@ -6,7 +6,7 @@ extern crate decl_with_default as decl; -#[unsafe(decl::eii1)] //~ ERROR multiple implementations of `#[eii1]` +#[decl::eii1] //~ ERROR multiple implementations of `#[eii1]` fn other(x: u64) { println!("1{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl1.rs b/tests/ui/eii/duplicate/auxiliary/impl1.rs index ffa2cd79818cc..86017eec20763 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl1.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl1.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("1{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl2.rs b/tests/ui/eii/duplicate/auxiliary/impl2.rs index 592234f53fd40..928433d832c2d 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl2.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl2.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("2{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl3.rs b/tests/ui/eii/duplicate/auxiliary/impl3.rs index 5e9fdaba0bb6b..3f0be04377fce 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl3.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl3.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("3{x}"); } diff --git a/tests/ui/eii/duplicate/auxiliary/impl4.rs b/tests/ui/eii/duplicate/auxiliary/impl4.rs index 068cc18d78e6a..959746f5117f3 100644 --- a/tests/ui/eii/duplicate/auxiliary/impl4.rs +++ b/tests/ui/eii/duplicate/auxiliary/impl4.rs @@ -5,7 +5,7 @@ extern crate decl; -#[unsafe(decl::eii1)] +#[decl::eii1] fn other(x: u64) { println!("4{x}"); } diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.rs b/tests/ui/eii/duplicate/dylib_default_duplicate.rs index 0ac3669715f85..28c79ae35dd97 100644 --- a/tests/ui/eii/duplicate/dylib_default_duplicate.rs +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.rs @@ -11,7 +11,7 @@ extern crate dylib_default; -#[unsafe(dylib_default::eii1)] +#[dylib_default::eii1] fn other(x: u64) { //~^ ERROR multiple implementations of `#[eii1]` println!("1{x}"); diff --git a/tests/ui/eii/safe_eii_unsafe_impl.rs b/tests/ui/eii/safe_eii_unsafe_impl.rs new file mode 100644 index 0000000000000..e30be843d5f76 --- /dev/null +++ b/tests/ui/eii/safe_eii_unsafe_impl.rs @@ -0,0 +1,14 @@ +// Tests that safe EIIs reject `unsafe(...)` implementation attributes. +#![feature(extern_item_impls)] + +#[eii] +fn foo(x: u64) -> u64; + +#[unsafe(foo)] //~ ERROR `foo` is not unsafe to implement +fn foo_impl(x: u64) -> u64 { + x +} + +fn main() { + foo(0); +} diff --git a/tests/ui/eii/safe_eii_unsafe_impl.stderr b/tests/ui/eii/safe_eii_unsafe_impl.stderr new file mode 100644 index 0000000000000..78501e5c0080e --- /dev/null +++ b/tests/ui/eii/safe_eii_unsafe_impl.stderr @@ -0,0 +1,10 @@ +error: `foo` is not unsafe to implement + --> $DIR/safe_eii_unsafe_impl.rs:7:1 + | +LL | #[unsafe(foo)] + | ^^------^^^^^^ + | | + | `unsafe` is not allowed here + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/type_checking/cross_crate_type_ok.rs b/tests/ui/eii/type_checking/cross_crate_type_ok.rs index 157d659bfd64b..41b756095b074 100644 --- a/tests/ui/eii/type_checking/cross_crate_type_ok.rs +++ b/tests/ui/eii/type_checking/cross_crate_type_ok.rs @@ -9,7 +9,7 @@ extern crate cross_crate_eii_declaration; -#[unsafe(cross_crate_eii_declaration::foo)] +#[cross_crate_eii_declaration::foo] fn other(x: u64) -> u64 { x } diff --git a/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs b/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs index 7ae1a5995a86e..7e3306870a2e2 100644 --- a/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs +++ b/tests/ui/eii/type_checking/cross_crate_wrong_ty.rs @@ -8,7 +8,7 @@ extern crate cross_crate_eii_declaration; -#[unsafe(cross_crate_eii_declaration::foo)] +#[cross_crate_eii_declaration::foo] fn other() -> u64 { //~^ ERROR `other` has 0 parameters but #[foo] requires it to have 1 0 diff --git a/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr b/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr index b2190a075b631..fd0141754b819 100644 --- a/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr +++ b/tests/ui/eii/type_checking/cross_crate_wrong_ty.stderr @@ -1,8 +1,8 @@ error[E0806]: `other` has 0 parameters but #[foo] requires it to have 1 --> $DIR/cross_crate_wrong_ty.rs:12:1 | -LL | #[unsafe(cross_crate_eii_declaration::foo)] - | ------------------------------------------- required because of this attribute +LL | #[cross_crate_eii_declaration::foo] + | ----------------------------------- required because of this attribute LL | fn other() -> u64 { | ^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0 | From 4bd52e9b0997811bab7232d3789fdd5b6ab8989d Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:52:51 +0200 Subject: [PATCH 20/80] Remove redundant `#[rustc_paren_sugar]` feature gate --- .../src/attributes/traits.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 3 --- compiler/rustc_hir_analysis/src/diagnostics.rs | 10 ---------- .../feature-gate-unboxed-closures.rs | 4 ++++ .../feature-gate-unboxed-closures.stderr | 18 ++++++++++++++---- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index a2348eef975cf..69bdccb85c5cd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -53,7 +53,7 @@ pub(crate) struct RustcParenSugarParser; impl NoArgsAttributeParser for RustcParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const STABILITY: AttributeStability = unstable!(rustc_attrs); + const STABILITY: AttributeStability = unstable!(unboxed_closures); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcParenSugar; } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 605b6e3751ef3..6c393ed3dfc6b 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -947,9 +947,6 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let attrs = tcx.get_all_attrs(def_id); let paren_sugar = find_attr!(attrs, RustcParenSugar); - if paren_sugar && !tcx.features().unboxed_closures() { - tcx.dcx().emit_err(diagnostics::ParenSugarAttribute { span: item.span }); - } // Only regular traits can be marker. let is_marker = !is_alias && find_attr!(attrs, Marker); diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index a6e274ceb4bc6..0ea353cf14cee 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -862,16 +862,6 @@ pub(crate) struct EnumDiscriminantOverflowed { pub wrapped_discr: String, } -#[derive(Diagnostic)] -#[diag( - "the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation" -)] -#[help("add `#![feature(unboxed_closures)]` to the crate attributes to use it")] -pub(crate) struct ParenSugarAttribute { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("use of SIMD type{$snip} in FFI is highly experimental and may result in invalid code")] #[help("add `#![feature(simd_ffi)]` to the crate attributes to enable")] diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs index 74cc20c581cb9..fd4ea70bb350c 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs @@ -2,6 +2,10 @@ struct Test; + +#[rustc_paren_sugar] +//~^ ERROR the `rustc_paren_sugar` attribute is an experimental feature +pub trait Fn {} impl FnOnce<(u32, u32)> for Test { //~^ ERROR the precise format of `Fn`-family traits' type parameters is subject to change //~| ERROR manual implementations of `FnOnce` are experimental diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr index 36932c1f86f9f..66a3a92c07901 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr @@ -1,5 +1,15 @@ +error[E0658]: the `rustc_paren_sugar` attribute is an experimental feature + --> $DIR/feature-gate-unboxed-closures.rs:6:3 + | +LL | #[rustc_paren_sugar] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #29625 for more information + = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0658]: the extern "rust-call" ABI is experimental and subject to change - --> $DIR/feature-gate-unboxed-closures.rs:10:12 + --> $DIR/feature-gate-unboxed-closures.rs:14:12 | LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { | ^^^^^^^^^^^ @@ -9,7 +19,7 @@ LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ @@ -19,14 +29,14 @@ LL | impl FnOnce<(u32, u32)> for Test { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0183]: manual implementations of `FnOnce` are experimental - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ manual implementations of `FnOnce` are experimental | = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors Some errors have detailed explanations: E0183, E0658. For more information about an error, try `rustc --explain E0183`. From 75529d4b8e485945858e2811d2db0c898b0ec450 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 24 Jul 2026 16:48:22 +0300 Subject: [PATCH 21/80] rustc_parse: Stop returning `Option` from statement parsing --- compiler/rustc_attr_parsing/src/parser.rs | 6 +- compiler/rustc_builtin_macros/src/cfg_eval.rs | 5 +- compiler/rustc_expand/src/expand.rs | 4 +- compiler/rustc_parse/src/parser/expr.rs | 6 +- compiler/rustc_parse/src/parser/mod.rs | 9 +- .../rustc_parse/src/parser/nonterminal.rs | 2 +- compiler/rustc_parse/src/parser/stmt.rs | 102 +++++++++--------- 7 files changed, 58 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index ed926330ed289..c4b2a5b509051 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -660,9 +660,8 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable // when macro metavariables are involved. let snapshot = self.parser.create_snapshot_for_diagnostic(); - let stmt = self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false); - match stmt { - Ok(Some(stmt)) => { + match self.parser.parse_stmt_without_recovery(false, ForceCollect::No, false) { + Ok(stmt) => { // The user tried to write something like // `#[deprecated(note = concat!("a", "b"))]`. err.descr = stmt.kind.descr().to_string(); @@ -692,7 +691,6 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { }); } } - Ok(None) => {} Err(e) => { e.cancel(); self.parser.restore_snapshot(snapshot); diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 60854d67ec946..2fb89939181e6 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -126,9 +126,8 @@ impl CfgEval<'_> { Annotatable::ForeignItem(self.flat_map_foreign_item(item).pop().unwrap()) } Annotatable::Stmt(_) => { - let stmt = parser - .parse_stmt_without_recovery(false, ForceCollect::Yes, false)? - .unwrap(); + let stmt = + parser.parse_stmt_without_recovery(false, ForceCollect::Yes, false)?; Annotatable::Stmt(Box::new(self.flat_map_stmt(stmt).pop().unwrap())) } Annotatable::Expr(_) => { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 0c775e22ccf23..4f55e0506e799 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1120,9 +1120,7 @@ pub fn parse_ast_fragment<'a>( let mut stmts = SmallVec::new(); // Won't make progress on a `}`. while this.token != token::Eof && this.token != token::CloseBrace { - if let Some(stmt) = this.parse_full_stmt(AttemptLocalParseRecovery::Yes)? { - stmts.push(stmt); - } + stmts.push(this.parse_full_stmt(AttemptLocalParseRecovery::Yes)?); } AstFragment::Stmts(stmts) } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 9798138b48b80..94d3c7dccfc6c 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -3331,13 +3331,9 @@ impl<'a> Parser<'a> { self.restore_snapshot(pre_pat_snapshot); match self.parse_stmt_without_recovery(true, ForceCollect::No, false) { // Consume statements for as long as possible. - Ok(Some(stmt)) => { + Ok(stmt) => { stmts.push(stmt); } - Ok(None) => { - self.restore_snapshot(start_snapshot); - break; - } // We couldn't parse either yet another statement missing it's // enclosing block nor the next arm's pattern or closing brace. Err(stmt_err) => { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index fc539405249a6..ef62316abf941 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -987,12 +987,9 @@ impl<'a> Parser<'a> { let initial_semicolon = self.token.span; while self.eat(exp!(Semi)) { - let _ = self - .parse_stmt_without_recovery(false, ForceCollect::No, false) - .unwrap_or_else(|e| { - e.cancel(); - None - }); + if let Err(e) = self.parse_stmt_without_recovery(false, ForceCollect::No, false) { + e.cancel(); + } } expect_err diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index e4602abf7779f..9f9545c194082 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -139,7 +139,7 @@ impl<'a> Parser<'a> { this.parse_block().map(|block| WithTokens::new(block)) })?)) } - NonterminalKind::Stmt => match self.parse_stmt(ForceCollect::Yes)? { + NonterminalKind::Stmt => match self.parse_stmt_nonterminal(ForceCollect::Yes) { Some(stmt) => Ok(ParseNtResult::Stmt(Box::new(stmt))), None => { Err(self.dcx().create_err(UnexpectedNonterminal::Statement(self.token.span))) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 27a047f598513..6dddc8b2407dc 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -27,18 +27,22 @@ use crate::diagnostics::{self, MalformedLoopLabel}; use crate::exp; impl<'a> Parser<'a> { - /// Parses a statement. This stops just before trailing semicolons on everything but items. + /// Parses a statement nonterminal, which has a peculiar syntax preserved for backward + /// compatibility. The parsing stops just before trailing semicolons on everything but items. /// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed. /// /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of /// whether or not we have attributes. // Public for rustfmt usage. - pub fn parse_stmt(&mut self, force_collect: ForceCollect) -> PResult<'a, Option> { - Ok(self.parse_stmt_without_recovery(false, force_collect, false).unwrap_or_else(|e| { - e.emit(); - self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); - None - })) + pub fn parse_stmt_nonterminal(&mut self, force_collect: ForceCollect) -> Option { + match self.parse_stmt_without_recovery(false, force_collect, false) { + Ok(stmt) => Some(stmt), + Err(e) => { + e.emit(); + self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); + None + } + } } /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of @@ -49,19 +53,18 @@ impl<'a> Parser<'a> { capture_semi: bool, force_collect: ForceCollect, force_full_expr: bool, - ) -> PResult<'a, Option> { + ) -> PResult<'a, Stmt> { let pre_attr_pos = self.collect_pos(); let attrs = self.parse_outer_attributes()?; let lo = self.token.span; - if let Some(stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { + if let Some(mut stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { this.parse_stmt_without_recovery(false, ForceCollect::Yes, false) }) { - let mut stmt = stmt.expect("an actual statement"); stmt.visit_attrs(|stmt_attrs| { attrs.prepend_to_nt_inner(stmt_attrs); }); - return Ok(Some(stmt)); + return Ok(stmt); } if self.token.is_keyword(kw::Mut) && self.is_keyword_ahead(1, &[kw::Let]) { @@ -161,9 +164,12 @@ impl<'a> Parser<'a> { self.mk_stmt(lo.to(item.span), StmtKind::Item(Box::new(item))) } else if self.eat(exp!(Semi)) { // Do not attempt to parse an expression if we're done here. - self.error_outer_attrs(attrs); + self.error_outer_attrs(attrs)?; self.mk_stmt(lo, StmtKind::Empty) - } else if self.token != token::CloseBrace { + } else if self.token == token::CloseBrace { + self.error_outer_attrs(attrs)?; + self.dcx().span_bug(self.token.span, "don't parse a statement if you see `}`"); + } else { // Remainder are line-expr stmts. This is similar to the `parse_stmt_path_start` case // above. let restrictions = @@ -185,13 +191,10 @@ impl<'a> Parser<'a> { .emit_err(diagnostics::AssignmentElseNotAllowed { span: e.span.to(bl.span) }); } self.mk_stmt(lo.to(e.span), StmtKind::Expr(e)) - } else { - self.error_outer_attrs(attrs); - return Ok(None); }; self.maybe_augment_stashed_expr_in_pats_with_suggestions(&stmt); - Ok(Some(stmt)) + Ok(stmt) } fn parse_stmt_path_start(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> { @@ -272,20 +275,21 @@ impl<'a> Parser<'a> { /// Error on outer attributes in this context. /// Also error if the previous token was a doc comment. - fn error_outer_attrs(&self, attrs: AttrWrapper) { - if !attrs.is_empty() - && let attrs @ [.., last] = &*attrs.take_for_recovery(self.psess) - { - if last.is_doc_comment() { - self.dcx().emit_err(diagnostics::DocCommentDoesNotDocumentAnything { - span: last.span, - missing_comma: None, - }); - } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) { - self.dcx() - .emit_err(diagnostics::ExpectedStatementAfterOuterAttr { span: last.span }); - } + fn error_outer_attrs(&self, attrs: AttrWrapper) -> PResult<'a, ()> { + if attrs.is_empty() { + return Ok(()); } + let attrs = attrs.take_for_recovery(self.psess); + let last = attrs.last().unwrap(); + Err(if last.is_doc_comment() { + self.dcx().create_err(diagnostics::DocCommentDoesNotDocumentAnything { + span: last.span, + missing_comma: None, + }) + } else { + assert_eq!(last.style, AttrStyle::Outer); + self.dcx().create_err(diagnostics::ExpectedStatementAfterOuterAttr { span: last.span }) + }) } fn recover_stmt_local_after_let( @@ -521,6 +525,10 @@ impl<'a> Parser<'a> { let sp = self.token.span; let mut err = self.dcx().struct_span_err(sp, msg); self.label_expected_raw_ref(&mut err); + err.span_label(sp, "expected `{`"); + if self.token == token::CloseBrace { + return err; + } let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon @@ -547,13 +555,13 @@ impl<'a> Parser<'a> { // since we want to protect against: // `if 1 1 + 1 {` being suggested as `if { 1 } 1 + 1 {` // + + - Ok(Some(_)) + Ok(_) if (!self.token.is_keyword(kw::Else) && self.look_ahead(1, |t| t == &token::OpenBrace)) || do_not_suggest_help => {} // Do not suggest `if foo println!("") {;}` (as would be seen in test for #46836). - Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {} - Ok(Some(stmt)) => { + Ok(Stmt { kind: StmtKind::Empty, .. }) => {} + Ok(stmt) => { let stmt_own_line = self.psess.source_map().is_line_before_span_empty(sp); let stmt_span = if stmt_own_line && self.eat(exp!(Semi)) { // Expand the span to include the semicolon. @@ -571,9 +579,7 @@ impl<'a> Parser<'a> { Err(e) => { e.delay_as_bug(); } - _ => {} } - err.span_label(sp, "expected `{`"); err } @@ -773,17 +779,12 @@ impl<'a> Parser<'a> { let guar = err.emit(); self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore); - Some(self.mk_stmt_err(self.token.span, guar)) + self.mk_stmt_err(self.token.span, guar) } Ok(stmt) => stmt, Err(err) => return Err(err), }; - if let Some(stmt) = stmt { - stmts.push(stmt); - } else { - // Found only `;` or `}`. - continue; - }; + stmts.push(stmt); } Ok(self.mk_block(stmts, s, lo.to(self.prev_token.span))) } @@ -943,10 +944,7 @@ impl<'a> Parser<'a> { } /// Parses a statement, including the trailing semicolon. - pub fn parse_full_stmt( - &mut self, - recover: AttemptLocalParseRecovery, - ) -> PResult<'a, Option> { + pub fn parse_full_stmt(&mut self, recover: AttemptLocalParseRecovery) -> PResult<'a, Stmt> { // Skip looking for a trailing semicolon when we have a metavar seq. if let Some(stmt) = self.eat_metavar_seq(MetaVarKind::Stmt, |this| { // Why pass `true` for `force_full_expr`? Statement expressions are less expressive @@ -959,14 +957,10 @@ impl<'a> Parser<'a> { // will reparse successfully. this.parse_stmt_without_recovery(false, ForceCollect::No, true) }) { - let stmt = stmt.expect("an actual statement"); - return Ok(Some(stmt)); + return Ok(stmt); } - let Some(mut stmt) = self.parse_stmt_without_recovery(true, ForceCollect::No, false)? - else { - return Ok(None); - }; + let mut stmt = self.parse_stmt_without_recovery(true, ForceCollect::No, false)?; let mut eat_semi = true; let mut add_semi_to_stmt = false; @@ -1086,7 +1080,7 @@ impl<'a> Parser<'a> { StmtKind::Expr(_) | StmtKind::MacCall(_) => {} StmtKind::Let(local) => { if self.try_recover_let_missing_semi(local).is_some() { - return Ok(Some(stmt)); + return Ok(stmt); } if let Err(mut e) = self.expect_semi() { // We might be at the `,` in `let x = foo;`. Try to recover. @@ -1159,7 +1153,7 @@ impl<'a> Parser<'a> { } stmt.span = stmt.span.to(self.prev_token.span); - Ok(Some(stmt)) + Ok(stmt) } pub(super) fn mk_block( From a32fa442efe45fb9af7a5260b63ede856edc8e7f Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Fri, 24 Jul 2026 12:55:03 -0400 Subject: [PATCH 22/80] Updated expect messages for CString struct and method documentation --- library/alloc/src/ffi/c_str.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index e6e6fcf5420f3..b340cf9566f2e 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -84,7 +84,7 @@ use crate::vec::Vec; /// /// // We are certain that our string doesn't have 0 bytes in the middle, /// // so we can .expect() -/// let c_to_print = CString::new("Hello, world!").expect("CString::new failed"); +/// let c_to_print = CString::new("Hello, world!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// my_printer(c_to_print.as_ptr()); /// } @@ -242,7 +242,7 @@ impl CString { /// /// extern "C" { fn puts(s: *const c_char); } /// - /// let to_print = CString::new("Hello!").expect("CString::new failed"); + /// let to_print = CString::new("Hello!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// puts(to_print.as_ptr()); /// } @@ -466,12 +466,12 @@ impl CString { /// use std::ffi::CString; /// /// let valid_utf8 = vec![b'f', b'o', b'o']; - /// let cstring = CString::new(valid_utf8).expect("CString::new failed"); - /// assert_eq!(cstring.into_string().expect("into_string() call failed"), "foo"); + /// let cstring = CString::new(valid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// assert_eq!(cstring.into_string().expect("we provided bytes that are valid UTF-8, so `into_string` should not fail"), "foo"); /// /// let invalid_utf8 = vec![b'f', 0xff, b'o', b'o']; - /// let cstring = CString::new(invalid_utf8).expect("CString::new failed"); - /// let err = cstring.into_string().err().expect("into_string().err() failed"); + /// let cstring = CString::new(invalid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// let err = cstring.into_string().expect_err("we provided bytes that are invalid UTF-8, so `into_string` should fail"); /// assert_eq!(err.utf8_error().valid_up_to(), 1); /// ``` #[stable(feature = "cstring_into", since = "1.7.0")] @@ -577,7 +577,7 @@ impl CString { /// let c_string = CString::from(c"foo"); /// let cstr = c_string.as_c_str(); /// assert_eq!(cstr, - /// CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed")); + /// CStr::from_bytes_with_nul(b"foo\0").expect("we provided bytes that has one NUL byte exactly at the end, so CStr::from_bytes_with_nul should not fail")); /// ``` #[inline] #[must_use] @@ -660,7 +660,7 @@ impl CString { /// use std::ffi::CString; /// assert_eq!( /// CString::from_vec_with_nul(b"abc\0".to_vec()) - /// .expect("CString::from_vec_with_nul failed"), + /// .expect("we provided bytes that has one NUL byte exactly at the end, so CString::from_vec_with_nul should not fail"), /// c"abc".to_owned() /// ); /// ``` From 36ef11a72c9fb1a5de60b0c8880c025f87111ddd Mon Sep 17 00:00:00 2001 From: Cole Kauder-McMurrich Date: Sat, 25 Jul 2026 00:50:42 -0400 Subject: [PATCH 23/80] Update expect messages in library/alloc/boxed.rs and library/alloc/string.rs to follow the style guide --- library/alloc/src/boxed.rs | 2 +- library/alloc/src/string.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 58996703023ce..5190baaab8a26 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1368,7 +1368,7 @@ impl Box { /// /// unsafe { /// let non_null = NonNull::new(alloc(Layout::new::()).cast::()) - /// .expect("allocation failed"); + /// .expect("alloc should have successfully allocated memory"); /// // In general .write is required to avoid attempting to destruct /// // the (uninitialized) previous contents of `non_null`. /// non_null.write(5); diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 6c19ba816050d..cc321660e6ea4 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -1314,7 +1314,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -1355,7 +1355,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { From 05bb184cd682e2ffcedf904c7dd16994dcc39e0b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 11:41:29 +0200 Subject: [PATCH 24/80] sembr src/variance.md --- src/doc/rustc-dev-guide/src/variance.md | 189 ++++++++++++------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index 7aa0140715517..a8a72b44e87ad 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,30 +4,32 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime -parameters. The algorithm is taken from Section 4 of the paper ["Taming the +During type checking we must infer the variance of type and lifetime parameters. +The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf -This inference is explicitly designed *not* to consider the uses of -types within code. To determine the variance of type parameters +This inference is explicitly designed *not* to consider the uses of types within code. +To determine the variance of type parameters defined on type `X`, we only consider the definition of the type `X` and the definitions of any types it references. -We only infer variance for type parameters found on *data types* -like structs and enums. In these cases, there is a fairly straightforward -explanation for what variance means. The variance of the type -or lifetime parameters defines whether `T` is a subtype of `T` -(resp. `T<'a>` and `T<'b>`) based on the relationship of `A` and `B` -(resp. `'a` and `'b`). +We only infer variance for type parameters found on *data types* like structs and enums. +In these cases, there is a fairly straightforward explanation for what variance means. +The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` +(resp. +`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. +`'a` and `'b`). We do not infer variance for type parameters found on traits, functions, -or impls. Variance on trait parameters can indeed make sense +or impls. +Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in -meaning and not that useful in practice, so we removed it. See the -[addendum] for some details. Variances on function/impl parameters, on the +meaning and not that useful in practice, so we removed it. +See the [addendum] for some details. +Variances on function/impl parameters, on the other hand, doesn't make sense because these parameters are instantiated and then forgotten, they don't persist in types or compiled byproducts. @@ -44,13 +46,12 @@ then forgotten, they don't persist in types or compiled byproducts. ## The algorithm -The basic idea is quite straightforward. We iterate over the types -defined and, for each use of a type parameter `X`, accumulate a -constraint indicating that the variance of `X` must be valid for the -variance of that use site. We then iteratively refine the variance of -`X` until all constraints are met. There is *always* a solution, because at -the limit we can declare all type parameters to be invariant and all -constraints will be satisfied. +The basic idea is quite straightforward. +We iterate over the types defined and, for each use of a type parameter `X`, accumulate a +constraint indicating that the variance of `X` must be valid for the variance of that use site. +We then iteratively refine the variance of `X` until all constraints are met. +There is *always* a solution, because at +the limit we can declare all type parameters to be invariant and all constraints will be satisfied. As a simple example, consider: @@ -71,8 +72,8 @@ Here, we will generate the constraints: These indicate that (1) the variance of A must be at most covariant; (2) the variance of B must be at most contravariant; and (3, 4) the -variance of C must be at most covariant *and* contravariant. All of these -results are based on a variance lattice defined as follows: +variance of C must be at most covariant *and* contravariant. +All of these results are based on a variance lattice defined as follows: ```text * Top (bivariant) @@ -80,14 +81,13 @@ results are based on a variance lattice defined as follows: o Bottom (invariant) ``` -Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the -optimal solution. Note that there is always a naive solution which -just declares all variables to be invariant. +Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the optimal solution. +Note that there is always a naive solution which just declares all variables to be invariant. -You may be wondering why fixed-point iteration is required. The reason -is that the variance of a use site may itself be a function of the -variance of other type parameters. In full generality, our constraints -take the form: +You may be wondering why fixed-point iteration is required. +The reason is that the variance of a use site may itself be a function of the +variance of other type parameters. +In full generality, our constraints take the form: ```text V(X) <= Term @@ -95,8 +95,8 @@ Term := + | - | * | o | V(X) | Term x Term ``` Here the notation `V(X)` indicates the variance of a type/region -parameter `X` with respect to its defining class. `Term x Term` -represents the "variance transform" as defined in the paper: +parameter `X` with respect to its defining class. +`Term x Term` represents the "variance transform" as defined in the paper: > If the variance of a type variable `X` in type expression `E` is `V2` and the definition-site variance of the corresponding type parameter @@ -112,19 +112,21 @@ struct Foo { ... } ``` you might wonder whether the variance of `T` with respect to `Bar` affects the -variance `T` with respect to `Foo`. I claim no. The reason: assume that `T` is -invariant with respect to `Bar` but covariant with respect to `Foo`. And then -we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while -`X : Bar`, `Y : Bar` does not hold. In that case, the upcast will be illegal, +variance `T` with respect to `Foo`. +I claim no. + The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. +However, while `X : Bar`, `Y : Bar` does not hold. + In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type -`Foo` is itself just not well-formed. Basically we get to assume -well-formedness of all types involved before considering variance. +`Foo` is itself just not well-formed. +Basically we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management Because variance is a whole-crate inference, its dependency graph -can become quite muddled if we are not careful. To resolve this, we refactor -into two queries: +can become quite muddled if we are not careful. +To resolve this, we refactor into two queries: - `crate_variances` computes the variance for all items in the current crate. - `variances_of` accesses the variance for an individual reading; it @@ -133,7 +135,8 @@ into two queries: If you limit yourself to reading `variances_of`, your code will only depend then on the inference of that particular item. -Ultimately, this setup relies on the [red-green algorithm][rga]. In particular, +Ultimately, this setup relies on the [red-green algorithm][rga]. +In particular, every variance query effectively depends on all type definitions in the entire crate (through `crate_variances`), but since most changes will not result in a change to the actual results from variance inference, the `variances_of` query @@ -145,19 +148,18 @@ will wind up being considered green after it is re-evaluated. ## Addendum: Variance on traits -As mentioned above, we used to permit variance on traits. This was -computed based on the appearance of trait type parameters in +As mentioned above, we used to permit variance on traits. +This was computed based on the appearance of trait type parameters in method signatures and was used to represent the compatibility of -vtables in trait objects (and also "virtual" vtables or dictionary -in trait bounds). One complication was that variance for +vtables in trait objects (and also "virtual" vtables or dictionary in trait bounds). +One complication was that variance for associated types is less obvious, since they can be projected out and put to myriad uses, so it's not clear when it is safe to allow -`X::Bar` to vary (or indeed just what that means). Moreover (as -covered below) all inputs on any trait with an associated type had -to be invariant, limiting the applicability. Finally, the -annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all -trait type parameters had a variance were confusing and annoying -for little benefit. +`X::Bar` to vary (or indeed just what that means). +Moreover (as covered below) all inputs on any trait with an associated type had +to be invariant, limiting the applicability. +Finally, the annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all +trait type parameters had a variance were confusing and annoying for little benefit. Just for historical reference, I am going to preserve some text indicating how one could interpret variance and trait matching. @@ -166,13 +168,12 @@ one could interpret variance and trait matching. Just as with structs and enums, we can decide the subtyping relationship between two object types `&Trait` and `&Trait` -based on the relationship of `A` and `B`. Note that for object -types we ignore the `Self` type parameter – it is unknown, and +based on the relationship of `A` and `B`. +Note that for object types we ignore the `Self` type parameter – it is unknown, and the nature of dynamic dispatch ensures that we will always call a -function that is expected the appropriate `Self` type. However, we -must be careful with the other type parameters, or else we could -end up calling a function that is expecting one type but provided -another. +function that is expected the appropriate `Self` type. +However, we must be careful with the other type parameters, or else we could +end up calling a function that is expecting one type but provided another. To see what I mean, consider a trait like so: @@ -184,29 +185,29 @@ trait ConvertTo { Intuitively, If we had one object `O=&ConvertTo` and another `S=&ConvertTo`, then `S <: O` because `String <: Object` -(presuming Java-like "string" and "object" types, my go to examples -for subtyping). The actual algorithm would be to compare the +(presuming Java-like "string" and "object" types, my go to examples for subtyping). +The actual algorithm would be to compare the (explicit) type parameters pairwise respecting their variance: here, the type parameter A is covariant (it appears only in a return position), and hence we require that `String <: Object`. You'll note though that we did not consider the binding for the -(implicit) `Self` type parameter: in fact, it is unknown, so that's -good. The reason we can ignore that parameter is precisely because we +(implicit) `Self` type parameter: in fact, it is unknown, so that's good. +The reason we can ignore that parameter is precisely because we don't need to know its value until a call occurs, and at that time (as you said) the dynamic nature of virtual dispatch means the code we run will be correct for whatever value `Self` happens to be bound to for -the particular object whose method we called. `Self` is thus different -from `A`, because the caller requires that `A` be known in order to -know the return type of the method `convertTo()`. (As an aside, we -have rules preventing methods where `Self` appears outside of the +the particular object whose method we called. +`Self` is thus different from `A`, because the caller requires that `A` be known in order to +know the return type of the method `convertTo()`. +(As an aside, we have rules preventing methods where `Self` appears outside of the receiver position from being called via an object.) ### Trait variance and vtable resolution -But traits aren't only used with objects. They're also used when -deciding whether a given impl satisfies a given trait bound. To set the -scene here, imagine I had a function: +But traits aren't only used with objects. +They're also used when deciding whether a given impl satisfies a given trait bound. +To set the scene here, imagine I had a function: ```rust,ignore fn convertAll>(v: &[T]) { ... } @@ -218,8 +219,8 @@ Now imagine that I have an implementation of `ConvertTo` for `Object`: impl ConvertTo for Object { ... } ``` -And I want to call `convertAll` on an array of strings. Suppose -further that for whatever reason I specifically supply the value of +And I want to call `convertAll` on an array of strings. +Suppose further that for whatever reason I specifically supply the value of `String` for the type parameter `T`: ```rust,ignore @@ -227,26 +228,25 @@ let mut vector = vec!["string", ...]; convertAll::(vector); ``` -Is this legal? To put another way, can we apply the `impl` for -`Object` to the type `String`? The answer is yes, but to see why -we have to expand out what will happen: +Is this legal? +To put another way, can we apply the `impl` for `Object` to the type `String`? +The answer is yes, but to see why we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` -- It will then call the impl of `convertTo()` that is intended - for use with objects. This has the type `fn(self: &Object) -> i32`. +- It will then call the impl of `convertTo()` that is intended for use with objects. + This has the type `fn(self: &Object) -> i32`. - It is OK to provide a value for `self` of type `&String` because - `&String <: &Object`. + It is OK to provide a value for `self` of type `&String` because `&String <: &Object`. OK, so intuitively we want this to be legal, so let's bring this back -to variance and see whether we are computing the correct result. We -must first figure out how to phrase the question "is an impl for +to variance and see whether we are computing the correct result. +We must first figure out how to phrase the question "is an impl for `Object,i32` usable where an impl for `String,i32` is expected?" -Maybe it's helpful to think of a dictionary-passing implementation of -type classes. In that case, `convertAll()` takes an implicit parameter -representing the impl. In short, we *have* an impl of type: +Maybe it's helpful to think of a dictionary-passing implementation of type classes. +In that case, `convertAll()` takes an implicit parameter representing the impl. +In short, we *have* an impl of type: ```text V_O = ConvertTo for Object @@ -259,9 +259,10 @@ V_S = ConvertTo for String ``` As with any argument, this is legal if the type of the value given -(`V_O`) is a subtype of the type expected (`V_S`). So is `V_O <: V_S`? -The answer will depend on the variance of the various parameters. In -this case, because the `Self` parameter is contravariant and `A` is +(`V_O`) is a subtype of the type expected (`V_S`). +So is `V_O <: V_S`? +The answer will depend on the variance of the various parameters. +In this case, because the `Self` parameter is contravariant and `A` is covariant, it means that: ```text @@ -275,27 +276,26 @@ These conditions are satisfied and so we are happy. ### Variance and associated types Traits with associated types – or at minimum projection -expressions – must be invariant with respect to all of their -inputs. To see why this makes sense, consider what subtyping for a -trait reference means: +expressions – must be invariant with respect to all of their inputs. +To see why this makes sense, consider what subtyping for a trait reference means: ```text <: ``` -means that if I know that `T as Trait`, I also know that `U as -Trait`. Moreover, if you think of it as dictionary passing style, +means that if I know that `T as Trait`, I also know that `U as Trait`. +Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. The problem is that when you can project types out from ``, the relationship to types projected out of `` -is completely unknown unless `T==U` (see #21726 for more -details). Making `Trait` invariant ensures that this is true. +is completely unknown unless `T==U` (see #21726 for more details). +Making `Trait` invariant ensures that this is true. Another related reason is that if we didn't make traits with -associated types invariant, then projection is no longer a -function with a single result. Consider: +associated types invariant, then projection is no longer a function with a single result. +Consider: ```rust,ignore trait Identity { type Out; fn foo(&self); } @@ -313,4 +313,5 @@ if 'static : 'a -- Subtyping rules for relations This change otoh means that `<'static () as Identity>::Out` is always `&'static ()` (which might then be upcast to `'a ()`, -separately). This was helpful in solving #21750. +separately). +This was helpful in solving #21750. From 5d19c9290758240c9bbcfee9eebfb47f5360792b Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Sat, 25 Jul 2026 09:42:21 +0000 Subject: [PATCH 25/80] Prepare for merging from rust-lang/rust This updates the rust-version file to da86f4d0726be475afbbffe40cb2f65741c51ad3. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 25a5238f538ee..1a74dff9d23d8 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -390279b302ca98ae270f434100ae3730531d1246 +da86f4d0726be475afbbffe40cb2f65741c51ad3 From acb94075972be23872ca1c010029bf8f2c399a10 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:01:39 +0200 Subject: [PATCH 26/80] Split multiline derives into std/rustc macros --- compiler/rustc_data_structures/src/svh.rs | 13 +----- .../rustc_hir/src/attrs/data_structures.rs | 15 +------ compiler/rustc_lint_defs/src/lib.rs | 5 +-- .../rustc_middle/src/dep_graph/dep_node.rs | 5 +-- compiler/rustc_span/src/lib.rs | 6 +-- compiler/rustc_target/src/asm/amdgpu.rs | 45 +++---------------- 6 files changed, 17 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_data_structures/src/svh.rs b/compiler/rustc_data_structures/src/svh.rs index 2c4e00c824d80..67594f6dae79d 100644 --- a/compiler/rustc_data_structures/src/svh.rs +++ b/compiler/rustc_data_structures/src/svh.rs @@ -11,17 +11,8 @@ use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash}; use crate::fingerprint::Fingerprint; -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Encodable_NoContext, - Decodable_NoContext, - Hash, - StableHash -)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Encodable_NoContext, Decodable_NoContext, StableHash)] pub struct Svh { hash: Fingerprint, } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index db492475a3aa4..efe3657f21ea7 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -370,19 +370,8 @@ pub enum PeImportNameType { Undecorated, } -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Encodable, - Decodable, - PrintAttribute -)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, PrintAttribute)] #[derive(StableHash)] pub enum NativeLibKind { /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6949090781cf6..767fa647612af 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -149,9 +149,8 @@ impl From for LintExpectationId { /// Setting for how to handle a lint. /// /// See: -#[derive( - Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub enum Level { /// The `allow` level will not issue any message. Allow, diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 391bd41be828c..f460fd42cb31c 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -217,9 +217,8 @@ pub struct DepKindVTable<'tcx> { /// some independent path or string that persists between runs without /// the need to be mapped or unmapped. (This ensures we can serialize /// them even in the absence of a tcx.) -#[derive( - Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct WorkProductId { hash: Fingerprint, } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 318d0b60e240d..b8cf472309ba5 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -2126,10 +2126,8 @@ impl fmt::Debug for SourceFile { /// /// When `SourceFile`s are exported in crate metadata, the `StableSourceFileId` /// is updated to incorporate the `StableCrateId` of the exporting crate. -#[derive( - Debug, Clone, Copy, Hash, PartialEq, Eq, StableHash, Encodable, Decodable, Default, PartialOrd, - Ord -)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Default, Ord)] +#[derive(StableHash, Encodable, Decodable)] pub struct StableSourceFileId(Hash128); impl StableSourceFileId { diff --git a/compiler/rustc_target/src/asm/amdgpu.rs b/compiler/rustc_target/src/asm/amdgpu.rs index cc6f612cdb8b5..0f24ae5dea225 100644 --- a/compiler/rustc_target/src/asm/amdgpu.rs +++ b/compiler/rustc_target/src/asm/amdgpu.rs @@ -1,5 +1,6 @@ use std::fmt; +use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Symbol; use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; @@ -9,19 +10,8 @@ use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; /// Amdgpu register classes /// /// The number is the size of the register class in bits. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(StableHash, Encodable, Decodable)] pub enum AmdgpuInlineAsmRegClass { Sgpr(u16), Vgpr(u16), @@ -267,18 +257,8 @@ impl AmdgpuInlineAsmRegClass { /// Start index of a register. /// /// Together with the register size this gives the range occupied by a register. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] enum AmdgpuRegStart { /// Low 16-bit of the register at this index Low(u16), @@ -288,19 +268,8 @@ enum AmdgpuRegStart { Full(u16), } -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct AmdgpuInlineAsmReg { class: AmdgpuInlineAsmRegClass, range: AmdgpuRegStart, From d57ee2221b032116c9edac9666d720e3760dc9ef Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:03:12 +0200 Subject: [PATCH 27/80] improve variance.md --- src/doc/rustc-dev-guide/src/variance.md | 35 +++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index a8a72b44e87ad..96fde1d87cca5 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,7 +4,7 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime parameters. +During type checking, we must infer the variance of type and lifetime parameters. The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. @@ -12,26 +12,23 @@ PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf This inference is explicitly designed *not* to consider the uses of types within code. -To determine the variance of type parameters -defined on type `X`, we only consider the definition of the type `X` -and the definitions of any types it references. +To determine the variance of type parameters defined on type `X`, +we only consider the definition of the type `X` and the definitions of any types it references. We only infer variance for type parameters found on *data types* like structs and enums. In these cases, there is a fairly straightforward explanation for what variance means. The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` -(resp. -`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. -`'a` and `'b`). +(`T<'a>` and `T<'b>` respectively) +based on the relationship of `A` and `B` (`'a` and `'b` respectively). -We do not infer variance for type parameters found on traits, functions, -or impls. +We do not infer variance for type parameters found on traits, functions, or impls. Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in meaning and not that useful in practice, so we removed it. See the [addendum] for some details. -Variances on function/impl parameters, on the -other hand, doesn't make sense because these parameters are instantiated and -then forgotten, they don't persist in types or compiled byproducts. +Variances on function/impl parameters, on the other hand, +doesn't make sense because these parameters are instantiated and then forgotten; +they don't persist in types or compiled byproducts. [addendum]: #addendum @@ -111,16 +108,16 @@ If I have a struct or enum with where clauses: struct Foo { ... } ``` -you might wonder whether the variance of `T` with respect to `Bar` affects the +You might wonder whether the variance of `T` with respect to `Bar` affects the variance `T` with respect to `Foo`. I claim no. - The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while `X : Bar`, `Y : Bar` does not hold. - In that case, the upcast will be illegal, +In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type `Foo` is itself just not well-formed. -Basically we get to assume well-formedness of all types involved before considering variance. +Basically, we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management @@ -230,7 +227,7 @@ convertAll::(vector); Is this legal? To put another way, can we apply the `impl` for `Object` to the type `String`? -The answer is yes, but to see why we have to expand out what will happen: +The answer is yes, but to see why, we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` @@ -252,7 +249,7 @@ In short, we *have* an impl of type: V_O = ConvertTo for Object ``` -and the function prototype expects an impl of type: +And the function prototype expects an impl of type: ```text V_S = ConvertTo for String @@ -283,7 +280,7 @@ To see why this makes sense, consider what subtyping for a trait reference means <: ``` -means that if I know that `T as Trait`, I also know that `U as Trait`. +It means that if I know that `T as Trait`, I also know that `U as Trait`. Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. From 41962835685e134a52b6e43df81a173fb2adf39b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 25 Jul 2026 17:46:44 +0800 Subject: [PATCH 28/80] Add warning for breakage hazard for introducing new builtin attrs --- src/doc/rustc-dev-guide/src/attributes.md | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/attributes.md b/src/doc/rustc-dev-guide/src/attributes.md index 1056a257c2ee8..8dce9b8c17b18 100644 --- a/src/doc/rustc-dev-guide/src/attributes.md +++ b/src/doc/rustc-dev-guide/src/attributes.md @@ -25,6 +25,34 @@ For more information on these attributes, see the chapter about [attribute parsi [attr-parsing-chapter]: ./hir/attribute-parsing.md +### Note on adding new builtin attributes + +
+ +**Warning: Name resolution ambiguity potential when adding new builtin attributes** + +Please note that adding **new builtin attributes** (whose name is not reserved, i.e. a new builtin +attribute whose name does not start with `rustc`), even if *unstable*-gated, can introduce breakage +from name resolution ambiguity in stable code if (1) the stable code has a macro of the same name +which gets re-exported, or (2) or a proc-macro derive helper attribute of the same name. + +Typically, the builtin attributes probably has to start out as `#[rustc_foo]` instead of `#[foo]` to +avoid colliding with user-defined macros and proc-macro helper attributes. Then, prior to +stabilization, a rename to `#[foo]` should be done separately with a crater run to assess fallout, +with a deliberate breakage FCP proposal for T-lang to consider. + +Remember also that crater is *not* exhaustive and does not contain all existing stable code. + +See: +- [Built-in attributes are treated differently vs prelude attributes, unstable built-in attributes + can name-collide with stable macro, and built-in attributes can break back-compat + #134963](https://github.com/rust-lang/rust/issues/134963) and backlinks within this issue, + including design discussions on how to fix this kind of breakage hazard. +- [Broken build after updating: coverage is ambiguous; ambiguous because of a name conflict with a + builtin attribute #121157](https://github.com/rust-lang/rust/issues/121157). +- [Regression: align is ambiguous #143834](https://github.com/rust-lang/rust/issues/143834). +
+ ## 'Non-builtin'/'active' attributes These attributes are defined by a crate - either the standard library, or a proc-macro crate. From f36f2b96c6526b53eb3db56af6e8cfde596ff966 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:16:13 +0200 Subject: [PATCH 29/80] sembr src/rustdoc-internals/rustdoc-gui-test-suite.md --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index d18021bf278d9..e4909110d0aa6 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -5,7 +5,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of `rustdoc` (i.e., the HTML/JS/CSS as rendered in a browser). For other rustdoc-specific test suites, see [Rustdoc test suites]. -These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. +For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 0d9c6a4e5a06df464a3187d613ac97e9d5d69406 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:11 +0200 Subject: [PATCH 30/80] overlong --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index e4909110d0aa6..bce0fe4b0dc54 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -6,7 +6,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. -For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +For information on how to write this form of test, +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From c5659980e31f84cd0ea029fc33ebe52225b4bbbb Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:30 +0200 Subject: [PATCH 31/80] punctuation --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index bce0fe4b0dc54..951a541548b23 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -7,7 +7,7 @@ For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, -see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script]. [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 54c2643a9bb0946123d8a8efe21c493f503c548f Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:18:02 +0200 Subject: [PATCH 32/80] sembr src/rustdoc-internals/search.md --- .../src/rustdoc-internals/search.md | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md index e88d2703e3a23..d2235e84073b9 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md @@ -1,16 +1,13 @@ # Rustdoc search -Rustdoc Search is two programs: `search_index.rs` -and `search.js`. The first generates a nasty JSON -file with a full list of items and function signatures +Rustdoc Search is two programs: `search_index.rs` and `search.js`. +The first generates a nasty JSON file with a full list of items and function signatures in the crates in the doc bundle, and the second reads -it, turns it into some in-memory structures, and -scans them linearly to search. +it, turns it into some in-memory structures, and scans them linearly to search. ## Search index format -`search.js` calls this Raw, because it turns it into -a more normal object tree after loading it. +`search.js` calls this Raw, because it turns it into a more normal object tree after loading it. For space savings, it's also written without newlines or spaces. ```json @@ -44,8 +41,7 @@ For space savings, it's also written without newlines or spaces. ] ``` -[`src/librustdoc/html/static/js/rustdoc.d.ts`] -defines an actual schema in a TypeScript `type`. +[`src/librustdoc/html/static/js/rustdoc.d.ts`] defines an actual schema in a TypeScript `type`. | Key | Name | Description | | --- | -------------------- | ------------ | @@ -75,10 +71,8 @@ It makes a lot of compromises: * The `rustdoc` compiler runs on one crate at a time, so each crate has an essentially separate search index. - It [merges] them by having each crate on one line - and looking at the first quoted string. -* Names in the search index are given - in their original case and with underscores. + It [merges] them by having each crate on one line and looking at the first quoted string. +* Names in the search index are given in their original case and with underscores. When the search index is loaded, `search.js` stores the original names for display, but also folds them to lowercase and strips underscores for search. @@ -156,10 +150,8 @@ for (i, entry) in search_index.iter().enumerate() { } ``` -This is valid because everything has a parent module -(even if it's just the crate itself), -and is easy to assemble because the rustdoc generator sorts by path -before serializing. +This is valid because everything has a parent module (even if it's just the crate itself), +and is easy to assemble because the rustdoc generator sorts by path before serializing. Doing this allows rustdoc to not only make the search index smaller, but reuse the same string representing the parent path across multiple in-memory items. @@ -233,21 +225,21 @@ work through a "sandwich workload" of three steps: Reducing the amount of data downloaded here will almost always increase latency, by delaying the decision of what to download behind other work and/or adding -data dependencies where something can't be downloaded without first downloading -something else. In this case, we can't start downloading descriptions until +data dependencies where something can't be downloaded without first downloading something else. +In this case, we can't start downloading descriptions until after the search is done, because that's what allows it to decide *which* descriptions to download (it needs to sort the results then truncate to 200). To do this, two columns are stored in the search index, building on both Roaring Bitmaps and on VLQ Hex. -* `e` is an index of **e**mpty descriptions. It's a [roaring bitmap] of - each item (the crate itself is item 0, the rest start at 1). +* `e` is an index of **e**mpty descriptions. + It's a [roaring bitmap] of each item (the crate itself is item 0, the rest start at 1). * `D` is a shard list, stored in [VLQ hex] as flat list of integers. Each integer gives you the number of descriptions in the shard. As the decoder walks the index, it checks if the description is empty. - if it's not, then it's in the "current" shard. When all items are - exhausted, it goes on to the next shard. + if it's not, then it's in the "current" shard. + When all items are exhausted, it goes on to the next shard. Inside each shard is a newline-delimited list of descriptions, wrapped in a JSONP-style function call. @@ -280,8 +272,7 @@ Because of zigzag encoding, `` ` `` is +0, `a` is -0 (which is not used), ## Searching by name -Searching by name works by looping through the search index -and running these functions on each: +Searching by name works by looping through the search index and running these functions on each: * [`editDistance`] is always used to determine a match (unless quotes are specified, which would use simple equality instead). @@ -295,20 +286,19 @@ and running these functions on each: If it returns anything other than -1, the result is added, even if `editDistance` exceeds its threshold, and the index is stored for ranking. -* [`checkPath`] is used if, and only if, a parent path is specified - in the query. For example, `vec` has no parent path, but `vec::vec` does. +* [`checkPath`] is used if, and only if, a parent path is specified in the query. + For example, `vec` has no parent path, but `vec::vec` does. Within checkPath, editDistance and indexOf are used, and the path query has its own heuristic threshold, too. If it's not within the threshold, the entry is rejected, even if the first two pass. - If it's within the threshold, the path distance is stored - for ranking. + If it's within the threshold, the path distance is stored for ranking. * [`checkType`] is used only if there's a type filter, - like the struct in `struct:vec`. If it fails, + like the struct in `struct:vec`. + If it fails, the entry is rejected. -If all four criteria pass -(plus the crate filter, which isn't technically part of the query), +If all four criteria pass (plus the crate filter, which isn't technically part of the query), the results are sorted by [`sortResults`]. [`editDistance`]: https://github.com/rust-lang/rust/blob/79b710c13968a1a48d94431d024d2b1677940866/src/librustdoc/html/static/js/search.js#L137 @@ -336,17 +326,18 @@ is going to match the same things `T, u32` matches (though rustdoc will detect this particular problem and warn about it). Then, when actually looping over each item, -the bloom filter will probably reject entries that don't have every -type mentioned in the query. +the bloom filter will probably reject entries that don't have every type mentioned in the query. For example, the bloom query allows a query of `i32 -> u32` to match a function with the type `i32, u32 -> bool`, but unification will reject it later. The unification filter ensures that: -* Bag semantics are respected. If you query says `i32, i32`, +* Bag semantics are respected. + If you query says `i32, i32`, then the function has to mention *two* i32s, not just one. -* Nesting semantics are respected. If your query says `vec