//! This query borrow-checks the MIR to (further) ensure it is not broken.
-use crate::borrow_check::nll::region_infer::RegionInferenceContext;
use rustc::hir::{self, HirId};
use rustc::hir::Node;
use rustc::hir::def_id::DefId;
use rustc::lint::builtin::{MUTABLE_BORROW_RESERVATION_CONFLICT};
use rustc::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
use rustc::mir::{
- ClearCrossCrate, Local, Location, Body, Mutability, Operand, Place, PlaceBase, PlaceElem,
- PlaceRef, Static, StaticKind
+ ClearCrossCrate, Local, Location, Body, BodyAndCache, Mutability, Operand, Place, PlaceBase,
+ PlaceElem, PlaceRef, ReadOnlyBodyAndCache, Static, StaticKind, read_only
};
use rustc::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind};
use rustc::mir::{Terminator, TerminatorKind};
use crate::dataflow::EverInitializedPlaces;
use crate::dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
-use self::borrow_set::{BorrowData, BorrowSet};
use self::flows::Flows;
use self::location::LocationTable;
use self::prefixes::PrefixSet;
use self::MutateMode::{JustWrite, WriteAndRead};
-use self::mutability_errors::AccessKind;
+use self::diagnostics::AccessKind;
use self::path_utils::*;
-crate mod borrow_set;
-mod error_reporting;
+mod diagnostics;
mod flows;
mod location;
-mod conflict_errors;
-mod move_errors;
-mod mutability_errors;
mod path_utils;
-crate mod place_ext;
-crate mod places_conflict;
mod prefixes;
mod used_muts;
-
-pub(crate) mod nll;
+mod constraint_generation;
+mod facts;
+mod invalidation;
+mod renumber;
+mod member_constraints;
+mod constraints;
+mod universal_regions;
+mod type_check;
+mod region_infer;
+mod borrow_set;
+mod place_ext;
+mod places_conflict;
+mod nll;
+
+crate use region_infer::RegionInferenceContext;
+crate use borrow_set::{BorrowSet, BorrowData};
+crate use places_conflict::{places_conflict, PlaceConflictBias};
+crate use place_ext::PlaceExt;
+crate use nll::ToRegionVid;
// FIXME(eddyb) perhaps move this somewhere more centrally.
#[derive(Debug)]
fn do_mir_borrowck<'a, 'tcx>(
infcx: &InferCtxt<'a, 'tcx>,
input_body: &Body<'tcx>,
- input_promoted: &IndexVec<Promoted, Body<'tcx>>,
+ input_promoted: &IndexVec<Promoted, BodyAndCache<'tcx>>,
def_id: DefId,
) -> BorrowCheckResult<'tcx> {
debug!("do_mir_borrowck(def_id = {:?})", def_id);
.as_local_hir_id(def_id)
.expect("do_mir_borrowck: non-local DefId");
+ let mut local_names = IndexVec::from_elem(None, &input_body.local_decls);
+ for var_debug_info in &input_body.var_debug_info {
+ if let Some(local) = var_debug_info.place.as_local() {
+ if let Some(prev_name) = local_names[local] {
+ if var_debug_info.name != prev_name {
+ span_bug!(var_debug_info.source_info.span,
+ "local {:?} has many names (`{}` vs `{}`)",
+ local, prev_name, var_debug_info.name);
+ }
+ }
+ local_names[local] = Some(var_debug_info.name);
+ }
+ }
+
// Gather the upvars of a closure, if any.
let tables = tcx.typeck_tables_of(def_id);
let upvars: Vec<_> = tables
};
let bm = *tables.pat_binding_modes().get(var_hir_id)
.expect("missing binding mode");
- if bm == ty::BindByValue(hir::MutMutable) {
+ if bm == ty::BindByValue(hir::Mutability::Mutable) {
upvar.mutability = Mutability::Mut;
}
upvar
// requires first making our own copy of the MIR. This copy will
// be modified (in place) to contain non-lexical lifetimes. It
// will have a lifetime tied to the inference context.
- let mut body: Body<'tcx> = input_body.clone();
- let mut promoted: IndexVec<Promoted, Body<'tcx>> = input_promoted.clone();
+ let body_clone: Body<'tcx> = input_body.clone();
+ let mut promoted = input_promoted.clone();
+ let mut body = BodyAndCache::new(body_clone);
let free_regions =
nll::replace_regions_in_mir(infcx, def_id, param_env, &mut body, &mut promoted);
- let body = &body; // no further changes
- let location_table = &LocationTable::new(body);
+ let body = read_only!(body); // no further changes
+ let promoted: IndexVec<_, _> = promoted
+ .iter_mut()
+ .map(|body| read_only!(body))
+ .collect();
+
+ let location_table = &LocationTable::new(&body);
let mut errors_buffer = Vec::new();
let (move_data, move_errors): (MoveData<'tcx>, Option<Vec<(Place<'tcx>, MoveError<'tcx>)>>) =
- match MoveData::gather_moves(body, tcx) {
+ match MoveData::gather_moves(&body, tcx, param_env) {
Ok(move_data) => (move_data, None),
Err((move_data, move_errors)) => (move_data, Some(move_errors)),
};
let dead_unwinds = BitSet::new_empty(body.basic_blocks().len());
let mut flow_inits = FlowAtLocation::new(do_dataflow(
tcx,
- body,
+ &body,
def_id,
&attributes,
&dead_unwinds,
- MaybeInitializedPlaces::new(tcx, body, &mdpe),
+ MaybeInitializedPlaces::new(tcx, &body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]),
));
let locals_are_invalidated_at_exit = tcx.hir().body_owner_kind(id).is_fn_or_closure();
let borrow_set = Rc::new(BorrowSet::build(
- tcx, body, locals_are_invalidated_at_exit, &mdpe.move_data));
+ tcx, body, locals_are_invalidated_at_exit, &mdpe.move_data));
// If we are in non-lexical mode, compute the non-lexical lifetimes.
let (regioncx, polonius_output, opt_closure_req) = nll::compute_regions(
free_regions,
body,
&promoted,
+ &local_names,
&upvars,
location_table,
param_env,
let flow_borrows = FlowAtLocation::new(do_dataflow(
tcx,
- body,
+ &body,
def_id,
&attributes,
&dead_unwinds,
- Borrows::new(tcx, body, param_env, regioncx.clone(), &borrow_set),
+ Borrows::new(tcx, &body, param_env, regioncx.clone(), &borrow_set),
|rs, i| DebugFormatted::new(&rs.location(i)),
));
let flow_uninits = FlowAtLocation::new(do_dataflow(
tcx,
- body,
+ &body,
def_id,
&attributes,
&dead_unwinds,
- MaybeUninitializedPlaces::new(tcx, body, &mdpe),
+ MaybeUninitializedPlaces::new(tcx, &body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]),
));
let flow_ever_inits = FlowAtLocation::new(do_dataflow(
tcx,
- body,
+ &body,
def_id,
&attributes,
&dead_unwinds,
- EverInitializedPlaces::new(tcx, body, &mdpe),
+ EverInitializedPlaces::new(tcx, &body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().inits[i]),
));
let movable_generator = match tcx.hir().get(id) {
Node::Expr(&hir::Expr {
- kind: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)),
+ kind: hir::ExprKind::Closure(.., Some(hir::Movability::Static)),
..
}) => false,
_ => true,
borrow_set,
dominators,
upvars,
+ local_names,
};
let mut state = Flows::new(
let mut initial_diag =
mbcx.report_conflicting_borrow(location, (&place, span), bk, &borrow);
- let lint_root = if let ClearCrossCrate::Set(ref vsi) = mbcx.body.source_scope_local_data {
- let scope = mbcx.body.source_info(location).scope;
- vsi[scope].lint_root
- } else {
- id
+ let scope = mbcx.body.source_info(location).scope;
+ let lint_root = match &mbcx.body.source_scopes[scope].local_data {
+ ClearCrossCrate::Set(data) => data.lint_root,
+ _ => id,
};
// Span and message don't matter; we overwrite them below anyway
// would have a chance of erroneously adding non-user-defined mutable vars
// to the set.
let temporary_used_locals: FxHashSet<Local> = mbcx.used_mut.iter()
- .filter(|&local| mbcx.body.local_decls[*local].is_user_variable.is_none())
+ .filter(|&local| !mbcx.body.local_decls[*local].is_user_variable())
.cloned()
.collect();
// For the remaining unused locals that are marked as mutable, we avoid linting any that
debug!("mbcx.used_mut: {:?}", mbcx.used_mut);
let used_mut = mbcx.used_mut;
- for local in mbcx.body.mut_vars_and_args_iter().filter(|local| !used_mut.contains(local)) {
- if let ClearCrossCrate::Set(ref vsi) = mbcx.body.source_scope_local_data {
- let local_decl = &mbcx.body.local_decls[local];
-
- // Skip implicit `self` argument for closures
- if local.index() == 1 && tcx.is_closure(mbcx.mir_def_id) {
- continue;
- }
-
- // Skip over locals that begin with an underscore or have no name
- match local_decl.name {
- Some(name) => if name.as_str().starts_with("_") {
- continue;
- },
- None => continue,
- }
+ for local in mbcx.body.mut_vars_and_args_iter().filter(|local| !used_mut.contains(local))
+ {
+ let local_decl = &mbcx.body.local_decls[local];
+ let lint_root = match &mbcx.body.source_scopes[local_decl.source_info.scope].local_data {
+ ClearCrossCrate::Set(data) => data.lint_root,
+ _ => continue,
+ };
- let span = local_decl.source_info.span;
- if span.desugaring_kind().is_some() {
- // If the `mut` arises as part of a desugaring, we should ignore it.
+ // Skip over locals that begin with an underscore or have no name
+ match mbcx.local_names[local] {
+ Some(name) => if name.as_str().starts_with("_") {
continue;
- }
+ },
+ None => continue,
+ }
- let mut_span = tcx.sess.source_map().span_until_non_whitespace(span);
- tcx.struct_span_lint_hir(
- UNUSED_MUT,
- vsi[local_decl.source_info.scope].lint_root,
- span,
- "variable does not need to be mutable",
- )
- .span_suggestion_short(
- mut_span,
- "remove this `mut`",
- String::new(),
- Applicability::MachineApplicable,
- )
- .emit();
+ let span = local_decl.source_info.span;
+ if span.desugaring_kind().is_some() {
+ // If the `mut` arises as part of a desugaring, we should ignore it.
+ continue;
}
+
+ let mut_span = tcx.sess.source_map().span_until_non_whitespace(span);
+ tcx.struct_span_lint_hir(
+ UNUSED_MUT,
+ lint_root,
+ span,
+ "variable does not need to be mutable",
+ )
+ .span_suggestion_short(
+ mut_span,
+ "remove this `mut`",
+ String::new(),
+ Applicability::MachineApplicable,
+ )
+ .emit();
}
// Buffer any move errors that we collected and de-duplicated.
}
if !mbcx.errors_buffer.is_empty() {
- mbcx.errors_buffer.sort_by_key(|diag| diag.span.primary_span());
+ mbcx.errors_buffer.sort_by_key(|diag| diag.sort_span);
for diag in mbcx.errors_buffer.drain(..) {
mbcx.infcx.tcx.sess.diagnostic().emit_diagnostic(&diag);
crate struct MirBorrowckCtxt<'cx, 'tcx> {
crate infcx: &'cx InferCtxt<'cx, 'tcx>,
- body: &'cx Body<'tcx>,
+ body: ReadOnlyBodyAndCache<'cx, 'tcx>,
mir_def_id: DefId,
param_env: ty::ParamEnv<'tcx>,
move_data: &'cx MoveData<'tcx>,
/// Information about upvars not necessarily preserved in types or MIR
upvars: Vec<Upvar>,
+
+ /// Names of local (user) variables (extracted from `var_debug_info`).
+ local_names: IndexVec<Local, Option<Name>>,
}
// Check that:
type FlowState = Flows<'cx, 'tcx>;
fn body(&self) -> &'cx Body<'tcx> {
- self.body
+ *self.body
}
fn visit_block_entry(&mut self, bb: BasicBlock, flow_state: &Self::FlowState) {
let tcx = self.infcx.tcx;
// Compute the type with accurate region information.
- let drop_place_ty = drop_place.ty(self.body, self.infcx.tcx);
+ let drop_place_ty = drop_place.ty(*self.body, self.infcx.tcx);
// Erase the regions.
let drop_place_ty = self.infcx.tcx.erase_regions(&drop_place_ty).ty;
self.check_access_for_conflict(location, place_span, sd, rw, flow_state);
if let (Activation(_, borrow_idx), true) = (kind.1, conflict_error) {
- // Suppress this warning when there's an error being emited for the
+ // Suppress this warning when there's an error being emitted for the
// same borrow: fixing the error is likely to fix the warning.
self.reservation_warnings.remove(&borrow_idx);
}
let mut error_reported = false;
let tcx = self.infcx.tcx;
let body = self.body;
+ let body: &Body<'_> = &body;
let param_env = self.param_env;
let location_table = self.location_table.start_index(location);
let borrow_set = self.borrow_set.clone();
match *operand {
Operand::Move(ref place) | Operand::Copy(ref place) => {
match place.as_local() {
- Some(local) if self.body.local_decls[local].is_user_variable.is_none() => {
+ Some(local) if !self.body.local_decls[local].is_user_variable() => {
if self.body.local_decls[local].ty.is_mutable_ptr() {
// The variable will be marked as mutable by the borrow.
return;
_ => bug!("temporary initialized in arguments"),
};
- let bbd = &self.body[loc.block];
+ let body = self.body;
+ let bbd = &body[loc.block];
let stmt = &bbd.statements[loc.statement_index];
debug!("temporary assigned in: stmt={:?}", stmt);
) {
debug!("check_for_invalidation_at_exit({:?})", borrow);
let place = &borrow.borrowed_place;
- let root_place = self.prefixes(place.as_ref(), PrefixSet::All).last().unwrap();
+ let deref = [ProjectionElem::Deref];
+ let mut root_place = PlaceRef { base: &place.base, projection: &[] };
// FIXME(nll-rfc#40): do more precise destructor tracking here. For now
// we just know that all locals are dropped at function exit (otherwise
//
// FIXME: allow thread-locals to borrow other thread locals?
- assert!(root_place.projection.is_empty());
let (might_be_alive, will_be_dropped) = match root_place.base {
- PlaceBase::Static(box Static {
- kind: StaticKind::Promoted(..),
- ..
- }) => {
+ PlaceBase::Static(_) => {
(true, false)
}
- PlaceBase::Static(box Static {
- kind: StaticKind::Static,
- ..
- }) => {
- // Thread-locals might be dropped after the function exits, but
- // "true" statics will never be.
- (true, self.is_place_thread_local(root_place))
- }
- PlaceBase::Local(_) => {
- // Locals are always dropped at function exit, and if they
- // have a destructor it would've been called already.
- (false, self.locals_are_invalidated_at_exit)
+ PlaceBase::Local(local) => {
+ if self.body.local_decls[*local].is_ref_to_thread_local() {
+ // Thread-locals might be dropped after the function exits
+ // We have to dereference the outer reference because
+ // borrows don't conflict behind shared references.
+ root_place.projection = &deref;
+ (true, true)
+ } else {
+ (false, self.locals_are_invalidated_at_exit)
+ }
}
};
if places_conflict::borrow_conflicts_with_place(
self.infcx.tcx,
self.param_env,
- self.body,
+ &self.body,
place,
borrow.kind,
root_place,
(prefix, place_span.0, place_span.1),
mpi,
);
- return; // don't bother finding other problems.
}
}
Err(NoMovePathFound::ReachedStatic) => {
}
}
+ /// Subslices correspond to multiple move paths, so we iterate through the
+ /// elements of the base array. For each element we check
+ ///
+ /// * Does this element overlap with our slice.
+ /// * Is any part of it uninitialized.
+ fn check_if_subslice_element_is_moved(
+ &mut self,
+ location: Location,
+ desired_action: InitializationRequiringAction,
+ place_span: (PlaceRef<'cx, 'tcx>, Span),
+ maybe_uninits: &FlowAtLocation<'tcx, MaybeUninitializedPlaces<'cx, 'tcx>>,
+ from: u32,
+ to: u32,
+ ) {
+ if let Some(mpi) = self.move_path_for_place(place_span.0) {
+ let mut child = self.move_data.move_paths[mpi].first_child;
+ while let Some(child_mpi) = child {
+ let child_move_place = &self.move_data.move_paths[child_mpi];
+ let child_place = &child_move_place.place;
+ let last_proj = child_place.projection.last().unwrap();
+ if let ProjectionElem::ConstantIndex { offset, from_end, .. } = last_proj {
+ debug_assert!(!from_end, "Array constant indexing shouldn't be `from_end`.");
+
+ if (from..to).contains(offset) {
+ if let Some(uninit_child) = maybe_uninits.has_any_child_of(child_mpi) {
+ self.report_use_of_moved_or_uninitialized(
+ location,
+ desired_action,
+ (place_span.0, place_span.0, place_span.1),
+ uninit_child,
+ );
+ return; // don't bother finding other problems.
+ }
+ }
+ }
+ child = child_move_place.next_sibling;
+ }
+ }
+ }
+
fn check_if_path_or_subpath_is_moved(
&mut self,
location: Location,
self.check_if_full_path_is_moved(location, desired_action, place_span, flow_state);
+ if let [
+ base_proj @ ..,
+ ProjectionElem::Subslice { from, to, from_end: false },
+ ] = place_span.0.projection {
+ let place_ty = Place::ty_from(
+ place_span.0.base,
+ base_proj,
+ self.body(),
+ self.infcx.tcx,
+ );
+ if let ty::Array(..) = place_ty.ty.kind {
+ let array_place = PlaceRef { base: place_span.0.base, projection: base_proj };
+ self.check_if_subslice_element_is_moved(
+ location,
+ desired_action,
+ (array_place, place_span.1),
+ maybe_uninits,
+ *from,
+ *to,
+ );
+ return;
+ }
+ }
+
// A move of any shallow suffix of `place` also interferes
// with an attempt to use `place`. This is scenario 3 above.
//
/// static variable, as we do not track those in the MoveData.
fn move_path_closest_to(
&mut self,
- place: PlaceRef<'cx, 'tcx>,
+ place: PlaceRef<'_, 'tcx>,
) -> Result<(PlaceRef<'cx, 'tcx>, MovePathIndex), NoMovePathFound> {
- let mut last_prefix = place.base;
-
- for prefix in self.prefixes(place, PrefixSet::All) {
- if let Some(mpi) = self.move_path_for_place(prefix) {
- return Ok((prefix, mpi));
- }
-
- last_prefix = prefix.base;
- }
-
- match last_prefix {
- PlaceBase::Local(_) => panic!("should have move path for every Local"),
- PlaceBase::Static(_) => Err(NoMovePathFound::ReachedStatic),
+ match self.move_data.rev_lookup.find(place) {
+ LookupResult::Parent(Some(mpi))
+ | LookupResult::Exact(mpi) => Ok((self.move_data.move_paths[mpi].place.as_ref(), mpi)),
+ LookupResult::Parent(None) => Err(NoMovePathFound::ReachedStatic),
}
}
- fn move_path_for_place(&mut self, place: PlaceRef<'cx, 'tcx>) -> Option<MovePathIndex> {
+ fn move_path_for_place(&mut self, place: PlaceRef<'_, 'tcx>) -> Option<MovePathIndex> {
// If returns None, then there is no move path corresponding
// to a direct owner of `place` (which means there is nothing
// that borrowck tracks for its analysis).
// assigning to `P.f` requires `P` itself
// be already initialized
let tcx = self.infcx.tcx;
- let base_ty = Place::ty_from(&place.base, proj_base, self.body, tcx).ty;
+ let base_ty = Place::ty_from(&place.base, proj_base, self.body(), tcx).ty;
match base_ty.kind {
ty::Adt(def, _) if def.has_dtor(tcx) => {
self.check_if_path_or_subpath_is_moved(
// of the union - we should error in that case.
let tcx = this.infcx.tcx;
if let ty::Adt(def, _) =
- Place::ty_from(base.base, base.projection, this.body, tcx).ty.kind
+ Place::ty_from(base.base, base.projection, this.body(), tcx).ty.kind
{
if def.is_union() {
if this.move_data.path_map[mpi].iter().any(|moi| {
let error_access;
let the_place_err;
- // rust-lang/rust#21232, #54986: during period where we reject
- // partial initialization, do not complain about mutability
- // errors except for actual mutation (as opposed to an attempt
- // to do a partial initialization).
- let previously_initialized = if let PlaceBase::Local(local) = place.base {
- self.is_local_ever_initialized(local, flow_state).is_some()
- } else {
- true
- };
-
match kind {
Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Unique))
| Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Mut { .. }))
}
}
+ // rust-lang/rust#21232, #54986: during period where we reject
+ // partial initialization, do not complain about mutability
+ // errors except for actual mutation (as opposed to an attempt
+ // to do a partial initialization).
+ let previously_initialized = if let PlaceBase::Local(local) = place.base {
+ self.is_local_ever_initialized(local, flow_state).is_some()
+ } else {
+ true
+ };
+
// at this point, we have set up the error reporting state.
- return if previously_initialized {
+ if previously_initialized {
self.report_mutability_error(
place,
span,
true
} else {
false
- };
+ }
}
fn is_local_ever_initialized(
match elem {
ProjectionElem::Deref => {
let base_ty =
- Place::ty_from(place.base, proj_base, self.body, self.infcx.tcx).ty;
+ Place::ty_from(place.base, proj_base, self.body(), self.infcx.tcx).ty;
// Check the kind of deref to decide
match base_ty.kind {
ty::Ref(_, _, mutbl) => {
match mutbl {
// Shared borrowed data is never mutable
- hir::MutImmutable => Err(place),
+ hir::Mutability::Immutable => Err(place),
// Mutably borrowed data is mutable, but only if we have a
// unique path to the `&mut`
- hir::MutMutable => {
+ hir::Mutability::Mutable => {
let mode = match self.is_upvar_field_projection(place) {
Some(field)
if self.upvars[field.index()].by_ref =>
ty::RawPtr(tnm) => {
match tnm.mutbl {
// `*const` raw pointers are not mutable
- hir::MutImmutable => Err(place),
+ hir::Mutability::Immutable => Err(place),
// `*mut` raw pointers are always mutable, regardless of
// context. The users have to check by themselves.
- hir::MutMutable => {
+ hir::Mutability::Mutable => {
Ok(RootPlace {
place_base: place.base,
place_projection: place.projection,
match place_projection {
[base @ .., ProjectionElem::Field(field, _ty)] => {
let tcx = self.infcx.tcx;
- let base_ty = Place::ty_from(place_ref.base, base, self.body, tcx).ty;
+ let base_ty = Place::ty_from(place_ref.base, base, self.body(), tcx).ty;
if (base_ty.is_closure() || base_ty.is_generator()) &&
(!by_ref || self.upvars[field.index()].by_ref) {