Skip to content
Open
200 changes: 174 additions & 26 deletions compiler/rustc_borrowck/src/borrow_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rustc_hir::Mutability;
use rustc_index::IndexVec;
use rustc_index::bit_set::DenseBitSet;
use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{self, Body, Local, Location, traversal};
use rustc_middle::mir::{self, Body, Local, Location, PlaceElem, traversal};
use rustc_middle::ty::data_structures::IndexSet;
use rustc_middle::ty::{RegionVid, TyCtxt};
use rustc_middle::{bug, span_bug, ty};
Expand Down Expand Up @@ -265,6 +265,154 @@ impl<'a, 'tcx> GatherBorrows<'a, 'tcx> {
}
idx
}

fn insert_borrows(
&mut self,
location: Location,
borrows: SmallVec<[BorrowData<'tcx>; 1]>,
) -> SmallVec<[BorrowIndex; 1]> {
let mut idxs = SmallVec::<[BorrowIndex; 1]>::with_capacity(borrows.len());
// FIXME(reborrow): why doesn't SmallVec offer reserve?
for borrow in borrows {
idxs.push(self.borrows.push(borrow));
}
match self.location_map.entry(location) {
Entry::Occupied(entry) => {
bug!(
"Inserting borrows {idxs:?} at {location:?} attempted to override an existing list {entry:?}"
);
}
Entry::Vacant(entry) => {
entry.insert(idxs.clone());
}
}
idxs
}

fn gather_reborrows(
&mut self,
v: &mut SmallVec<[BorrowData<'tcx>; 1]>,
kind: mir::BorrowKind,
location: Location,
target_adt: ty::AdtDef<'tcx>,
target_args: &'tcx ty::List<ty::GenericArg<'tcx>>,
target_place: mir::Place<'tcx>,
source_adt: ty::AdtDef<'tcx>,
source_args: &'tcx ty::List<ty::GenericArg<'tcx>>,
source_place: mir::Place<'tcx>,
) {
let mut did_reborrow = false;
for (source_idx, source_field) in source_adt.all_fields().enumerate() {
let source_field_ty = source_field.ty(self.tcx, source_args).skip_norm_wip();
match source_field_ty.kind() {
ty::Ref(source_region, _, source_mutability) if source_mutability.is_mut() => {
if source_region.is_static() {
bug!(
"Cannot implement Reborrow on a type containing a &'static mut T field"
);
}
let Some((target_idx, target_field)) = target_adt
.all_fields()
.enumerate()
.find(|(_, f)| f.name == source_field.name)
else {
// Reborrow dropped this field.
continue;
};
let ty::Ref(target_region, _, _) =
target_field.ty(self.tcx, target_args).skip_norm_wip().kind()
else {
bug!(
"Reborrow source field type is &mut T but target field is not a reference"
);
};

did_reborrow = true;
let source_field_deref_place = source_place.project_deeper(
&[PlaceElem::Field(source_idx.into(), source_field_ty), PlaceElem::Deref],
self.tcx,
);
let target_field_place = target_place.project_to_field(
target_idx.into(),
&self.body.local_decls,
self.tcx,
);
v.push(BorrowData {
kind,
region: target_region.as_var(),
reserve_location: location,
activation_location: TwoPhaseActivation::NotTwoPhase,
borrowed_place: source_field_deref_place,
assigned_place: target_field_place,
});
}
ty::Adt(source_field_adt, source_field_args)
if source_field_args.get(0).is_some_and(|f| f.as_region().is_some())
&& !self.tcx.type_is_copy_modulo_regions(
self.body.typing_env(self.tcx),
self.tcx.erase_and_anonymize_regions(source_field_ty),
) =>
{
let Some((target_idx, target_field)) = target_adt
.all_fields()
.enumerate()
.find(|(_, f)| f.name == source_field.name)
else {
// Reborrow dropped this field.
continue;
};
let ty::Adt(target_field_adt, target_field_args) =
target_field.ty(self.tcx, target_args).skip_norm_wip().kind()
else {
bug!("Reborrow source field type is a !Copy ADT but target field is not");
};

did_reborrow = true;
let source_field_place = source_place.project_to_field(
source_idx.into(),
&self.body.local_decls,
self.tcx,
);
let target_field_place = target_place.project_to_field(
target_idx.into(),
&self.body.local_decls,
self.tcx,
);
self.gather_reborrows(
v,
kind,
location,
*target_field_adt,
target_field_args,
target_field_place,
*source_field_adt,
source_field_args,
source_field_place,
);
}
_ => continue,
}
}
if !did_reborrow {
// If source contained no reference, perform a phantom dereference.
let source_phantom_deref_place =
source_place.project_deeper(&[PlaceElem::PhantomDeref], self.tcx);
if target_args.regions().count() != 1 {
bug!(
"ADT containing no '&mut T' or 'T: Reborrow' fields must only have one lifetime to implement Reborrow"
);
}
let target_region = target_args.regions().next().unwrap();
v.push(BorrowData {
kind,
region: target_region.as_var(),
reserve_location: location,
activation_location: TwoPhaseActivation::NotTwoPhase,
borrowed_place: source_phantom_deref_place,
assigned_place: target_place,
});
}
}
}

impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
Expand Down Expand Up @@ -323,23 +471,14 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
};

