From ec5b34679a595ee34d7f52a280c917f9aa838e40 Mon Sep 17 00:00:00 2001 From: b-naber Date: Fri, 13 Feb 2026 20:50:30 +0000 Subject: [PATCH 1/6] use UnsafePinned for self-referential fields in coroutine struct --- compiler/rustc_mir_transform/src/coroutine.rs | 287 ++++++++++++++++-- 1 file changed, 268 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index 332196e3afee7..b8bc2181b349c 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -210,6 +210,9 @@ struct TransformVisitor<'tcx> { old_yield_ty: Ty<'tcx>, old_ret_ty: Ty<'tcx>, + + // Self-referential fields in the coroutine struct that aren't pinned. + locals_needing_unsafe_pinned: DenseBitSet, } impl<'tcx> TransformVisitor<'tcx> { @@ -355,6 +358,45 @@ impl<'tcx> TransformVisitor<'tcx> { Place { local: base.local, projection: self.tcx.mk_place_elems(&projection) } } + // Create a `Place` referencing a self-referential coroutine struct field. + // Self-referential coroutine struct fields are wrapped in `UnsafePinned`. This method + // creates the projections to get the `Place` behind `UnsafePinned`. + #[tracing::instrument(level = "trace", skip(self), ret)] + fn make_self_referential_field( + &self, + variant_index: VariantIdx, + idx: FieldIdx, + ty: Ty<'tcx>, + ) -> Place<'tcx> { + let self_place = Place::from(SELF_ARG); + let base = self.tcx.mk_place_downcast_unnamed(self_place, variant_index); + + let ty::Adt(adt_def, args) = ty.kind() else { + bug!("expected self-referential field to be an ADT, but it is {:?}", ty); + }; + assert_eq!(adt_def.did(), self.tcx.require_lang_item(LangItem::UnsafePinned, DUMMY_SP)); + let original_ty = args.type_at(0); + + let unsafe_cell_def_id = self.tcx.require_lang_item(LangItem::UnsafeCell, DUMMY_SP); + let unsafe_cell_ty = Ty::new_adt( + self.tcx, + self.tcx.adt_def(unsafe_cell_def_id), + self.tcx.mk_args(&[original_ty.into()]), + ); + + let mut projection = base.projection.to_vec(); + // self.field (UnsafePinned) + projection.push(ProjectionElem::Field(idx, ty)); + // value (UnsafeCell) + projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), unsafe_cell_ty)); + // value (T) + projection.push(ProjectionElem::Field(FieldIdx::from_u32(0), original_ty)); + + let place = Place { local: base.local, projection: self.tcx.mk_place_elems(&projection) }; + debug!(?place); + place + } + // Create a statement which changes the discriminant #[tracing::instrument(level = "trace", skip(self))] fn set_discr(&self, state_disc: VariantIdx, source_info: SourceInfo) -> Statement<'tcx> { @@ -411,8 +453,17 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { #[tracing::instrument(level = "trace", skip(self), ret)] fn visit_place(&mut self, place: &mut Place<'tcx>, _: PlaceContext, _location: Location) { // Replace an Local in the remap with a coroutine struct access - if let Some(&Some((ty, variant_index, idx))) = self.remap.get(place.local) { - replace_base(place, self.make_field(variant_index, idx, ty), self.tcx); + let local = place.local; + if let Some(&Some((ty, variant_index, idx))) = self.remap.get(local) { + if self.locals_needing_unsafe_pinned.contains(local) { + replace_base( + place, + self.make_self_referential_field(variant_index, idx, ty), + self.tcx, + ); + } else { + replace_base(place, self.make_field(variant_index, idx, ty), self.tcx); + } } } @@ -709,7 +760,7 @@ fn locals_live_across_suspend_points<'tcx>( body: &Body<'tcx>, always_live_locals: &DenseBitSet, movable: bool, -) -> LivenessInfo { +) -> (LivenessInfo, DenseBitSet) { // Calculate when MIR locals have live storage. This gives us an upper bound of their // lifetimes. let mut storage_live = MaybeStorageLive::new(std::borrow::Cow::Borrowed(always_live_locals)) @@ -735,6 +786,10 @@ fn locals_live_across_suspend_points<'tcx>( let mut source_info_at_suspension_points = Vec::new(); let mut live_locals_at_any_suspension_point = DenseBitSet::new_empty(body.local_decls.len()); + // Every Local that is live due to an outstanding borrow is a self-referential field of + // the coroutine struct. + let mut locals_live_due_to_borrow = DenseBitSet::new_empty(body.local_decls.len()); + for (block, data) in body.basic_blocks.iter_enumerated() { let TerminatorKind::Yield { .. } = data.terminator().kind else { continue }; @@ -742,6 +797,11 @@ fn locals_live_across_suspend_points<'tcx>( liveness.seek_to_block_end(block); let mut live_locals = liveness.get().clone(); + debug!(?live_locals); + + borrowed_locals_cursor2.seek_before_primary_effect(loc); + let borrowed_locals = borrowed_locals_cursor2.get(); + locals_live_due_to_borrow.union(borrowed_locals); if !movable { // The `liveness` variable contains the liveness of MIR locals ignoring borrows. @@ -754,8 +814,7 @@ fn locals_live_across_suspend_points<'tcx>( // If a borrow is converted to a raw reference, we must also assume that it lives // forever. Note that the final liveness is still bounded by the storage liveness // of the local, which happens using the `intersect` operation below. - borrowed_locals_cursor2.seek_before_primary_effect(loc); - live_locals.union(borrowed_locals_cursor2.get()); + live_locals.union(borrowed_locals); } // Store the storage liveness for later use so we can restore the state @@ -784,6 +843,12 @@ fn locals_live_across_suspend_points<'tcx>( debug!(?live_locals_at_any_suspension_point); let saved_locals = CoroutineSavedLocals(live_locals_at_any_suspension_point); + debug!(?saved_locals); + + debug!(?locals_live_due_to_borrow); + locals_live_due_to_borrow.intersect(&saved_locals.0); + let self_referential_fields = locals_live_due_to_borrow; + debug!(?self_referential_fields); // Renumber our liveness_map bitsets to include only the locals we are // saving. @@ -799,13 +864,16 @@ fn locals_live_across_suspend_points<'tcx>( &requires_storage, ); - LivenessInfo { - saved_locals, - live_locals_at_suspension_points, - source_info_at_suspension_points, - storage_conflicts, - storage_liveness: storage_liveness_map, - } + ( + LivenessInfo { + saved_locals, + live_locals_at_suspension_points, + source_info_at_suspension_points, + storage_conflicts, + storage_liveness: storage_liveness_map, + }, + self_referential_fields, + ) } /// The set of `Local`s that must be saved across yield points. @@ -813,6 +881,7 @@ fn locals_live_across_suspend_points<'tcx>( /// `CoroutineSavedLocal` is indexed in terms of the elements in this set; /// i.e. `CoroutineSavedLocal::new(1)` corresponds to the second local /// included in this set. +#[derive(Debug)] struct CoroutineSavedLocals(DenseBitSet); impl CoroutineSavedLocals { @@ -963,10 +1032,12 @@ impl StorageConflictVisitor<'_, '_> { } } -#[tracing::instrument(level = "trace", skip(liveness, body))] +#[tracing::instrument(level = "trace", skip(tcx, liveness, body))] fn compute_layout<'tcx>( + tcx: TyCtxt<'tcx>, liveness: LivenessInfo, body: &Body<'tcx>, + locals_needing_unsafe_pinned: &DenseBitSet, ) -> ( IndexVec, VariantIdx, FieldIdx)>>, CoroutineLayout<'tcx>, @@ -1005,8 +1076,19 @@ fn compute_layout<'tcx>( ClearCrossCrate::Set(box LocalInfo::FakeBorrow) => true, _ => false, }; + + // Use `UnsafePinned` for self-referential fields that aren't pinned. + let local_ty = if locals_needing_unsafe_pinned.contains(local) { + let unsafe_pinned_did = tcx.require_lang_item(LangItem::UnsafePinned, body.span); + let unsafe_pinned_adt_def = tcx.adt_def(unsafe_pinned_did); + let args = tcx.mk_args(&[decl.ty.into()]); + Ty::new_adt(tcx, unsafe_pinned_adt_def, args) + } else { + decl.ty + }; + let decl = - CoroutineSavedTy { ty: decl.ty, source_info: decl.source_info, ignore_for_traits }; + CoroutineSavedTy { ty: local_ty, source_info: decl.source_info, ignore_for_traits }; debug!(?decl); tys.push(decl); @@ -1408,12 +1490,16 @@ pub(crate) fn mir_coroutine_witnesses<'tcx>( // The witness simply contains all locals live across suspend points. let always_live_locals = always_storage_live_locals(body); - let liveness_info = locals_live_across_suspend_points(tcx, body, &always_live_locals, movable); + let (liveness_info, self_referential_fields) = + locals_live_across_suspend_points(tcx, body, &always_live_locals, movable); + let locals_needing_unsafe_pinned = + find_locals_needing_unsafe_pinned(tcx, body, self_referential_fields); // Extract locals which are live across suspension point into `layout` // `remap` gives a mapping from local indices onto coroutine struct indices // `storage_liveness` tells us which locals have live storage at suspension points - let (_, coroutine_layout, _) = compute_layout(liveness_info, body); + let (_, coroutine_layout, _) = + compute_layout(tcx, liveness_info, body, &locals_needing_unsafe_pinned); check_suspend_tys(tcx, &coroutine_layout, body); check_field_tys_sized(tcx, &coroutine_layout, def_id); @@ -1541,9 +1627,10 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { cleanup_async_drops(body); } + debug!(?coroutine_kind); let always_live_locals = always_storage_live_locals(body); let movable = coroutine_kind.movability() == hir::Movability::Movable; - let liveness_info = + let (liveness_info, self_referential_fields) = locals_live_across_suspend_points(tcx, body, &always_live_locals, movable); if tcx.sess.opts.unstable_opts.validate_mir { @@ -1556,10 +1643,17 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { vis.visit_body(body); } + debug!(?self_referential_fields); + let locals_needing_unsafe_pinned = + find_locals_needing_unsafe_pinned(tcx, body, self_referential_fields); + debug!("saved_locals: {:?}", liveness_info.saved_locals); + debug!(?locals_needing_unsafe_pinned); + // Extract locals which are live across suspension point into `layout` // `remap` gives a mapping from local indices onto coroutine struct indices // `storage_liveness` tells us which locals have live storage at suspension points - let (remap, layout, storage_liveness) = compute_layout(liveness_info, body); + let (remap, layout, storage_liveness) = + compute_layout(tcx, liveness_info, body, &locals_needing_unsafe_pinned); let can_return = can_return(tcx, body, body.typing_env(tcx)); @@ -1584,6 +1678,7 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { new_ret_local, old_ret_ty, old_yield_ty, + locals_needing_unsafe_pinned, }; transform.visit_body(body); @@ -1598,7 +1693,13 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { 0..0, args_iter.filter_map(|local| { let (ty, variant_index, idx) = transform.remap[local]?; - let lhs = transform.make_field(variant_index, idx, ty); + + let lhs = if transform.locals_needing_unsafe_pinned.contains(local) { + transform.make_self_referential_field(variant_index, idx, ty) + } else { + transform.make_field(variant_index, idx, ty) + }; + let rhs = Rvalue::Use(Operand::Move(local.into())); let assign = StatementKind::Assign(Box::new((lhs, rhs))); Some(Statement::new(source_info, assign)) @@ -1888,6 +1989,16 @@ fn check_must_not_suspend_ty<'tcx>( SuspendCheckData { descr_pre: &format!("{}allocator ", data.descr_pre), ..data }, ) } + ty::Adt(def, args) if def.is_unsafe_pinned() => { + let inner_ty = args.type_at(0); + debug!(?inner_ty); + check_must_not_suspend_ty( + tcx, + inner_ty, + hir_id, + SuspendCheckData { descr_pre: &format!("{}boxed ", data.descr_pre), ..data }, + ) + } // FIXME(sized_hierarchy): This should be replaced with a requirement that types in // coroutines implement `const Sized`. Scalable vectors are temporarily `Sized` while // `feature(sized_hierarchy)` is not fully implemented, but in practice are @@ -2009,3 +2120,141 @@ fn check_must_not_suspend_def( false } } + +/// Finds all `Local`s that need to be wrapped in `UnsafePinned`. These are all self-referential +/// fields of the coroutine struct which aren't pinned. +fn find_locals_needing_unsafe_pinned<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'_>, + mut self_referential_fields: DenseBitSet, +) -> DenseBitSet { + // We use the DefId of `Pin::new_unchecked` when looking for pinned locals + let Some(pin_fn) = tcx.lang_items().new_unchecked_fn() else { + return self_referential_fields; + }; + + let mut pinned_locals_finder = PinnedLocalsFinder::new(pin_fn); + pinned_locals_finder.visit_body(body); + let pinned_locals = pinned_locals_finder.pinned_locals; + debug!(?pinned_locals); + + let pinned_targets = + find_pinned_self_referential_fields(body, &pinned_locals[..], &self_referential_fields); + debug!(?pinned_targets); + + self_referential_fields.subtract(&pinned_targets); + self_referential_fields +} + +struct PinnedLocalsFinder { + pin_fn: DefId, + pinned_locals: Vec<(Local, Location)>, +} + +impl PinnedLocalsFinder { + fn new(pin_fn: DefId) -> Self { + PinnedLocalsFinder { pin_fn, pinned_locals: vec![] } + } +} + +impl<'tcx> Visitor<'tcx> for PinnedLocalsFinder { + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + match &terminator.kind { + TerminatorKind::Call { func, args, .. } => { + if let Some((fn_def, _)) = func.const_fn_def() { + if fn_def == self.pin_fn { + assert!(args.len() == 1); + match args[0].node { + Operand::Move(pinned_place) => { + self.pinned_locals.push((pinned_place.local, location)); + } + _ => bug!("expected Operand::Move for argument to Pin::new_unchecked"), + } + } + } + } + _ => {} + } + self.super_terminator(terminator, location); + } +} + +/// Traces pinned `Local`s backwards to find if they refer to self-referential fields of the coroutine struct. +/// Without this function we would have to use `UnsafePinned` on all self-referential fields, even those that +/// are pinned. This would imply that we wouldn't be able to use `noalias` on coroutines. Hence, we do our best +/// to identify these to allow us to forego using `UnsafePinned` on them. +/// Note: this is a best-effort attempt to heuristically find pinned `Local`s, but isn't guaranteed to find every +/// such one. +fn find_pinned_self_referential_fields<'tcx>( + body: &Body<'tcx>, + pin_calls: &[(Local, Location)], // locals that are pinned with Pin::new_unchecked call locations + self_referential_fields: &DenseBitSet, +) -> DenseBitSet { + // For each pinned local we walk backwards in the CFG to find the definition of the pointee. + let mut pinned_self_ref_fields = DenseBitSet::new_empty(body.local_decls.len()); + for &(pinned_local, call_loc) in pin_calls { + let mut worklist = vec![(call_loc.block, call_loc.statement_index, pinned_local)]; + let mut visited = FxHashSet::default(); + + while let Some((bb, worklist_stmt_idx, mut tracked_local)) = worklist.pop() { + if !visited.insert((bb, worklist_stmt_idx, tracked_local)) { + continue; + } + + let block_data = &body.basic_blocks[bb]; + let mut found_definition = false; + + // walk backwards through current block + for stmt_idx in (0..worklist_stmt_idx).rev() { + let stmt = &block_data.statements[stmt_idx]; + + if let StatementKind::Assign(box (lhs, rhs)) = &stmt.kind { + if lhs.local == tracked_local && lhs.projection.is_empty() { + found_definition = true; + + match rhs { + Rvalue::Ref(_, _, place) => { + if place.is_indirect() { + // re-borrow, continue searching with new `tracked_local` + tracked_local = place.local; + found_definition = false; + } else { + if self_referential_fields.contains(place.local) { + pinned_self_ref_fields.insert(place.local); + } + + break; + } + } + Rvalue::Use(operand) | Rvalue::Cast(_, operand, _) => { + if let Some(place) = operand.place() { + tracked_local = place.local; + found_definition = false; + } else { + break; + } + } + _ => { + break; + } + } + } + } + + if found_definition { + break; + } + } + + // check the predecessors of this basic block + if !found_definition { + for &pred in body.basic_blocks.predecessors()[bb].iter() { + let pred_last_stmt_idx = body.basic_blocks[pred].statements.len(); + worklist.push((pred, pred_last_stmt_idx, tracked_local)); + } + } + } + } + + pinned_self_ref_fields +} From 8d47b2f9a818c1d25f635ae34b4eabd36b6c2998 Mon Sep 17 00:00:00 2001 From: b-naber Date: Sat, 21 Feb 2026 17:41:27 +0000 Subject: [PATCH 2/6] add llvm filecheck tests --- tests/codegen-llvm/non-self-ref-coroutine.rs | 30 +++++++++++++++++++ tests/codegen-llvm/self-ref-coroutine.rs | 31 ++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 tests/codegen-llvm/non-self-ref-coroutine.rs create mode 100644 tests/codegen-llvm/self-ref-coroutine.rs diff --git a/tests/codegen-llvm/non-self-ref-coroutine.rs b/tests/codegen-llvm/non-self-ref-coroutine.rs new file mode 100644 index 0000000000000..229c289b663e3 --- /dev/null +++ b/tests/codegen-llvm/non-self-ref-coroutine.rs @@ -0,0 +1,30 @@ +// Tests that the coroutine struct is not noalias if it has no self-referential +// fields. +// NOTE: this should eventually use noalias + +//@ compile-flags: -C opt-level=3 +//@ edition: 2021 + +#![crate_type = "lib"] + +use std::future::Future; +use std::pin::Pin; + +async fn inner() {} + +// CHECK-LABEL: ; non_self_ref_coroutine::my_async_fn::{closure#0} +// CHECK-LABEL: my_async_fn +// CHECK-NOT: noalias +// CHECK-SAME: %_1 +async fn my_async_fn(b: bool) -> i32 { + let x = Box::new(5); + if b { + inner().await; + } + *x + 1 +} + +#[no_mangle] +pub fn create_future_as_trait(b: bool) -> Pin>> { + Box::pin(my_async_fn(b)) +} diff --git a/tests/codegen-llvm/self-ref-coroutine.rs b/tests/codegen-llvm/self-ref-coroutine.rs new file mode 100644 index 0000000000000..f09db99704c7b --- /dev/null +++ b/tests/codegen-llvm/self-ref-coroutine.rs @@ -0,0 +1,31 @@ +// Tests that the coroutine struct is not noalias if it has self-referential +// fields. + +//@ compile-flags: -C opt-level=3 +//@ edition: 2021 + +#![crate_type = "lib"] + +use std::future::Future; +use std::pin::Pin; + +async fn inner() {} + +// CHECK-LABEL: ; self_ref_coroutine::my_async_fn::{closure#0} +// CHECK-LABEL: my_async_fn +// CHECK-NOT: noalias +// CHECK-SAME: %_1 +async fn my_async_fn(b: bool) -> i32 { + let x = Box::new(5); + let y = &x; + if b { + inner().await; + std::hint::black_box(y); + } + *x + 1 +} + +#[no_mangle] +pub fn create_future_as_trait(b: bool) -> Pin>> { + Box::pin(my_async_fn(b)) +} From dc0913abfb59e0ba55997c66c6ad56f8b234bd47 Mon Sep 17 00:00:00 2001 From: b-naber Date: Sat, 21 Feb 2026 18:46:17 +0000 Subject: [PATCH 3/6] add mir-opt test --- ...nc_fn-{closure#0}.StateTransform.after.mir | 331 ++++++++++++++++++ .../mir-opt/async_self_referential_fields.rs | 38 ++ 2 files changed, 369 insertions(+) create mode 100644 tests/mir-opt/async_self_referential_fields.my_async_fn-{closure#0}.StateTransform.after.mir create mode 100644 tests/mir-opt/async_self_referential_fields.rs diff --git a/tests/mir-opt/async_self_referential_fields.my_async_fn-{closure#0}.StateTransform.after.mir b/tests/mir-opt/async_self_referential_fields.my_async_fn-{closure#0}.StateTransform.after.mir new file mode 100644 index 0000000000000..40c4c4fde1a7b --- /dev/null +++ b/tests/mir-opt/async_self_referential_fields.my_async_fn-{closure#0}.StateTransform.after.mir @@ -0,0 +1,331 @@ +// MIR for `my_async_fn::{closure#0}` after StateTransform +/* coroutine_layout = CoroutineLayout { + field_tys: { + _s0: CoroutineSavedTy { + ty: std::pin::UnsafePinned>, + source_info: SourceInfo { + span: $DIR/async_self_referential_fields.rs:27:9: 27:10 (#0), + scope: scope[0], + }, + ignore_for_traits: false, + }, + _s1: CoroutineSavedTy { + ty: &'{erased} std::boxed::Box, + source_info: SourceInfo { + span: $DIR/async_self_referential_fields.rs:28:9: 28:10 (#0), + scope: scope[1], + }, + ignore_for_traits: false, + }, + _s2: CoroutineSavedTy { + ty: Coroutine( + DefId(0:15 ~ async_self_referential_fields[ea9b]::inner_async_fn::{closure#0}), + [ + (), + std::future::ResumeTy, + (), + (), + (), + ], + ), + source_info: SourceInfo { + span: $DIR/async_self_referential_fields.rs:29:5: 29:27 (#10), + scope: scope[2], + }, + ignore_for_traits: false, + }, + }, + variant_fields: { + Unresumed(0): [], + Returned (1): [], + Panicked (2): [], + Suspend0 (3): [_s0, _s1, _s2], + }, + storage_conflicts: BitMatrix(3x3) { + (_s0, _s0), + (_s0, _s1), + (_s0, _s2), + (_s1, _s0), + (_s1, _s1), + (_s1, _s2), + (_s2, _s0), + (_s2, _s1), + (_s2, _s2), + }, +} */ + +fn my_async_fn::{closure#0}(_1: Pin<&mut {async fn body of my_async_fn()}>, _2: &mut std::task::Context<'_>) -> std::task::Poll { + debug _task_context => _2; + let mut _0: std::task::Poll; + let _3: std::boxed::Box; + let _5: (); + let mut _6: {async fn body of inner_async_fn()}; + let mut _7: {async fn body of inner_async_fn()}; + let mut _9: (); + let _10: (); + let mut _11: std::task::Poll<()>; + let mut _12: std::pin::Pin<&mut {async fn body of inner_async_fn()}>; + let mut _13: &mut {async fn body of inner_async_fn()}; + let mut _14: &mut {async fn body of inner_async_fn()}; + let mut _15: &mut std::task::Context<'_>; + let mut _16: &mut std::task::Context<'_>; + let mut _17: &mut std::task::Context<'_>; + let mut _18: isize; + let mut _20: !; + let mut _21: &mut std::task::Context<'_>; + let mut _22: (); + let _23: &std::boxed::Box; + let mut _24: &std::boxed::Box; + let mut _25: i32; + let mut _26: *const i32; + let mut _27: i32; + let mut _28: u32; + let mut _29: &mut {async fn body of my_async_fn()}; + scope 1 { + debug x => (((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box); + let _4: &std::boxed::Box; + scope 2 { + debug y => (((*_29) as variant#3).1: &std::boxed::Box); + let mut _8: {async fn body of inner_async_fn()}; + scope 3 { + debug __awaitee => (((*_29) as variant#3).2: {async fn body of inner_async_fn()}); + let _19: (); + scope 4 { + debug result => _19; + } + } + } + } + + bb0: { + _29 = copy (_1.0: &mut {async fn body of my_async_fn()}); + _28 = discriminant((*_29)); + switchInt(move _28) -> [0: bb1, 1: bb33, 2: bb32, 3: bb31, otherwise: bb9]; + } + + bb1: { + nop; + (((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box) = Box::::new(const 5_i32) -> [return: bb2, unwind: bb27]; + } + + bb2: { + nop; + (((*_29) as variant#3).1: &std::boxed::Box) = &(((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box); + StorageLive(_5); + StorageLive(_6); + StorageLive(_7); + _7 = inner_async_fn() -> [return: bb3, unwind: bb24]; + } + + bb3: { + _6 = <{async fn body of inner_async_fn()} as IntoFuture>::into_future(move _7) -> [return: bb4, unwind: bb23]; + } + + bb4: { + StorageDead(_7); + PlaceMention(_6); + nop; + (((*_29) as variant#3).2: {async fn body of inner_async_fn()}) = move _6; + goto -> bb5; + } + + bb5: { + StorageLive(_10); + StorageLive(_11); + StorageLive(_12); + StorageLive(_13); + StorageLive(_14); + _14 = &mut (((*_29) as variant#3).2: {async fn body of inner_async_fn()}); + _13 = &mut (*_14); + _12 = Pin::<&mut {async fn body of inner_async_fn()}>::new_unchecked(move _13) -> [return: bb6, unwind: bb20]; + } + + bb6: { + StorageDead(_13); + StorageLive(_15); + StorageLive(_16); + StorageLive(_17); + _17 = copy _2; + _16 = move _17; + goto -> bb7; + } + + bb7: { + _15 = &mut (*_16); + StorageDead(_17); + _11 = <{async fn body of inner_async_fn()} as Future>::poll(move _12, move _15) -> [return: bb8, unwind: bb19]; + } + + bb8: { + StorageDead(_15); + StorageDead(_12); + PlaceMention(_11); + _18 = discriminant(_11); + switchInt(move _18) -> [0: bb11, 1: bb10, otherwise: bb9]; + } + + bb9: { + unreachable; + } + + bb10: { + _10 = const (); + StorageDead(_16); + StorageDead(_14); + StorageDead(_11); + StorageDead(_10); + StorageLive(_21); + StorageLive(_22); + _22 = (); + _0 = std::task::Poll::::Pending; + StorageDead(_5); + StorageDead(_6); + StorageDead(_21); + StorageDead(_22); + discriminant((*_29)) = 3; + return; + } + + bb11: { + StorageLive(_19); + _19 = copy ((_11 as Ready).0: ()); + _5 = copy _19; + StorageDead(_19); + StorageDead(_16); + StorageDead(_14); + StorageDead(_11); + StorageDead(_10); + drop((((*_29) as variant#3).2: {async fn body of inner_async_fn()})) -> [return: bb13, unwind: bb22]; + } + + bb12: { + StorageDead(_22); + _2 = move _21; + StorageDead(_21); + _9 = const (); + goto -> bb5; + } + + bb13: { + nop; + goto -> bb14; + } + + bb14: { + StorageDead(_6); + StorageDead(_5); + StorageLive(_23); + StorageLive(_24); + _24 = copy (((*_29) as variant#3).1: &std::boxed::Box); + _23 = std::hint::black_box::<&Box>(move _24) -> [return: bb15, unwind: bb18]; + } + + bb15: { + StorageDead(_24); + StorageDead(_23); + StorageLive(_25); + _26 = copy (((((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box).0: std::ptr::Unique).0: std::ptr::NonNull) as *const i32 (Transmute); + _25 = copy (*_26); + _27 = Add(move _25, const 1_i32); + StorageDead(_25); + nop; + drop((((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box)) -> [return: bb16, unwind: bb27]; + } + + bb16: { + nop; + goto -> bb29; + } + + bb17: { + _0 = std::task::Poll::::Ready(move _27); + discriminant((*_29)) = 1; + return; + } + + bb18 (cleanup): { + StorageDead(_24); + StorageDead(_23); + goto -> bb26; + } + + bb19 (cleanup): { + StorageDead(_15); + StorageDead(_12); + StorageDead(_16); + goto -> bb21; + } + + bb20 (cleanup): { + StorageDead(_13); + StorageDead(_12); + goto -> bb21; + } + + bb21 (cleanup): { + StorageDead(_14); + StorageDead(_11); + StorageDead(_10); + drop((((*_29) as variant#3).2: {async fn body of inner_async_fn()})) -> [return: bb22, unwind terminate(cleanup)]; + } + + bb22 (cleanup): { + nop; + goto -> bb25; + } + + bb23 (cleanup): { + goto -> bb24; + } + + bb24 (cleanup): { + StorageDead(_7); + goto -> bb25; + } + + bb25 (cleanup): { + StorageDead(_6); + StorageDead(_5); + goto -> bb26; + } + + bb26 (cleanup): { + nop; + drop((((((*_29) as variant#3).0: std::pin::UnsafePinned>).0: std::cell::UnsafeCell>).0: std::boxed::Box)) -> [return: bb27, unwind terminate(cleanup)]; + } + + bb27 (cleanup): { + nop; + goto -> bb28; + } + + bb28 (cleanup): { + goto -> bb30; + } + + bb29: { + goto -> bb17; + } + + bb30 (cleanup): { + discriminant((*_29)) = 2; + resume; + } + + bb31: { + StorageLive(_5); + StorageLive(_6); + StorageLive(_21); + StorageLive(_22); + _21 = move _2; + goto -> bb12; + } + + bb32: { + assert(const false, "`async fn` resumed after panicking") -> [success: bb32, unwind continue]; + } + + bb33: { + assert(const false, "`async fn` resumed after completion") -> [success: bb33, unwind continue]; + } +} diff --git a/tests/mir-opt/async_self_referential_fields.rs b/tests/mir-opt/async_self_referential_fields.rs new file mode 100644 index 0000000000000..c676f9fd3f290 --- /dev/null +++ b/tests/mir-opt/async_self_referential_fields.rs @@ -0,0 +1,38 @@ +//@ edition:2021 +// skip-filecheck +// EMIT_MIR async_self_referential_fields.my_async_fn-{closure#0}.StateTransform.after.mir + +#![allow(unused)] + +use std::future::Future; +use std::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}; +use std::pin::pin; +use std::task::*; + +pub fn block_on(fut: impl Future) -> T { + let mut fut = pin!(fut); + let ctx = &mut Context::from_waker(Waker::noop()); + + loop { + match fut.as_mut().poll(ctx) { + Poll::Pending => {} + Poll::Ready(t) => break t, + } + } +} + +async fn inner_async_fn() {} + +async fn my_async_fn() -> i32 { + let x = Box::new(5); + let y = &x; + inner_async_fn().await; + std::hint::black_box(y); + *x + 1 +} + +fn main() { + block_on(async { + my_async_fn().await; + }); +} From 30322eb6716d92438758d4981c8b29c1cdee7562 Mon Sep 17 00:00:00 2001 From: b-naber Date: Wed, 18 Mar 2026 19:41:43 +0000 Subject: [PATCH 4/6] bless tests --- ....main-{closure#0}.StateTransform.after.mir | 12 +++---- ....main-{closure#1}.StateTransform.after.mir | 12 +++---- tests/ui/async-await/async-fn-nonsend.stderr | 2 +- .../drop-track-field-assign-nonsend.stderr | 11 +++---- .../async-await/field-assign-nonsend.stderr | 11 +++---- .../ui/async-await/issues/issue-67893.stderr | 4 +++ .../partial-drop-partial-reinit.rs | 17 +++++----- .../partial-drop-partial-reinit.stderr | 33 +++++++------------ .../stalled-coroutine-obligations.stderr | 2 +- tests/ui/lint/must_not_suspend/dedup.stderr | 2 +- 10 files changed, 47 insertions(+), 59 deletions(-) diff --git a/tests/mir-opt/building/coroutine.main-{closure#0}.StateTransform.after.mir b/tests/mir-opt/building/coroutine.main-{closure#0}.StateTransform.after.mir index b61215dc28cb4..dfd9ccd7231a6 100644 --- a/tests/mir-opt/building/coroutine.main-{closure#0}.StateTransform.after.mir +++ b/tests/mir-opt/building/coroutine.main-{closure#0}.StateTransform.after.mir @@ -2,7 +2,7 @@ /* coroutine_layout = CoroutineLayout { field_tys: { _s0: CoroutineSavedTy { - ty: std::string::String, + ty: std::pin::UnsafePinned, source_info: SourceInfo { span: $DIR/coroutine.rs:18:6: 18:9 (#0), scope: scope[0], @@ -23,7 +23,7 @@ } */ fn main::{closure#0}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:18:5: 18:18}>, _2: String) -> CoroutineState<(&str, String, &Location<'_>), ()> { - debug arg => (((*_18) as variant#4).0: std::string::String); + debug arg => (((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); let mut _0: std::ops::CoroutineState<(&str, std::string::String, &std::panic::Location<'_>), ()>; let _3: std::string::String; let mut _4: (&str, std::string::String, &std::panic::Location<'_>); @@ -49,12 +49,12 @@ fn main::{closure#0}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:18:5: 18:18}>, _2 } bb1: { - (((*_18) as variant#4).0: std::string::String) = move _2; + (((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String) = move _2; StorageLive(_3); StorageLive(_4); StorageLive(_5); StorageLive(_6); - _6 = &(((*_18) as variant#4).0: std::string::String); + _6 = &(((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); _5 = ::clone(move _6) -> [return: bb2, unwind unreachable]; } @@ -98,7 +98,7 @@ fn main::{closure#0}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:18:5: 18:18}>, _2 _10 = &(*_11); StorageLive(_12); StorageLive(_13); - _13 = &(((*_18) as variant#4).0: std::string::String); + _13 = &(((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); _12 = ::clone(move _13) -> [return: bb8, unwind unreachable]; } @@ -142,7 +142,7 @@ fn main::{closure#0}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:18:5: 18:18}>, _2 StorageDead(_11); StorageDead(_8); _16 = const (); - drop((((*_18) as variant#4).0: std::string::String)) -> [return: bb14, unwind unreachable]; + drop((((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String)) -> [return: bb14, unwind unreachable]; } bb14: { diff --git a/tests/mir-opt/building/coroutine.main-{closure#1}.StateTransform.after.mir b/tests/mir-opt/building/coroutine.main-{closure#1}.StateTransform.after.mir index aac028a9e6c0e..a77b1c55fb623 100644 --- a/tests/mir-opt/building/coroutine.main-{closure#1}.StateTransform.after.mir +++ b/tests/mir-opt/building/coroutine.main-{closure#1}.StateTransform.after.mir @@ -2,7 +2,7 @@ /* coroutine_layout = CoroutineLayout { field_tys: { _s0: CoroutineSavedTy { - ty: std::string::String, + ty: std::pin::UnsafePinned, source_info: SourceInfo { span: $DIR/coroutine.rs:25:6: 25:9 (#0), scope: scope[0], @@ -23,7 +23,7 @@ } */ fn main::{closure#1}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:25:5: 25:18}>, _2: String) -> CoroutineState<(&str, String, &Location<'_>), ()> { - debug arg => (((*_18) as variant#4).0: std::string::String); + debug arg => (((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); let mut _0: std::ops::CoroutineState<(&str, std::string::String, &std::panic::Location<'_>), ()>; let _3: std::string::String; let mut _4: (&str, std::string::String, &std::panic::Location<'_>); @@ -49,12 +49,12 @@ fn main::{closure#1}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:25:5: 25:18}>, _2 } bb1: { - (((*_18) as variant#4).0: std::string::String) = move _2; + (((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String) = move _2; StorageLive(_3); StorageLive(_4); StorageLive(_5); StorageLive(_6); - _6 = &(((*_18) as variant#4).0: std::string::String); + _6 = &(((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); _5 = ::clone(move _6) -> [return: bb2, unwind unreachable]; } @@ -98,7 +98,7 @@ fn main::{closure#1}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:25:5: 25:18}>, _2 _10 = &(*_11); StorageLive(_12); StorageLive(_13); - _13 = &(((*_18) as variant#4).0: std::string::String); + _13 = &(((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String); _12 = ::clone(move _13) -> [return: bb8, unwind unreachable]; } @@ -142,7 +142,7 @@ fn main::{closure#1}(_1: Pin<&mut {coroutine@$DIR/coroutine.rs:25:5: 25:18}>, _2 StorageDead(_11); StorageDead(_8); _16 = const (); - drop((((*_18) as variant#4).0: std::string::String)) -> [return: bb14, unwind unreachable]; + drop((((((*_18) as variant#4).0: std::pin::UnsafePinned).0: std::cell::UnsafeCell).0: std::string::String)) -> [return: bb14, unwind unreachable]; } bb14: { diff --git a/tests/ui/async-await/async-fn-nonsend.stderr b/tests/ui/async-await/async-fn-nonsend.stderr index 0ced6c36f4715..aab2b6448e737 100644 --- a/tests/ui/async-await/async-fn-nonsend.stderr +++ b/tests/ui/async-await/async-fn-nonsend.stderr @@ -29,7 +29,7 @@ note: future is not `Send` as this value is used across an await --> $DIR/async-fn-nonsend.rs:46:15 | LL | let f: &mut std::fmt::Formatter = &mut get_formatter(); - | --------------- has type `Formatter<'_>` which is not `Send` + | --------------- has type `UnsafePinned>` which is not `Send` ... LL | fut().await; | ^^^^^ await occurs here, with `get_formatter()` maybe used later diff --git a/tests/ui/async-await/drop-track-field-assign-nonsend.stderr b/tests/ui/async-await/drop-track-field-assign-nonsend.stderr index 9fce4d61b3b6f..e8d3591b06303 100644 --- a/tests/ui/async-await/drop-track-field-assign-nonsend.stderr +++ b/tests/ui/async-await/drop-track-field-assign-nonsend.stderr @@ -5,14 +5,11 @@ LL | assert_send(agent.handle()); | ^^^^^^^^^^^^^^ future returned by `handle` is not `Send` | = help: within `impl Future`, the trait `Send` is not implemented for `Rc` -note: future is not `Send` as this value is used across an await - --> $DIR/drop-track-field-assign-nonsend.rs:20:39 +note: captured value is not `Send` because `&mut` references cannot be sent unless their referent is `Send` + --> $DIR/drop-track-field-assign-nonsend.rs:16:21 | -LL | let mut info = self.info_result.clone(); - | -------- has type `InfoResult` which is not `Send` -... -LL | let _ = send_element(element).await; - | ^^^^^ await occurs here, with `mut info` maybe used later +LL | async fn handle(&mut self) { + | ^^^^^^^^^ has type `&mut Agent` which is not `Send`, because `Agent` is not `Send` note: required by a bound in `assert_send` --> $DIR/drop-track-field-assign-nonsend.rs:37:19 | diff --git a/tests/ui/async-await/field-assign-nonsend.stderr b/tests/ui/async-await/field-assign-nonsend.stderr index 418a0829c657e..9fec60618b8e2 100644 --- a/tests/ui/async-await/field-assign-nonsend.stderr +++ b/tests/ui/async-await/field-assign-nonsend.stderr @@ -5,14 +5,11 @@ LL | assert_send(agent.handle()); | ^^^^^^^^^^^^^^ future returned by `handle` is not `Send` | = help: within `impl Future`, the trait `Send` is not implemented for `Rc` -note: future is not `Send` as this value is used across an await - --> $DIR/field-assign-nonsend.rs:20:39 +note: captured value is not `Send` because `&mut` references cannot be sent unless their referent is `Send` + --> $DIR/field-assign-nonsend.rs:16:21 | -LL | let mut info = self.info_result.clone(); - | -------- has type `InfoResult` which is not `Send` -... -LL | let _ = send_element(element).await; - | ^^^^^ await occurs here, with `mut info` maybe used later +LL | async fn handle(&mut self) { + | ^^^^^^^^^ has type `&mut Agent` which is not `Send`, because `Agent` is not `Send` note: required by a bound in `assert_send` --> $DIR/field-assign-nonsend.rs:37:19 | diff --git a/tests/ui/async-await/issues/issue-67893.stderr b/tests/ui/async-await/issues/issue-67893.stderr index 610ed60bc8fda..53eaded463b19 100644 --- a/tests/ui/async-await/issues/issue-67893.stderr +++ b/tests/ui/async-await/issues/issue-67893.stderr @@ -12,6 +12,10 @@ LL | pub async fn run() { | - within this `impl Future` | = help: within `impl Future`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, ()>` +note: required because it appears within the type `UnsafeCell>` + --> $SRC_DIR/core/src/cell.rs:LL:COL +note: required because it appears within the type `UnsafePinned>` + --> $SRC_DIR/core/src/pin/unsafe_pinned.rs:LL:COL note: required because it's used within this `async` fn body --> $DIR/auxiliary/issue_67893.rs:9:20 | diff --git a/tests/ui/async-await/partial-drop-partial-reinit.rs b/tests/ui/async-await/partial-drop-partial-reinit.rs index b72552ed32479..ec76966d9c72f 100644 --- a/tests/ui/async-await/partial-drop-partial-reinit.rs +++ b/tests/ui/async-await/partial-drop-partial-reinit.rs @@ -4,19 +4,18 @@ fn main() { gimme_send(foo()); - //~^ ERROR cannot be sent between threads safely - //~| NOTE cannot be sent - //~| NOTE bound introduced by - //~| NOTE appears within the type + //~^ ERROR future cannot be sent between threads safely + //~| NOTE future returned by `foo` is not `Send` } fn gimme_send(t: T) { - //~^ NOTE required by this bound - //~| NOTE required by a bound + //~^ NOTE required by this bound in `gimme_send` + //~| NOTE required by a bound in `gimme_send` drop(t); } struct NotSend {} +//~^ HELP within `impl Future`, the trait `Send` is not implemented for `NotSend` impl Drop for NotSend { fn drop(&mut self) {} @@ -25,12 +24,14 @@ impl Drop for NotSend { impl !Send for NotSend {} async fn foo() { - //~^ NOTE used within this `async` fn body - //~| NOTE within this `impl Future let mut x = (NotSend {},); + //~^ NOTE has type `UnsafePinned<(NotSend,)>` which is not `Send` drop(x.0); x.0 = NotSend {}; bar().await; + //~^ NOTE future is not `Send` as this value is used across an await + //~| NOTE await occurs here, with `mut x` maybe used later + //~| NOTE in this expansion of desugaring of `await` expression } async fn bar() {} diff --git a/tests/ui/async-await/partial-drop-partial-reinit.stderr b/tests/ui/async-await/partial-drop-partial-reinit.stderr index cf4b408ad12b8..6ab4cec28e65d 100644 --- a/tests/ui/async-await/partial-drop-partial-reinit.stderr +++ b/tests/ui/async-await/partial-drop-partial-reinit.stderr @@ -1,38 +1,27 @@ -error[E0277]: `NotSend` cannot be sent between threads safely +error: future cannot be sent between threads safely --> $DIR/partial-drop-partial-reinit.rs:6:16 | LL | gimme_send(foo()); - | ---------- ^^^^^ `NotSend` cannot be sent between threads safely - | | - | required by a bound introduced by this call -... -LL | async fn foo() { - | - within this `impl Future` + | ^^^^^ future returned by `foo` is not `Send` | help: within `impl Future`, the trait `Send` is not implemented for `NotSend` - --> $DIR/partial-drop-partial-reinit.rs:19:1 + --> $DIR/partial-drop-partial-reinit.rs:17:1 | LL | struct NotSend {} | ^^^^^^^^^^^^^^ - = note: required because it appears within the type `(NotSend,)` -note: required because it's used within this `async` fn body - --> $DIR/partial-drop-partial-reinit.rs:27:16 +note: future is not `Send` as this value is used across an await + --> $DIR/partial-drop-partial-reinit.rs:31:11 | -LL | async fn foo() { - | ________________^ -LL | | -LL | | -LL | | let mut x = (NotSend {},); -... | -LL | | bar().await; -LL | | } - | |_^ +LL | let mut x = (NotSend {},); + | ----- has type `UnsafePinned<(NotSend,)>` which is not `Send` +... +LL | bar().await; + | ^^^^^ await occurs here, with `mut x` maybe used later note: required by a bound in `gimme_send` - --> $DIR/partial-drop-partial-reinit.rs:13:18 + --> $DIR/partial-drop-partial-reinit.rs:11:18 | LL | fn gimme_send(t: T) { | ^^^^ required by this bound in `gimme_send` error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/coroutine/stalled-coroutine-obligations.stderr b/tests/ui/coroutine/stalled-coroutine-obligations.stderr index cbf395dd6cfb3..acd359138763f 100644 --- a/tests/ui/coroutine/stalled-coroutine-obligations.stderr +++ b/tests/ui/coroutine/stalled-coroutine-obligations.stderr @@ -14,7 +14,7 @@ note: future is not `Send` as this value is used across an await --> $DIR/stalled-coroutine-obligations.rs:13:18 | LL | let non_send = null::<()>(); - | -------- has type `*const ()` which is not `Send` + | -------- has type `UnsafePinned<*const ()>` which is not `Send` LL | &non_send; LL | async {}.await | ^^^^^ await occurs here, with `non_send` maybe used later diff --git a/tests/ui/lint/must_not_suspend/dedup.stderr b/tests/ui/lint/must_not_suspend/dedup.stderr index 2876e1cf6750e..b6ee185a17c72 100644 --- a/tests/ui/lint/must_not_suspend/dedup.stderr +++ b/tests/ui/lint/must_not_suspend/dedup.stderr @@ -1,4 +1,4 @@ -error: `No` held across a suspend point, but should not be +error: boxed `No` held across a suspend point, but should not be --> $DIR/dedup.rs:16:9 | LL | let no = No {}; From 4998cf23710383b5bc93d7ea24851016b2a30998 Mon Sep 17 00:00:00 2001 From: b-naber Date: Wed, 18 Mar 2026 21:36:17 +0000 Subject: [PATCH 5/6] update miri async-shared-mutable test for unsafepinned --- .../fail/async-shared-mutable.stack.stderr | 36 --------------- .../fail/async-shared-mutable.tree.stderr | 44 ------------------- .../{fail => pass}/async-shared-mutable.rs | 3 +- .../tests/pass/packed-struct-dyn-trait.rs | 1 - 4 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 src/tools/miri/tests/fail/async-shared-mutable.stack.stderr delete mode 100644 src/tools/miri/tests/fail/async-shared-mutable.tree.stderr rename src/tools/miri/tests/{fail => pass}/async-shared-mutable.rs (85%) diff --git a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr b/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr deleted file mode 100644 index 7cedb24a25644..0000000000000 --- a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr +++ /dev/null @@ -1,36 +0,0 @@ -error: Undefined Behavior: attempting a write access using at ALLOC[OFFSET], but that tag does not exist in the borrow stack for this location - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | *x = 1; - | ^^^^^^ this error occurs as part of an access at ALLOC[OFFSET] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [OFFSET] - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | / core::future::poll_fn(move |_| { -LL | | *x = 1; -LL | | Poll::<()>::Pending -LL | | }) -LL | | .await - | |______________^ -help: was later invalidated at offsets [OFFSET] by a SharedReadOnly retag - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | let _: Pin<&_> = f.as_ref(); // Or: `f.as_mut().into_ref()`. - | ^^^^^^^^^^ - = note: stack backtrace: - 0: main::{closure#0}::{closure#0} - at tests/fail/async-shared-mutable.rs:LL:CC - 1: as std::future::Future>::poll - at RUSTLIB/core/src/future/poll_fn.rs:LL:CC - 2: main::{closure#0} - at tests/fail/async-shared-mutable.rs:LL:CC - 3: main - at tests/fail/async-shared-mutable.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr deleted file mode 100644 index fb68161837683..0000000000000 --- a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr +++ /dev/null @@ -1,44 +0,0 @@ -error: Undefined Behavior: write access through at ALLOC[OFFSET] is forbidden - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | *x = 1; - | ^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Frozen which forbids this child write access -help: the accessed tag was created here, in the initial state Reserved - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | / core::future::poll_fn(move |_| { -LL | | *x = 1; -LL | | Poll::<()>::Pending -LL | | }) -LL | | .await - | |______________^ -help: the accessed tag later transitioned to Unique due to a child write access at offsets [OFFSET] - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | *x = 1; - | ^^^^^^ - = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference -help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [OFFSET] - --> tests/fail/async-shared-mutable.rs:LL:CC - | -LL | let _: Pin<&_> = f.as_ref(); // Or: `f.as_mut().into_ref()`. - | ^^^^^^^^^^ - = help: this transition corresponds to a loss of write permissions - = note: stack backtrace: - 0: main::{closure#0}::{closure#0} - at tests/fail/async-shared-mutable.rs:LL:CC - 1: as std::future::Future>::poll - at RUSTLIB/core/src/future/poll_fn.rs:LL:CC - 2: main::{closure#0} - at tests/fail/async-shared-mutable.rs:LL:CC - 3: main - at tests/fail/async-shared-mutable.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/async-shared-mutable.rs b/src/tools/miri/tests/pass/async-shared-mutable.rs similarity index 85% rename from src/tools/miri/tests/fail/async-shared-mutable.rs rename to src/tools/miri/tests/pass/async-shared-mutable.rs index 62780e7a11c96..43cc2d664c406 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.rs +++ b/src/tools/miri/tests/pass/async-shared-mutable.rs @@ -1,4 +1,3 @@ -//! FIXME: This test should pass! However, `async fn` does not yet use `UnsafePinned`. //! This is a regression test for : //! `UnsafePinned` must include the effects of `UnsafeCell`. //@revisions: stack tree @@ -13,7 +12,7 @@ fn main() { let mut f = pin!(async move { let x = &mut 0u8; core::future::poll_fn(move |_| { - *x = 1; //~ERROR: write access + *x = 1; Poll::<()>::Pending }) .await diff --git a/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs index bb73c26c18a0d..2e61e77a221e1 100644 --- a/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs +++ b/src/tools/miri/tests/pass/packed-struct-dyn-trait.rs @@ -1,4 +1,3 @@ -// run-pass use std::ptr::addr_of; // When the unsized tail is a `dyn Trait`, its alignments is only dynamically known. This means the From f5e7edde0a4b5d4ddd8882ac624220ab93b764fd Mon Sep 17 00:00:00 2001 From: b-naber Date: Sat, 21 Mar 2026 12:18:57 +0000 Subject: [PATCH 6/6] account for UnsafePinned in AwaitHolding clippy lint --- .../clippy_lints/src/await_holding_invalid.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs index 9e5b57a055808..297c6bf15ab0a 100644 --- a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs +++ b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs @@ -7,7 +7,7 @@ use rustc_hir as hir; use rustc_hir::def_id::{DefId, DefIdMap}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::CoroutineLayout; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{TyCtxt, Ty}; use rustc_session::impl_lint_pass; use rustc_span::Span; @@ -213,7 +213,8 @@ impl<'tcx> LateLintPass<'tcx> for AwaitHolding { impl AwaitHolding { fn check_interior_types(&self, cx: &LateContext<'_>, coroutine: &CoroutineLayout<'_>) { for (ty_index, ty_cause) in coroutine.field_tys.iter_enumerated() { - if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() { + let coroutine_field_ty = maybe_unwrap_unsafe_pinned(ty_cause.ty); + if let rustc_middle::ty::Adt(adt, _) = coroutine_field_ty.kind() { let await_points = || { coroutine .variant_source_info @@ -226,6 +227,7 @@ impl AwaitHolding { }) .collect::>() }; + if is_mutex_guard(cx, adt.did()) { span_lint_and_then( cx, @@ -263,6 +265,18 @@ impl AwaitHolding { } } } + +} + + +// self-referential coroutine fields are wrapped in `UnsafePinned`. +fn maybe_unwrap_unsafe_pinned<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> { + match ty.kind() { + rustc_middle::ty::Adt(adt, args) if adt.is_unsafe_pinned() => { + args.type_at(0) + } + _ => ty + } } fn emit_invalid_type(