blob: e437c08c956ec73123764af4e3880236df599ead [file] [log] [blame]
// ----------------------------------------------------------------------
// Gathering loans
//
// The borrow check proceeds in two phases. In phase one, we gather the full
// set of loans that are required at any point. These are sorted according to
// their associated scopes. In phase two, checking loans, we will then make
// sure that all of these loans are honored.
use crate::borrowck::*;
use crate::borrowck::move_data::MoveData;
use rustc::middle::expr_use_visitor as euv;
use rustc::middle::mem_categorization as mc;
use rustc::middle::mem_categorization::Categorization;
use rustc::middle::region;
use rustc::ty::{self, TyCtxt};
use syntax_pos::Span;
use rustc::hir;
use log::debug;
use restrictions::RestrictionResult;
mod lifetime;
mod restrictions;
mod gather_moves;
mod move_error;
pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
body: hir::BodyId)
-> (Vec<Loan<'tcx>>, move_data::MoveData<'tcx>) {
let def_id = bccx.tcx.hir().body_owner_def_id(body);
let param_env = bccx.tcx.param_env(def_id);
let mut glcx = GatherLoanCtxt {
bccx,
all_loans: Vec::new(),
item_ub: region::Scope {
id: bccx.tcx.hir().body(body).value.hir_id.local_id,
data: region::ScopeData::Node
},
move_data: MoveData::default(),
move_error_collector: move_error::MoveErrorCollector::new(),
};
let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
euv::ExprUseVisitor::new(&mut glcx,
bccx.tcx,
def_id,
param_env,
&bccx.region_scope_tree,
bccx.tables,
Some(rvalue_promotable_map))
.consume_body(bccx.body);
glcx.report_potential_errors();
let GatherLoanCtxt { all_loans, move_data, .. } = glcx;
(all_loans, move_data)
}
struct GatherLoanCtxt<'a, 'tcx: 'a> {
bccx: &'a BorrowckCtxt<'a, 'tcx>,
move_data: move_data::MoveData<'tcx>,
move_error_collector: move_error::MoveErrorCollector<'tcx>,
all_loans: Vec<Loan<'tcx>>,
/// `item_ub` is used as an upper-bound on the lifetime whenever we
/// ask for the scope of an expression categorized as an upvar.
item_ub: region::Scope,
}
impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
fn consume(&mut self,
consume_id: hir::HirId,
_consume_span: Span,
cmt: &mc::cmt_<'tcx>,
mode: euv::ConsumeMode) {
debug!("consume(consume_id={}, cmt={:?}, mode={:?})",
consume_id, cmt, mode);
match mode {
euv::Move(move_reason) => {
gather_moves::gather_move_from_expr(
self.bccx, &self.move_data, &mut self.move_error_collector,
consume_id.local_id, cmt, move_reason);
}
euv::Copy => { }
}
}
fn matched_pat(&mut self,
matched_pat: &hir::Pat,
cmt: &mc::cmt_<'tcx>,
mode: euv::MatchMode) {
debug!("matched_pat(matched_pat={:?}, cmt={:?}, mode={:?})",
matched_pat,
cmt,
mode);
}
fn consume_pat(&mut self,
consume_pat: &hir::Pat,
cmt: &mc::cmt_<'tcx>,
mode: euv::ConsumeMode) {
debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})",
consume_pat,
cmt,
mode);
match mode {
euv::Copy => { return; }
euv::Move(_) => { }
}
gather_moves::gather_move_from_pat(
self.bccx, &self.move_data, &mut self.move_error_collector,
consume_pat, cmt);
}
fn borrow(&mut self,
borrow_id: hir::HirId,
borrow_span: Span,
cmt: &mc::cmt_<'tcx>,
loan_region: ty::Region<'tcx>,
bk: ty::BorrowKind,
loan_cause: euv::LoanCause)
{
debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
bk={:?}, loan_cause={:?})",
borrow_id, cmt, loan_region,
bk, loan_cause);
self.guarantee_valid(borrow_id.local_id,
borrow_span,
cmt,
bk,
loan_region,
loan_cause);
}
fn mutate(&mut self,
assignment_id: hir::HirId,
assignment_span: Span,
assignee_cmt: &mc::cmt_<'tcx>,
_: euv::MutateMode)
{
self.guarantee_assignment_valid(assignment_id,
assignment_span,
assignee_cmt);
}
fn decl_without_init(&mut self, id: hir::HirId, _span: Span) {
let ty = self.bccx
.tables
.node_type(id);
gather_moves::gather_decl(self.bccx, &self.move_data, id, ty);
}
fn nested_body(&mut self, body_id: hir::BodyId) {
debug!("nested_body(body_id={:?})", body_id);
// rust-lang/rust#58776: MIR and AST borrow check disagree on where
// certain closure errors are reported. As such migrate borrowck has to
// operate at the level of items, rather than bodies. Check if the
// contained closure had any errors and set `signalled_any_error` if it
// has.
let bccx = self.bccx;
if bccx.tcx.migrate_borrowck() {
if let SignalledError::NoErrorsSeen = bccx.signalled_any_error.get() {
let closure_def_id = bccx.tcx.hir().body_owner_def_id(body_id);
debug!("checking closure: {:?}", closure_def_id);
bccx.signalled_any_error.set(bccx.tcx.borrowck(closure_def_id).signalled_any_error);
}
}
}
}
/// Implements the A-* rules in README.md.
fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
borrow_span: Span,
loan_cause: AliasableViolationKind,
cmt: &mc::cmt_<'tcx>,
req_kind: ty::BorrowKind)
-> Result<(),()> {
let aliasability = cmt.freely_aliasable();
debug!("check_aliasability aliasability={:?} req_kind={:?}",
aliasability, req_kind);
match (aliasability, req_kind) {
(mc::Aliasability::NonAliasable, _) => {
/* Uniquely accessible path -- OK for `&` and `&mut` */
Ok(())
}
(mc::Aliasability::FreelyAliasable(mc::AliasableStatic), ty::ImmBorrow) => {
// Borrow of an immutable static item.
Ok(())
}
(mc::Aliasability::FreelyAliasable(mc::AliasableStaticMut), _) => {
// Even touching a static mut is considered unsafe. We assume the
// user knows what they're doing in these cases.
Ok(())
}
(mc::Aliasability::FreelyAliasable(alias_cause), ty::UniqueImmBorrow) |
(mc::Aliasability::FreelyAliasable(alias_cause), ty::MutBorrow) => {
bccx.report_aliasability_violation(
borrow_span,
loan_cause,
alias_cause,
cmt);
Err(())
}
(..) => {
Ok(())
}
}
}
/// Implements the M-* rules in README.md.
fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
borrow_span: Span,
cause: AliasableViolationKind,
cmt: &mc::cmt_<'tcx>,
req_kind: ty::BorrowKind)
-> Result<(),()> {
debug!("check_mutability(cause={:?} cmt={:?} req_kind={:?}",
cause, cmt, req_kind);
match req_kind {
ty::UniqueImmBorrow | ty::ImmBorrow => {
match cmt.mutbl {
// I am intentionally leaving this here to help
// refactoring if, in the future, we should add new
// kinds of mutability.
mc::McImmutable | mc::McDeclared | mc::McInherited => {
// both imm and mut data can be lent as imm;
// for mutable data, this is a freeze
Ok(())
}
}
}
ty::MutBorrow => {
// Only mutable data can be lent as mutable.
if !cmt.mutbl.is_mutable() {
Err(bccx.report(BckError { span: borrow_span,
cause,
cmt,
code: err_mutbl }))
} else {
Ok(())
}
}
}
}
impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
pub fn tcx(&self) -> TyCtxt<'a, 'tcx, 'tcx> { self.bccx.tcx }
/// Guarantees that `cmt` is assignable, or reports an error.
fn guarantee_assignment_valid(&mut self,
assignment_id: hir::HirId,
assignment_span: Span,
cmt: &mc::cmt_<'tcx>) {
let opt_lp = opt_loan_path(cmt);
debug!("guarantee_assignment_valid(assignment_id={}, cmt={:?}) opt_lp={:?}",
assignment_id, cmt, opt_lp);
if let Categorization::Local(..) = cmt.cat {
// Only re-assignments to locals require it to be
// mutable - this is checked in check_loans.
} else {
// Check that we don't allow assignments to non-mutable data.
if check_mutability(self.bccx, assignment_span, MutabilityViolation,
cmt, ty::MutBorrow).is_err() {
return; // reported an error, no sense in reporting more.
}
}
// Check that we don't allow assignments to aliasable data
if check_aliasability(self.bccx, assignment_span, MutabilityViolation,
cmt, ty::MutBorrow).is_err() {
return; // reported an error, no sense in reporting more.
}
match opt_lp {
Some(lp) => {
if let Categorization::Local(..) = cmt.cat {
// Only re-assignments to locals require it to be
// mutable - this is checked in check_loans.
} else {
self.mark_loan_path_as_mutated(&lp);
}
gather_moves::gather_assignment(self.bccx, &self.move_data,
assignment_id.local_id,
assignment_span,
lp);
}
None => {
// This can occur with e.g., `*foo() = 5`. In such
// cases, there is no need to check for conflicts
// with moves etc, just ignore.
}
}
}
/// Guarantees that `addr_of(cmt)` will be valid for the duration of `static_scope_r`, or
/// reports an error. This may entail taking out loans, which will be added to the
/// `req_loan_map`.
fn guarantee_valid(&mut self,
borrow_id: hir::ItemLocalId,
borrow_span: Span,
cmt: &mc::cmt_<'tcx>,
req_kind: ty::BorrowKind,
loan_region: ty::Region<'tcx>,
cause: euv::LoanCause) {
debug!("guarantee_valid(borrow_id={:?}, cmt={:?}, \
req_mutbl={:?}, loan_region={:?})",
borrow_id,
cmt,
req_kind,
loan_region);
// a loan for the empty region can never be dereferenced, so
// it is always safe
if *loan_region == ty::ReEmpty {
return;
}
// Check that the lifetime of the borrow does not exceed
// the lifetime of the data being borrowed.
if lifetime::guarantee_lifetime(self.bccx, self.item_ub,
borrow_span, cause, cmt, loan_region).is_err() {
return; // reported an error, no sense in reporting more.
}
// Check that we don't allow mutable borrows of non-mutable data.
if check_mutability(self.bccx, borrow_span, BorrowViolation(cause),
cmt, req_kind).is_err() {
return; // reported an error, no sense in reporting more.
}
// Check that we don't allow mutable borrows of aliasable data.
if check_aliasability(self.bccx, borrow_span, BorrowViolation(cause),
cmt, req_kind).is_err() {
return; // reported an error, no sense in reporting more.
}
// Compute the restrictions that are required to enforce the
// loan is safe.
let restr = restrictions::compute_restrictions(
self.bccx, borrow_span, cause, &cmt, loan_region);
debug!("guarantee_valid(): restrictions={:?}", restr);
// Create the loan record (if needed).
let loan = match restr {
RestrictionResult::Safe => {
// No restrictions---no loan record necessary
return;
}
RestrictionResult::SafeIf(loan_path, restricted_paths) => {
let loan_scope = match *loan_region {
ty::ReScope(scope) => scope,
ty::ReEarlyBound(ref br) => {
self.bccx.region_scope_tree.early_free_scope(self.tcx(), br)
}
ty::ReFree(ref fr) => {
self.bccx.region_scope_tree.free_scope(self.tcx(), fr)
}
ty::ReStatic => self.item_ub,
ty::ReEmpty |
ty::ReClosureBound(..) |
ty::ReLateBound(..) |
ty::ReVar(..) |
ty::RePlaceholder(..) |
ty::ReErased => {
span_bug!(
cmt.span,
"invalid borrow lifetime: {:?}",
loan_region);
}
};
debug!("loan_scope = {:?}", loan_scope);
let borrow_scope = region::Scope {
id: borrow_id,
data: region::ScopeData::Node
};
let gen_scope = self.compute_gen_scope(borrow_scope, loan_scope);
debug!("gen_scope = {:?}", gen_scope);
let kill_scope = self.compute_kill_scope(loan_scope, &loan_path);
debug!("kill_scope = {:?}", kill_scope);
if req_kind == ty::MutBorrow {
self.mark_loan_path_as_mutated(&loan_path);
}
Loan {
index: self.all_loans.len(),
loan_path,
kind: req_kind,
gen_scope,
kill_scope,
span: borrow_span,
restricted_paths,
cause,
}
}
};
debug!("guarantee_valid(borrow_id={:?}), loan={:?}",
borrow_id, loan);
// let loan_path = loan.loan_path;
// let loan_gen_scope = loan.gen_scope;
// let loan_kill_scope = loan.kill_scope;
self.all_loans.push(loan);
// if loan_gen_scope != borrow_id {
// FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls
//
// Typically, the scope of the loan includes the point at
// which the loan is originated. This
// This is a subtle case. See the test case
// <compile-fail/borrowck-bad-nested-calls-free.rs>
// to see what we are guarding against.
//let restr = restrictions::compute_restrictions(
// self.bccx, borrow_span, cmt, RESTR_EMPTY);
//let loan = {
// Loan {
// index: self.all_loans.len(),
// loan_path,
// cmt,
// mutbl: ConstMutability,
// gen_scope: borrow_id,
// kill_scope,
// span: borrow_span,
// restrictions,
// }
// }
}
pub fn mark_loan_path_as_mutated(&self, loan_path: &LoanPath<'_>) {
//! For mutable loans of content whose mutability derives
//! from a local variable, mark the mutability decl as necessary.
let mut wrapped_path = Some(loan_path);
let mut through_borrow = false;
while let Some(current_path) = wrapped_path {
wrapped_path = match current_path.kind {
LpVar(hir_id) => {
if !through_borrow {
self.bccx.used_mut_nodes.borrow_mut().insert(hir_id);
}
None
}
LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath { hir_id }, closure_expr_id: _ }) => {
self.bccx.used_mut_nodes.borrow_mut().insert(hir_id);
None
}
LpExtend(ref base, mc::McInherited, LpDeref(pointer_kind)) |
LpExtend(ref base, mc::McDeclared, LpDeref(pointer_kind)) => {
if pointer_kind != mc::Unique {
through_borrow = true;
}
Some(base)
}
LpDowncast(ref base, _) |
LpExtend(ref base, mc::McInherited, _) |
LpExtend(ref base, mc::McDeclared, _) => {
Some(base)
}
LpExtend(_, mc::McImmutable, _) => {
// Nothing to do.
None
}
}
}
}
pub fn compute_gen_scope(&self,
borrow_scope: region::Scope,
loan_scope: region::Scope)
-> region::Scope {
//! Determine when to introduce the loan. Typically the loan
//! is introduced at the point of the borrow, but in some cases,
//! notably method arguments, the loan may be introduced only
//! later, once it comes into scope.
if self.bccx.region_scope_tree.is_subscope_of(borrow_scope, loan_scope) {
borrow_scope
} else {
loan_scope
}
}
pub fn compute_kill_scope(&self, loan_scope: region::Scope, lp: &LoanPath<'tcx>)
-> region::Scope {
//! Determine when the loan restrictions go out of scope.
//! This is either when the lifetime expires or when the
//! local variable which roots the loan-path goes out of scope,
//! whichever happens faster.
//!
//! It may seem surprising that we might have a loan region
//! larger than the variable which roots the loan-path; this can
//! come about when variables of `&mut` type are re-borrowed,
//! as in this example:
//!
//! struct Foo { counter: u32 }
//!
//! fn counter<'a>(v: &'a mut Foo) -> &'a mut u32 {
//! &mut v.counter
//! }
//!
//! In this case, the reference (`'a`) outlives the
//! variable `v` that hosts it. Note that this doesn't come up
//! with immutable `&` pointers, because borrows of such pointers
//! do not require restrictions and hence do not cause a loan.
let lexical_scope = lp.kill_scope(self.bccx);
if self.bccx.region_scope_tree.is_subscope_of(lexical_scope, loan_scope) {
lexical_scope
} else {
assert!(self.bccx.region_scope_tree.is_subscope_of(loan_scope, lexical_scope));
loan_scope
}
}
pub fn report_potential_errors(&self) {
self.move_error_collector.report_potential_errors(self.bccx);
}
}