self.local_map.entry(borrowed_place.local).or_default().insert(idx);
} else if let &mir::Rvalue::Reborrow(target, mutability, borrowed_place) = rvalue {
let borrowed_place_ty = borrowed_place.ty(self.body, self.tcx).ty;
let &ty::Adt(reborrowed_adt, _reborrowed_args) = borrowed_place_ty.kind() else {
unreachable!()
};
let &ty::Adt(target_adt, assigned_args) = target.kind() else { unreachable!() };
let Some(ty::GenericArgKind::Lifetime(region)) = assigned_args.get(0).map(|r| r.kind())
else {
bug!(
"hir-typeck passed but {} does not have a lifetime argument",
if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" }
);
};
let region = region.as_var();
} else if let &mir::Rvalue::Reborrow(target, mutability, source_place) = rvalue {
let source_ty = source_place.ty(self.body, self.tcx).ty;
let &ty::Adt(source_adt, source_args) = source_ty.kind() else { unreachable!() };
let &ty::Adt(target_adt, target_args) = target.kind() else { unreachable!() };

let kind = if mutability == Mutability::Mut {
// Reborrow
if target_adt.did() != reborrowed_adt.did() {
if target_adt.did() != source_adt.did() {
bug!(
"hir-typeck passed but Reborrow involves mismatching types at {location:?}"
)
Expand All @@ -348,24 +487,33 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default }
} else {
// CoerceShared
if target_adt.did() == reborrowed_adt.did() {
if target_adt.did() == source_adt.did() {
bug!(
"hir-typeck passed but CoerceShared involves matching types at {location:?}"
)
}
mir::BorrowKind::Shared
};
let borrow = BorrowData {

let mut reborrows = smallvec![];
self.gather_reborrows(
&mut reborrows,
kind,
region,
reserve_location: location,
activation_location: TwoPhaseActivation::NotTwoPhase,
borrowed_place,
assigned_place: *assigned_place,
};
let idx = self.insert_borrow(location, borrow);
location,
target_adt,
target_args,
*assigned_place,
source_adt,
source_args,
source_place,
);

self.local_map.entry(borrowed_place.local).or_default().insert(idx);
let idxs = self.insert_borrows(location, reborrows);

let locals = self.local_map.entry(source_place.local).or_default();
for idx in idxs {
locals.insert(idx);
}
}

self.super_assign(assigned_place, rvalue, location)
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4272,6 +4272,13 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> {
}
StorageDeadOrDrop::Destructor(_) => kind,
},
ProjectionElem::PhantomDeref => match kind {
StorageDeadOrDrop::LocalStorageDead
| StorageDeadOrDrop::BoxedStorageDead => {
StorageDeadOrDrop::BoxedStorageDead
}
StorageDeadOrDrop::Destructor(_) => kind,
},
ProjectionElem::OpaqueCast { .. }
| ProjectionElem::Field(..)
| ProjectionElem::Downcast(..) => {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
}
}
}
ProjectionElem::PhantomDeref => (),
ProjectionElem::Downcast(..) if opt.including_downcast => return None,
ProjectionElem::Downcast(..) => (),
ProjectionElem::OpaqueCast(..) => (),
Expand Down Expand Up @@ -486,6 +487,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
PlaceTy::from_ty(*ty)
}
ProjectionElem::Field(_, field_type) => PlaceTy::from_ty(*field_type),
ProjectionElem::PhantomDeref => unreachable!("not a field"),
},
};
self.describe_field_from_ty(
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
}
}

