Auto merge of #54509 - matthewjasper:better-drop-access, r=pnkfelix

[NLL] Rework checking for borrows conflicting with drops

Previously, we would split the drop access into multiple checks for each
field of a struct/tuple/closure and through `Box` dereferences. This
changes this to check if the borrow is accessed by the drop in
`places_conflict`.

We also now handle enums containing `Drop` types.

Closes #53569

r? @nikomatsakis
cc @pnkfelix
This commit is contained in:
bors 2018-09-24 14:47:17 +00:00
commit a072d1bca6
21 changed files with 282 additions and 416 deletions

View File

@ -8,14 +8,14 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use borrow_check::{WriteKind, StorageDeadOrDrop};
use borrow_check::WriteKind;
use borrow_check::prefixes::IsPrefixOf;
use borrow_check::nll::explain_borrow::BorrowExplanation;
use rustc::middle::region::ScopeTree;
use rustc::mir::{
self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, FakeReadCause, Field, Local,
LocalDecl, LocalKind, Location, Operand, Place, ProjectionElem, Rvalue, Statement,
StatementKind, TerminatorKind, VarBindingForm,
LocalDecl, LocalKind, Location, Operand, Place, PlaceProjection, ProjectionElem, Rvalue,
Statement, StatementKind, TerminatorKind, VarBindingForm,
};
use rustc::hir;
use rustc::hir::def_id::DefId;
@ -452,13 +452,21 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
self.access_place_error_reported
.insert((root_place.clone(), borrow_span));
if let Some(WriteKind::StorageDeadOrDrop(StorageDeadOrDrop::Destructor)) = kind {
if let StorageDeadOrDrop::Destructor(dropped_ty)
= self.classify_drop_access_kind(&borrow.borrowed_place)
{
// If a borrow of path `B` conflicts with drop of `D` (and
// we're not in the uninteresting case where `B` is a
// prefix of `D`), then report this as a more interesting
// destructor conflict.
if !borrow.borrowed_place.is_prefix_of(place_span.0) {
self.report_borrow_conflicts_with_destructor(context, borrow, place_span, kind);
self.report_borrow_conflicts_with_destructor(
context,
borrow,
place_span,
kind,
dropped_ty,
);
return;
}
}
@ -566,6 +574,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
borrow: &BorrowData<'tcx>,
(place, drop_span): (&Place<'tcx>, Span),
kind: Option<WriteKind>,
dropped_ty: ty::Ty<'tcx>,
) {
debug!(
"report_borrow_conflicts_with_destructor(\
@ -579,28 +588,19 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
let mut err = self.infcx.tcx.cannot_borrow_across_destructor(borrow_span, Origin::Mir);
let (what_was_dropped, dropped_ty) = {
let desc = match self.describe_place(place) {
Some(name) => format!("`{}`", name.as_str()),
None => format!("temporary value"),
};
let ty = place.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx);
(desc, ty)
let what_was_dropped = match self.describe_place(place) {
Some(name) => format!("`{}`", name.as_str()),
None => format!("temporary value"),
};
let label = match dropped_ty.sty {
ty::Adt(adt, _) if adt.has_dtor(self.infcx.tcx) && !adt.is_box() => {
match self.describe_place(&borrow.borrowed_place) {
Some(borrowed) =>
format!("here, drop of {D} needs exclusive access to `{B}`, \
because the type `{T}` implements the `Drop` trait",
D=what_was_dropped, T=dropped_ty, B=borrowed),
None =>
format!("here is drop of {D}; whose type `{T}` implements the `Drop` trait",
D=what_was_dropped, T=dropped_ty),
}
}
_ => format!("drop of {D} occurs here", D=what_was_dropped),
let label = match self.describe_place(&borrow.borrowed_place) {
Some(borrowed) =>
format!("here, drop of {D} needs exclusive access to `{B}`, \
because the type `{T}` implements the `Drop` trait",
D=what_was_dropped, T=dropped_ty, B=borrowed),
None =>
format!("here is drop of {D}; whose type `{T}` implements the `Drop` trait",
D=what_was_dropped, T=dropped_ty),
};
err.span_label(drop_span, label);
@ -880,6 +880,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
pub(super) struct IncludingDowncast(bool);
/// Which case a StorageDeadOrDrop is for.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum StorageDeadOrDrop<'tcx> {
LocalStorageDead,
BoxedStorageDead,
Destructor(ty::Ty<'tcx>),
}
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
// End-user visible description of `place` if one can be found. If the
// place is a temporary for instance, None will be returned.
@ -1167,6 +1175,56 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
}
}
fn classify_drop_access_kind(&self, place: &Place<'tcx>) -> StorageDeadOrDrop<'tcx> {
let tcx = self.infcx.tcx;
match place {
Place::Local(_)
| Place::Static(_)
| Place::Promoted(_) => StorageDeadOrDrop::LocalStorageDead,
Place::Projection(box PlaceProjection { base, elem }) => {
let base_access = self.classify_drop_access_kind(base);
match elem {
ProjectionElem::Deref => {
match base_access {
StorageDeadOrDrop::LocalStorageDead
| StorageDeadOrDrop::BoxedStorageDead => {
assert!(base.ty(self.mir, tcx).to_ty(tcx).is_box(),
"Drop of value behind a reference or raw pointer");
StorageDeadOrDrop::BoxedStorageDead
}
StorageDeadOrDrop::Destructor(_) => {
base_access
}
}
}
ProjectionElem::Field(..)
| ProjectionElem::Downcast(..) => {
let base_ty = base.ty(self.mir, tcx).to_ty(tcx);
match base_ty.sty {
ty::Adt(def, _) if def.has_dtor(tcx) => {
// Report the outermost adt with a destructor
match base_access {
StorageDeadOrDrop::Destructor(_) => {
base_access
}
StorageDeadOrDrop::LocalStorageDead
| StorageDeadOrDrop::BoxedStorageDead => {
StorageDeadOrDrop::Destructor(base_ty)
}
}
}
_ => base_access,
}
}
ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. }
| ProjectionElem::Index(_) => base_access,
}
}
}
}
/// Annotate argument and return type of function and closure with (synthesized) lifetime for
/// borrow of local value that does not live long enough.
fn annotate_argument_and_return_for_borrow(

View File

@ -23,13 +23,12 @@ use rustc::mir::{ClearCrossCrate, Local, Location, Mir, Mutability, Operand, Pla
use rustc::mir::{Field, Projection, ProjectionElem, Rvalue, Statement, StatementKind};
use rustc::mir::{Terminator, TerminatorKind};
use rustc::ty::query::Providers;
use rustc::ty::{self, ParamEnv, TyCtxt, Ty};
use rustc::ty::{self, TyCtxt};
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, Level};
use rustc_data_structures::bit_set::BitSet;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::graph::dominators::Dominators;
use rustc_data_structures::indexed_vec::Idx;
use smallvec::SmallVec;
use std::rc::Rc;
@ -251,7 +250,6 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
mir,
mir_def_id: def_id,
move_data: &mdpe.move_data,
param_env: param_env,
location_table,
movable_generator,
locals_are_invalidated_at_exit,
@ -393,7 +391,6 @@ pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
/// when MIR borrowck begins.
location_table: &'cx LocationTable,
param_env: ParamEnv<'gcx>,
movable_generator: bool,
/// This keeps track of whether local variables are free-ed when the function
/// exits even without a `StorageDead`, which appears to be the case for
@ -574,8 +571,7 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
self.access_place(
ContextKind::StorageDead.new(location),
(&Place::Local(local), span),
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
StorageDeadOrDrop::LocalStorageDead))),
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
LocalMutationIsAllowed::Yes,
flow_state,
);
@ -630,8 +626,13 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
loc: {:?} term: {:?} drop_place: {:?} drop_place_ty: {:?} span: {:?}",
loc, term, drop_place, drop_place_ty, span);
self.visit_terminator_drop(
loc, term, flow_state, drop_place, drop_place_ty, span, SeenTy(None));
self.access_place(
ContextKind::Drop.new(loc),
(drop_place, span),
(AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
LocalMutationIsAllowed::Yes,
flow_state,
);
}
TerminatorKind::DropAndReplace {
location: ref drop_place,
@ -748,7 +749,7 @@ enum MutateMode {
}
use self::ReadOrWrite::{Activation, Read, Reservation, Write};
use self::ShallowOrDeep::{Deep, Shallow};
use self::AccessDepth::{Deep, Shallow};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ArtificialField {
@ -757,7 +758,7 @@ enum ArtificialField {
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ShallowOrDeep {
enum AccessDepth {
/// From the RFC: "A *shallow* access means that the immediate
/// fields reached at P are accessed, but references or pointers
/// found within are not dereferenced. Right now, the only access
@ -769,6 +770,10 @@ enum ShallowOrDeep {
/// through the given place may be invalidated or accesses by
/// this action."
Deep,
/// Access is Deep only when there is a Drop implementation that
/// can reach the data behind the reference.
Drop,
}
/// Kind of access to a value: read or write
@ -803,21 +808,12 @@ enum ReadKind {
/// (For informational purposes only)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum WriteKind {
StorageDeadOrDrop(StorageDeadOrDrop),
StorageDeadOrDrop,
MutableBorrow(BorrowKind),
Mutate,
Move,
}
/// Specify whether which case a StorageDeadOrDrop is in.
/// (For informational purposes only)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum StorageDeadOrDrop {
LocalStorageDead,
BoxedStorageDead,
Destructor,
}
/// When checking permissions for a place access, this flag is used to indicate that an immutable
/// local place can be mutated.
///
@ -868,231 +864,7 @@ impl InitializationRequiringAction {
}
}
/// A simple linked-list threaded up the stack of recursive calls in `visit_terminator_drop`.
#[derive(Copy, Clone, Debug)]
struct SeenTy<'a, 'gcx: 'a>(Option<(Ty<'gcx>, &'a SeenTy<'a, 'gcx>)>);
impl<'a, 'gcx> SeenTy<'a, 'gcx> {
/// Return a new list with `ty` prepended to the front of `self`.
fn cons(&'a self, ty: Ty<'gcx>) -> Self {
SeenTy(Some((ty, self)))
}
/// True if and only if `ty` occurs on the linked list `self`.
fn have_seen(self, ty: Ty) -> bool {
let mut this = self.0;
loop {
match this {
None => return false,
Some((seen_ty, recur)) => {
if seen_ty == ty {
return true;
} else {
this = recur.0;
continue;
}
}
}
}
}
}
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
/// Invokes `access_place` as appropriate for dropping the value
/// at `drop_place`. Note that the *actual* `Drop` in the MIR is
/// always for a variable (e.g., `Drop(x)`) -- but we recursively
/// break this variable down into subpaths (e.g., `Drop(x.foo)`)
/// to indicate more precisely which fields might actually be
/// accessed by a destructor.
fn visit_terminator_drop(
&mut self,
loc: Location,
term: &Terminator<'tcx>,
flow_state: &Flows<'cx, 'gcx, 'tcx>,
drop_place: &Place<'tcx>,
erased_drop_place_ty: ty::Ty<'gcx>,
span: Span,
prev_seen: SeenTy<'_, 'gcx>,
) {
if prev_seen.have_seen(erased_drop_place_ty) {
// if we have directly seen the input ty `T`, then we must
// have had some *direct* ownership loop between `T` and
// some directly-owned (as in, actually traversed by
// recursive calls below) part that is also of type `T`.
//
// Note: in *all* such cases, the data in question cannot
// be constructed (nor destructed) in finite time/space.
//
// Proper examples, some of which are statically rejected:
//
// * `struct A { field: A, ... }`:
// statically rejected as infinite size
//
// * `type B = (B, ...);`:
// statically rejected as cyclic
//
// * `struct C { field: Box<C>, ... }`
// * `struct D { field: Box<(D, D)>, ... }`:
// *accepted*, though impossible to construct
//
// Here is *NOT* an example:
// * `struct Z { field: Option<Box<Z>>, ... }`:
// Here, the type is both representable in finite space (due to the boxed indirection)
// and constructable in finite time (since the recursion can bottom out with `None`).
// This is an obvious instance of something the compiler must accept.
//
// Since some of the above impossible cases like `C` and
// `D` are accepted by the compiler, we must take care not
// to infinite-loop while processing them. But since such
// cases cannot actually arise, it is sound for us to just
// skip them during drop. If the developer uses unsafe
// code to construct them, they should not be surprised by
// weird drop behavior in their resulting code.
debug!("visit_terminator_drop previously seen \
erased_drop_place_ty: {:?} on prev_seen: {:?}; returning early.",
erased_drop_place_ty, prev_seen);
return;
}
let gcx = self.infcx.tcx.global_tcx();
let drop_field = |mir: &mut MirBorrowckCtxt<'cx, 'gcx, 'tcx>,
(index, field): (usize, ty::Ty<'gcx>)| {
let field_ty = gcx.normalize_erasing_regions(mir.param_env, field);
let place = drop_place.clone().field(Field::new(index), field_ty);
debug!("visit_terminator_drop drop_field place: {:?} field_ty: {:?}", place, field_ty);
let seen = prev_seen.cons(erased_drop_place_ty);
mir.visit_terminator_drop(loc, term, flow_state, &place, field_ty, span, seen);
};
match erased_drop_place_ty.sty {
// When a struct is being dropped, we need to check
// whether it has a destructor, if it does, then we can
// call it, if it does not then we need to check the
// individual fields instead. This way if `foo` has a
// destructor but `bar` does not, we will only check for
// borrows of `x.foo` and not `x.bar`. See #47703.
ty::Adt(def, substs) if def.is_struct() && !def.has_dtor(self.infcx.tcx) => {
def.all_fields()
.map(|field| field.ty(gcx, substs))
.enumerate()
.for_each(|field| drop_field(self, field));
}
// Same as above, but for tuples.
ty::Tuple(tys) => {
tys.iter()
.cloned()
.enumerate()
.for_each(|field| drop_field(self, field));
}
// Closures also have disjoint fields, but they are only
// directly accessed in the body of the closure.
ty::Closure(def, substs)
if *drop_place == Place::Local(Local::new(1))
&& !self.mir.upvar_decls.is_empty() =>
{
substs
.upvar_tys(def, self.infcx.tcx)
.enumerate()
.for_each(|field| drop_field(self, field));
}
// Generators also have disjoint fields, but they are only
// directly accessed in the body of the generator.
ty::Generator(def, substs, _)
if *drop_place == Place::Local(Local::new(1))
&& !self.mir.upvar_decls.is_empty() =>
{
substs
.upvar_tys(def, self.infcx.tcx)
.enumerate()
.for_each(|field| drop_field(self, field));
}
// #45696: special-case Box<T> by treating its dtor as
// only deep *across owned content*. Namely, we know
// dropping a box does not touch data behind any
// references it holds; if we were to instead fall into
// the base case below, we would have a Deep Write due to
// the box being `needs_drop`, and that Deep Write would
// touch `&mut` data in the box.
ty::Adt(def, _) if def.is_box() => {
// When/if we add a `&own T` type, this action would
// be like running the destructor of the `&own T`.
// (And the owner of backing storage referenced by the
// `&own T` would be responsible for deallocating that
// backing storage.)
// we model dropping any content owned by the box by
// recurring on box contents. This catches cases like
// `Box<Box<ScribbleWhenDropped<&mut T>>>`, while
// still restricting Write to *owned* content.
let ty = erased_drop_place_ty.boxed_ty();
let deref_place = drop_place.clone().deref();
debug!("visit_terminator_drop drop-box-content deref_place: {:?} ty: {:?}",
deref_place, ty);
let seen = prev_seen.cons(erased_drop_place_ty);
self.visit_terminator_drop(
loc, term, flow_state, &deref_place, ty, span, seen);
}
_ => {
// We have now refined the type of the value being
// dropped (potentially) to just the type of a
// subfield; so check whether that field's type still
// "needs drop".
if erased_drop_place_ty.needs_drop(gcx, self.param_env) {
// If so, we assume that the destructor may access
// any data it likes (i.e., a Deep Write).
self.access_place(
ContextKind::Drop.new(loc),
(drop_place, span),
(Deep, Write(WriteKind::StorageDeadOrDrop(
StorageDeadOrDrop::Destructor))),
LocalMutationIsAllowed::Yes,
flow_state,
);
} else {
// If there is no destructor, we still include a
// *shallow* write. This essentially ensures that
// borrows of the memory directly at `drop_place`
// cannot continue to be borrowed across the drop.
//
// If we were to use a Deep Write here, then any
// `&mut T` that is reachable from `drop_place`
// would get invalidated; fixing that is the
// essence of resolving issue #45696.
//
// * Note: In the compiler today, doing a Deep
// Write here would not actually break
// anything beyond #45696; for example it does not
// break this example:
//
// ```rust
// fn reborrow(x: &mut i32) -> &mut i32 { &mut *x }
// ```
//
// Why? Because we do not schedule/emit
// `Drop(x)` in the MIR unless `x` needs drop in
// the first place.
self.access_place(
ContextKind::Drop.new(loc),
(drop_place, span),
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
// rust-lang/rust#52059: distinguish
// between invaliding the backing storage
// vs running a destructor.
//
// See also: rust-lang/rust#52782,
// specifically #discussion_r206658948
StorageDeadOrDrop::BoxedStorageDead))),
LocalMutationIsAllowed::Yes,
flow_state,
);
}
}
}
}
/// Checks an access to the given place to see if it is allowed. Examines the set of borrows
/// that are in scope, as well as which paths have been initialized, to ensure that (a) the
/// place is initialized and (b) it is not borrowed in some way that would prevent this
@ -1103,7 +875,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
&mut self,
context: Context,
place_span: (&Place<'tcx>, Span),
kind: (ShallowOrDeep, ReadOrWrite),
kind: (AccessDepth, ReadOrWrite),
is_local_mutation_allowed: LocalMutationIsAllowed,
flow_state: &Flows<'cx, 'gcx, 'tcx>,
) {
@ -1159,7 +931,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
&mut self,
context: Context,
place_span: (&Place<'tcx>, Span),
sd: ShallowOrDeep,
sd: AccessDepth,
rw: ReadOrWrite,
flow_state: &Flows<'cx, 'gcx, 'tcx>,
) -> bool {
@ -1252,7 +1024,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
error_reported = true;
this.report_conflicting_borrow(context, place_span, bk, &borrow)
}
WriteKind::StorageDeadOrDrop(_) => {
WriteKind::StorageDeadOrDrop => {
error_reported = true;
this.report_borrowed_value_does_not_live_long_enough(
context,
@ -1281,7 +1053,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
&mut self,
context: Context,
place_span: (&Place<'tcx>, Span),
kind: ShallowOrDeep,
kind: AccessDepth,
mode: MutateMode,
flow_state: &Flows<'cx, 'gcx, 'tcx>,
) {
@ -1925,9 +1697,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
Reservation(wk @ WriteKind::Move)
| Write(wk @ WriteKind::Move)
| Reservation(wk @ WriteKind::StorageDeadOrDrop(_))
| Reservation(wk @ WriteKind::StorageDeadOrDrop)
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
| Write(wk @ WriteKind::StorageDeadOrDrop(_))
| Write(wk @ WriteKind::StorageDeadOrDrop)
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared)) => {
if let Err(_place_err) = self.is_mutable(place, is_local_mutation_allowed) {
if self.infcx.tcx.migrate_borrowck() {
@ -1942,7 +1714,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
error_access = match wk {
WriteKind::MutableBorrow(_) => AccessKind::MutableBorrow,
WriteKind::Move => AccessKind::Move,
WriteKind::StorageDeadOrDrop(_) |
WriteKind::StorageDeadOrDrop |
WriteKind::Mutate => AccessKind::Mutate,
};
self.report_mutability_error(

View File

@ -143,13 +143,15 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
Some(Cause::DropVar(local, location)) => match &mir.local_decls[local].name {
Some(local_name) => {
let mut should_note_order = false;
if let Some((WriteKind::StorageDeadOrDrop(_), place)) = kind_place {
if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
if let Place::Local(borrowed_local) = place {
let dropped_local_scope = mir.local_decls[local].visibility_scope;
let borrowed_local_scope =
mir.local_decls[*borrowed_local].visibility_scope;
if mir.is_sub_scope(borrowed_local_scope, dropped_local_scope) {
if mir.is_sub_scope(borrowed_local_scope, dropped_local_scope)
&& local != *borrowed_local
{
should_note_order = true;
}
}

View File

@ -11,32 +11,28 @@
use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::LocationTable;
use borrow_check::{JustWrite, WriteAndRead};
use borrow_check::{ShallowOrDeep, Deep, Shallow};
use borrow_check::{AccessDepth, Deep, Shallow};
use borrow_check::{ReadOrWrite, Activation, Read, Reservation, Write};
use borrow_check::{Context, ContextKind};
use borrow_check::{LocalMutationIsAllowed, MutateMode};
use borrow_check::ArtificialField;
use borrow_check::{ReadKind, WriteKind, StorageDeadOrDrop};
use borrow_check::{ReadKind, WriteKind};
use borrow_check::nll::facts::AllFacts;
use borrow_check::path_utils::*;
use dataflow::move_paths::indexes::BorrowIndex;
use rustc::hir::def_id::DefId;
use rustc::infer::InferCtxt;
use rustc::ty::TyCtxt;
use rustc::mir::visit::Visitor;
use rustc::mir::{BasicBlock, Location, Mir, Place, Rvalue, Local};
use rustc::mir::{BasicBlock, Location, Mir, Place, Rvalue};
use rustc::mir::{Statement, StatementKind};
use rustc::mir::{Terminator, TerminatorKind};
use rustc::mir::{Field, Operand, BorrowKind};
use rustc::ty::{self, ParamEnv};
use rustc_data_structures::indexed_vec::Idx;
use rustc::mir::{Operand, BorrowKind};
use rustc_data_structures::graph::dominators::Dominators;
pub(super) fn generate_invalidates<'cx, 'gcx, 'tcx>(
infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
tcx: TyCtxt<'cx, 'gcx, 'tcx>,
all_facts: &mut Option<AllFacts>,
location_table: &LocationTable,
mir: &Mir<'tcx>,
mir_def_id: DefId,
borrow_set: &BorrowSet<'tcx>,
) {
if !all_facts.is_some() {
@ -44,37 +40,32 @@ pub(super) fn generate_invalidates<'cx, 'gcx, 'tcx>(
return;
}
let param_env = infcx.tcx.param_env(mir_def_id);
if let Some(all_facts) = all_facts {
let dominators = mir.dominators();
let mut ig = InvalidationGenerator {
all_facts,
borrow_set,
infcx,
tcx,
location_table,
mir,
dominators,
param_env,
};
ig.visit_mir(mir);
}
}
/// 'cg = the duration of the constraint generation process itself.
struct InvalidationGenerator<'cg, 'cx: 'cg, 'tcx: 'cx, 'gcx: 'tcx> {
infcx: &'cg InferCtxt<'cx, 'gcx, 'tcx>,
all_facts: &'cg mut AllFacts,
location_table: &'cg LocationTable,
mir: &'cg Mir<'tcx>,
struct InvalidationGenerator<'cx, 'tcx: 'cx, 'gcx: 'tcx> {
tcx: TyCtxt<'cx, 'gcx, 'tcx>,
all_facts: &'cx mut AllFacts,
location_table: &'cx LocationTable,
mir: &'cx Mir<'tcx>,
dominators: Dominators<BasicBlock>,
borrow_set: &'cg BorrowSet<'tcx>,
param_env: ParamEnv<'gcx>,
borrow_set: &'cx BorrowSet<'tcx>,
}
/// Visits the whole MIR and generates invalidates() facts
/// Most of the code implementing this was stolen from borrow_check/mod.rs
impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
impl<'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx, 'gcx> {
fn visit_statement(&mut self,
block: BasicBlock,
statement: &Statement<'tcx>,
@ -154,8 +145,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tc
self.access_place(
ContextKind::StorageDead.new(location),
&Place::Local(local),
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
StorageDeadOrDrop::LocalStorageDead))),
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
LocalMutationIsAllowed::Yes,
);
}
@ -184,12 +174,12 @@ impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tc
target: _,
unwind: _,
} => {
let tcx = self.infcx.tcx;
let gcx = tcx.global_tcx();
let drop_place_ty = drop_place.ty(self.mir, tcx);
let drop_place_ty = tcx.erase_regions(&drop_place_ty).to_ty(tcx);
let drop_place_ty = gcx.lift(&drop_place_ty).unwrap();
self.visit_terminator_drop(location, terminator, drop_place, drop_place_ty);
self.access_place(
ContextKind::Drop.new(location),
drop_place,
(AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
LocalMutationIsAllowed::Yes,
);
}
TerminatorKind::DropAndReplace {
location: ref drop_place,
@ -286,83 +276,13 @@ impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tc
}
}
impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
/// Simulates dropping of a variable
fn visit_terminator_drop(
&mut self,
loc: Location,
term: &Terminator<'tcx>,
drop_place: &Place<'tcx>,
erased_drop_place_ty: ty::Ty<'gcx>,
) {
let gcx = self.infcx.tcx.global_tcx();
let drop_field = |
ig: &mut InvalidationGenerator<'cg, 'cx, 'gcx, 'tcx>,
(index, field): (usize, ty::Ty<'gcx>),
| {
let field_ty = gcx.normalize_erasing_regions(ig.param_env, field);
let place = drop_place.clone().field(Field::new(index), field_ty);
ig.visit_terminator_drop(loc, term, &place, field_ty);
};
match erased_drop_place_ty.sty {
// When a struct is being dropped, we need to check
// whether it has a destructor, if it does, then we can
// call it, if it does not then we need to check the
// individual fields instead. This way if `foo` has a
// destructor but `bar` does not, we will only check for
// borrows of `x.foo` and not `x.bar`. See #47703.
ty::Adt(def, substs) if def.is_struct() && !def.has_dtor(self.infcx.tcx) => {
def.all_fields()
.map(|field| field.ty(gcx, substs))
.enumerate()
.for_each(|field| drop_field(self, field));
}
// Same as above, but for tuples.
ty::Tuple(tys) => {
tys.iter().cloned().enumerate()
.for_each(|field| drop_field(self, field));
}
// Closures and generators also have disjoint fields, but they are only
// directly accessed in the body of the closure/generator.
ty::Generator(def, substs, ..)
if *drop_place == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty()
=> {
substs.upvar_tys(def, self.infcx.tcx).enumerate()
.for_each(|field| drop_field(self, field));
}
ty::Closure(def, substs)
if *drop_place == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty()
=> {
substs.upvar_tys(def, self.infcx.tcx).enumerate()
.for_each(|field| drop_field(self, field));
}
_ => {
// We have now refined the type of the value being
// dropped (potentially) to just the type of a
// subfield; so check whether that field's type still
// "needs drop". If so, we assume that the destructor
// may access any data it likes (i.e., a Deep Write).
if erased_drop_place_ty.needs_drop(gcx, self.param_env) {
self.access_place(
ContextKind::Drop.new(loc),
drop_place,
(Deep, Write(WriteKind::StorageDeadOrDrop(
StorageDeadOrDrop::Destructor))),
LocalMutationIsAllowed::Yes,
);
}
}
}
}
impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cx, 'tcx, 'gcx> {
/// Simulates mutation of a place
fn mutate_place(
&mut self,
context: Context,
place: &Place<'tcx>,
kind: ShallowOrDeep,
kind: AccessDepth,
_mode: MutateMode,
) {
self.access_place(
@ -412,7 +332,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
BorrowKind::Unique | BorrowKind::Mut { .. } => {
let wk = WriteKind::MutableBorrow(bk);
if allow_two_phase_borrow(&self.infcx.tcx, bk) {
if allow_two_phase_borrow(&self.tcx, bk) {
(Deep, Reservation(wk))
} else {
(Deep, Write(wk))
@ -471,7 +391,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
&mut self,
context: Context,
place: &Place<'tcx>,
kind: (ShallowOrDeep, ReadOrWrite),
kind: (AccessDepth, ReadOrWrite),
_is_local_mutation_allowed: LocalMutationIsAllowed,
) {
let (sd, rw) = kind;
@ -483,7 +403,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
&mut self,
context: Context,
place: &Place<'tcx>,
sd: ShallowOrDeep,
sd: AccessDepth,
rw: ReadOrWrite,
) {
debug!(
@ -494,7 +414,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
sd,
rw,
);
let tcx = self.infcx.tcx;
let tcx = self.tcx;
let mir = self.mir;
let borrow_set = self.borrow_set.clone();
let indices = self.borrow_set.borrows.indices();
@ -527,7 +447,7 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
// Reading from mere reservations of mutable-borrows is OK.
if !is_active(&this.dominators, borrow, context.loc) {
// If the borrow isn't active yet, reads don't invalidate it
assert!(allow_two_phase_borrow(&this.infcx.tcx, borrow.kind));
assert!(allow_two_phase_borrow(&this.tcx, borrow.kind));
return Control::Continue;
}

View File

@ -160,11 +160,10 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
// Generate various additional constraints.
invalidation::generate_invalidates(
infcx,
infcx.tcx,
&mut all_facts,
location_table,
&mir,
def_id,
borrow_set,
);

View File

@ -11,7 +11,7 @@
use borrow_check::borrow_set::{BorrowSet, BorrowData, TwoPhaseActivation};
use borrow_check::places_conflict;
use borrow_check::Context;
use borrow_check::ShallowOrDeep;
use borrow_check::AccessDepth;
use dataflow::indexes::BorrowIndex;
use rustc::mir::{BasicBlock, Location, Mir, Place};
use rustc::mir::{ProjectionElem, BorrowKind};
@ -43,7 +43,7 @@ pub(super) fn each_borrow_involving_path<'a, 'tcx, 'gcx: 'tcx, F, I, S> (
tcx: TyCtxt<'a, 'gcx, 'tcx>,
mir: &Mir<'tcx>,
_context: Context,
access_place: (ShallowOrDeep, &Place<'tcx>),
access_place: (AccessDepth, &Place<'tcx>),
borrow_set: &BorrowSet<'tcx>,
candidates: I,
mut op: F,

View File

@ -10,7 +10,7 @@
use borrow_check::ArtificialField;
use borrow_check::Overlap;
use borrow_check::{Deep, Shallow, ShallowOrDeep};
use borrow_check::{Deep, Shallow, AccessDepth};
use rustc::hir;
use rustc::mir::{Mir, Place};
use rustc::mir::{Projection, ProjectionElem};
@ -22,7 +22,7 @@ pub(super) fn places_conflict<'gcx, 'tcx>(
mir: &Mir<'tcx>,
borrow_place: &Place<'tcx>,
access_place: &Place<'tcx>,
access: ShallowOrDeep,
access: AccessDepth,
) -> bool {
debug!(
"places_conflict({:?},{:?},{:?})",
@ -49,7 +49,7 @@ fn place_components_conflict<'gcx, 'tcx>(
mir: &Mir<'tcx>,
mut borrow_components: PlaceComponentsIter<'_, 'tcx>,
mut access_components: PlaceComponentsIter<'_, 'tcx>,
access: ShallowOrDeep,
access: AccessDepth,
) -> bool {
// The borrowck rules for proving disjointness are applied from the "root" of the
// borrow forwards, iterating over "similar" projections in lockstep until
@ -179,16 +179,26 @@ fn place_components_conflict<'gcx, 'tcx>(
return false;
}
(ProjectionElem::Deref, ty::Ref(_, _, hir::MutImmutable), _) => {
// the borrow goes through a dereference of a shared reference.
//
// I'm not sure why we are tracking these borrows - shared
// references can *always* be aliased, which means the
// permission check already account for this borrow.
debug!("places_conflict: behind a shared ref");
// Shouldn't be tracked
bug!("Tracking borrow behind shared reference.");
}
(ProjectionElem::Deref, ty::Ref(_, _, hir::MutMutable), AccessDepth::Drop) => {
// Values behind a mutatble reference are not access either by Dropping a
// value, or by StorageDead
debug!("places_conflict: drop access behind ptr");
return false;
}
(ProjectionElem::Field { .. }, ty::Adt(def, _), AccessDepth::Drop) => {
// Drop can read/write arbitrary projections, so places
// conflict regardless of further projections.
if def.has_dtor(tcx) {
return true;
}
}
(ProjectionElem::Deref, _, Deep)
| (ProjectionElem::Deref, _, AccessDepth::Drop)
| (ProjectionElem::Field { .. }, _, _)
| (ProjectionElem::Index { .. }, _, _)
| (ProjectionElem::ConstantIndex { .. }, _, _)

View File

@ -8,8 +8,6 @@ LL | }
| |
| `v` dropped here while still borrowed
| borrow later used here, when `v` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error: aborting due to previous error

View File

@ -9,6 +9,8 @@ LL | }
| |
| `*cell` dropped here while still borrowed
| borrow later used here, when `gen` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `ref_` does not live long enough
--> $DIR/dropck.rs:22:11

View File

@ -23,7 +23,7 @@ LL | &mut *(*s).0 //[nll]~ ERROR borrow may still be in use when destructor
| ^^^^^^^^^^^^
...
LL | }
| - here, drop of `*s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 72:20...
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:72:20
@ -41,7 +41,7 @@ LL | &mut *(**s).0 //[nll]~ ERROR borrow may still be in use when destructor
| ^^^^^^^^^^^^^
...
LL | }
| - here, drop of `**s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 82:26...
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:82:26

View File

@ -20,7 +20,7 @@ LL | &mut *(*s).0 //[nll]~ ERROR borrow may still be in use when destructor
| ^^^^^^^^^^^^
...
LL | }
| - here, drop of `*s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 72:20...
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:72:20
@ -35,7 +35,7 @@ LL | &mut *(**s).0 //[nll]~ ERROR borrow may still be in use when destructor
| ^^^^^^^^^^^^^
...
LL | }
| - here, drop of `**s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 82:26...
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:82:26

View File

@ -0,0 +1,51 @@
#![feature(nll)]
enum DropOption<T> {
Some(T),
None,
}
impl<T> Drop for DropOption<T> {
fn drop(&mut self) {}
}
// Dropping opt could access the value behind the reference,
fn drop_enum(opt: DropOption<&mut i32>) -> Option<&mut i32> {
match opt {
DropOption::Some(&mut ref mut r) => { //~ ERROR
Some(r)
},
DropOption::None => None,
}
}
fn optional_drop_enum(opt: Option<DropOption<&mut i32>>) -> Option<&mut i32> {
match opt {
Some(DropOption::Some(&mut ref mut r)) => { //~ ERROR
Some(r)
},
Some(DropOption::None) | None => None,
}
}
// Ok, dropping opt doesn't access the reference
fn optional_tuple(opt: Option<(&mut i32, String)>) -> Option<&mut i32> {
match opt {
Some((&mut ref mut r, _)) => {
Some(r)
},
None => None,
}
}
// Ok, dropping res doesn't access the Ok case.
fn different_variants(res: Result<&mut i32, String>) -> Option<&mut i32> {
match res {
Ok(&mut ref mut r) => {
Some(r)
},
Err(_) => None,
}
}
fn main() {}

View File

@ -0,0 +1,45 @@
error[E0713]: borrow may still be in use when destructor runs
--> $DIR/enum-drop-access.rs:15:31
|
LL | DropOption::Some(&mut ref mut r) => { //~ ERROR
| ^^^^^^^^^
...
LL | }
| - here, drop of `opt` needs exclusive access to `*opt.0`, because the type `DropOption<&mut i32>` implements the `Drop` trait
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 13:1...
--> $DIR/enum-drop-access.rs:13:1
|
LL | / fn drop_enum(opt: DropOption<&mut i32>) -> Option<&mut i32> {
LL | | match opt {
LL | | DropOption::Some(&mut ref mut r) => { //~ ERROR
LL | | Some(r)
... |
LL | | }
LL | | }
| |_^
error[E0713]: borrow may still be in use when destructor runs
--> $DIR/enum-drop-access.rs:24:36
|
LL | Some(DropOption::Some(&mut ref mut r)) => { //~ ERROR
| ^^^^^^^^^
...
LL | }
| - here, drop of `opt` needs exclusive access to `*opt.0.0`, because the type `DropOption<&mut i32>` implements the `Drop` trait
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 22:1...
--> $DIR/enum-drop-access.rs:22:1
|
LL | / fn optional_drop_enum(opt: Option<DropOption<&mut i32>>) -> Option<&mut i32> {
LL | | match opt {
LL | | Some(DropOption::Some(&mut ref mut r)) => { //~ ERROR
LL | | Some(r)
... |
LL | | }
LL | | }
| |_^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0713`.

View File

@ -1,11 +1,11 @@
error[E0713]: borrow may still be in use when destructor runs
--> $DIR/borrowck-ref-into-rvalue.rs:14:14
error[E0597]: borrowed value does not live long enough
--> $DIR/borrowck-ref-into-rvalue.rs:13:11
|
LL | Some(ref m) => {
| ^^^^^
LL | match Some("Hello".to_string()) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
...
LL | }
| - drop of temporary value occurs here
| - temporary value only lives until here
LL | println!("{}", *msg);
| ---- borrow later used here
|
@ -13,4 +13,4 @@ LL | println!("{}", *msg);
error: aborting due to previous error
For more information about this error, try `rustc --explain E0713`.
For more information about this error, try `rustc --explain E0597`.

View File

@ -9,6 +9,8 @@ LL | }
| |
| `b2` dropped here while still borrowed
| borrow later used here, when `b1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `b3` does not live long enough
--> $DIR/dropck_arr_cycle_checked.rs:105:24
@ -21,6 +23,8 @@ LL | }
| |
| `b3` dropped here while still borrowed
| borrow later used here, when `b1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `b1` does not live long enough
--> $DIR/dropck_arr_cycle_checked.rs:111:24

View File

@ -23,8 +23,6 @@ LL | }
| |
| `d1` dropped here while still borrowed
| borrow later used here, when `d1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error: aborting due to 2 previous errors

View File

@ -9,6 +9,8 @@ LL | }
| |
| `c2` dropped here while still borrowed
| borrow later used here, when `c1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `c3` does not live long enough
--> $DIR/dropck_vec_cycle_checked.rs:115:24
@ -21,6 +23,8 @@ LL | }
| |
| `c3` dropped here while still borrowed
| borrow later used here, when `c1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `c1` does not live long enough
--> $DIR/dropck_vec_cycle_checked.rs:121:24

View File

@ -8,6 +8,8 @@ LL | }
| |
| `x` dropped here while still borrowed
| borrow later used here, when `y` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `x` does not live long enough
--> $DIR/issue-29106.rs:33:25
@ -19,6 +21,8 @@ LL | }
| |
| `x` dropped here while still borrowed
| borrow later used here, when `y` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error: aborting due to 2 previous errors

View File

@ -11,7 +11,6 @@ LL | }
| borrow later used here, when `foo` is dropped
|
= note: consider using a `let` binding to create a longer lived value
= note: values in a scope are dropped in the opposite order they are defined
error: aborting due to previous error

View File

@ -9,6 +9,8 @@ LL | }
| |
| `c2` dropped here while still borrowed
| borrow later used here, when `c1` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0597]: `c1` does not live long enough
--> $DIR/vec-must-not-hide-type-from-dropck.rs:129:24

View File

@ -11,8 +11,6 @@ LL | }
| |
| `factorial` dropped here while still borrowed
| borrow later used here, when `factorial` is dropped
|
= note: values in a scope are dropped in the opposite order they are defined
error[E0506]: cannot assign to `factorial` because it is borrowed
--> $DIR/unboxed-closures-failed-recursive-fn-1.rs:30:5