PlaceRef { local: _, projection: [ProjectionElem::PhantomDeref] } => {
item_msg = String::new();
reason = String::new();
}
PlaceRef { local: _, projection: [_proj_base @ .., ProjectionElem::PhantomDeref] } => {
item_msg = String::new();
reason = String::new();
}

PlaceRef {
local: _,
projection:
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2022,7 +2022,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
// So it's safe to skip these.
ProjectionElem::OpaqueCast(_)
| ProjectionElem::Downcast(_, _)
| ProjectionElem::UnwrapUnsafeBinder(_) => (),
| ProjectionElem::UnwrapUnsafeBinder(_)
| ProjectionElem::PhantomDeref => (),
}

place_ty = place_ty.projection_ty(tcx, elem);
Expand Down Expand Up @@ -2272,6 +2273,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
break;
}

ProjectionElem::PhantomDeref => {
panic!("we don't allow assignments to PhantomDeref, location {location:?}");
}

ProjectionElem::Subslice { .. } => {
panic!("we don't allow assignments to subslices, location: {location:?}");
}
Expand Down Expand Up @@ -2642,6 +2647,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
_ => bug!("Deref of unexpected type: {:?}", base_ty),
}
}
ProjectionElem::PhantomDeref => {
bug!("encountered PhantomDeref in is_mutable")
}
// Check as the inner reference type if it is a field projection
// from the `&pin` pattern
ProjectionElem::Field(FieldIdx::ZERO, _)
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_borrowck/src/places_conflict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ fn place_components_conflict<'tcx>(
return false;
}

(ProjectionElem::PhantomDeref, _, Shallow(None)) => {
// e.g., a reborrow of `x.y` while we shallowly access `x.y` or some prefix
// thereof - the shallow access cannot invalidate the reborrowed copy.
debug!("borrow_conflicts_with_place: shallow access behind reborrow");
return false;
}

(ProjectionElem::Field { .. }, ty::Adt(def, _), AccessDepth::Drop) => {
// Drop can read/write arbitrary projections, so places
// conflict regardless of further projections.
Expand All @@ -244,6 +251,7 @@ fn place_components_conflict<'tcx>(

(ProjectionElem::Deref, _, Deep)
| (ProjectionElem::Deref, _, AccessDepth::Drop)
| (ProjectionElem::PhantomDeref, _, _)
| (ProjectionElem::Field { .. }, _, _)
| (ProjectionElem::Index { .. }, _, _)
| (ProjectionElem::ConstantIndex { .. }, _, _)
Expand Down Expand Up @@ -301,6 +309,11 @@ fn place_projection_conflict<'tcx>(
debug!("place_element_conflict: DISJOINT-OR-EQ-DEREF");
Overlap::EqualOrDisjoint
}
(ProjectionElem::PhantomDeref, ProjectionElem::PhantomDeref) => {
// phantom derefs (e.g., `x` vs. `x`) - recur.
debug!("place_element_conflict: DISJOINT-OR-EQ-PHANTOM-DEREF");
Overlap::EqualOrDisjoint
}
(ProjectionElem::OpaqueCast(_), ProjectionElem::OpaqueCast(_)) => {
// casts to other types may always conflict irrespective of the type being cast to.
debug!("place_element_conflict: DISJOINT-OR-EQ-OPAQUE");
Expand Down Expand Up @@ -506,6 +519,7 @@ fn place_projection_conflict<'tcx>(
}
(
ProjectionElem::Deref
| ProjectionElem::PhantomDeref
| ProjectionElem::Field(..)
| ProjectionElem::Index(..)
| ProjectionElem::ConstantIndex { .. }
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_borrowck/src/prefixes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ impl<'tcx> Iterator for Prefixes<'tcx> {
| ProjectionElem::Index(_) => {
cursor = cursor_base;
}
ProjectionElem::PhantomDeref => {
unreachable!("PhantomDeref should not be present in prefixes")
}
ProjectionElem::Deref => {
match self.kind {
PrefixSet::Shallow => {
Expand Down
Loading
Loading