serialize getopts collections test time rand \
log regex graphviz core rbml alloc rustrt \
unicode
-RUSTC_CRATES := rustc rustc_typeck rustc_driver rustc_trans rustc_back rustc_llvm
+RUSTC_CRATES := rustc rustc_typeck rustc_borrowck rustc_driver rustc_trans rustc_back rustc_llvm
HOST_CRATES := syntax $(RUSTC_CRATES) rustdoc regex_macros fmt_macros
CRATES := $(TARGET_CRATES) $(HOST_CRATES)
TOOLS := compiletest rustdoc rustc
native:rust_builtin native:backtrace
DEPS_graphviz := std
DEPS_syntax := std term serialize log fmt_macros arena libc
-DEPS_rustc_driver := arena flate getopts graphviz libc rustc rustc_back \
+DEPS_rustc_driver := arena flate getopts graphviz libc rustc rustc_back rustc_borrowck \
rustc_typeck log syntax serialize rustc_llvm rustc_trans
DEPS_rustc_trans := arena flate getopts graphviz libc rustc rustc_back \
log syntax serialize rustc_llvm
DEPS_rustc_typeck := rustc syntax
+DEPS_rustc_borrowck := rustc log graphviz syntax
DEPS_rustc := syntax flate arena serialize getopts rbml \
time log graphviz rustc_llvm rustc_back
DEPS_rustc_llvm := native:rustllvm libc std
DOC_CRATES := $(filter-out rustc, \
$(filter-out rustc_trans, \
$(filter-out rustc_typeck, \
+ $(filter-out rustc_borrowck, \
$(filter-out rustc_driver, \
- $(filter-out syntax, $(CRATES))))))
-COMPILER_DOC_CRATES := rustc rustc_trans rustc_typeck rustc_driver syntax
+ $(filter-out syntax, $(CRATES)))))))
+COMPILER_DOC_CRATES := rustc rustc_trans rustc_borrowck rustc_typeck rustc_driver syntax
# This macro creates some simple definitions for each crate being built, just
# some munging of all of the parameters above.
TEST_TARGET_CRATES = $(filter-out core unicode,$(TARGET_CRATES)) coretest
TEST_DOC_CRATES = $(DOC_CRATES)
-TEST_HOST_CRATES = $(filter-out rustc_typeck rustc_trans,$(HOST_CRATES))
+TEST_HOST_CRATES = $(filter-out rustc_typeck rustc_borrowck rustc_trans,$(HOST_CRATES))
TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
######################################################################
pub mod middle {
pub mod astconv_util;
pub mod astencode;
- pub mod borrowck;
pub mod cfg;
pub mod check_const;
pub mod check_static_recursion;
+++ /dev/null
-// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-// ----------------------------------------------------------------------
-// Checking loans
-//
-// Phase 2 of check: we walk down the tree and check that:
-// 1. assignments are always made to mutable locations;
-// 2. loans made in overlapping scopes do not conflict
-// 3. assignments do not affect things loaned out as immutable
-// 4. moves do not affect things loaned out in any way
-use self::UseError::*;
-
-use middle::borrowck::*;
-use middle::borrowck::LoanPathElem::*;
-use middle::borrowck::LoanPathKind::*;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::region;
-use middle::ty::ParameterEnvironment;
-use middle::ty;
-use syntax::ast::NodeId;
-use syntax::ast;
-use syntax::codemap::Span;
-use util::ppaux::Repr;
-
-use std::rc::Rc;
-
-// FIXME (#16118): These functions are intended to allow the borrow checker to
-// be less precise in its handling of Box while still allowing moves out of a
-// Box. They should be removed when OwnedPtr is removed from LoanPath.
-
-fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> {
- //! Returns the base of the leftmost dereference of an OwnedPtr in
- //! `loan_path`. If there is no dereference of an OwnedPtr in `loan_path`,
- //! then it just returns `loan_path` itself.
-
- return match helper(loan_path) {
- Some(new_loan_path) => new_loan_path,
- None => loan_path.clone()
- };
-
- fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> {
- match loan_path.kind {
- LpVar(_) | LpUpvar(_) => None,
- LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
- match helper(&**lp_base) {
- v @ Some(_) => v,
- None => Some(&**lp_base)
- }
- }
- LpDowncast(ref lp_base, _) |
- LpExtend(ref lp_base, _, _) => helper(&**lp_base)
- }
- }
-}
-
-fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Rc<LoanPath<'tcx>> {
- //! The equivalent of `owned_ptr_base_path` for an &Rc<LoanPath> rather than
- //! a &LoanPath.
-
- return match helper(loan_path) {
- Some(new_loan_path) => new_loan_path,
- None => loan_path.clone()
- };
-
- fn helper<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Option<Rc<LoanPath<'tcx>>> {
- match loan_path.kind {
- LpVar(_) | LpUpvar(_) => None,
- LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
- match helper(lp_base) {
- v @ Some(_) => v,
- None => Some(lp_base.clone())
- }
- }
- LpDowncast(ref lp_base, _) |
- LpExtend(ref lp_base, _, _) => helper(lp_base)
- }
- }
-}
-
-struct CheckLoanCtxt<'a, 'tcx: 'a> {
- bccx: &'a BorrowckCtxt<'a, 'tcx>,
- dfcx_loans: &'a LoanDataFlow<'a, 'tcx>,
- move_data: move_data::FlowedMoveData<'a, 'tcx>,
- all_loans: &'a [Loan<'tcx>],
- param_env: &'a ParameterEnvironment<'tcx>,
-}
-
-impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
- fn consume(&mut self,
- consume_id: ast::NodeId,
- consume_span: Span,
- cmt: mc::cmt<'tcx>,
- mode: euv::ConsumeMode) {
- debug!("consume(consume_id={}, cmt={}, mode={})",
- consume_id, cmt.repr(self.tcx()), mode);
-
- self.consume_common(consume_id, consume_span, cmt, mode);
- }
-
- fn matched_pat(&mut self,
- _matched_pat: &ast::Pat,
- _cmt: mc::cmt,
- _mode: euv::MatchMode) { }
-
- fn consume_pat(&mut self,
- consume_pat: &ast::Pat,
- cmt: mc::cmt<'tcx>,
- mode: euv::ConsumeMode) {
- debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
- consume_pat.repr(self.tcx()),
- cmt.repr(self.tcx()),
- mode);
-
- self.consume_common(consume_pat.id, consume_pat.span, cmt, mode);
- }
-
- fn borrow(&mut self,
- borrow_id: ast::NodeId,
- borrow_span: Span,
- cmt: mc::cmt<'tcx>,
- loan_region: ty::Region,
- bk: ty::BorrowKind,
- loan_cause: euv::LoanCause)
- {
- debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
- bk={}, loan_cause={})",
- borrow_id, cmt.repr(self.tcx()), loan_region,
- bk, loan_cause);
-
- match opt_loan_path(&cmt) {
- Some(lp) => {
- let moved_value_use_kind = match loan_cause {
- euv::ClosureCapture(_) => MovedInCapture,
- _ => MovedInUse,
- };
- self.check_if_path_is_moved(borrow_id, borrow_span, moved_value_use_kind, &lp);
- }
- None => { }
- }
-
- self.check_for_conflicting_loans(region::CodeExtent::from_node_id(borrow_id));
- }
-
- fn mutate(&mut self,
- assignment_id: ast::NodeId,
- assignment_span: Span,
- assignee_cmt: mc::cmt<'tcx>,
- mode: euv::MutateMode)
- {
- debug!("mutate(assignment_id={}, assignee_cmt={})",
- assignment_id, assignee_cmt.repr(self.tcx()));
-
- match opt_loan_path(&assignee_cmt) {
- Some(lp) => {
- match mode {
- euv::Init | euv::JustWrite => {
- // In a case like `path = 1`, then path does not
- // have to be *FULLY* initialized, but we still
- // must be careful lest it contains derefs of
- // pointers.
- self.check_if_assigned_path_is_moved(assignee_cmt.id,
- assignment_span,
- MovedInUse,
- &lp);
- }
- euv::WriteAndRead => {
- // In a case like `path += 1`, then path must be
- // fully initialized, since we will read it before
- // we write it.
- self.check_if_path_is_moved(assignee_cmt.id,
- assignment_span,
- MovedInUse,
- &lp);
- }
- }
- }
- None => { }
- }
-
- self.check_assignment(assignment_id, assignment_span, assignee_cmt, mode);
- }
-
- fn decl_without_init(&mut self, _id: ast::NodeId, _span: Span) { }
-}
-
-pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- dfcx_loans: &LoanDataFlow<'b, 'tcx>,
- move_data: move_data::FlowedMoveData<'c, 'tcx>,
- all_loans: &[Loan<'tcx>],
- fn_id: NodeId,
- decl: &ast::FnDecl,
- body: &ast::Block) {
- debug!("check_loans(body id={})", body.id);
-
- let param_env = ParameterEnvironment::for_item(bccx.tcx, fn_id);
-
- let mut clcx = CheckLoanCtxt {
- bccx: bccx,
- dfcx_loans: dfcx_loans,
- move_data: move_data,
- all_loans: all_loans,
- param_env: ¶m_env,
- };
-
- {
- let mut euv = euv::ExprUseVisitor::new(&mut clcx,
- bccx.tcx,
- param_env.clone());
- euv.walk_fn(decl, body);
- }
-}
-
-#[deriving(PartialEq)]
-enum UseError<'tcx> {
- UseOk,
- UseWhileBorrowed(/*loan*/Rc<LoanPath<'tcx>>, /*loan*/Span)
-}
-
-fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
- borrow_kind2: ty::BorrowKind)
- -> bool {
- borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow
-}
-
-impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
- pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx }
-
- pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan<'tcx>| -> bool)
- -> bool {
- //! Iterates over each loan that has been issued
- //! on entrance to `scope`, regardless of whether it is
- //! actually *in scope* at that point. Sometimes loans
- //! are issued for future scopes and thus they may have been
- //! *issued* but not yet be in effect.
-
- self.dfcx_loans.each_bit_on_entry(scope.node_id(), |loan_index| {
- let loan = &self.all_loans[loan_index];
- op(loan)
- })
- }
-
- pub fn each_in_scope_loan(&self,
- scope: region::CodeExtent,
- op: |&Loan<'tcx>| -> bool)
- -> bool {
- //! Like `each_issued_loan()`, but only considers loans that are
- //! currently in scope.
-
- let tcx = self.tcx();
- self.each_issued_loan(scope, |loan| {
- if tcx.region_maps.is_subscope_of(scope, loan.kill_scope) {
- op(loan)
- } else {
- true
- }
- })
- }
-
- fn each_in_scope_loan_affecting_path(&self,
- scope: region::CodeExtent,
- loan_path: &LoanPath<'tcx>,
- op: |&Loan<'tcx>| -> bool)
- -> bool {
- //! Iterates through all of the in-scope loans affecting `loan_path`,
- //! calling `op`, and ceasing iteration if `false` is returned.
-
- // First, we check for a loan restricting the path P being used. This
- // accounts for borrows of P but also borrows of subpaths, like P.a.b.
- // Consider the following example:
- //
- // let x = &mut a.b.c; // Restricts a, a.b, and a.b.c
- // let y = a; // Conflicts with restriction
-
- let loan_path = owned_ptr_base_path(loan_path);
- let cont = self.each_in_scope_loan(scope, |loan| {
- let mut ret = true;
- for restr_path in loan.restricted_paths.iter() {
- if **restr_path == *loan_path {
- if !op(loan) {
- ret = false;
- break;
- }
- }
- }
- ret
- });
-
- if !cont {
- return false;
- }
-
- // Next, we must check for *loans* (not restrictions) on the path P or
- // any base path. This rejects examples like the following:
- //
- // let x = &mut a.b;
- // let y = a.b.c;
- //
- // Limiting this search to *loans* and not *restrictions* means that
- // examples like the following continue to work:
- //
- // let x = &mut a.b;
- // let y = a.c;
-
- let mut loan_path = loan_path;
- loop {
- match loan_path.kind {
- LpVar(_) | LpUpvar(_) => {
- break;
- }
- LpDowncast(ref lp_base, _) |
- LpExtend(ref lp_base, _, _) => {
- loan_path = &**lp_base;
- }
- }
-
- let cont = self.each_in_scope_loan(scope, |loan| {
- if *loan.loan_path == *loan_path {
- op(loan)
- } else {
- true
- }
- });
-
- if !cont {
- return false;
- }
- }
-
- return true;
- }
-
- pub fn loans_generated_by(&self, scope: region::CodeExtent) -> Vec<uint> {
- //! Returns a vector of the loans that are generated as
- //! we enter `scope`.
-
- let mut result = Vec::new();
- self.dfcx_loans.each_gen_bit(scope.node_id(), |loan_index| {
- result.push(loan_index);
- true
- });
- return result;
- }
-
- pub fn check_for_conflicting_loans(&self, scope: region::CodeExtent) {
- //! Checks to see whether any of the loans that are issued
- //! on entrance to `scope` conflict with loans that have already been
- //! issued when we enter `scope` (for example, we do not
- //! permit two `&mut` borrows of the same variable).
- //!
- //! (Note that some loans can be *issued* without necessarily
- //! taking effect yet.)
-
- debug!("check_for_conflicting_loans(scope={})", scope);
-
- let new_loan_indices = self.loans_generated_by(scope);
- debug!("new_loan_indices = {}", new_loan_indices);
-
- self.each_issued_loan(scope, |issued_loan| {
- for &new_loan_index in new_loan_indices.iter() {
- let new_loan = &self.all_loans[new_loan_index];
- self.report_error_if_loans_conflict(issued_loan, new_loan);
- }
- true
- });
-
- for (i, &x) in new_loan_indices.iter().enumerate() {
- let old_loan = &self.all_loans[x];
- for &y in new_loan_indices.slice_from(i+1).iter() {
- let new_loan = &self.all_loans[y];
- self.report_error_if_loans_conflict(old_loan, new_loan);
- }
- }
- }
-
- pub fn report_error_if_loans_conflict(&self,
- old_loan: &Loan<'tcx>,
- new_loan: &Loan<'tcx>) {
- //! Checks whether `old_loan` and `new_loan` can safely be issued
- //! simultaneously.
-
- debug!("report_error_if_loans_conflict(old_loan={}, new_loan={})",
- old_loan.repr(self.tcx()),
- new_loan.repr(self.tcx()));
-
- // Should only be called for loans that are in scope at the same time.
- assert!(self.tcx().region_maps.scopes_intersect(old_loan.kill_scope,
- new_loan.kill_scope));
-
- self.report_error_if_loan_conflicts_with_restriction(
- old_loan, new_loan, old_loan, new_loan) &&
- self.report_error_if_loan_conflicts_with_restriction(
- new_loan, old_loan, old_loan, new_loan);
- }
-
- pub fn report_error_if_loan_conflicts_with_restriction(&self,
- loan1: &Loan<'tcx>,
- loan2: &Loan<'tcx>,
- old_loan: &Loan<'tcx>,
- new_loan: &Loan<'tcx>)
- -> bool {
- //! Checks whether the restrictions introduced by `loan1` would
- //! prohibit `loan2`. Returns false if an error is reported.
-
- debug!("report_error_if_loan_conflicts_with_restriction(\
- loan1={}, loan2={})",
- loan1.repr(self.tcx()),
- loan2.repr(self.tcx()));
-
- if compatible_borrow_kinds(loan1.kind, loan2.kind) {
- return true;
- }
-
- let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path);
- for restr_path in loan1.restricted_paths.iter() {
- if *restr_path != loan2_base_path { continue; }
-
- // If new_loan is something like `x.a`, and old_loan is something like `x.b`, we would
- // normally generate a rather confusing message (in this case, for multiple mutable
- // borrows):
- //
- // error: cannot borrow `x.b` as mutable more than once at a time
- // note: previous borrow of `x.a` occurs here; the mutable borrow prevents
- // subsequent moves, borrows, or modification of `x.a` until the borrow ends
- //
- // What we want to do instead is get the 'common ancestor' of the two borrow paths and
- // use that for most of the message instead, giving is something like this:
- //
- // error: cannot borrow `x` as mutable more than once at a time
- // note: previous borrow of `x` occurs here (through borrowing `x.a`); the mutable
- // borrow prevents subsequent moves, borrows, or modification of `x` until the
- // borrow ends
-
- let common = new_loan.loan_path.common(&*old_loan.loan_path);
- let (nl, ol, new_loan_msg, old_loan_msg) =
- if new_loan.loan_path.has_fork(&*old_loan.loan_path) && common.is_some() {
- let nl = self.bccx.loan_path_to_string(&common.unwrap());
- let ol = nl.clone();
- let new_loan_msg = format!(" (here through borrowing `{}`)",
- self.bccx.loan_path_to_string(
- &*new_loan.loan_path));
- let old_loan_msg = format!(" (through borrowing `{}`)",
- self.bccx.loan_path_to_string(
- &*old_loan.loan_path));
- (nl, ol, new_loan_msg, old_loan_msg)
- } else {
- (self.bccx.loan_path_to_string(&*new_loan.loan_path),
- self.bccx.loan_path_to_string(&*old_loan.loan_path),
- String::new(), String::new())
- };
-
- let ol_pronoun = if new_loan.loan_path == old_loan.loan_path {
- "it".to_string()
- } else {
- format!("`{}`", ol)
- };
-
- match (new_loan.kind, old_loan.kind) {
- (ty::MutBorrow, ty::MutBorrow) => {
- self.bccx.span_err(
- new_loan.span,
- format!("cannot borrow `{}`{} as mutable \
- more than once at a time",
- nl, new_loan_msg).as_slice())
- }
-
- (ty::UniqueImmBorrow, _) => {
- self.bccx.span_err(
- new_loan.span,
- format!("closure requires unique access to `{}` \
- but {} is already borrowed{}",
- nl, ol_pronoun, old_loan_msg).as_slice());
- }
-
- (_, ty::UniqueImmBorrow) => {
- self.bccx.span_err(
- new_loan.span,
- format!("cannot borrow `{}`{} as {} because \
- previous closure requires unique access",
- nl, new_loan_msg, new_loan.kind.to_user_str()).as_slice());
- }
-
- (_, _) => {
- self.bccx.span_err(
- new_loan.span,
- format!("cannot borrow `{}`{} as {} because \
- {} is also borrowed as {}{}",
- nl,
- new_loan_msg,
- new_loan.kind.to_user_str(),
- ol_pronoun,
- old_loan.kind.to_user_str(),
- old_loan_msg).as_slice());
- }
- }
-
- match new_loan.cause {
- euv::ClosureCapture(span) => {
- self.bccx.span_note(
- span,
- format!("borrow occurs due to use of `{}` in closure",
- nl).as_slice());
- }
- _ => { }
- }
-
- let rule_summary = match old_loan.kind {
- ty::MutBorrow => {
- format!("the mutable borrow prevents subsequent \
- moves, borrows, or modification of `{0}` \
- until the borrow ends",
- ol)
- }
-
- ty::ImmBorrow => {
- format!("the immutable borrow prevents subsequent \
- moves or mutable borrows of `{0}` \
- until the borrow ends",
- ol)
- }
-
- ty::UniqueImmBorrow => {
- format!("the unique capture prevents subsequent \
- moves or borrows of `{0}` \
- until the borrow ends",
- ol)
- }
- };
-
- let borrow_summary = match old_loan.cause {
- euv::ClosureCapture(_) => {
- format!("previous borrow of `{}` occurs here{} due to \
- use in closure",
- ol, old_loan_msg)
- }
-
- euv::OverloadedOperator(..) |
- euv::AddrOf(..) |
- euv::AutoRef(..) |
- euv::ClosureInvocation(..) |
- euv::ForLoop(..) |
- euv::RefBinding(..) |
- euv::MatchDiscriminant(..) => {
- format!("previous borrow of `{}` occurs here{}",
- ol, old_loan_msg)
- }
- };
-
- self.bccx.span_note(
- old_loan.span,
- format!("{}; {}", borrow_summary, rule_summary).as_slice());
-
- let old_loan_span = self.tcx().map.span(old_loan.kill_scope.node_id());
- self.bccx.span_end_note(old_loan_span,
- "previous borrow ends here");
-
- return false;
- }
-
- true
- }
-
- fn is_local_variable_or_arg(&self, cmt: mc::cmt<'tcx>) -> bool {
- match cmt.cat {
- mc::cat_local(_) => true,
- _ => false
- }
- }
-
- fn consume_common(&self,
- id: ast::NodeId,
- span: Span,
- cmt: mc::cmt<'tcx>,
- mode: euv::ConsumeMode) {
- match opt_loan_path(&cmt) {
- Some(lp) => {
- let moved_value_use_kind = match mode {
- euv::Copy => {
- self.check_for_copy_of_frozen_path(id, span, &*lp);
- MovedInUse
- }
- euv::Move(_) => {
- match self.move_data.kind_of_move_of_path(id, &lp) {
- None => {
- // Sometimes moves don't have a move kind;
- // this either means that the original move
- // was from something illegal to move,
- // or was moved from referent of an unsafe
- // pointer or something like that.
- MovedInUse
- }
- Some(move_kind) => {
- self.check_for_move_of_borrowed_path(id, span,
- &*lp, move_kind);
- if move_kind == move_data::Captured {
- MovedInCapture
- } else {
- MovedInUse
- }
- }
- }
- }
- };
-
- self.check_if_path_is_moved(id, span, moved_value_use_kind, &lp);
- }
- None => { }
- }
- }
-
- fn check_for_copy_of_frozen_path(&self,
- id: ast::NodeId,
- span: Span,
- copy_path: &LoanPath<'tcx>) {
- match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) {
- UseOk => { }
- UseWhileBorrowed(loan_path, loan_span) => {
- self.bccx.span_err(
- span,
- format!("cannot use `{}` because it was mutably borrowed",
- self.bccx.loan_path_to_string(copy_path).as_slice())
- .as_slice());
- self.bccx.span_note(
- loan_span,
- format!("borrow of `{}` occurs here",
- self.bccx.loan_path_to_string(&*loan_path).as_slice())
- .as_slice());
- }
- }
- }
-
- fn check_for_move_of_borrowed_path(&self,
- id: ast::NodeId,
- span: Span,
- move_path: &LoanPath<'tcx>,
- move_kind: move_data::MoveKind) {
- // We want to detect if there are any loans at all, so we search for
- // any loans incompatible with MutBorrrow, since all other kinds of
- // loans are incompatible with that.
- match self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow) {
- UseOk => { }
- UseWhileBorrowed(loan_path, loan_span) => {
- let err_message = match move_kind {
- move_data::Captured =>
- format!("cannot move `{}` into closure because it is borrowed",
- self.bccx.loan_path_to_string(move_path).as_slice()),
- move_data::Declared |
- move_data::MoveExpr |
- move_data::MovePat =>
- format!("cannot move out of `{}` because it is borrowed",
- self.bccx.loan_path_to_string(move_path).as_slice())
- };
-
- self.bccx.span_err(span, err_message.as_slice());
- self.bccx.span_note(
- loan_span,
- format!("borrow of `{}` occurs here",
- self.bccx.loan_path_to_string(&*loan_path).as_slice())
- .as_slice());
- }
- }
- }
-
- pub fn analyze_restrictions_on_use(&self,
- expr_id: ast::NodeId,
- use_path: &LoanPath<'tcx>,
- borrow_kind: ty::BorrowKind)
- -> UseError<'tcx> {
- debug!("analyze_restrictions_on_use(expr_id={}, use_path={})",
- self.tcx().map.node_to_string(expr_id),
- use_path.repr(self.tcx()));
-
- let mut ret = UseOk;
-
- self.each_in_scope_loan_affecting_path(
- region::CodeExtent::from_node_id(expr_id), use_path, |loan| {
- if !compatible_borrow_kinds(loan.kind, borrow_kind) {
- ret = UseWhileBorrowed(loan.loan_path.clone(), loan.span);
- false
- } else {
- true
- }
- });
-
- return ret;
- }
-
- /// Reports an error if `expr` (which should be a path)
- /// is using a moved/uninitialized value
- fn check_if_path_is_moved(&self,
- id: ast::NodeId,
- span: Span,
- use_kind: MovedValueUseKind,
- lp: &Rc<LoanPath<'tcx>>) {
- debug!("check_if_path_is_moved(id={}, use_kind={}, lp={})",
- id, use_kind, lp.repr(self.bccx.tcx));
- let base_lp = owned_ptr_base_path_rc(lp);
- self.move_data.each_move_of(id, &base_lp, |the_move, moved_lp| {
- self.bccx.report_use_of_moved_value(
- span,
- use_kind,
- &**lp,
- the_move,
- moved_lp,
- self.param_env);
- false
- });
- }
-
- /// Reports an error if assigning to `lp` will use a
- /// moved/uninitialized value. Mainly this is concerned with
- /// detecting derefs of uninitialized pointers.
- ///
- /// For example:
- ///
- /// ```
- /// let a: int;
- /// a = 10; // ok, even though a is uninitialized
- ///
- /// struct Point { x: uint, y: uint }
- /// let p: Point;
- /// p.x = 22; // ok, even though `p` is uninitialized
- ///
- /// let p: ~Point;
- /// (*p).x = 22; // not ok, p is uninitialized, can't deref
- /// ```
- fn check_if_assigned_path_is_moved(&self,
- id: ast::NodeId,
- span: Span,
- use_kind: MovedValueUseKind,
- lp: &Rc<LoanPath<'tcx>>)
- {
- match lp.kind {
- LpVar(_) | LpUpvar(_) => {
- // assigning to `x` does not require that `x` is initialized
- }
- LpDowncast(ref lp_base, _) => {
- // assigning to `(P->Variant).f` is ok if assigning to `P` is ok
- self.check_if_assigned_path_is_moved(id, span,
- use_kind, lp_base);
- }
- LpExtend(ref lp_base, _, LpInterior(_)) => {
- // assigning to `P.f` is ok if assigning to `P` is ok
- self.check_if_assigned_path_is_moved(id, span,
- use_kind, lp_base);
- }
- LpExtend(ref lp_base, _, LpDeref(_)) => {
- // assigning to `(*P)` requires that `P` be initialized
- self.check_if_path_is_moved(id, span,
- use_kind, lp_base);
- }
- }
- }
-
- fn check_assignment(&self,
- assignment_id: ast::NodeId,
- assignment_span: Span,
- assignee_cmt: mc::cmt<'tcx>,
- mode: euv::MutateMode) {
- debug!("check_assignment(assignee_cmt={})", assignee_cmt.repr(self.tcx()));
-
- // Mutable values can be assigned, as long as they obey loans
- // and aliasing restrictions:
- if assignee_cmt.mutbl.is_mutable() {
- if check_for_aliasable_mutable_writes(self, assignment_span, assignee_cmt.clone()) {
- if mode != euv::Init {
- check_for_assignment_to_borrowed_path(
- self, assignment_id, assignment_span, assignee_cmt.clone());
- mark_variable_as_used_mut(self, assignee_cmt);
- }
- }
- return;
- }
-
- // Initializations are OK.
- if mode == euv::Init {
- return
- }
-
- // For immutable local variables, assignments are legal
- // if they cannot already have been assigned
- if self.is_local_variable_or_arg(assignee_cmt.clone()) {
- assert!(assignee_cmt.mutbl.is_immutable()); // no "const" locals
- let lp = opt_loan_path(&assignee_cmt).unwrap();
- self.move_data.each_assignment_of(assignment_id, &lp, |assign| {
- self.bccx.report_reassigned_immutable_variable(
- assignment_span,
- &*lp,
- assign);
- false
- });
- return;
- }
-
- // Otherwise, just a plain error.
- match assignee_cmt.note {
- mc::NoteClosureEnv(upvar_id) => {
- // If this is an `Fn` closure, it simply can't mutate upvars.
- // If it's an `FnMut` closure, the original variable was declared immutable.
- // We need to determine which is the case here.
- let kind = match assignee_cmt.upvar().unwrap().cat {
- mc::cat_upvar(mc::Upvar { kind, .. }) => kind,
- _ => unreachable!()
- };
- if kind == ty::FnUnboxedClosureKind {
- self.bccx.span_err(
- assignment_span,
- format!("cannot assign to {}",
- self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
- self.bccx.span_help(
- self.tcx().map.span(upvar_id.closure_expr_id),
- "consider changing this closure to take self by mutable reference");
- } else {
- self.bccx.span_err(
- assignment_span,
- format!("cannot assign to {} {}",
- assignee_cmt.mutbl.to_user_str(),
- self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
- }
- }
- _ => match opt_loan_path(&assignee_cmt) {
- Some(lp) => {
- self.bccx.span_err(
- assignment_span,
- format!("cannot assign to {} {} `{}`",
- assignee_cmt.mutbl.to_user_str(),
- self.bccx.cmt_to_string(&*assignee_cmt),
- self.bccx.loan_path_to_string(&*lp)).as_slice());
- }
- None => {
- self.bccx.span_err(
- assignment_span,
- format!("cannot assign to {} {}",
- assignee_cmt.mutbl.to_user_str(),
- self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
- }
- }
- }
- return;
-
- fn mark_variable_as_used_mut<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
- mut cmt: mc::cmt<'tcx>) {
- //! If the mutability of the `cmt` being written is inherited
- //! from a local variable, liveness will
- //! not have been able to detect that this variable's mutability
- //! is important, so we must add the variable to the
- //! `used_mut_nodes` table here.
-
- loop {
- debug!("mark_variable_as_used_mut(cmt={})", cmt.repr(this.tcx()));
- match cmt.cat.clone() {
- mc::cat_upvar(mc::Upvar { id: ty::UpvarId { var_id: id, .. }, .. }) |
- mc::cat_local(id) => {
- this.tcx().used_mut_nodes.borrow_mut().insert(id);
- return;
- }
-
- mc::cat_rvalue(..) |
- mc::cat_static_item |
- mc::cat_deref(_, _, mc::UnsafePtr(..)) |
- mc::cat_deref(_, _, mc::Implicit(..)) => {
- assert_eq!(cmt.mutbl, mc::McDeclared);
- return;
- }
-
- mc::cat_deref(_, _, mc::BorrowedPtr(..)) => {
- assert_eq!(cmt.mutbl, mc::McDeclared);
- // We need to drill down to upvar if applicable
- match cmt.upvar() {
- Some(b) => cmt = b,
- None => return
- }
- }
-
- mc::cat_deref(b, _, mc::OwnedPtr) => {
- assert_eq!(cmt.mutbl, mc::McInherited);
- cmt = b;
- }
-
- mc::cat_downcast(b, _) |
- mc::cat_interior(b, _) => {
- assert_eq!(cmt.mutbl, mc::McInherited);
- cmt = b;
- }
- }
- }
- }
-
- fn check_for_aliasable_mutable_writes<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
- span: Span,
- cmt: mc::cmt<'tcx>) -> bool {
- //! Safety checks related to writes to aliasable, mutable locations
-
- let guarantor = cmt.guarantor();
- debug!("check_for_aliasable_mutable_writes(cmt={}, guarantor={})",
- cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
- if let mc::cat_deref(ref b, _, mc::BorrowedPtr(ty::MutBorrow, _)) = guarantor.cat {
- // Statically prohibit writes to `&mut` when aliasable
- check_for_aliasability_violation(this, span, b.clone());
- }
-
- return true; // no errors reported
- }
-
- fn check_for_aliasability_violation<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
- span: Span,
- cmt: mc::cmt<'tcx>)
- -> bool {
- match cmt.freely_aliasable(this.tcx()) {
- None => {
- return true;
- }
- Some(mc::AliasableStaticMut(..)) => {
- return true;
- }
- Some(cause) => {
- this.bccx.report_aliasability_violation(
- span,
- MutabilityViolation,
- cause);
- return false;
- }
- }
- }
-
- fn check_for_assignment_to_borrowed_path<'a, 'tcx>(
- this: &CheckLoanCtxt<'a, 'tcx>,
- assignment_id: ast::NodeId,
- assignment_span: Span,
- assignee_cmt: mc::cmt<'tcx>)
- {
- //! Check for assignments that violate the terms of an
- //! outstanding loan.
-
- let loan_path = match opt_loan_path(&assignee_cmt) {
- Some(lp) => lp,
- None => { return; /* no loan path, can't be any loans */ }
- };
-
- let scope = region::CodeExtent::from_node_id(assignment_id);
- this.each_in_scope_loan_affecting_path(scope, &*loan_path, |loan| {
- this.report_illegal_mutation(assignment_span, &*loan_path, loan);
- false
- });
- }
- }
-
- pub fn report_illegal_mutation(&self,
- span: Span,
- loan_path: &LoanPath<'tcx>,
- loan: &Loan) {
- self.bccx.span_err(
- span,
- format!("cannot assign to `{}` because it is borrowed",
- self.bccx.loan_path_to_string(loan_path)).as_slice());
- self.bccx.span_note(
- loan.span,
- format!("borrow of `{}` occurs here",
- self.bccx.loan_path_to_string(loan_path)).as_slice());
- }
-}
+++ /dev/null
-// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! # The Borrow Checker
-//!
-//! This pass has the job of enforcing memory safety. This is a subtle
-//! topic. This docs aim to explain both the practice and the theory
-//! behind the borrow checker. They start with a high-level overview of
-//! how it works, and then proceed to dive into the theoretical
-//! background. Finally, they go into detail on some of the more subtle
-//! aspects.
-//!
-//! # Table of contents
-//!
-//! These docs are long. Search for the section you are interested in.
-//!
-//! - Overview
-//! - Formal model
-//! - Borrowing and loans
-//! - Moves and initialization
-//! - Drop flags and structural fragments
-//! - Future work
-//!
-//! # Overview
-//!
-//! The borrow checker checks one function at a time. It operates in two
-//! passes. The first pass, called `gather_loans`, walks over the function
-//! and identifies all of the places where borrows (e.g., `&` expressions
-//! and `ref` bindings) and moves (copies or captures of a linear value)
-//! occur. It also tracks initialization sites. For each borrow and move,
-//! it checks various basic safety conditions at this time (for example,
-//! that the lifetime of the borrow doesn't exceed the lifetime of the
-//! value being borrowed, or that there is no move out of an `&T`
-//! referent).
-//!
-//! It then uses the dataflow module to propagate which of those borrows
-//! may be in scope at each point in the procedure. A loan is considered
-//! to come into scope at the expression that caused it and to go out of
-//! scope when the lifetime of the resulting reference expires.
-//!
-//! Once the in-scope loans are known for each point in the program, the
-//! borrow checker walks the IR again in a second pass called
-//! `check_loans`. This pass examines each statement and makes sure that
-//! it is safe with respect to the in-scope loans.
-//!
-//! # Formal model
-//!
-//! Throughout the docs we'll consider a simple subset of Rust in which
-//! you can only borrow from lvalues, defined like so:
-//!
-//! ```text
-//! LV = x | LV.f | *LV
-//! ```
-//!
-//! Here `x` represents some variable, `LV.f` is a field reference,
-//! and `*LV` is a pointer dereference. There is no auto-deref or other
-//! niceties. This means that if you have a type like:
-//!
-//! ```text
-//! struct S { f: uint }
-//! ```
-//!
-//! and a variable `a: Box<S>`, then the rust expression `a.f` would correspond
-//! to an `LV` of `(*a).f`.
-//!
-//! Here is the formal grammar for the types we'll consider:
-//!
-//! ```text
-//! TY = () | S<'LT...> | Box<TY> | & 'LT MQ TY
-//! MQ = mut | imm | const
-//! ```
-//!
-//! Most of these types should be pretty self explanatory. Here `S` is a
-//! struct name and we assume structs are declared like so:
-//!
-//! ```text
-//! SD = struct S<'LT...> { (f: TY)... }
-//! ```
-//!
-//! # Borrowing and loans
-//!
-//! ## An intuitive explanation
-//!
-//! ### Issuing loans
-//!
-//! Now, imagine we had a program like this:
-//!
-//! ```text
-//! struct Foo { f: uint, g: uint }
-//! ...
-//! 'a: {
-//! let mut x: Box<Foo> = ...;
-//! let y = &mut (*x).f;
-//! x = ...;
-//! }
-//! ```
-//!
-//! This is of course dangerous because mutating `x` will free the old
-//! value and hence invalidate `y`. The borrow checker aims to prevent
-//! this sort of thing.
-//!
-//! #### Loans and restrictions
-//!
-//! The way the borrow checker works is that it analyzes each borrow
-//! expression (in our simple model, that's stuff like `&LV`, though in
-//! real life there are a few other cases to consider). For each borrow
-//! expression, it computes a `Loan`, which is a data structure that
-//! records (1) the value being borrowed, (2) the mutability and scope of
-//! the borrow, and (3) a set of restrictions. In the code, `Loan` is a
-//! struct defined in `middle::borrowck`. Formally, we define `LOAN` as
-//! follows:
-//!
-//! ```text
-//! LOAN = (LV, LT, MQ, RESTRICTION*)
-//! RESTRICTION = (LV, ACTION*)
-//! ACTION = MUTATE | CLAIM | FREEZE
-//! ```
-//!
-//! Here the `LOAN` tuple defines the lvalue `LV` being borrowed; the
-//! lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a
-//! list of restrictions. The restrictions indicate actions which, if
-//! taken, could invalidate the loan and lead to type safety violations.
-//!
-//! Each `RESTRICTION` is a pair of a restrictive lvalue `LV` (which will
-//! either be the path that was borrowed or some prefix of the path that
-//! was borrowed) and a set of restricted actions. There are three kinds
-//! of actions that may be restricted for the path `LV`:
-//!
-//! - `MUTATE` means that `LV` cannot be assigned to;
-//! - `CLAIM` means that the `LV` cannot be borrowed mutably;
-//! - `FREEZE` means that the `LV` cannot be borrowed immutably;
-//!
-//! Finally, it is never possible to move from an lvalue that appears in a
-//! restriction. This implies that the "empty restriction" `(LV, [])`,
-//! which contains an empty set of actions, still has a purpose---it
-//! prevents moves from `LV`. I chose not to make `MOVE` a fourth kind of
-//! action because that would imply that sometimes moves are permitted
-//! from restrictived values, which is not the case.
-//!
-//! #### Example
-//!
-//! To give you a better feeling for what kind of restrictions derived
-//! from a loan, let's look at the loan `L` that would be issued as a
-//! result of the borrow `&mut (*x).f` in the example above:
-//!
-//! ```text
-//! L = ((*x).f, 'a, mut, RS) where
-//! RS = [((*x).f, [MUTATE, CLAIM, FREEZE]),
-//! (*x, [MUTATE, CLAIM, FREEZE]),
-//! (x, [MUTATE, CLAIM, FREEZE])]
-//! ```
-//!
-//! The loan states that the expression `(*x).f` has been loaned as
-//! mutable for the lifetime `'a`. Because the loan is mutable, that means
-//! that the value `(*x).f` may be mutated via the newly created reference
-//! (and *only* via that pointer). This is reflected in the
-//! restrictions `RS` that accompany the loan.
-//!
-//! The first restriction `((*x).f, [MUTATE, CLAIM, FREEZE])` states that
-//! the lender may not mutate, freeze, nor alias `(*x).f`. Mutation is
-//! illegal because `(*x).f` is only supposed to be mutated via the new
-//! reference, not by mutating the original path `(*x).f`. Freezing is
-//! illegal because the path now has an `&mut` alias; so even if we the
-//! lender were to consider `(*x).f` to be immutable, it might be mutated
-//! via this alias. They will be enforced for the lifetime `'a` of the
-//! loan. After the loan expires, the restrictions no longer apply.
-//!
-//! The second restriction on `*x` is interesting because it does not
-//! apply to the path that was lent (`(*x).f`) but rather to a prefix of
-//! the borrowed path. This is due to the rules of inherited mutability:
-//! if the user were to assign to (or freeze) `*x`, they would indirectly
-//! overwrite (or freeze) `(*x).f`, and thus invalidate the reference
-//! that was created. In general it holds that when a path is
-//! lent, restrictions are issued for all the owning prefixes of that
-//! path. In this case, the path `*x` owns the path `(*x).f` and,
-//! because `x` is an owned pointer, the path `x` owns the path `*x`.
-//! Therefore, borrowing `(*x).f` yields restrictions on both
-//! `*x` and `x`.
-//!
-//! ### Checking for illegal assignments, moves, and reborrows
-//!
-//! Once we have computed the loans introduced by each borrow, the borrow
-//! checker uses a data flow propagation to compute the full set of loans
-//! in scope at each expression and then uses that set to decide whether
-//! that expression is legal. Remember that the scope of loan is defined
-//! by its lifetime LT. We sometimes say that a loan which is in-scope at
-//! a particular point is an "outstanding loan", and the set of
-//! restrictions included in those loans as the "outstanding
-//! restrictions".
-//!
-//! The kinds of expressions which in-scope loans can render illegal are:
-//! - *assignments* (`lv = v`): illegal if there is an in-scope restriction
-//! against mutating `lv`;
-//! - *moves*: illegal if there is any in-scope restriction on `lv` at all;
-//! - *mutable borrows* (`&mut lv`): illegal there is an in-scope restriction
-//! against claiming `lv`;
-//! - *immutable borrows* (`&lv`): illegal there is an in-scope restriction
-//! against freezing `lv`.
-//!
-//! ## Formal rules
-//!
-//! Now that we hopefully have some kind of intuitive feeling for how the
-//! borrow checker works, let's look a bit more closely now at the precise
-//! conditions that it uses. For simplicity I will ignore const loans.
-//!
-//! I will present the rules in a modified form of standard inference
-//! rules, which looks as follows:
-//!
-//! ```text
-//! PREDICATE(X, Y, Z) // Rule-Name
-//! Condition 1
-//! Condition 2
-//! Condition 3
-//! ```
-//!
-//! The initial line states the predicate that is to be satisfied. The
-//! indented lines indicate the conditions that must be met for the
-//! predicate to be satisfied. The right-justified comment states the name
-//! of this rule: there are comments in the borrowck source referencing
-//! these names, so that you can cross reference to find the actual code
-//! that corresponds to the formal rule.
-//!
-//! ### Invariants
-//!
-//! I want to collect, at a high-level, the invariants the borrow checker
-//! maintains. I will give them names and refer to them throughout the
-//! text. Together these invariants are crucial for the overall soundness
-//! of the system.
-//!
-//! **Mutability requires uniqueness.** To mutate a path
-//!
-//! **Unique mutability.** There is only one *usable* mutable path to any
-//! given memory at any given time. This implies that when claiming memory
-//! with an expression like `p = &mut x`, the compiler must guarantee that
-//! the borrowed value `x` can no longer be mutated so long as `p` is
-//! live. (This is done via restrictions, read on.)
-//!
-//! **.**
-//!
-//!
-//! ### The `gather_loans` pass
-//!
-//! We start with the `gather_loans` pass, which walks the AST looking for
-//! borrows. For each borrow, there are three bits of information: the
-//! lvalue `LV` being borrowed and the mutability `MQ` and lifetime `LT`
-//! of the resulting pointer. Given those, `gather_loans` applies four
-//! validity tests:
-//!
-//! 1. `MUTABILITY(LV, MQ)`: The mutability of the reference is
-//! compatible with the mutability of `LV` (i.e., not borrowing immutable
-//! data as mutable).
-//!
-//! 2. `ALIASABLE(LV, MQ)`: The aliasability of the reference is
-//! compatible with the aliasability of `LV`. The goal is to prevent
-//! `&mut` borrows of aliasability data.
-//!
-//! 3. `LIFETIME(LV, LT, MQ)`: The lifetime of the borrow does not exceed
-//! the lifetime of the value being borrowed.
-//!
-//! 4. `RESTRICTIONS(LV, LT, ACTIONS) = RS`: This pass checks and computes the
-//! restrictions to maintain memory safety. These are the restrictions
-//! that will go into the final loan. We'll discuss in more detail below.
-//!
-//! ## Checking mutability
-//!
-//! Checking mutability is fairly straightforward. We just want to prevent
-//! immutable data from being borrowed as mutable. Note that it is ok to
-//! borrow mutable data as immutable, since that is simply a
-//! freeze. Formally we define a predicate `MUTABLE(LV, MQ)` which, if
-//! defined, means that "borrowing `LV` with mutability `MQ` is ok. The
-//! Rust code corresponding to this predicate is the function
-//! `check_mutability` in `middle::borrowck::gather_loans`.
-//!
-//! ### Checking mutability of variables
-//!
-//! *Code pointer:* Function `check_mutability()` in `gather_loans/mod.rs`,
-//! but also the code in `mem_categorization`.
-//!
-//! Let's begin with the rules for variables, which state that if a
-//! variable is declared as mutable, it may be borrowed any which way, but
-//! otherwise the variable must be borrowed as immutable or const:
-//!
-//! ```text
-//! MUTABILITY(X, MQ) // M-Var-Mut
-//! DECL(X) = mut
-//!
-//! MUTABILITY(X, MQ) // M-Var-Imm
-//! DECL(X) = imm
-//! MQ = imm | const
-//! ```
-//!
-//! ### Checking mutability of owned content
-//!
-//! Fields and owned pointers inherit their mutability from
-//! their base expressions, so both of their rules basically
-//! delegate the check to the base expression `LV`:
-//!
-//! ```text
-//! MUTABILITY(LV.f, MQ) // M-Field
-//! MUTABILITY(LV, MQ)
-//!
-//! MUTABILITY(*LV, MQ) // M-Deref-Unique
-//! TYPE(LV) = Box<Ty>
-//! MUTABILITY(LV, MQ)
-//! ```
-//!
-//! ### Checking mutability of immutable pointer types
-//!
-//! Immutable pointer types like `&T` can only
-//! be borrowed if MQ is immutable or const:
-//!
-//! ```text
-//! MUTABILITY(*LV, MQ) // M-Deref-Borrowed-Imm
-//! TYPE(LV) = &Ty
-//! MQ == imm | const
-//! ```
-//!
-//! ### Checking mutability of mutable pointer types
-//!
-//! `&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
-//!
-//! ```text
-//! MUTABILITY(*LV, MQ) // M-Deref-Borrowed-Mut
-//! TYPE(LV) = &mut Ty
-//! ```
-//!
-//! ## Checking aliasability
-//!
-//! The goal of the aliasability check is to ensure that we never permit
-//! `&mut` borrows of aliasable data. Formally we define a predicate
-//! `ALIASABLE(LV, MQ)` which if defined means that
-//! "borrowing `LV` with mutability `MQ` is ok". The
-//! Rust code corresponding to this predicate is the function
-//! `check_aliasability()` in `middle::borrowck::gather_loans`.
-//!
-//! ### Checking aliasability of variables
-//!
-//! Local variables are never aliasable as they are accessible only within
-//! the stack frame.
-//!
-//! ```text
-//! ALIASABLE(X, MQ) // M-Var-Mut
-//! ```
-//!
-//! ### Checking aliasable of owned content
-//!
-//! Owned content is aliasable if it is found in an aliasable location:
-//!
-//! ```text
-//! ALIASABLE(LV.f, MQ) // M-Field
-//! ALIASABLE(LV, MQ)
-//!
-//! ALIASABLE(*LV, MQ) // M-Deref-Unique
-//! ALIASABLE(LV, MQ)
-//! ```
-//!
-//! ### Checking mutability of immutable pointer types
-//!
-//! Immutable pointer types like `&T` are aliasable, and hence can only be
-//! borrowed immutably:
-//!
-//! ```text
-//! ALIASABLE(*LV, imm) // M-Deref-Borrowed-Imm
-//! TYPE(LV) = &Ty
-//! ```
-//!
-//! ### Checking mutability of mutable pointer types
-//!
-//! `&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
-//!
-//! ```text
-//! ALIASABLE(*LV, MQ) // M-Deref-Borrowed-Mut
-//! TYPE(LV) = &mut Ty
-//! ```
-//!
-//! ## Checking lifetime
-//!
-//! These rules aim to ensure that no data is borrowed for a scope that exceeds
-//! its lifetime. These two computations wind up being intimately related.
-//! Formally, we define a predicate `LIFETIME(LV, LT, MQ)`, which states that
-//! "the lvalue `LV` can be safely borrowed for the lifetime `LT` with mutability
-//! `MQ`". The Rust code corresponding to this predicate is the module
-//! `middle::borrowck::gather_loans::lifetime`.
-//!
-//! ### The Scope function
-//!
-//! Several of the rules refer to a helper function `SCOPE(LV)=LT`. The
-//! `SCOPE(LV)` yields the lifetime `LT` for which the lvalue `LV` is
-//! guaranteed to exist, presuming that no mutations occur.
-//!
-//! The scope of a local variable is the block where it is declared:
-//!
-//! ```text
-//! SCOPE(X) = block where X is declared
-//! ```
-//!
-//! The scope of a field is the scope of the struct:
-//!
-//! ```text
-//! SCOPE(LV.f) = SCOPE(LV)
-//! ```
-//!
-//! The scope of a unique referent is the scope of the pointer, since
-//! (barring mutation or moves) the pointer will not be freed until
-//! the pointer itself `LV` goes out of scope:
-//!
-//! ```text
-//! SCOPE(*LV) = SCOPE(LV) if LV has type Box<T>
-//! ```
-//!
-//! The scope of a borrowed referent is the scope associated with the
-//! pointer. This is a conservative approximation, since the data that
-//! the pointer points at may actually live longer:
-//!
-//! ```text
-//! SCOPE(*LV) = LT if LV has type &'LT T or &'LT mut T
-//! ```
-//!
-//! ### Checking lifetime of variables
-//!
-//! The rule for variables states that a variable can only be borrowed a
-//! lifetime `LT` that is a subregion of the variable's scope:
-//!
-//! ```text
-//! LIFETIME(X, LT, MQ) // L-Local
-//! LT <= SCOPE(X)
-//! ```
-//!
-//! ### Checking lifetime for owned content
-//!
-//! The lifetime of a field or owned pointer is the same as the lifetime
-//! of its owner:
-//!
-//! ```text
-//! LIFETIME(LV.f, LT, MQ) // L-Field
-//! LIFETIME(LV, LT, MQ)
-//!
-//! LIFETIME(*LV, LT, MQ) // L-Deref-Send
-//! TYPE(LV) = Box<Ty>
-//! LIFETIME(LV, LT, MQ)
-//! ```
-//!
-//! ### Checking lifetime for derefs of references
-//!
-//! References have a lifetime `LT'` associated with them. The
-//! data they point at has been guaranteed to be valid for at least this
-//! lifetime. Therefore, the borrow is valid so long as the lifetime `LT`
-//! of the borrow is shorter than the lifetime `LT'` of the pointer
-//! itself:
-//!
-//! ```text
-//! LIFETIME(*LV, LT, MQ) // L-Deref-Borrowed
-//! TYPE(LV) = <' Ty OR <' mut Ty
-//! LT <= LT'
-//! ```
-//!
-//! ## Computing the restrictions
-//!
-//! The final rules govern the computation of *restrictions*, meaning that
-//! we compute the set of actions that will be illegal for the life of the
-//! loan. The predicate is written `RESTRICTIONS(LV, LT, ACTIONS) =
-//! RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from
-//! occurring on `LV`, the restrictions `RESTRICTION*` must be respected
-//! for the lifetime of the loan".
-//!
-//! Note that there is an initial set of restrictions: these restrictions
-//! are computed based on the kind of borrow:
-//!
-//! ```text
-//! &mut LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM|FREEZE)
-//! &LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM)
-//! &const LV => RESTRICTIONS(LV, LT, [])
-//! ```
-//!
-//! The reasoning here is that a mutable borrow must be the only writer,
-//! therefore it prevents other writes (`MUTATE`), mutable borrows
-//! (`CLAIM`), and immutable borrows (`FREEZE`). An immutable borrow
-//! permits other immutable borrows but forbids writes and mutable borrows.
-//! Finally, a const borrow just wants to be sure that the value is not
-//! moved out from under it, so no actions are forbidden.
-//!
-//! ### Restrictions for loans of a local variable
-//!
-//! The simplest case is a borrow of a local variable `X`:
-//!
-//! ```text
-//! RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable
-//! ```
-//!
-//! In such cases we just record the actions that are not permitted.
-//!
-//! ### Restrictions for loans of fields
-//!
-//! Restricting a field is the same as restricting the owner of that
-//! field:
-//!
-//! ```text
-//! RESTRICTIONS(LV.f, LT, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
-//! RESTRICTIONS(LV, LT, ACTIONS) = RS
-//! ```
-//!
-//! The reasoning here is as follows. If the field must not be mutated,
-//! then you must not mutate the owner of the field either, since that
-//! would indirectly modify the field. Similarly, if the field cannot be
-//! frozen or aliased, we cannot allow the owner to be frozen or aliased,
-//! since doing so indirectly freezes/aliases the field. This is the
-//! origin of inherited mutability.
-//!
-//! ### Restrictions for loans of owned referents
-//!
-//! Because the mutability of owned referents is inherited, restricting an
-//! owned referent is similar to restricting a field, in that it implies
-//! restrictions on the pointer. However, owned pointers have an important
-//! twist: if the owner `LV` is mutated, that causes the owned referent
-//! `*LV` to be freed! So whenever an owned referent `*LV` is borrowed, we
-//! must prevent the owned pointer `LV` from being mutated, which means
-//! that we always add `MUTATE` and `CLAIM` to the restriction set imposed
-//! on `LV`:
-//!
-//! ```text
-//! RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
-//! TYPE(LV) = Box<Ty>
-//! RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS
-//! ```
-//!
-//! ### Restrictions for loans of immutable borrowed referents
-//!
-//! Immutable borrowed referents are freely aliasable, meaning that
-//! the compiler does not prevent you from copying the pointer. This
-//! implies that issuing restrictions is useless. We might prevent the
-//! user from acting on `*LV` itself, but there could be another path
-//! `*LV1` that refers to the exact same memory, and we would not be
-//! restricting that path. Therefore, the rule for `&Ty` pointers
-//! always returns an empty set of restrictions, and it only permits
-//! restricting `MUTATE` and `CLAIM` actions:
-//!
-//! ```text
-//! RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
-//! TYPE(LV) = <' Ty
-//! LT <= LT' // (1)
-//! ACTIONS subset of [MUTATE, CLAIM]
-//! ```
-//!
-//! The reason that we can restrict `MUTATE` and `CLAIM` actions even
-//! without a restrictions list is that it is never legal to mutate nor to
-//! borrow mutably the contents of a `&Ty` pointer. In other words,
-//! those restrictions are already inherent in the type.
-//!
-//! Clause (1) in the rule for `&Ty` deserves mention. Here I
-//! specify that the lifetime of the loan must be less than the lifetime
-//! of the `&Ty` pointer. In simple cases, this clause is redundant, since
-//! the `LIFETIME()` function will already enforce the required rule:
-//!
-//! ```
-//! fn foo(point: &'a Point) -> &'static f32 {
-//! &point.x // Error
-//! }
-//! ```
-//!
-//! The above example fails to compile both because of clause (1) above
-//! but also by the basic `LIFETIME()` check. However, in more advanced
-//! examples involving multiple nested pointers, clause (1) is needed:
-//!
-//! ```
-//! fn foo(point: &'a &'b mut Point) -> &'b f32 {
-//! &point.x // Error
-//! }
-//! ```
-//!
-//! The `LIFETIME` rule here would accept `'b` because, in fact, the
-//! *memory is* guaranteed to remain valid (i.e., not be freed) for the
-//! lifetime `'b`, since the `&mut` pointer is valid for `'b`. However, we
-//! are returning an immutable reference, so we need the memory to be both
-//! valid and immutable. Even though `point.x` is referenced by an `&mut`
-//! pointer, it can still be considered immutable so long as that `&mut`
-//! pointer is found in an aliased location. That means the memory is
-//! guaranteed to be *immutable* for the lifetime of the `&` pointer,
-//! which is only `'a`, not `'b`. Hence this example yields an error.
-//!
-//! As a final twist, consider the case of two nested *immutable*
-//! pointers, rather than a mutable pointer within an immutable one:
-//!
-//! ```
-//! fn foo(point: &'a &'b Point) -> &'b f32 {
-//! &point.x // OK
-//! }
-//! ```
-//!
-//! This function is legal. The reason for this is that the inner pointer
-//! (`*point : &'b Point`) is enough to guarantee the memory is immutable
-//! and valid for the lifetime `'b`. This is reflected in
-//! `RESTRICTIONS()` by the fact that we do not recurse (i.e., we impose
-//! no restrictions on `LV`, which in this particular case is the pointer
-//! `point : &'a &'b Point`).
-//!
-//! #### Why both `LIFETIME()` and `RESTRICTIONS()`?
-//!
-//! Given the previous text, it might seem that `LIFETIME` and
-//! `RESTRICTIONS` should be folded together into one check, but there is
-//! a reason that they are separated. They answer separate concerns.
-//! The rules pertaining to `LIFETIME` exist to ensure that we don't
-//! create a borrowed pointer that outlives the memory it points at. So
-//! `LIFETIME` prevents a function like this:
-//!
-//! ```
-//! fn get_1<'a>() -> &'a int {
-//! let x = 1;
-//! &x
-//! }
-//! ```
-//!
-//! Here we would be returning a pointer into the stack. Clearly bad.
-//!
-//! However, the `RESTRICTIONS` rules are more concerned with how memory
-//! is used. The example above doesn't generate an error according to
-//! `RESTRICTIONS` because, for local variables, we don't require that the
-//! loan lifetime be a subset of the local variable lifetime. The idea
-//! here is that we *can* guarantee that `x` is not (e.g.) mutated for the
-//! lifetime `'a`, even though `'a` exceeds the function body and thus
-//! involves unknown code in the caller -- after all, `x` ceases to exist
-//! after we return and hence the remaining code in `'a` cannot possibly
-//! mutate it. This distinction is important for type checking functions
-//! like this one:
-//!
-//! ```
-//! fn inc_and_get<'a>(p: &'a mut Point) -> &'a int {
-//! p.x += 1;
-//! &p.x
-//! }
-//! ```
-//!
-//! In this case, we take in a `&mut` and return a frozen borrowed pointer
-//! with the same lifetime. So long as the lifetime of the returned value
-//! doesn't exceed the lifetime of the `&mut` we receive as input, this is
-//! fine, though it may seem surprising at first (it surprised me when I
-//! first worked it through). After all, we're guaranteeing that `*p`
-//! won't be mutated for the lifetime `'a`, even though we can't "see" the
-//! entirety of the code during that lifetime, since some of it occurs in
-//! our caller. But we *do* know that nobody can mutate `*p` except
-//! through `p`. So if we don't mutate `*p` and we don't return `p`, then
-//! we know that the right to mutate `*p` has been lost to our caller --
-//! in terms of capability, the caller passed in the ability to mutate
-//! `*p`, and we never gave it back. (Note that we can't return `p` while
-//! `*p` is borrowed since that would be a move of `p`, as `&mut` pointers
-//! are affine.)
-//!
-//! ### Restrictions for loans of const aliasable referents
-//!
-//! Freeze pointers are read-only. There may be `&mut` or `&` aliases, and
-//! we can not prevent *anything* but moves in that case. So the
-//! `RESTRICTIONS` function is only defined if `ACTIONS` is the empty set.
-//! Because moves from a `&const` lvalue are never legal, it is not
-//! necessary to add any restrictions at all to the final result.
-//!
-//! ```text
-//! RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed
-//! TYPE(LV) = &const Ty
-//! ```
-//!
-//! ### Restrictions for loans of mutable borrowed referents
-//!
-//! Mutable borrowed pointers are guaranteed to be the only way to mutate
-//! their referent. This permits us to take greater license with them; for
-//! example, the referent can be frozen simply be ensuring that we do not
-//! use the original pointer to perform mutate. Similarly, we can allow
-//! the referent to be claimed, so long as the original pointer is unused
-//! while the new claimant is live.
-//!
-//! The rule for mutable borrowed pointers is as follows:
-//!
-//! ```text
-//! RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed
-//! TYPE(LV) = <' mut Ty
-//! LT <= LT' // (1)
-//! RESTRICTIONS(LV, LT, ACTIONS) = RS // (2)
-//! ```
-//!
-//! Let's examine the two numbered clauses:
-//!
-//! Clause (1) specifies that the lifetime of the loan (`LT`) cannot
-//! exceed the lifetime of the `&mut` pointer (`LT'`). The reason for this
-//! is that the `&mut` pointer is guaranteed to be the only legal way to
-//! mutate its referent -- but only for the lifetime `LT'`. After that
-//! lifetime, the loan on the referent expires and hence the data may be
-//! modified by its owner again. This implies that we are only able to
-//! guarantee that the referent will not be modified or aliased for a
-//! maximum of `LT'`.
-//!
-//! Here is a concrete example of a bug this rule prevents:
-//!
-//! ```
-//! // Test region-reborrow-from-shorter-mut-ref.rs:
-//! fn copy_pointer<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T {
-//! &mut **p // ERROR due to clause (1)
-//! }
-//! fn main() {
-//! let mut x = 1;
-//! let mut y = &mut x; // <-'b-----------------------------+
-//! // +-'a--------------------+ |
-//! // v v |
-//! let z = copy_borrowed_ptr(&mut y); // y is lent |
-//! *y += 1; // Here y==z, so both should not be usable... |
-//! *z += 1; // ...and yet they would be, but for clause 1. |
-//! } // <------------------------------------------------------+
-//! ```
-//!
-//! Clause (2) propagates the restrictions on the referent to the pointer
-//! itself. This is the same as with an owned pointer, though the
-//! reasoning is mildly different. The basic goal in all cases is to
-//! prevent the user from establishing another route to the same data. To
-//! see what I mean, let's examine various cases of what can go wrong and
-//! show how it is prevented.
-//!
-//! **Example danger 1: Moving the base pointer.** One of the simplest
-//! ways to violate the rules is to move the base pointer to a new name
-//! and access it via that new name, thus bypassing the restrictions on
-//! the old name. Here is an example:
-//!
-//! ```
-//! // src/test/compile-fail/borrowck-move-mut-base-ptr.rs
-//! fn foo(t0: &mut int) {
-//! let p: &int = &*t0; // Freezes `*t0`
-//! let t1 = t0; //~ ERROR cannot move out of `t0`
-//! *t1 = 22; // OK, not a write through `*t0`
-//! }
-//! ```
-//!
-//! Remember that `&mut` pointers are linear, and hence `let t1 = t0` is a
-//! move of `t0` -- or would be, if it were legal. Instead, we get an
-//! error, because clause (2) imposes restrictions on `LV` (`t0`, here),
-//! and any restrictions on a path make it impossible to move from that
-//! path.
-//!
-//! **Example danger 2: Claiming the base pointer.** Another possible
-//! danger is to mutably borrow the base path. This can lead to two bad
-//! scenarios. The most obvious is that the mutable borrow itself becomes
-//! another path to access the same data, as shown here:
-//!
-//! ```
-//! // src/test/compile-fail/borrowck-mut-borrow-of-mut-base-ptr.rs
-//! fn foo<'a>(mut t0: &'a mut int,
-//! mut t1: &'a mut int) {
-//! let p: &int = &*t0; // Freezes `*t0`
-//! let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
-//! **t2 += 1; // Mutates `*t0`
-//! }
-//! ```
-//!
-//! In this example, `**t2` is the same memory as `*t0`. Because `t2` is
-//! an `&mut` pointer, `**t2` is a unique path and hence it would be
-//! possible to mutate `**t2` even though that memory was supposed to be
-//! frozen by the creation of `p`. However, an error is reported -- the
-//! reason is that the freeze `&*t0` will restrict claims and mutation
-//! against `*t0` which, by clause 2, in turn prevents claims and mutation
-//! of `t0`. Hence the claim `&mut t0` is illegal.
-//!
-//! Another danger with an `&mut` pointer is that we could swap the `t0`
-//! value away to create a new path:
-//!
-//! ```
-//! // src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
-//! fn foo<'a>(mut t0: &'a mut int,
-//! mut t1: &'a mut int) {
-//! let p: &int = &*t0; // Freezes `*t0`
-//! swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
-//! *t1 = 22;
-//! }
-//! ```
-//!
-//! This is illegal for the same reason as above. Note that if we added
-//! back a swap operator -- as we used to have -- we would want to be very
-//! careful to ensure this example is still illegal.
-//!
-//! **Example danger 3: Freeze the base pointer.** In the case where the
-//! referent is claimed, even freezing the base pointer can be dangerous,
-//! as shown in the following example:
-//!
-//! ```
-//! // src/test/compile-fail/borrowck-borrow-of-mut-base-ptr.rs
-//! fn foo<'a>(mut t0: &'a mut int,
-//! mut t1: &'a mut int) {
-//! let p: &mut int = &mut *t0; // Claims `*t0`
-//! let mut t2 = &t0; //~ ERROR cannot borrow `t0`
-//! let q: &int = &*t2; // Freezes `*t0` but not through `*p`
-//! *p += 1; // violates type of `*q`
-//! }
-//! ```
-//!
-//! Here the problem is that `*t0` is claimed by `p`, and hence `p` wants
-//! to be the controlling pointer through which mutation or freezes occur.
-//! But `t2` would -- if it were legal -- have the type `& &mut int`, and
-//! hence would be a mutable pointer in an aliasable location, which is
-//! considered frozen (since no one can write to `**t2` as it is not a
-//! unique path). Therefore, we could reasonably create a frozen `&int`
-//! pointer pointing at `*t0` that coexists with the mutable pointer `p`,
-//! which is clearly unsound.
-//!
-//! However, it is not always unsafe to freeze the base pointer. In
-//! particular, if the referent is frozen, there is no harm in it:
-//!
-//! ```
-//! // src/test/run-pass/borrowck-borrow-of-mut-base-ptr-safe.rs
-//! fn foo<'a>(mut t0: &'a mut int,
-//! mut t1: &'a mut int) {
-//! let p: &int = &*t0; // Freezes `*t0`
-//! let mut t2 = &t0;
-//! let q: &int = &*t2; // Freezes `*t0`, but that's ok...
-//! let r: &int = &*t0; // ...after all, could do same thing directly.
-//! }
-//! ```
-//!
-//! In this case, creating the alias `t2` of `t0` is safe because the only
-//! thing `t2` can be used for is to further freeze `*t0`, which is
-//! already frozen. In particular, we cannot assign to `*t0` through the
-//! new alias `t2`, as demonstrated in this test case:
-//!
-//! ```
-//! // src/test/run-pass/borrowck-borrow-mut-base-ptr-in-aliasable-loc.rs
-//! fn foo(t0: & &mut int) {
-//! let t1 = t0;
-//! let p: &int = &**t0;
-//! **t1 = 22; //~ ERROR cannot assign
-//! }
-//! ```
-//!
-//! This distinction is reflected in the rules. When doing an `&mut`
-//! borrow -- as in the first example -- the set `ACTIONS` will be
-//! `CLAIM|MUTATE|FREEZE`, because claiming the referent implies that it
-//! cannot be claimed, mutated, or frozen by anyone else. These
-//! restrictions are propagated back to the base path and hence the base
-//! path is considered unfreezable.
-//!
-//! In contrast, when the referent is merely frozen -- as in the second
-//! example -- the set `ACTIONS` will be `CLAIM|MUTATE`, because freezing
-//! the referent implies that it cannot be claimed or mutated but permits
-//! others to freeze. Hence when these restrictions are propagated back to
-//! the base path, it will still be considered freezable.
-//!
-//!
-//!
-//! **FIXME #10520: Restrictions against mutating the base pointer.** When
-//! an `&mut` pointer is frozen or claimed, we currently pass along the
-//! restriction against MUTATE to the base pointer. I do not believe this
-//! restriction is needed. It dates from the days when we had a way to
-//! mutate that preserved the value being mutated (i.e., swap). Nowadays
-//! the only form of mutation is assignment, which destroys the pointer
-//! being mutated -- therefore, a mutation cannot create a new path to the
-//! same data. Rather, it removes an existing path. This implies that not
-//! only can we permit mutation, we can have mutation kill restrictions in
-//! the dataflow sense.
-//!
-//! **WARNING:** We do not currently have `const` borrows in the
-//! language. If they are added back in, we must ensure that they are
-//! consistent with all of these examples. The crucial question will be
-//! what sorts of actions are permitted with a `&const &mut` pointer. I
-//! would suggest that an `&mut` referent found in an `&const` location be
-//! prohibited from both freezes and claims. This would avoid the need to
-//! prevent `const` borrows of the base pointer when the referent is
-//! borrowed.
-//!
-//! # Moves and initialization
-//!
-//! The borrow checker is also in charge of ensuring that:
-//!
-//! - all memory which is accessed is initialized
-//! - immutable local variables are assigned at most once.
-//!
-//! These are two separate dataflow analyses built on the same
-//! framework. Let's look at checking that memory is initialized first;
-//! the checking of immutable local variable assignments works in a very
-//! similar way.
-//!
-//! To track the initialization of memory, we actually track all the
-//! points in the program that *create uninitialized memory*, meaning
-//! moves and the declaration of uninitialized variables. For each of
-//! these points, we create a bit in the dataflow set. Assignments to a
-//! variable `x` or path `a.b.c` kill the move/uninitialization bits for
-//! those paths and any subpaths (e.g., `x`, `x.y`, `a.b.c`, `*a.b.c`).
-//! Bits are unioned when two control-flow paths join. Thus, the
-//! presence of a bit indicates that the move may have occurred without an
-//! intervening assignment to the same memory. At each use of a variable,
-//! we examine the bits in scope, and check that none of them are
-//! moves/uninitializations of the variable that is being used.
-//!
-//! Let's look at a simple example:
-//!
-//! ```
-//! fn foo(a: Box<int>) {
-//! let b: Box<int>; // Gen bit 0.
-//!
-//! if cond { // Bits: 0
-//! use(&*a);
-//! b = a; // Gen bit 1, kill bit 0.
-//! use(&*b);
-//! } else {
-//! // Bits: 0
-//! }
-//! // Bits: 0,1
-//! use(&*a); // Error.
-//! use(&*b); // Error.
-//! }
-//!
-//! fn use(a: &int) { }
-//! ```
-//!
-//! In this example, the variable `b` is created uninitialized. In one
-//! branch of an `if`, we then move the variable `a` into `b`. Once we
-//! exit the `if`, therefore, it is an error to use `a` or `b` since both
-//! are only conditionally initialized. I have annotated the dataflow
-//! state using comments. There are two dataflow bits, with bit 0
-//! corresponding to the creation of `b` without an initializer, and bit 1
-//! corresponding to the move of `a`. The assignment `b = a` both
-//! generates bit 1, because it is a move of `a`, and kills bit 0, because
-//! `b` is now initialized. On the else branch, though, `b` is never
-//! initialized, and so bit 0 remains untouched. When the two flows of
-//! control join, we union the bits from both sides, resulting in both
-//! bits 0 and 1 being set. Thus any attempt to use `a` uncovers the bit 1
-//! from the "then" branch, showing that `a` may be moved, and any attempt
-//! to use `b` uncovers bit 0, from the "else" branch, showing that `b`
-//! may not be initialized.
-//!
-//! ## Initialization of immutable variables
-//!
-//! Initialization of immutable variables works in a very similar way,
-//! except that:
-//!
-//! 1. we generate bits for each assignment to a variable;
-//! 2. the bits are never killed except when the variable goes out of scope.
-//!
-//! Thus the presence of an assignment bit indicates that the assignment
-//! may have occurred. Note that assignments are only killed when the
-//! variable goes out of scope, as it is not relevant whether or not there
-//! has been a move in the meantime. Using these bits, we can declare that
-//! an assignment to an immutable variable is legal iff there is no other
-//! assignment bit to that same variable in scope.
-//!
-//! ## Why is the design made this way?
-//!
-//! It may seem surprising that we assign dataflow bits to *each move*
-//! rather than *each path being moved*. This is somewhat less efficient,
-//! since on each use, we must iterate through all moves and check whether
-//! any of them correspond to the path in question. Similar concerns apply
-//! to the analysis for double assignments to immutable variables. The
-//! main reason to do it this way is that it allows us to print better
-//! error messages, because when a use occurs, we can print out the
-//! precise move that may be in scope, rather than simply having to say
-//! "the variable may not be initialized".
-//!
-//! ## Data structures used in the move analysis
-//!
-//! The move analysis maintains several data structures that enable it to
-//! cross-reference moves and assignments to determine when they may be
-//! moving/assigning the same memory. These are all collected into the
-//! `MoveData` and `FlowedMoveData` structs. The former represents the set
-//! of move paths, moves, and assignments, and the latter adds in the
-//! results of a dataflow computation.
-//!
-//! ### Move paths
-//!
-//! The `MovePath` tree tracks every path that is moved or assigned to.
-//! These paths have the same form as the `LoanPath` data structure, which
-//! in turn is the "real world version of the lvalues `LV` that we
-//! introduced earlier. The difference between a `MovePath` and a `LoanPath`
-//! is that move paths are:
-//!
-//! 1. Canonicalized, so that we have exactly one copy of each, and
-//! we can refer to move paths by index;
-//! 2. Cross-referenced with other paths into a tree, so that given a move
-//! path we can efficiently find all parent move paths and all
-//! extensions (e.g., given the `a.b` move path, we can easily find the
-//! move path `a` and also the move paths `a.b.c`)
-//! 3. Cross-referenced with moves and assignments, so that we can
-//! easily find all moves and assignments to a given path.
-//!
-//! The mechanism that we use is to create a `MovePath` record for each
-//! move path. These are arranged in an array and are referenced using
-//! `MovePathIndex` values, which are newtype'd indices. The `MovePath`
-//! structs are arranged into a tree, representing using the standard
-//! Knuth representation where each node has a child 'pointer' and a "next
-//! sibling" 'pointer'. In addition, each `MovePath` has a parent
-//! 'pointer'. In this case, the 'pointers' are just `MovePathIndex`
-//! values.
-//!
-//! In this way, if we want to find all base paths of a given move path,
-//! we can just iterate up the parent pointers (see `each_base_path()` in
-//! the `move_data` module). If we want to find all extensions, we can
-//! iterate through the subtree (see `each_extending_path()`).
-//!
-//! ### Moves and assignments
-//!
-//! There are structs to represent moves (`Move`) and assignments
-//! (`Assignment`), and these are also placed into arrays and referenced
-//! by index. All moves of a particular path are arranged into a linked
-//! lists, beginning with `MovePath.first_move` and continuing through
-//! `Move.next_move`.
-//!
-//! We distinguish between "var" assignments, which are assignments to a
-//! variable like `x = foo`, and "path" assignments (`x.f = foo`). This
-//! is because we need to assign dataflows to the former, but not the
-//! latter, so as to check for double initialization of immutable
-//! variables.
-//!
-//! ### Gathering and checking moves
-//!
-//! Like loans, we distinguish two phases. The first, gathering, is where
-//! we uncover all the moves and assignments. As with loans, we do some
-//! basic sanity checking in this phase, so we'll report errors if you
-//! attempt to move out of a borrowed pointer etc. Then we do the dataflow
-//! (see `FlowedMoveData::new`). Finally, in the `check_loans.rs` code, we
-//! walk back over, identify all uses, assignments, and captures, and
-//! check that they are legal given the set of dataflow bits we have
-//! computed for that program point.
-//!
-//! # Drop flags and structural fragments
-//!
-//! In addition to the job of enforcing memory safety, the borrow checker
-//! code is also responsible for identifying the *structural fragments* of
-//! data in the function, to support out-of-band dynamic drop flags
-//! allocated on the stack. (For background, see [RFC PR #320].)
-//!
-//! [RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320
-//!
-//! Semantically, each piece of data that has a destructor may need a
-//! boolean flag to indicate whether or not its destructor has been run
-//! yet. However, in many cases there is no need to actually maintain such
-//! a flag: It can be apparent from the code itself that a given path is
-//! always initialized (or always deinitialized) when control reaches the
-//! end of its owner's scope, and thus we can unconditionally emit (or
-//! not) the destructor invocation for that path.
-//!
-//! A simple example of this is the following:
-//!
-//! ```rust
-//! struct D { p: int }
-//! impl D { fn new(x: int) -> D { ... }
-//! impl Drop for D { ... }
-//!
-//! fn foo(a: D, b: D, t: || -> bool) {
-//! let c: D;
-//! let d: D;
-//! if t() { c = b; }
-//! }
-//! ```
-//!
-//! At the end of the body of `foo`, the compiler knows that `a` is
-//! initialized, introducing a drop obligation (deallocating the boxed
-//! integer) for the end of `a`'s scope that is run unconditionally.
-//! Likewise the compiler knows that `d` is not initialized, and thus it
-//! leave out the drop code for `d`.
-//!
-//! The compiler cannot statically know the drop-state of `b` nor `c` at
-//! the end of their scope, since that depends on the value of
-//! `t`. Therefore, we need to insert boolean flags to track whether we
-//! need to drop `b` and `c`.
-//!
-//! However, the matter is not as simple as just mapping local variables
-//! to their corresponding drop flags when necessary. In particular, in
-//! addition to being able to move data out of local variables, Rust
-//! allows one to move values in and out of structured data.
-//!
-//! Consider the following:
-//!
-//! ```rust
-//! struct S { x: D, y: D, z: D }
-//!
-//! fn foo(a: S, mut b: S, t: || -> bool) {
-//! let mut c: S;
-//! let d: S;
-//! let e: S = a.clone();
-//! if t() {
-//! c = b;
-//! b.x = e.y;
-//! }
-//! if t() { c.y = D::new(4); }
-//! }
-//! ```
-//!
-//! As before, the drop obligations of `a` and `d` can be statically
-//! determined, and again the state of `b` and `c` depend on dynamic
-//! state. But additionally, the dynamic drop obligations introduced by
-//! `b` and `c` are not just per-local boolean flags. For example, if the
-//! first call to `t` returns `false` and the second call `true`, then at
-//! the end of their scope, `b` will be completely initialized, but only
-//! `c.y` in `c` will be initialized. If both calls to `t` return `true`,
-//! then at the end of their scope, `c` will be completely initialized,
-//! but only `b.x` will be initialized in `b`, and only `e.x` and `e.z`
-//! will be initialized in `e`.
-//!
-//! Note that we need to cover the `z` field in each case in some way,
-//! since it may (or may not) need to be dropped, even though `z` is never
-//! directly mentioned in the body of the `foo` function. We call a path
-//! like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes
-//! from the same structure `S` that declared the field `x` in `b.x`.
-//!
-//! In general we need to maintain boolean flags that match the
-//! `S`-structure of both `b` and `c`. In addition, we need to consult
-//! such a flag when doing an assignment (such as `c.y = D::new(4);`
-//! above), in order to know whether or not there is a previous value that
-//! needs to be dropped before we do the assignment.
-//!
-//! So for any given function, we need to determine what flags are needed
-//! to track its drop obligations. Our strategy for determining the set of
-//! flags is to represent the fragmentation of the structure explicitly:
-//! by starting initially from the paths that are explicitly mentioned in
-//! moves and assignments (such as `b.x` and `c.y` above), and then
-//! traversing the structure of the path's type to identify leftover
-//! *unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z`
-//! are leftover unmoved fragments. Each fragment represents a drop
-//! obligation that may need to be tracked. Paths that are only moved or
-//! assigned in their entirety (like `a` and `d`) are treated as a single
-//! drop obligation.
-//!
-//! The fragment construction process works by piggy-backing on the
-//! existing `move_data` module. We already have callbacks that visit each
-//! direct move and assignment; these form the basis for the sets of
-//! moved_leaf_paths and assigned_leaf_paths. From these leaves, we can
-//! walk up their parent chain to identify all of their parent paths.
-//! We need to identify the parents because of cases like the following:
-//!
-//! ```rust
-//! struct Pair<X,Y>{ x: X, y: Y }
-//! fn foo(dd_d_d: Pair<Pair<Pair<D, D>, D>, D>) {
-//! other_function(dd_d_d.x.y);
-//! }
-//! ```
-//!
-//! In this code, the move of the path `dd_d.x.y` leaves behind not only
-//! the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well.
-//!
-//! Once we have identified the directly-referenced leaves and their
-//! parents, we compute the left-over fragments, in the function
-//! `fragments::add_fragment_siblings`. As of this writing this works by
-//! looking at each directly-moved or assigned path P, and blindly
-//! gathering all sibling fields of P (as well as siblings for the parents
-//! of P, etc). After accumulating all such siblings, we filter out the
-//! entries added as siblings of P that turned out to be
-//! directly-referenced paths (or parents of directly referenced paths)
-//! themselves, thus leaving the never-referenced "left-overs" as the only
-//! thing left from the gathering step.
-//!
-//! ## Array structural fragments
-//!
-//! A special case of the structural fragments discussed above are
-//! the elements of an array that has been passed by value, such as
-//! the following:
-//!
-//! ```rust
-//! fn foo(a: [D, ..10], i: uint) -> D {
-//! a[i]
-//! }
-//! ```
-//!
-//! The above code moves a single element out of the input array `a`.
-//! The remainder of the array still needs to be dropped; i.e., it
-//! is a structural fragment. Note that after performing such a move,
-//! it is not legal to read from the array `a`. There are a number of
-//! ways to deal with this, but the important thing to note is that
-//! the semantics needs to distinguish in some manner between a
-//! fragment that is the *entire* array versus a fragment that represents
-//! all-but-one element of the array. A place where that distinction
-//! would arise is the following:
-//!
-//! ```rust
-//! fn foo(a: [D, ..10], b: [D, ..10], i: uint, t: bool) -> D {
-//! if t {
-//! a[i]
-//! } else {
-//! b[i]
-//! }
-//!
-//! // When control exits, we will need either to drop all of `a`
-//! // and all-but-one of `b`, or to drop all of `b` and all-but-one
-//! // of `a`.
-//! }
-//! ```
-//!
-//! There are a number of ways that the trans backend could choose to
-//! compile this (e.g. a `[bool, ..10]` array for each such moved array;
-//! or an `Option<uint>` for each moved array). From the viewpoint of the
-//! borrow-checker, the important thing is to record what kind of fragment
-//! is implied by the relevant moves.
-//!
-//! # Future work
-//!
-//! While writing up these docs, I encountered some rules I believe to be
-//! stricter than necessary:
-//!
-//! - I think restricting the `&mut` LV against moves and `ALIAS` is sufficient,
-//! `MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was
-//! a built-in operator, but as it is not, it is implied by `CLAIM`,
-//! and `CLAIM` is implied by `ALIAS`. The only net effect of this is an
-//! extra error message in some cases, though.
-//! - I have not described how closures interact. Current code is unsound.
-//! I am working on describing and implementing the fix.
-//! - If we wish, we can easily extend the move checking to allow finer-grained
-//! tracking of what is initialized and what is not, enabling code like
-//! this:
-//!
-//! a = x.f.g; // x.f.g is now uninitialized
-//! // here, x and x.f are not usable, but x.f.h *is*
-//! x.f.g = b; // x.f.g is not initialized
-//! // now x, x.f, x.f.g, x.f.h are all usable
-//!
-//! What needs to change here, most likely, is that the `moves` module
-//! should record not only what paths are moved, but what expressions
-//! are actual *uses*. For example, the reference to `x` in `x.f.g = b`
-//! is not a true *use* in the sense that it requires `x` to be fully
-//! initialized. This is in fact why the above code produces an error
-//! today: the reference to `x` in `x.f.g = b` is considered illegal
-//! because `x` is not fully initialized.
-//!
-//! There are also some possible refactorings:
-//!
-//! - It might be nice to replace all loan paths with the MovePath mechanism,
-//! since they allow lightweight comparison using an integer.
+++ /dev/null
-// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! Helper routines used for fragmenting structural paths due to moves for
-//! tracking drop obligations. Please see the extensive comments in the
-//! section "Structural fragments" in `doc.rs`.
-
-use self::Fragment::*;
-
-use session::config;
-use middle::borrowck::{LoanPath};
-use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
-use middle::borrowck::LoanPathElem::{LpDeref, LpInterior};
-use middle::borrowck::move_data::{InvalidMovePathIndex};
-use middle::borrowck::move_data::{MoveData, MovePathIndex};
-use middle::ty;
-use middle::mem_categorization as mc;
-use util::ppaux::{Repr, UserString};
-
-use std::mem;
-use std::rc::Rc;
-use std::slice;
-use syntax::ast;
-use syntax::ast_map;
-use syntax::attr::AttrMetaMethods;
-use syntax::codemap::Span;
-
-#[deriving(PartialEq, Eq, PartialOrd, Ord)]
-enum Fragment {
- // This represents the path described by the move path index
- Just(MovePathIndex),
-
- // This represents the collection of all but one of the elements
- // from an array at the path described by the move path index.
- // Note that attached MovePathIndex should have mem_categorization
- // of InteriorElement (i.e. array dereference `[]`).
- AllButOneFrom(MovePathIndex),
-}
-
-impl Fragment {
- fn loan_path_repr<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
- let repr = |mpi| move_data.path_loan_path(mpi).repr(tcx);
- match *self {
- Just(mpi) => repr(mpi),
- AllButOneFrom(mpi) => format!("$(allbutone {})", repr(mpi)),
- }
- }
-
- fn loan_path_user_string<'tcx>(&self,
- move_data: &MoveData<'tcx>,
- tcx: &ty::ctxt<'tcx>) -> String {
- let user_string = |mpi| move_data.path_loan_path(mpi).user_string(tcx);
- match *self {
- Just(mpi) => user_string(mpi),
- AllButOneFrom(mpi) => format!("$(allbutone {})", user_string(mpi)),
- }
- }
-}
-
-pub struct FragmentSets {
- /// During move_data construction, `moved_leaf_paths` tracks paths
- /// that have been used directly by being moved out of. When
- /// move_data construction has been completed, `moved_leaf_paths`
- /// tracks such paths that are *leaf fragments* (e.g. `a.j` if we
- /// never move out any child like `a.j.x`); any parent paths
- /// (e.g. `a` for the `a.j` example) are moved over to
- /// `parents_of_fragments`.
- moved_leaf_paths: Vec<MovePathIndex>,
-
- /// `assigned_leaf_paths` tracks paths that have been used
- /// directly by being overwritten, but is otherwise much like
- /// `moved_leaf_paths`.
- assigned_leaf_paths: Vec<MovePathIndex>,
-
- /// `parents_of_fragments` tracks paths that are definitely
- /// parents of paths that have been moved.
- ///
- /// FIXME(pnkfelix) probably do not want/need
- /// `parents_of_fragments` at all, if we can avoid it.
- ///
- /// Update: I do not see a way to to avoid it. Maybe just remove
- /// above fixme, or at least document why doing this may be hard.
- parents_of_fragments: Vec<MovePathIndex>,
-
- /// During move_data construction (specifically the
- /// fixup_fragment_sets call), `unmoved_fragments` tracks paths
- /// that have been "left behind" after a sibling has been moved or
- /// assigned. When move_data construction has been completed,
- /// `unmoved_fragments` tracks paths that were *only* results of
- /// being left-behind, and never directly moved themselves.
- unmoved_fragments: Vec<Fragment>,
-}
-
-impl FragmentSets {
- pub fn new() -> FragmentSets {
- FragmentSets {
- unmoved_fragments: Vec::new(),
- moved_leaf_paths: Vec::new(),
- assigned_leaf_paths: Vec::new(),
- parents_of_fragments: Vec::new(),
- }
- }
-
- pub fn add_move(&mut self, path_index: MovePathIndex) {
- self.moved_leaf_paths.push(path_index);
- }
-
- pub fn add_assignment(&mut self, path_index: MovePathIndex) {
- self.assigned_leaf_paths.push(path_index);
- }
-}
-
-pub fn instrument_move_fragments<'tcx>(this: &MoveData<'tcx>,
- tcx: &ty::ctxt<'tcx>,
- sp: Span,
- id: ast::NodeId) {
- let (span_err, print) = {
- let attrs : &[ast::Attribute];
- attrs = match tcx.map.find(id) {
- Some(ast_map::NodeItem(ref item)) =>
- item.attrs.as_slice(),
- Some(ast_map::NodeImplItem(&ast::MethodImplItem(ref m))) =>
- m.attrs.as_slice(),
- Some(ast_map::NodeTraitItem(&ast::ProvidedMethod(ref m))) =>
- m.attrs.as_slice(),
- _ => [].as_slice(),
- };
-
- let span_err =
- attrs.iter().any(|a| a.check_name("rustc_move_fragments"));
- let print = tcx.sess.debugging_opt(config::PRINT_MOVE_FRAGMENTS);
-
- (span_err, print)
- };
-
- if !span_err && !print { return; }
-
- let instrument_all_paths = |kind, vec_rc: &Vec<MovePathIndex>| {
- for (i, mpi) in vec_rc.iter().enumerate() {
- let render = || this.path_loan_path(*mpi).user_string(tcx);
- if span_err {
- tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
- }
- if print {
- println!("id:{} {}[{}] `{}`", id, kind, i, render());
- }
- }
- };
-
- let instrument_all_fragments = |kind, vec_rc: &Vec<Fragment>| {
- for (i, f) in vec_rc.iter().enumerate() {
- let render = || f.loan_path_user_string(this, tcx);
- if span_err {
- tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
- }
- if print {
- println!("id:{} {}[{}] `{}`", id, kind, i, render());
- }
- }
- };
-
- let fragments = this.fragments.borrow();
- instrument_all_paths("moved_leaf_path", &fragments.moved_leaf_paths);
- instrument_all_fragments("unmoved_fragment", &fragments.unmoved_fragments);
- instrument_all_paths("parent_of_fragments", &fragments.parents_of_fragments);
- instrument_all_paths("assigned_leaf_path", &fragments.assigned_leaf_paths);
-}
-
-/// Normalizes the fragment sets in `this`; i.e., removes duplicate entries, constructs the set of
-/// parents, and constructs the left-over fragments.
-///
-/// Note: "left-over fragments" means paths that were not directly referenced in moves nor
-/// assignments, but must nonetheless be tracked as potential drop obligations.
-pub fn fixup_fragment_sets<'tcx>(this: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) {
-
- let mut fragments = this.fragments.borrow_mut();
-
- // Swap out contents of fragments so that we can modify the fields
- // without borrowing the common fragments.
- let mut unmoved = mem::replace(&mut fragments.unmoved_fragments, vec![]);
- let mut parents = mem::replace(&mut fragments.parents_of_fragments, vec![]);
- let mut moved = mem::replace(&mut fragments.moved_leaf_paths, vec![]);
- let mut assigned = mem::replace(&mut fragments.assigned_leaf_paths, vec![]);
-
- let path_lps = |mpis: &[MovePathIndex]| -> Vec<String> {
- mpis.iter().map(|mpi| this.path_loan_path(*mpi).repr(tcx)).collect()
- };
-
- let frag_lps = |fs: &[Fragment]| -> Vec<String> {
- fs.iter().map(|f| f.loan_path_repr(this, tcx)).collect()
- };
-
- // First, filter out duplicates
- moved.sort();
- moved.dedup();
- debug!("fragments 1 moved: {}", path_lps(moved.as_slice()));
-
- assigned.sort();
- assigned.dedup();
- debug!("fragments 1 assigned: {}", path_lps(assigned.as_slice()));
-
- // Second, build parents from the moved and assigned.
- for m in moved.iter() {
- let mut p = this.path_parent(*m);
- while p != InvalidMovePathIndex {
- parents.push(p);
- p = this.path_parent(p);
- }
- }
- for a in assigned.iter() {
- let mut p = this.path_parent(*a);
- while p != InvalidMovePathIndex {
- parents.push(p);
- p = this.path_parent(p);
- }
- }
-
- parents.sort();
- parents.dedup();
- debug!("fragments 2 parents: {}", path_lps(parents.as_slice()));
-
- // Third, filter the moved and assigned fragments down to just the non-parents
- moved.retain(|f| non_member(*f, parents.as_slice()));
- debug!("fragments 3 moved: {}", path_lps(moved.as_slice()));
-
- assigned.retain(|f| non_member(*f, parents.as_slice()));
- debug!("fragments 3 assigned: {}", path_lps(assigned.as_slice()));
-
- // Fourth, build the leftover from the moved, assigned, and parents.
- for m in moved.iter() {
- let lp = this.path_loan_path(*m);
- add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
- }
- for a in assigned.iter() {
- let lp = this.path_loan_path(*a);
- add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
- }
- for p in parents.iter() {
- let lp = this.path_loan_path(*p);
- add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
- }
-
- unmoved.sort();
- unmoved.dedup();
- debug!("fragments 4 unmoved: {}", frag_lps(unmoved.as_slice()));
-
- // Fifth, filter the leftover fragments down to its core.
- unmoved.retain(|f| match *f {
- AllButOneFrom(_) => true,
- Just(mpi) => non_member(mpi, parents.as_slice()) &&
- non_member(mpi, moved.as_slice()) &&
- non_member(mpi, assigned.as_slice())
- });
- debug!("fragments 5 unmoved: {}", frag_lps(unmoved.as_slice()));
-
- // Swap contents back in.
- fragments.unmoved_fragments = unmoved;
- fragments.parents_of_fragments = parents;
- fragments.moved_leaf_paths = moved;
- fragments.assigned_leaf_paths = assigned;
-
- return;
-
- fn non_member(elem: MovePathIndex, set: &[MovePathIndex]) -> bool {
- match set.binary_search_elem(&elem) {
- slice::BinarySearchResult::Found(_) => false,
- slice::BinarySearchResult::NotFound(_) => true,
- }
- }
-}
-
-/// Adds all of the precisely-tracked siblings of `lp` as potential move paths of interest. For
-/// example, if `lp` represents `s.x.j`, then adds moves paths for `s.x.i` and `s.x.k`, the
-/// siblings of `s.x.j`.
-fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>,
- tcx: &ty::ctxt<'tcx>,
- gathered_fragments: &mut Vec<Fragment>,
- lp: Rc<LoanPath<'tcx>>,
- origin_id: Option<ast::NodeId>) {
- match lp.kind {
- LpVar(_) | LpUpvar(..) => {} // Local variables have no siblings.
-
- // Consuming a downcast is like consuming the original value, so propage inward.
- LpDowncast(ref loan_parent, _) => {
- add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
- }
-
- // *LV for OwnedPtr consumes the contents of the box (at
- // least when it is non-copy...), so propagate inward.
- LpExtend(ref loan_parent, _, LpDeref(mc::OwnedPtr)) => {
- add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
- }
-
- // *LV for unsafe and borrowed pointers do not consume their loan path, so stop here.
- LpExtend(_, _, LpDeref(mc::UnsafePtr(..))) |
- LpExtend(_, _, LpDeref(mc::Implicit(..))) |
- LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) => {}
-
- // FIXME(pnkfelix): LV[j] should be tracked, at least in the
- // sense of we will track the remaining drop obligation of the
- // rest of the array.
- //
- // LV[j] is not tracked precisely
- LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
- let mp = this.move_path(tcx, lp.clone());
- gathered_fragments.push(AllButOneFrom(mp));
- }
-
- // field access LV.x and tuple access LV#k are the cases
- // we are interested in
- LpExtend(ref loan_parent, mc,
- LpInterior(mc::InteriorField(ref field_name))) => {
- let enum_variant_info = match loan_parent.kind {
- LpDowncast(ref loan_parent_2, variant_def_id) =>
- Some((variant_def_id, loan_parent_2.clone())),
- LpExtend(..) | LpVar(..) | LpUpvar(..) =>
- None,
- };
- add_fragment_siblings_for_extension(
- this,
- tcx,
- gathered_fragments,
- loan_parent, mc, field_name, &lp, origin_id, enum_variant_info);
- }
- }
-}
-
-/// We have determined that `origin_lp` destructures to LpExtend(parent, original_field_name).
-/// Based on this, add move paths for all of the siblings of `origin_lp`.
-fn add_fragment_siblings_for_extension<'tcx>(this: &MoveData<'tcx>,
- tcx: &ty::ctxt<'tcx>,
- gathered_fragments: &mut Vec<Fragment>,
- parent_lp: &Rc<LoanPath<'tcx>>,
- mc: mc::MutabilityCategory,
- origin_field_name: &mc::FieldName,
- origin_lp: &Rc<LoanPath<'tcx>>,
- origin_id: Option<ast::NodeId>,
- enum_variant_info: Option<(ast::DefId,
- Rc<LoanPath<'tcx>>)>) {
- let parent_ty = parent_lp.to_type();
-
- let add_fragment_sibling_local = |field_name, variant_did| {
- add_fragment_sibling_core(
- this, tcx, gathered_fragments, parent_lp.clone(), mc, field_name, origin_lp,
- variant_did);
- };
-
- match (&parent_ty.sty, enum_variant_info) {
- (&ty::ty_tup(ref v), None) => {
- let tuple_idx = match *origin_field_name {
- mc::PositionalField(tuple_idx) => tuple_idx,
- mc::NamedField(_) =>
- panic!("tuple type {} should not have named fields.",
- parent_ty.repr(tcx)),
- };
- let tuple_len = v.len();
- for i in range(0, tuple_len) {
- if i == tuple_idx { continue }
- let field_name = mc::PositionalField(i);
- add_fragment_sibling_local(field_name, None);
- }
- }
-
- (&ty::ty_struct(def_id, ref _substs), None) => {
- let fields = ty::lookup_struct_fields(tcx, def_id);
- match *origin_field_name {
- mc::NamedField(ast_name) => {
- for f in fields.iter() {
- if f.name == ast_name {
- continue;
- }
- let field_name = mc::NamedField(f.name);
- add_fragment_sibling_local(field_name, None);
- }
- }
- mc::PositionalField(tuple_idx) => {
- for (i, _f) in fields.iter().enumerate() {
- if i == tuple_idx {
- continue
- }
- let field_name = mc::PositionalField(i);
- add_fragment_sibling_local(field_name, None);
- }
- }
- }
- }
-
- (&ty::ty_enum(enum_def_id, ref substs), ref enum_variant_info) => {
- let variant_info = {
- let mut variants = ty::substd_enum_variants(tcx, enum_def_id, substs);
- match *enum_variant_info {
- Some((variant_def_id, ref _lp2)) =>
- variants.iter()
- .find(|variant| variant.id == variant_def_id)
- .expect("enum_variant_with_id(): no variant exists with that ID")
- .clone(),
- None => {
- assert_eq!(variants.len(), 1);
- variants.pop().unwrap()
- }
- }
- };
- match *origin_field_name {
- mc::NamedField(ast_name) => {
- let variant_arg_names = variant_info.arg_names.as_ref().unwrap();
- for variant_arg_ident in variant_arg_names.iter() {
- if variant_arg_ident.name == ast_name {
- continue;
- }
- let field_name = mc::NamedField(variant_arg_ident.name);
- add_fragment_sibling_local(field_name, Some(variant_info.id));
- }
- }
- mc::PositionalField(tuple_idx) => {
- let variant_arg_types = &variant_info.args;
- for (i, _variant_arg_ty) in variant_arg_types.iter().enumerate() {
- if tuple_idx == i {
- continue;
- }
- let field_name = mc::PositionalField(i);
- add_fragment_sibling_local(field_name, None);
- }
- }
- }
- }
-
- ref sty_and_variant_info => {
- let msg = format!("type {} ({}) is not fragmentable",
- parent_ty.repr(tcx), sty_and_variant_info);
- let opt_span = origin_id.and_then(|id|tcx.map.opt_span(id));
- tcx.sess.opt_span_bug(opt_span, msg.as_slice())
- }
- }
-}
-
-/// Adds the single sibling `LpExtend(parent, new_field_name)` of `origin_lp` (the original
-/// loan-path).
-fn add_fragment_sibling_core<'tcx>(this: &MoveData<'tcx>,
- tcx: &ty::ctxt<'tcx>,
- gathered_fragments: &mut Vec<Fragment>,
- parent: Rc<LoanPath<'tcx>>,
- mc: mc::MutabilityCategory,
- new_field_name: mc::FieldName,
- origin_lp: &Rc<LoanPath<'tcx>>,
- enum_variant_did: Option<ast::DefId>) -> MovePathIndex {
- let opt_variant_did = match parent.kind {
- LpDowncast(_, variant_did) => Some(variant_did),
- LpVar(..) | LpUpvar(..) | LpExtend(..) => enum_variant_did,
- };
-
- let loan_path_elem = LpInterior(mc::InteriorField(new_field_name));
- let new_lp_type = match new_field_name {
- mc::NamedField(ast_name) =>
- ty::named_element_ty(tcx, parent.to_type(), ast_name, opt_variant_did),
- mc::PositionalField(idx) =>
- ty::positional_element_ty(tcx, parent.to_type(), idx, opt_variant_did),
- };
- let new_lp_variant = LpExtend(parent, mc, loan_path_elem);
- let new_lp = LoanPath::new(new_lp_variant, new_lp_type.unwrap());
- debug!("add_fragment_sibling_core(new_lp={}, origin_lp={})",
- new_lp.repr(tcx), origin_lp.repr(tcx));
- let mp = this.move_path(tcx, Rc::new(new_lp));
-
- // Do not worry about checking for duplicates here; we will sort
- // and dedup after all are added.
- gathered_fragments.push(Just(mp));
-
- mp
-}
+++ /dev/null
-// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! Computes moves.
-
-use middle::borrowck::*;
-use middle::borrowck::LoanPathKind::*;
-use middle::borrowck::gather_loans::move_error::MoveSpanAndPath;
-use middle::borrowck::gather_loans::move_error::{MoveError, MoveErrorCollector};
-use middle::borrowck::move_data::*;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::ty;
-use syntax::ast;
-use syntax::codemap::Span;
-use util::ppaux::Repr;
-
-use std::rc::Rc;
-
-struct GatherMoveInfo<'tcx> {
- id: ast::NodeId,
- kind: MoveKind,
- cmt: mc::cmt<'tcx>,
- span_path_opt: Option<MoveSpanAndPath>
-}
-
-pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- decl_id: ast::NodeId,
- _decl_span: Span,
- var_id: ast::NodeId) {
- let ty = ty::node_id_to_type(bccx.tcx, var_id);
- let loan_path = Rc::new(LoanPath::new(LpVar(var_id), ty));
- move_data.add_move(bccx.tcx, loan_path, decl_id, Declared);
-}
-
-pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- move_error_collector: &MoveErrorCollector<'tcx>,
- move_expr_id: ast::NodeId,
- cmt: mc::cmt<'tcx>,
- move_reason: euv::MoveReason) {
- let kind = match move_reason {
- euv::DirectRefMove | euv::PatBindingMove => MoveExpr,
- euv::CaptureMove => Captured
- };
- let move_info = GatherMoveInfo {
- id: move_expr_id,
- kind: kind,
- cmt: cmt,
- span_path_opt: None,
- };
- gather_move(bccx, move_data, move_error_collector, move_info);
-}
-
-pub fn gather_match_variant<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- _move_error_collector: &MoveErrorCollector<'tcx>,
- move_pat: &ast::Pat,
- cmt: mc::cmt<'tcx>,
- mode: euv::MatchMode) {
- let tcx = bccx.tcx;
- debug!("gather_match_variant(move_pat={}, cmt={}, mode={})",
- move_pat.id, cmt.repr(tcx), mode);
-
- let opt_lp = opt_loan_path(&cmt);
- match opt_lp {
- Some(lp) => {
- match lp.kind {
- LpDowncast(ref base_lp, _) =>
- move_data.add_variant_match(
- tcx, lp.clone(), move_pat.id, base_lp.clone(), mode),
- _ => panic!("should only call gather_match_variant \
- for cat_downcast cmt"),
- }
- }
- None => {
- // We get None when input to match is non-path (e.g.
- // temporary result like a function call). Since no
- // loan-path is being matched, no need to record a
- // downcast.
- return;
- }
- }
-}
-
-pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- move_error_collector: &MoveErrorCollector<'tcx>,
- move_pat: &ast::Pat,
- cmt: mc::cmt<'tcx>) {
- let pat_span_path_opt = match move_pat.node {
- ast::PatIdent(_, ref path1, _) => {
- Some(MoveSpanAndPath{span: move_pat.span,
- ident: path1.node})
- },
- _ => None,
- };
- let move_info = GatherMoveInfo {
- id: move_pat.id,
- kind: MovePat,
- cmt: cmt,
- span_path_opt: pat_span_path_opt,
- };
- gather_move(bccx, move_data, move_error_collector, move_info);
-}
-
-fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- move_error_collector: &MoveErrorCollector<'tcx>,
- move_info: GatherMoveInfo<'tcx>) {
- debug!("gather_move(move_id={}, cmt={})",
- move_info.id, move_info.cmt.repr(bccx.tcx));
-
- let potentially_illegal_move =
- check_and_get_illegal_move_origin(bccx, &move_info.cmt);
- match potentially_illegal_move {
- Some(illegal_move_origin) => {
- debug!("illegal_move_origin={}", illegal_move_origin.repr(bccx.tcx));
- let error = MoveError::with_move_info(illegal_move_origin,
- move_info.span_path_opt);
- move_error_collector.add_error(error);
- return
- }
- None => ()
- }
-
- match opt_loan_path(&move_info.cmt) {
- Some(loan_path) => {
- move_data.add_move(bccx.tcx, loan_path,
- move_info.id, move_info.kind);
- }
- None => {
- // move from rvalue or unsafe pointer, hence ok
- }
- }
-}
-
-pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_data: &MoveData<'tcx>,
- assignment_id: ast::NodeId,
- assignment_span: Span,
- assignee_loan_path: Rc<LoanPath<'tcx>>,
- assignee_id: ast::NodeId,
- mode: euv::MutateMode) {
- move_data.add_assignment(bccx.tcx,
- assignee_loan_path,
- assignment_id,
- assignment_span,
- assignee_id,
- mode);
-}
-
-fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- cmt: &mc::cmt<'tcx>)
- -> Option<mc::cmt<'tcx>> {
- match cmt.cat {
- mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
- mc::cat_deref(_, _, mc::Implicit(..)) |
- mc::cat_deref(_, _, mc::UnsafePtr(..)) |
- mc::cat_static_item => {
- Some(cmt.clone())
- }
-
- mc::cat_rvalue(..) |
- mc::cat_local(..) |
- mc::cat_upvar(..) => {
- None
- }
-
- mc::cat_downcast(ref b, _) |
- mc::cat_interior(ref b, _) => {
- match b.ty.sty {
- ty::ty_struct(did, _) | ty::ty_enum(did, _) => {
- if ty::has_dtor(bccx.tcx, did) {
- Some(cmt.clone())
- } else {
- check_and_get_illegal_move_origin(bccx, b)
- }
- }
- _ => {
- check_and_get_illegal_move_origin(bccx, b)
- }
- }
- }
-
- mc::cat_deref(ref b, _, mc::OwnedPtr) => {
- check_and_get_illegal_move_origin(bccx, b)
- }
- }
-}
+++ /dev/null
-// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! This module implements the check that the lifetime of a borrow
-//! does not exceed the lifetime of the value being borrowed.
-
-use middle::borrowck::*;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::region;
-use middle::ty;
-use util::ppaux::Repr;
-use syntax::ast;
-use syntax::codemap::Span;
-
-type R = Result<(),()>;
-
-pub fn guarantee_lifetime<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- item_scope: region::CodeExtent,
- span: Span,
- cause: euv::LoanCause,
- cmt: mc::cmt<'tcx>,
- loan_region: ty::Region,
- _: ty::BorrowKind)
- -> Result<(),()> {
- //! Reports error if `loan_region` is larger than S
- //! where S is `item_scope` if `cmt` is an upvar,
- //! and is scope of `cmt` otherwise.
- debug!("guarantee_lifetime(cmt={}, loan_region={})",
- cmt.repr(bccx.tcx), loan_region.repr(bccx.tcx));
- let ctxt = GuaranteeLifetimeContext {bccx: bccx,
- item_scope: item_scope,
- span: span,
- cause: cause,
- loan_region: loan_region,
- cmt_original: cmt.clone()};
- ctxt.check(&cmt, None)
-}
-
-///////////////////////////////////////////////////////////////////////////
-// Private
-
-struct GuaranteeLifetimeContext<'a, 'tcx: 'a> {
- bccx: &'a BorrowckCtxt<'a, 'tcx>,
-
- // the scope of the function body for the enclosing item
- item_scope: region::CodeExtent,
-
- span: Span,
- cause: euv::LoanCause,
- loan_region: ty::Region,
- cmt_original: mc::cmt<'tcx>
-}
-
-impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
-
- fn check(&self, cmt: &mc::cmt<'tcx>, discr_scope: Option<ast::NodeId>) -> R {
- //! Main routine. Walks down `cmt` until we find the
- //! "guarantor". Reports an error if `self.loan_region` is
- //! larger than scope of `cmt`.
- debug!("guarantee_lifetime.check(cmt={}, loan_region={})",
- cmt.repr(self.bccx.tcx),
- self.loan_region.repr(self.bccx.tcx));
-
- match cmt.cat {
- mc::cat_rvalue(..) |
- mc::cat_local(..) | // L-Local
- mc::cat_upvar(..) |
- mc::cat_deref(_, _, mc::BorrowedPtr(..)) | // L-Deref-Borrowed
- mc::cat_deref(_, _, mc::Implicit(..)) |
- mc::cat_deref(_, _, mc::UnsafePtr(..)) => {
- self.check_scope(self.scope(cmt))
- }
-
- mc::cat_static_item => {
- Ok(())
- }
-
- mc::cat_downcast(ref base, _) |
- mc::cat_deref(ref base, _, mc::OwnedPtr) | // L-Deref-Send
- mc::cat_interior(ref base, _) => { // L-Field
- self.check(base, discr_scope)
- }
- }
- }
-
- fn check_scope(&self, max_scope: ty::Region) -> R {
- //! Reports an error if `loan_region` is larger than `max_scope`
-
- if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
- Err(self.report_error(err_out_of_scope(max_scope, self.loan_region)))
- } else {
- Ok(())
- }
- }
-
- fn scope(&self, cmt: &mc::cmt) -> ty::Region {
- //! Returns the maximal region scope for the which the
- //! lvalue `cmt` is guaranteed to be valid without any
- //! rooting etc, and presuming `cmt` is not mutated.
-
- // See the SCOPE(LV) function in doc.rs
-
- match cmt.cat {
- mc::cat_rvalue(temp_scope) => {
- temp_scope
- }
- mc::cat_upvar(..) => {
- ty::ReScope(self.item_scope)
- }
- mc::cat_static_item => {
- ty::ReStatic
- }
- mc::cat_local(local_id) => {
- ty::ReScope(self.bccx.tcx.region_maps.var_scope(local_id))
- }
- mc::cat_deref(_, _, mc::UnsafePtr(..)) => {
- ty::ReStatic
- }
- mc::cat_deref(_, _, mc::BorrowedPtr(_, r)) |
- mc::cat_deref(_, _, mc::Implicit(_, r)) => {
- r
- }
- mc::cat_downcast(ref cmt, _) |
- mc::cat_deref(ref cmt, _, mc::OwnedPtr) |
- mc::cat_interior(ref cmt, _) => {
- self.scope(cmt)
- }
- }
- }
-
- fn report_error(&self, code: bckerr_code) {
- self.bccx.report(BckError { cmt: self.cmt_original.clone(),
- span: self.span,
- cause: self.cause,
- code: code });
- }
-}
+++ /dev/null
-// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-// ----------------------------------------------------------------------
-// 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 middle::borrowck::*;
-use middle::borrowck::LoanPathKind::*;
-use middle::borrowck::move_data::MoveData;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::region;
-use middle::ty::ParameterEnvironment;
-use middle::ty;
-use util::ppaux::{Repr};
-
-use syntax::ast;
-use syntax::codemap::Span;
-use syntax::visit;
-use syntax::visit::Visitor;
-use syntax::ast::{Expr, FnDecl, Block, NodeId, Pat};
-
-mod lifetime;
-mod restrictions;
-mod gather_moves;
-mod move_error;
-
-pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- fn_id: NodeId,
- decl: &ast::FnDecl,
- body: &ast::Block)
- -> (Vec<Loan<'tcx>>,
- move_data::MoveData<'tcx>) {
- let mut glcx = GatherLoanCtxt {
- bccx: bccx,
- all_loans: Vec::new(),
- item_ub: region::CodeExtent::from_node_id(body.id),
- move_data: MoveData::new(),
- move_error_collector: move_error::MoveErrorCollector::new(),
- };
-
- let param_env = ParameterEnvironment::for_item(bccx.tcx, fn_id);
-
- {
- let mut euv = euv::ExprUseVisitor::new(&mut glcx,
- bccx.tcx,
- param_env);
- euv.walk_fn(decl, 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::CodeExtent,
-}
-
-impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
- fn consume(&mut self,
- consume_id: ast::NodeId,
- _consume_span: Span,
- cmt: mc::cmt<'tcx>,
- mode: euv::ConsumeMode) {
- debug!("consume(consume_id={}, cmt={}, mode={})",
- consume_id, cmt.repr(self.tcx()), mode);
-
- match mode {
- euv::Move(move_reason) => {
- gather_moves::gather_move_from_expr(
- self.bccx, &self.move_data, &self.move_error_collector,
- consume_id, cmt, move_reason);
- }
- euv::Copy => { }
- }
- }
-
- fn matched_pat(&mut self,
- matched_pat: &ast::Pat,
- cmt: mc::cmt<'tcx>,
- mode: euv::MatchMode) {
- debug!("matched_pat(matched_pat={}, cmt={}, mode={})",
- matched_pat.repr(self.tcx()),
- cmt.repr(self.tcx()),
- mode);
-
- if let mc::cat_downcast(..) = cmt.cat {
- gather_moves::gather_match_variant(
- self.bccx, &self.move_data, &self.move_error_collector,
- matched_pat, cmt, mode);
- }
- }
-
- fn consume_pat(&mut self,
- consume_pat: &ast::Pat,
- cmt: mc::cmt<'tcx>,
- mode: euv::ConsumeMode) {
- debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
- consume_pat.repr(self.tcx()),
- cmt.repr(self.tcx()),
- mode);
-
- match mode {
- euv::Copy => { return; }
- euv::Move(_) => { }
- }
-
- gather_moves::gather_move_from_pat(
- self.bccx, &self.move_data, &self.move_error_collector,
- consume_pat, cmt);
- }
-
- fn borrow(&mut self,
- borrow_id: ast::NodeId,
- borrow_span: Span,
- cmt: mc::cmt<'tcx>,
- loan_region: ty::Region,
- bk: ty::BorrowKind,
- loan_cause: euv::LoanCause)
- {
- debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
- bk={}, loan_cause={})",
- borrow_id, cmt.repr(self.tcx()), loan_region,
- bk, loan_cause);
-
- self.guarantee_valid(borrow_id,
- borrow_span,
- cmt,
- bk,
- loan_region,
- loan_cause);
- }
-
- fn mutate(&mut self,
- assignment_id: ast::NodeId,
- assignment_span: Span,
- assignee_cmt: mc::cmt<'tcx>,
- mode: euv::MutateMode)
- {
- debug!("mutate(assignment_id={}, assignee_cmt={})",
- assignment_id, assignee_cmt.repr(self.tcx()));
-
- match opt_loan_path(&assignee_cmt) {
- Some(lp) => {
- gather_moves::gather_assignment(self.bccx, &self.move_data,
- assignment_id, assignment_span,
- lp, assignee_cmt.id, mode);
- }
- 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.
- }
- }
- }
-
- fn decl_without_init(&mut self, id: ast::NodeId, span: Span) {
- gather_moves::gather_decl(self.bccx, &self.move_data, id, span, id);
- }
-}
-
-/// Implements the A-* rules in doc.rs.
-fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- borrow_span: Span,
- loan_cause: euv::LoanCause,
- cmt: mc::cmt<'tcx>,
- req_kind: ty::BorrowKind)
- -> Result<(),()> {
-
- match (cmt.freely_aliasable(bccx.tcx), req_kind) {
- (None, _) => {
- /* Uniquely accessible path -- OK for `&` and `&mut` */
- Ok(())
- }
- (Some(mc::AliasableStatic(safety)), ty::ImmBorrow) => {
- // Borrow of an immutable static item:
- match safety {
- mc::InteriorUnsafe => {
- // If the static item contains an Unsafe<T>, it has interior
- // mutability. In such cases, another phase of the compiler
- // will ensure that the type is `Sync` and then trans will
- // not put it in rodata, so this is ok to allow.
- Ok(())
- }
- mc::InteriorSafe => {
- // Immutable static can be borrowed, no problem.
- Ok(())
- }
- }
- }
- (Some(mc::AliasableStaticMut(..)), _) => {
- // Even touching a static mut is considered unsafe. We assume the
- // user knows what they're doing in these cases.
- Ok(())
- }
- (Some(alias_cause), ty::UniqueImmBorrow) |
- (Some(alias_cause), ty::MutBorrow) => {
- bccx.report_aliasability_violation(
- borrow_span,
- BorrowViolation(loan_cause),
- alias_cause);
- Err(())
- }
- (_, _) => {
- Ok(())
- }
- }
-}
-
-impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
- pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx }
-
- /// 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: ast::NodeId,
- borrow_span: Span,
- cmt: mc::cmt<'tcx>,
- req_kind: ty::BorrowKind,
- loan_region: ty::Region,
- cause: euv::LoanCause) {
- debug!("guarantee_valid(borrow_id={}, cmt={}, \
- req_mutbl={}, loan_region={})",
- borrow_id,
- cmt.repr(self.tcx()),
- 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.clone(), loan_region,
- req_kind).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, cause,
- cmt.clone(), 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, cause,
- cmt.clone(), 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.clone(), loan_region);
-
- debug!("guarantee_valid(): restrictions={}", restr);
-
- // Create the loan record (if needed).
- let loan = match restr {
- restrictions::Safe => {
- // No restrictions---no loan record necessary
- return;
- }
-
- restrictions::SafeIf(loan_path, restricted_paths) => {
- let loan_scope = match loan_region {
- ty::ReScope(scope) => scope,
-
- ty::ReFree(ref fr) => fr.scope,
-
- ty::ReStatic => {
- // If we get here, an error must have been
- // reported in
- // `lifetime::guarantee_lifetime()`, because
- // the only legal ways to have a borrow with a
- // static lifetime should not require
- // restrictions. To avoid reporting derived
- // errors, we just return here without adding
- // any loans.
- return;
- }
-
- ty::ReEmpty |
- ty::ReLateBound(..) |
- ty::ReEarlyBound(..) |
- ty::ReInfer(..) => {
- self.tcx().sess.span_bug(
- cmt.span,
- format!("invalid borrow lifetime: {}",
- loan_region).as_slice());
- }
- };
- debug!("loan_scope = {}", loan_scope);
-
- let borrow_scope = region::CodeExtent::from_node_id(borrow_id);
- 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: loan_path,
- kind: req_kind,
- gen_scope: gen_scope,
- kill_scope: kill_scope,
- span: borrow_span,
- restricted_paths: restricted_paths,
- cause: cause,
- }
- }
- };
-
- debug!("guarantee_valid(borrow_id={}), loan={}",
- borrow_id, loan.repr(self.tcx()));
-
- // 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(#6268) 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 = {
- // let all_loans = &mut *self.all_loans; // FIXME(#5074)
- // Loan {
- // index: all_loans.len(),
- // loan_path: loan_path,
- // cmt: cmt,
- // mutbl: ConstMutability,
- // gen_scope: borrow_id,
- // kill_scope: kill_scope,
- // span: borrow_span,
- // restrictions: restrictions
- // }
- // }
-
- fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- borrow_span: Span,
- cause: euv::LoanCause,
- cmt: mc::cmt<'tcx>,
- req_kind: ty::BorrowKind)
- -> Result<(),()> {
- //! Implements the M-* rules in doc.rs.
-
- 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: cause,
- cmt: cmt,
- code: err_mutbl }))
- } else {
- Ok(())
- }
- }
- }
- }
- }
-
- 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.
-
- match loan_path.kind {
- LpVar(local_id) |
- LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => {
- self.tcx().used_mut_nodes.borrow_mut().insert(local_id);
- }
- LpDowncast(ref base, _) |
- LpExtend(ref base, mc::McInherited, _) |
- LpExtend(ref base, mc::McDeclared, _) => {
- self.mark_loan_path_as_mutated(&**base);
- }
- LpExtend(_, mc::McImmutable, _) => {
- // Nothing to do.
- }
- }
- }
-
- pub fn compute_gen_scope(&self,
- borrow_scope: region::CodeExtent,
- loan_scope: region::CodeExtent)
- -> region::CodeExtent {
- //! 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.tcx.region_maps.is_subscope_of(borrow_scope, loan_scope) {
- borrow_scope
- } else {
- loan_scope
- }
- }
-
- pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath<'tcx>)
- -> region::CodeExtent {
- //! 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:
- //!
- //! fn counter<'a>(v: &'a mut Foo) -> &'a mut uint {
- //! &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.tcx);
- let rm = &self.bccx.tcx.region_maps;
- if rm.is_subscope_of(lexical_scope, loan_scope) {
- lexical_scope
- } else {
- assert!(self.bccx.tcx.region_maps.is_subscope_of(loan_scope, lexical_scope));
- loan_scope
- }
- }
-
- pub fn report_potential_errors(&self) {
- self.move_error_collector.report_potential_errors(self.bccx);
- }
-}
-
-/// Context used while gathering loans on static initializers
-///
-/// This visitor walks static initializer's expressions and makes
-/// sure the loans being taken are sound.
-struct StaticInitializerCtxt<'a, 'tcx: 'a> {
- bccx: &'a BorrowckCtxt<'a, 'tcx>
-}
-
-impl<'a, 'tcx, 'v> Visitor<'v> for StaticInitializerCtxt<'a, 'tcx> {
- fn visit_expr(&mut self, ex: &Expr) {
- if let ast::ExprAddrOf(mutbl, ref base) = ex.node {
- let base_cmt = self.bccx.cat_expr(&**base);
- let borrow_kind = ty::BorrowKind::from_mutbl(mutbl);
- // Check that we don't allow borrows of unsafe static items.
- if check_aliasability(self.bccx, ex.span, euv::AddrOf,
- base_cmt, borrow_kind).is_err() {
- return; // reported an error, no sense in reporting more.
- }
- }
-
- visit::walk_expr(self, ex);
- }
-}
-
-pub fn gather_loans_in_static_initializer(bccx: &mut BorrowckCtxt, expr: &ast::Expr) {
-
- debug!("gather_loans_in_static_initializer(expr={})", expr.repr(bccx.tcx));
-
- let mut sicx = StaticInitializerCtxt {
- bccx: bccx
- };
-
- sicx.visit_expr(expr);
-}
+++ /dev/null
-// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use middle::mem_categorization as mc;
-use middle::borrowck::BorrowckCtxt;
-use middle::ty;
-
-use std::cell::RefCell;
-use syntax::ast;
-use syntax::codemap;
-use syntax::print::pprust;
-use util::ppaux::UserString;
-
-pub struct MoveErrorCollector<'tcx> {
- errors: RefCell<Vec<MoveError<'tcx>>>
-}
-
-impl<'tcx> MoveErrorCollector<'tcx> {
- pub fn new() -> MoveErrorCollector<'tcx> {
- MoveErrorCollector {
- errors: RefCell::new(Vec::new())
- }
- }
-
- pub fn add_error(&self, error: MoveError<'tcx>) {
- self.errors.borrow_mut().push(error);
- }
-
- pub fn report_potential_errors<'a>(&self, bccx: &BorrowckCtxt<'a, 'tcx>) {
- report_move_errors(bccx, self.errors.borrow().deref())
- }
-}
-
-pub struct MoveError<'tcx> {
- move_from: mc::cmt<'tcx>,
- move_to: Option<MoveSpanAndPath>
-}
-
-impl<'tcx> MoveError<'tcx> {
- pub fn with_move_info(move_from: mc::cmt<'tcx>,
- move_to: Option<MoveSpanAndPath>)
- -> MoveError<'tcx> {
- MoveError {
- move_from: move_from,
- move_to: move_to,
- }
- }
-}
-
-#[deriving(Clone)]
-pub struct MoveSpanAndPath {
- pub span: codemap::Span,
- pub ident: ast::Ident
-}
-
-pub struct GroupedMoveErrors<'tcx> {
- move_from: mc::cmt<'tcx>,
- move_to_places: Vec<MoveSpanAndPath>
-}
-
-fn report_move_errors<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- errors: &Vec<MoveError<'tcx>>) {
- let grouped_errors = group_errors_with_same_origin(errors);
- for error in grouped_errors.iter() {
- report_cannot_move_out_of(bccx, error.move_from.clone());
- let mut is_first_note = true;
- for move_to in error.move_to_places.iter() {
- note_move_destination(bccx, move_to.span,
- &move_to.ident, is_first_note);
- is_first_note = false;
- }
- }
-}
-
-fn group_errors_with_same_origin<'tcx>(errors: &Vec<MoveError<'tcx>>)
- -> Vec<GroupedMoveErrors<'tcx>> {
- let mut grouped_errors = Vec::new();
- for error in errors.iter() {
- append_to_grouped_errors(&mut grouped_errors, error)
- }
- return grouped_errors;
-
- fn append_to_grouped_errors<'tcx>(grouped_errors: &mut Vec<GroupedMoveErrors<'tcx>>,
- error: &MoveError<'tcx>) {
- let move_from_id = error.move_from.id;
- debug!("append_to_grouped_errors(move_from_id={})", move_from_id);
- let move_to = if error.move_to.is_some() {
- vec!(error.move_to.clone().unwrap())
- } else {
- Vec::new()
- };
- for ge in grouped_errors.iter_mut() {
- if move_from_id == ge.move_from.id && error.move_to.is_some() {
- debug!("appending move_to to list");
- ge.move_to_places.extend(move_to.into_iter());
- return
- }
- }
- debug!("found a new move from location");
- grouped_errors.push(GroupedMoveErrors {
- move_from: error.move_from.clone(),
- move_to_places: move_to
- })
- }
-}
-
-fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- move_from: mc::cmt<'tcx>) {
- match move_from.cat {
- mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
- mc::cat_deref(_, _, mc::Implicit(..)) |
- mc::cat_deref(_, _, mc::UnsafePtr(..)) |
- mc::cat_static_item => {
- bccx.span_err(
- move_from.span,
- format!("cannot move out of {}",
- bccx.cmt_to_string(&*move_from)).as_slice());
- }
-
- mc::cat_downcast(ref b, _) |
- mc::cat_interior(ref b, _) => {
- match b.ty.sty {
- ty::ty_struct(did, _)
- | ty::ty_enum(did, _) if ty::has_dtor(bccx.tcx, did) => {
- bccx.span_err(
- move_from.span,
- format!("cannot move out of type `{}`, \
- which defines the `Drop` trait",
- b.ty.user_string(bccx.tcx)).as_slice());
- },
- _ => panic!("this path should not cause illegal move")
- }
- }
- _ => panic!("this path should not cause illegal move")
- }
-}
-
-fn note_move_destination(bccx: &BorrowckCtxt,
- move_to_span: codemap::Span,
- pat_ident: &ast::Ident,
- is_first_note: bool) {
- let pat_name = pprust::ident_to_string(pat_ident);
- if is_first_note {
- bccx.span_note(
- move_to_span,
- "attempting to move value to here");
- bccx.span_help(
- move_to_span,
- format!("to prevent the move, \
- use `ref {0}` or `ref mut {0}` to capture value by \
- reference",
- pat_name).as_slice());
- } else {
- bccx.span_note(move_to_span,
- format!("and here (use `ref {0}` or `ref mut {0}`)",
- pat_name).as_slice());
- }
-}
+++ /dev/null
-// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! Computes the restrictions that result from a borrow.
-
-pub use self::RestrictionResult::*;
-
-use middle::borrowck::*;
-use middle::borrowck::LoanPathElem::*;
-use middle::borrowck::LoanPathKind::*;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::ty;
-use syntax::codemap::Span;
-use util::ppaux::Repr;
-
-use std::rc::Rc;
-
-#[deriving(Show)]
-pub enum RestrictionResult<'tcx> {
- Safe,
- SafeIf(Rc<LoanPath<'tcx>>, Vec<Rc<LoanPath<'tcx>>>)
-}
-
-pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
- span: Span,
- cause: euv::LoanCause,
- cmt: mc::cmt<'tcx>,
- loan_region: ty::Region)
- -> RestrictionResult<'tcx> {
- let ctxt = RestrictionsContext {
- bccx: bccx,
- span: span,
- cause: cause,
- loan_region: loan_region,
- };
-
- ctxt.restrict(cmt)
-}
-
-///////////////////////////////////////////////////////////////////////////
-// Private
-
-struct RestrictionsContext<'a, 'tcx: 'a> {
- bccx: &'a BorrowckCtxt<'a, 'tcx>,
- span: Span,
- loan_region: ty::Region,
- cause: euv::LoanCause,
-}
-
-impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
- fn restrict(&self,
- cmt: mc::cmt<'tcx>) -> RestrictionResult<'tcx> {
- debug!("restrict(cmt={})", cmt.repr(self.bccx.tcx));
-
- let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
-
- match cmt.cat.clone() {
- mc::cat_rvalue(..) => {
- // Effectively, rvalues are stored into a
- // non-aliasable temporary on the stack. Since they
- // are inherently non-aliasable, they can only be
- // accessed later through the borrow itself and hence
- // must inherently comply with its terms.
- Safe
- }
-
- mc::cat_local(local_id) => {
- // R-Variable, locally declared
- let lp = new_lp(LpVar(local_id));
- SafeIf(lp.clone(), vec![lp])
- }
-
- mc::cat_upvar(mc::Upvar { id, .. }) => {
- // R-Variable, captured into closure
- let lp = new_lp(LpUpvar(id));
- SafeIf(lp.clone(), vec![lp])
- }
-
- mc::cat_downcast(cmt_base, _) => {
- // When we borrow the interior of an enum, we have to
- // ensure the enum itself is not mutated, because that
- // could cause the type of the memory to change.
- self.restrict(cmt_base)
- }
-
- mc::cat_interior(cmt_base, i) => {
- // R-Field
- //
- // Overwriting the base would not change the type of
- // the memory, so no additional restrictions are
- // needed.
- let result = self.restrict(cmt_base);
- self.extend(result, &cmt, LpInterior(i))
- }
-
- mc::cat_static_item(..) => {
- Safe
- }
-
- mc::cat_deref(cmt_base, _, pk) => {
- match pk {
- mc::OwnedPtr => {
- // R-Deref-Send-Pointer
- //
- // When we borrow the interior of an owned pointer, we
- // cannot permit the base to be mutated, because that
- // would cause the unique pointer to be freed.
- //
- // Eventually we should make these non-special and
- // just rely on Deref<T> implementation.
- let result = self.restrict(cmt_base);
- self.extend(result, &cmt, LpDeref(pk))
- }
- mc::Implicit(bk, lt) | mc::BorrowedPtr(bk, lt) => {
- // R-Deref-[Mut-]Borrowed
- if !self.bccx.is_subregion_of(self.loan_region, lt) {
- self.bccx.report(
- BckError {
- span: self.span,
- cause: self.cause,
- cmt: cmt_base,
- code: err_borrowed_pointer_too_short(
- self.loan_region, lt)});
- return Safe;
- }
-
- match bk {
- ty::ImmBorrow => Safe,
- ty::MutBorrow | ty::UniqueImmBorrow => {
- // R-Deref-Mut-Borrowed
- //
- // The referent can be aliased after the
- // references lifetime ends (by a newly-unfrozen
- // borrow).
- let result = self.restrict(cmt_base);
- self.extend(result, &cmt, LpDeref(pk))
- }
- }
- }
- // Borrowck is not relevant for unsafe pointers
- mc::UnsafePtr(..) => Safe
- }
- }
- }
- }
-
- fn extend(&self,
- result: RestrictionResult<'tcx>,
- cmt: &mc::cmt<'tcx>,
- elem: LoanPathElem) -> RestrictionResult<'tcx> {
- match result {
- Safe => Safe,
- SafeIf(base_lp, mut base_vec) => {
- let v = LpExtend(base_lp, cmt.mutbl, elem);
- let lp = Rc::new(LoanPath::new(v, cmt.ty));
- base_vec.push(lp.clone());
- SafeIf(lp, base_vec)
- }
- }
- }
-}
+++ /dev/null
-// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! This module provides linkage between rustc::middle::graph and
-//! libgraphviz traits, specialized to attaching borrowck analysis
-//! data to rendered labels.
-
-pub use self::Variant::*;
-
-/// For clarity, rename the graphviz crate locally to dot.
-use graphviz as dot;
-pub use middle::cfg::graphviz::{Node, Edge};
-use middle::cfg::graphviz as cfg_dot;
-
-use middle::borrowck;
-use middle::borrowck::{BorrowckCtxt, LoanPath};
-use middle::cfg::{CFGIndex};
-use middle::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
-use middle::dataflow;
-
-use std::rc::Rc;
-
-#[deriving(Show)]
-pub enum Variant {
- Loans,
- Moves,
- Assigns,
-}
-
-impl Copy for Variant {}
-
-impl Variant {
- pub fn short_name(&self) -> &'static str {
- match *self {
- Loans => "loans",
- Moves => "moves",
- Assigns => "assigns",
- }
- }
-}
-
-pub struct DataflowLabeller<'a, 'tcx: 'a> {
- pub inner: cfg_dot::LabelledCFG<'a, 'tcx>,
- pub variants: Vec<Variant>,
- pub borrowck_ctxt: &'a BorrowckCtxt<'a, 'tcx>,
- pub analysis_data: &'a borrowck::AnalysisData<'a, 'tcx>,
-}
-
-impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> {
- fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
- let id = n.val1().data.id;
- debug!("dataflow_for({}, id={}) {}", e, id, self.variants);
- let mut sets = "".to_string();
- let mut seen_one = false;
- for &variant in self.variants.iter() {
- if seen_one { sets.push_str(" "); } else { seen_one = true; }
- sets.push_str(variant.short_name());
- sets.push_str(": ");
- sets.push_str(self.dataflow_for_variant(e, n, variant).as_slice());
- }
- sets
- }
-
- fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node, v: Variant) -> String {
- let cfgidx = n.val0();
- match v {
- Loans => self.dataflow_loans_for(e, cfgidx),
- Moves => self.dataflow_moves_for(e, cfgidx),
- Assigns => self.dataflow_assigns_for(e, cfgidx),
- }
- }
-
- fn build_set<O:DataFlowOperator>(&self,
- e: EntryOrExit,
- cfgidx: CFGIndex,
- dfcx: &DataFlowContext<'a, 'tcx, O>,
- to_lp: |uint| -> Rc<LoanPath<'tcx>>) -> String {
- let mut saw_some = false;
- let mut set = "{".to_string();
- dfcx.each_bit_for_node(e, cfgidx, |index| {
- let lp = to_lp(index);
- if saw_some {
- set.push_str(", ");
- }
- let loan_str = self.borrowck_ctxt.loan_path_to_string(&*lp);
- set.push_str(loan_str.as_slice());
- saw_some = true;
- true
- });
- set.push_str("}");
- set
- }
-
- fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
- let dfcx = &self.analysis_data.loans;
- let loan_index_to_path = |loan_index| {
- let all_loans = &self.analysis_data.all_loans;
- all_loans[loan_index].loan_path()
- };
- self.build_set(e, cfgidx, dfcx, loan_index_to_path)
- }
-
- fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
- let dfcx = &self.analysis_data.move_data.dfcx_moves;
- let move_index_to_path = |move_index| {
- let move_data = &self.analysis_data.move_data.move_data;
- let moves = move_data.moves.borrow();
- let the_move = &(*moves)[move_index];
- move_data.path_loan_path(the_move.path)
- };
- self.build_set(e, cfgidx, dfcx, move_index_to_path)
- }
-
- fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
- let dfcx = &self.analysis_data.move_data.dfcx_assign;
- let assign_index_to_path = |assign_index| {
- let move_data = &self.analysis_data.move_data.move_data;
- let assignments = move_data.var_assignments.borrow();
- let assignment = &(*assignments)[assign_index];
- move_data.path_loan_path(assignment.path)
- };
- self.build_set(e, cfgidx, dfcx, assign_index_to_path)
- }
-}
-
-impl<'a, 'tcx> dot::Labeller<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a, 'tcx> {
- fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() }
- fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) }
- fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> {
- let prefix = self.dataflow_for(dataflow::Entry, n);
- let suffix = self.dataflow_for(dataflow::Exit, n);
- let inner_label = self.inner.node_label(n);
- inner_label
- .prefix_line(dot::LabelStr(prefix.into_cow()))
- .suffix_line(dot::LabelStr(suffix.into_cow()))
- }
- fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
-}
-
-impl<'a, 'tcx> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a, 'tcx> {
- fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() }
- fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() }
- fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) }
- fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) }
-}
+++ /dev/null
-// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! See doc.rs for a thorough explanation of the borrow checker
-
-#![allow(non_camel_case_types)]
-
-pub use self::LoanPathKind::*;
-pub use self::LoanPathElem::*;
-pub use self::bckerr_code::*;
-pub use self::AliasableViolationKind::*;
-pub use self::MovedValueUseKind::*;
-
-use middle::cfg;
-use middle::dataflow::DataFlowContext;
-use middle::dataflow::BitwiseOperator;
-use middle::dataflow::DataFlowOperator;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::region;
-use middle::ty::{mod, ParameterEnvironment, Ty};
-use util::ppaux::{note_and_explain_region, Repr, UserString};
-
-use std::rc::Rc;
-use std::string::String;
-use syntax::ast;
-use syntax::ast_map;
-use syntax::ast_map::blocks::{FnLikeNode, FnParts};
-use syntax::ast_util;
-use syntax::codemap::Span;
-use syntax::parse::token;
-use syntax::visit;
-use syntax::visit::{Visitor, FnKind};
-use syntax::ast::{FnDecl, Block, NodeId};
-
-macro_rules! if_ok(
- ($inp: expr) => (
- match $inp {
- Ok(v) => { v }
- Err(e) => { return Err(e); }
- }
- )
-)
-
-pub mod doc;
-
-pub mod check_loans;
-
-pub mod gather_loans;
-
-pub mod graphviz;
-
-pub mod move_data;
-
-#[deriving(Clone)]
-pub struct LoanDataFlowOperator;
-
-impl Copy for LoanDataFlowOperator {}
-
-pub type LoanDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, LoanDataFlowOperator>;
-
-impl<'a, 'tcx, 'v> Visitor<'v> for BorrowckCtxt<'a, 'tcx> {
- fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v FnDecl,
- b: &'v Block, s: Span, id: ast::NodeId) {
- borrowck_fn(self, fk, fd, b, s, id);
- }
-
- fn visit_item(&mut self, item: &ast::Item) {
- borrowck_item(self, item);
- }
-}
-
-pub fn check_crate(tcx: &ty::ctxt) {
- let mut bccx = BorrowckCtxt {
- tcx: tcx,
- stats: BorrowStats {
- loaned_paths_same: 0,
- loaned_paths_imm: 0,
- stable_paths: 0,
- guaranteed_paths: 0
- }
- };
-
- visit::walk_crate(&mut bccx, tcx.map.krate());
-
- if tcx.sess.borrowck_stats() {
- println!("--- borrowck stats ---");
- println!("paths requiring guarantees: {}",
- bccx.stats.guaranteed_paths);
- println!("paths requiring loans : {}",
- make_stat(&bccx, bccx.stats.loaned_paths_same));
- println!("paths requiring imm loans : {}",
- make_stat(&bccx, bccx.stats.loaned_paths_imm));
- println!("stable paths : {}",
- make_stat(&bccx, bccx.stats.stable_paths));
- }
-
- fn make_stat(bccx: &BorrowckCtxt, stat: uint) -> String {
- let total = bccx.stats.guaranteed_paths as f64;
- let perc = if total == 0.0 { 0.0 } else { stat as f64 * 100.0 / total };
- format!("{} ({:.0}%)", stat, perc)
- }
-}
-
-fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) {
- // Gather loans for items. Note that we don't need
- // to check loans for single expressions. The check
- // loan step is intended for things that have a data
- // flow dependent conditions.
- match item.node {
- ast::ItemStatic(_, _, ref ex) |
- ast::ItemConst(_, ref ex) => {
- gather_loans::gather_loans_in_static_initializer(this, &**ex);
- }
- _ => {
- visit::walk_item(this, item);
- }
- }
-}
-
-/// Collection of conclusions determined via borrow checker analyses.
-pub struct AnalysisData<'a, 'tcx: 'a> {
- pub all_loans: Vec<Loan<'tcx>>,
- pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>,
- pub move_data: move_data::FlowedMoveData<'a, 'tcx>,
-}
-
-fn borrowck_fn(this: &mut BorrowckCtxt,
- fk: FnKind,
- decl: &ast::FnDecl,
- body: &ast::Block,
- sp: Span,
- id: ast::NodeId) {
- debug!("borrowck_fn(id={})", id);
- let cfg = cfg::CFG::new(this.tcx, body);
- let AnalysisData { all_loans,
- loans: loan_dfcx,
- move_data:flowed_moves } =
- build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id);
-
- move_data::fragments::instrument_move_fragments(&flowed_moves.move_data,
- this.tcx, sp, id);
-
- check_loans::check_loans(this,
- &loan_dfcx,
- flowed_moves,
- all_loans.as_slice(),
- id,
- decl,
- body);
-
- visit::walk_fn(this, fk, decl, body, sp);
-}
-
-fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>,
- fk: FnKind,
- decl: &ast::FnDecl,
- cfg: &cfg::CFG,
- body: &ast::Block,
- sp: Span,
- id: ast::NodeId) -> AnalysisData<'a, 'tcx> {
- // Check the body of fn items.
- let id_range = ast_util::compute_id_range_for_fn_body(fk, decl, body, sp, id);
- let (all_loans, move_data) =
- gather_loans::gather_loans_in_fn(this, id, decl, body);
-
- let mut loan_dfcx =
- DataFlowContext::new(this.tcx,
- "borrowck",
- Some(decl),
- cfg,
- LoanDataFlowOperator,
- id_range,
- all_loans.len());
- for (loan_idx, loan) in all_loans.iter().enumerate() {
- loan_dfcx.add_gen(loan.gen_scope.node_id(), loan_idx);
- loan_dfcx.add_kill(loan.kill_scope.node_id(), loan_idx);
- }
- loan_dfcx.add_kills_from_flow_exits(cfg);
- loan_dfcx.propagate(cfg, body);
-
- let flowed_moves = move_data::FlowedMoveData::new(move_data,
- this.tcx,
- cfg,
- id_range,
- decl,
- body);
-
- AnalysisData { all_loans: all_loans,
- loans: loan_dfcx,
- move_data:flowed_moves }
-}
-
-/// This and a `ty::ctxt` is all you need to run the dataflow analyses
-/// used in the borrow checker.
-pub struct FnPartsWithCFG<'a> {
- pub fn_parts: FnParts<'a>,
- pub cfg: &'a cfg::CFG,
-}
-
-impl<'a> FnPartsWithCFG<'a> {
- pub fn from_fn_like(f: &'a FnLikeNode,
- g: &'a cfg::CFG) -> FnPartsWithCFG<'a> {
- FnPartsWithCFG { fn_parts: f.to_fn_parts(), cfg: g }
- }
-}
-
-/// Accessor for introspective clients inspecting `AnalysisData` and
-/// the `BorrowckCtxt` itself , e.g. the flowgraph visualizer.
-pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
- tcx: &'a ty::ctxt<'tcx>,
- input: FnPartsWithCFG<'a>) -> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'a, 'tcx>) {
-
- let mut bccx = BorrowckCtxt {
- tcx: tcx,
- stats: BorrowStats {
- loaned_paths_same: 0,
- loaned_paths_imm: 0,
- stable_paths: 0,
- guaranteed_paths: 0
- }
- };
-
- let p = input.fn_parts;
-
- let dataflow_data = build_borrowck_dataflow_data(&mut bccx,
- p.kind,
- &*p.decl,
- input.cfg,
- &*p.body,
- p.span,
- p.id);
-
- (bccx, dataflow_data)
-}
-
-// ----------------------------------------------------------------------
-// Type definitions
-
-pub struct BorrowckCtxt<'a, 'tcx: 'a> {
- tcx: &'a ty::ctxt<'tcx>,
-
- // Statistics:
- stats: BorrowStats
-}
-
-struct BorrowStats {
- loaned_paths_same: uint,
- loaned_paths_imm: uint,
- stable_paths: uint,
- guaranteed_paths: uint
-}
-
-pub type BckResult<'tcx, T> = Result<T, BckError<'tcx>>;
-
-///////////////////////////////////////////////////////////////////////////
-// Loans and loan paths
-
-/// Record of a loan that was issued.
-pub struct Loan<'tcx> {
- index: uint,
- loan_path: Rc<LoanPath<'tcx>>,
- kind: ty::BorrowKind,
- restricted_paths: Vec<Rc<LoanPath<'tcx>>>,
-
- /// gen_scope indicates where loan is introduced. 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. See also
- /// `GatherLoanCtxt::compute_gen_scope`.
- gen_scope: region::CodeExtent,
-
- /// kill_scope indicates when the loan goes 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. See also `GatherLoanCtxt::compute_kill_scope`.
- kill_scope: region::CodeExtent,
- span: Span,
- cause: euv::LoanCause,
-}
-
-impl<'tcx> Loan<'tcx> {
- pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
- self.loan_path.clone()
- }
-}
-
-#[deriving(Eq, Hash, Show)]
-pub struct LoanPath<'tcx> {
- kind: LoanPathKind<'tcx>,
- ty: ty::Ty<'tcx>,
-}
-
-impl<'tcx> LoanPath<'tcx> {
- pub fn eq_debug(&self, that: &LoanPath<'tcx>, tcx: &ty::ctxt<'tcx>) -> bool {
- let r = self.kind == that.kind;
- if r && self.ty != that.ty {
- panic!("eq variants ineq types: {} == {}, {} != {}",
- self.repr(tcx), that.repr(tcx),
- self.ty.repr(tcx), that.ty.repr(tcx));
- }
- r
- }
-}
-
-impl<'tcx> PartialEq for LoanPath<'tcx> {
- fn eq(&self, that: &LoanPath<'tcx>) -> bool {
- let r = self.kind == that.kind;
- debug_assert!(self.ty == that.ty || !r,
- "Somehow loan paths are equal though their tys are not.");
- r
- }
-}
-
-#[deriving(PartialEq, Eq, Hash, Show)]
-pub enum LoanPathKind<'tcx> {
- LpVar(ast::NodeId), // `x` in doc.rs
- LpUpvar(ty::UpvarId), // `x` captured by-value into closure
- LpDowncast(Rc<LoanPath<'tcx>>, ast::DefId), // `x` downcast to particular enum variant
- LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem)
-}
-
-impl<'tcx> LoanPath<'tcx> {
- fn new(kind: LoanPathKind<'tcx>, ty: ty::Ty<'tcx>) -> LoanPath<'tcx> {
- LoanPath { kind: kind, ty: ty }
- }
-
- fn to_type(&self) -> ty::Ty<'tcx> { self.ty }
-}
-
-// FIXME (pnkfelix): See discussion here
-// https://github.com/pnkfelix/rust/commit/
-// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
-static DOWNCAST_PRINTED_OPERATOR : &'static str = " as ";
-
-#[deriving(PartialEq, Eq, Hash, Show)]
-pub enum LoanPathElem {
- LpDeref(mc::PointerKind), // `*LV` in doc.rs
- LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
-}
-
-impl Copy for LoanPathElem {}
-
-pub fn closure_to_block(closure_id: ast::NodeId,
- tcx: &ty::ctxt) -> ast::NodeId {
- match tcx.map.get(closure_id) {
- ast_map::NodeExpr(expr) => match expr.node {
- ast::ExprProc(_, ref block) |
- ast::ExprClosure(_, _, _, ref block) => {
- block.id
- }
- _ => {
- panic!("encountered non-closure id: {}", closure_id)
- }
- },
- _ => panic!("encountered non-expr id: {}", closure_id)
- }
-}
-
-impl<'tcx> LoanPath<'tcx> {
- pub fn kill_scope(&self, tcx: &ty::ctxt<'tcx>) -> region::CodeExtent {
- match self.kind {
- LpVar(local_id) => tcx.region_maps.var_scope(local_id),
- LpUpvar(upvar_id) => {
- let block_id = closure_to_block(upvar_id.closure_expr_id, tcx);
- region::CodeExtent::from_node_id(block_id)
- }
- LpDowncast(ref base, _) |
- LpExtend(ref base, _, _) => base.kill_scope(tcx),
- }
- }
-
- fn has_fork(&self, other: &LoanPath<'tcx>) -> bool {
- match (&self.kind, &other.kind) {
- (&LpExtend(ref base, _, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) =>
- if id == id2 {
- base.has_fork(&**base2)
- } else {
- true
- },
- (&LpExtend(ref base, _, LpDeref(_)), _) => base.has_fork(other),
- (_, &LpExtend(ref base, _, LpDeref(_))) => self.has_fork(&**base),
- _ => false,
- }
- }
-
- fn depth(&self) -> uint {
- match self.kind {
- LpExtend(ref base, _, LpDeref(_)) => base.depth(),
- LpExtend(ref base, _, LpInterior(_)) => base.depth() + 1,
- _ => 0,
- }
- }
-
- fn common(&self, other: &LoanPath<'tcx>) -> Option<LoanPath<'tcx>> {
- match (&self.kind, &other.kind) {
- (&LpExtend(ref base, a, LpInterior(id)),
- &LpExtend(ref base2, _, LpInterior(id2))) => {
- if id == id2 {
- base.common(&**base2).map(|x| {
- let xd = x.depth();
- if base.depth() == xd && base2.depth() == xd {
- assert_eq!(base.ty, base2.ty);
- assert_eq!(self.ty, other.ty);
- LoanPath {
- kind: LpExtend(Rc::new(x), a, LpInterior(id)),
- ty: self.ty,
- }
- } else {
- x
- }
- })
- } else {
- base.common(&**base2)
- }
- }
- (&LpExtend(ref base, _, LpDeref(_)), _) => base.common(other),
- (_, &LpExtend(ref other, _, LpDeref(_))) => self.common(&**other),
- (&LpVar(id), &LpVar(id2)) => {
- if id == id2 {
- assert_eq!(self.ty, other.ty);
- Some(LoanPath { kind: LpVar(id), ty: self.ty })
- } else {
- None
- }
- }
- (&LpUpvar(id), &LpUpvar(id2)) => {
- if id == id2 {
- assert_eq!(self.ty, other.ty);
- Some(LoanPath { kind: LpUpvar(id), ty: self.ty })
- } else {
- None
- }
- }
- _ => None,
- }
- }
-}
-
-pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
- //! Computes the `LoanPath` (if any) for a `cmt`.
- //! Note that this logic is somewhat duplicated in
- //! the method `compute()` found in `gather_loans::restrictions`,
- //! which allows it to share common loan path pieces as it
- //! traverses the CMT.
-
- let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
-
- match cmt.cat {
- mc::cat_rvalue(..) |
- mc::cat_static_item => {
- None
- }
-
- mc::cat_local(id) => {
- Some(new_lp(LpVar(id)))
- }
-
- mc::cat_upvar(mc::Upvar { id, .. }) => {
- Some(new_lp(LpUpvar(id)))
- }
-
- mc::cat_deref(ref cmt_base, _, pk) => {
- opt_loan_path(cmt_base).map(|lp| {
- new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
- })
- }
-
- mc::cat_interior(ref cmt_base, ik) => {
- opt_loan_path(cmt_base).map(|lp| {
- new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
- })
- }
-
- mc::cat_downcast(ref cmt_base, variant_def_id) =>
- opt_loan_path(cmt_base)
- .map(|lp| {
- new_lp(LpDowncast(lp, variant_def_id))
- }),
-
- }
-}
-
-///////////////////////////////////////////////////////////////////////////
-// Errors
-
-// Errors that can occur
-#[deriving(PartialEq)]
-#[allow(missing_copy_implementations)]
-pub enum bckerr_code {
- err_mutbl,
- err_out_of_scope(ty::Region, ty::Region), // superscope, subscope
- err_borrowed_pointer_too_short(ty::Region, ty::Region), // loan, ptr
-}
-
-// Combination of an error code and the categorization of the expression
-// that caused it
-#[deriving(PartialEq)]
-pub struct BckError<'tcx> {
- span: Span,
- cause: euv::LoanCause,
- cmt: mc::cmt<'tcx>,
- code: bckerr_code
-}
-
-pub enum AliasableViolationKind {
- MutabilityViolation,
- BorrowViolation(euv::LoanCause)
-}
-
-impl Copy for AliasableViolationKind {}
-
-#[deriving(Show)]
-pub enum MovedValueUseKind {
- MovedInUse,
- MovedInCapture,
-}
-
-impl Copy for MovedValueUseKind {}
-
-///////////////////////////////////////////////////////////////////////////
-// Misc
-
-impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
- pub fn is_subregion_of(&self, r_sub: ty::Region, r_sup: ty::Region)
- -> bool {
- self.tcx.region_maps.is_subregion_of(r_sub, r_sup)
- }
-
- pub fn mc(&self) -> mc::MemCategorizationContext<'a, ty::ctxt<'tcx>> {
- mc::MemCategorizationContext::new(self.tcx)
- }
-
- pub fn cat_expr(&self, expr: &ast::Expr) -> mc::cmt<'tcx> {
- match self.mc().cat_expr(expr) {
- Ok(c) => c,
- Err(()) => {
- self.tcx.sess.span_bug(expr.span, "error in mem categorization");
- }
- }
- }
-
- pub fn report(&self, err: BckError<'tcx>) {
- self.span_err(
- err.span,
- self.bckerr_to_string(&err).as_slice());
- self.note_and_explain_bckerr(err);
- }
-
- pub fn report_use_of_moved_value(&self,
- use_span: Span,
- use_kind: MovedValueUseKind,
- lp: &LoanPath<'tcx>,
- the_move: &move_data::Move,
- moved_lp: &LoanPath<'tcx>,
- param_env: &ParameterEnvironment<'tcx>) {
- let verb = match use_kind {
- MovedInUse => "use",
- MovedInCapture => "capture",
- };
-
- let (ol, moved_lp_msg) = match the_move.kind {
- move_data::Declared => {
- self.tcx.sess.span_err(
- use_span,
- format!("{} of possibly uninitialized variable: `{}`",
- verb,
- self.loan_path_to_string(lp)).as_slice());
- (self.loan_path_to_string(moved_lp),
- String::new())
- }
- _ => {
- // If moved_lp is something like `x.a`, and lp is something like `x.b`, we would
- // normally generate a rather confusing message:
- //
- // error: use of moved value: `x.b`
- // note: `x.a` moved here...
- //
- // What we want to do instead is get the 'common ancestor' of the two moves and
- // use that for most of the message instead, giving is something like this:
- //
- // error: use of moved value: `x`
- // note: `x` moved here (through moving `x.a`)...
-
- let common = moved_lp.common(lp);
- let has_common = common.is_some();
- let has_fork = moved_lp.has_fork(lp);
- let (nl, ol, moved_lp_msg) =
- if has_fork && has_common {
- let nl = self.loan_path_to_string(&common.unwrap());
- let ol = nl.clone();
- let moved_lp_msg = format!(" (through moving `{}`)",
- self.loan_path_to_string(moved_lp));
- (nl, ol, moved_lp_msg)
- } else {
- (self.loan_path_to_string(lp),
- self.loan_path_to_string(moved_lp),
- String::new())
- };
-
- let partial = moved_lp.depth() > lp.depth();
- let msg = if !has_fork && partial { "partially " }
- else if has_fork && !has_common { "collaterally "}
- else { "" };
- self.tcx.sess.span_err(
- use_span,
- format!("{} of {}moved value: `{}`",
- verb,
- msg,
- nl).as_slice());
- (ol, moved_lp_msg)
- }
- };
-
- match the_move.kind {
- move_data::Declared => {}
-
- move_data::MoveExpr => {
- let (expr_ty, expr_span) = match self.tcx
- .map
- .find(the_move.id) {
- Some(ast_map::NodeExpr(expr)) => {
- (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
- }
- r => {
- self.tcx.sess.bug(format!("MoveExpr({}) maps to \
- {}, not Expr",
- the_move.id,
- r).as_slice())
- }
- };
- let (suggestion, _) = move_suggestion(self.tcx, param_env, expr_ty,
- ("moved by default", ""));
- self.tcx.sess.span_note(
- expr_span,
- format!("`{}` moved here{} because it has type `{}`, which is {}",
- ol,
- moved_lp_msg,
- expr_ty.user_string(self.tcx),
- suggestion).as_slice());
- }
-
- move_data::MovePat => {
- let pat_ty = ty::node_id_to_type(self.tcx, the_move.id);
- let span = self.tcx.map.span(the_move.id);
- self.tcx.sess.span_note(span,
- format!("`{}` moved here{} because it has type `{}`, \
- which is moved by default",
- ol,
- moved_lp_msg,
- pat_ty.user_string(self.tcx)).as_slice());
- self.tcx.sess.span_help(span,
- "use `ref` to override");
- }
-
- move_data::Captured => {
- let (expr_ty, expr_span) = match self.tcx
- .map
- .find(the_move.id) {
- Some(ast_map::NodeExpr(expr)) => {
- (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
- }
- r => {
- self.tcx.sess.bug(format!("Captured({}) maps to \
- {}, not Expr",
- the_move.id,
- r).as_slice())
- }
- };
- let (suggestion, help) = move_suggestion(self.tcx,
- param_env,
- expr_ty,
- ("moved by default", "make a copy and \
- capture that instead to override"));
- self.tcx.sess.span_note(
- expr_span,
- format!("`{}` moved into closure environment here{} because it \
- has type `{}`, which is {}",
- ol,
- moved_lp_msg,
- expr_ty.user_string(self.tcx),
- suggestion).as_slice());
- self.tcx.sess.span_help(expr_span, help);
- }
- }
-
- fn move_suggestion<'tcx>(tcx: &ty::ctxt<'tcx>,
- param_env: &ty::ParameterEnvironment<'tcx>,
- ty: Ty<'tcx>,
- default_msgs: (&'static str, &'static str))
- -> (&'static str, &'static str) {
- match ty.sty {
- ty::ty_closure(box ty::ClosureTy {
- store: ty::RegionTraitStore(..),
- ..
- }) =>
- ("a non-copyable stack closure",
- "capture it in a new closure, e.g. `|x| f(x)`, to override"),
- _ if ty::type_moves_by_default(tcx, ty, param_env) =>
- ("non-copyable",
- "perhaps you meant to use `clone()`?"),
- _ => default_msgs,
- }
- }
- }
-
- pub fn report_reassigned_immutable_variable(&self,
- span: Span,
- lp: &LoanPath<'tcx>,
- assign:
- &move_data::Assignment) {
- self.tcx.sess.span_err(
- span,
- format!("re-assignment of immutable variable `{}`",
- self.loan_path_to_string(lp)).as_slice());
- self.tcx.sess.span_note(assign.span, "prior assignment occurs here");
- }
-
- pub fn span_err(&self, s: Span, m: &str) {
- self.tcx.sess.span_err(s, m);
- }
-
- pub fn span_note(&self, s: Span, m: &str) {
- self.tcx.sess.span_note(s, m);
- }
-
- pub fn span_end_note(&self, s: Span, m: &str) {
- self.tcx.sess.span_end_note(s, m);
- }
-
- pub fn span_help(&self, s: Span, m: &str) {
- self.tcx.sess.span_help(s, m);
- }
-
- pub fn bckerr_to_string(&self, err: &BckError<'tcx>) -> String {
- match err.code {
- err_mutbl => {
- let descr = match err.cmt.note {
- mc::NoteClosureEnv(_) | mc::NoteUpvarRef(_) => {
- self.cmt_to_string(&*err.cmt)
- }
- _ => match opt_loan_path(&err.cmt) {
- None => {
- format!("{} {}",
- err.cmt.mutbl.to_user_str(),
- self.cmt_to_string(&*err.cmt))
- }
- Some(lp) => {
- format!("{} {} `{}`",
- err.cmt.mutbl.to_user_str(),
- self.cmt_to_string(&*err.cmt),
- self.loan_path_to_string(&*lp))
- }
- }
- };
-
- match err.cause {
- euv::ClosureCapture(_) => {
- format!("closure cannot assign to {}", descr)
- }
- euv::OverloadedOperator |
- euv::AddrOf |
- euv::RefBinding |
- euv::AutoRef |
- euv::ForLoop |
- euv::MatchDiscriminant => {
- format!("cannot borrow {} as mutable", descr)
- }
- euv::ClosureInvocation => {
- self.tcx.sess.span_bug(err.span,
- "err_mutbl with a closure invocation");
- }
- }
- }
- err_out_of_scope(..) => {
- let msg = match opt_loan_path(&err.cmt) {
- None => "borrowed value".to_string(),
- Some(lp) => {
- format!("`{}`", self.loan_path_to_string(&*lp))
- }
- };
- format!("{} does not live long enough", msg)
- }
- err_borrowed_pointer_too_short(..) => {
- let descr = match opt_loan_path(&err.cmt) {
- Some(lp) => {
- format!("`{}`", self.loan_path_to_string(&*lp))
- }
- None => self.cmt_to_string(&*err.cmt),
- };
-
- format!("lifetime of {} is too short to guarantee \
- its contents can be safely reborrowed",
- descr)
- }
- }
- }
-
- pub fn report_aliasability_violation(&self,
- span: Span,
- kind: AliasableViolationKind,
- cause: mc::AliasableReason) {
- let mut is_closure = false;
- let prefix = match kind {
- MutabilityViolation => {
- "cannot assign to data"
- }
- BorrowViolation(euv::ClosureCapture(_)) => {
- // I don't think we can get aliasability violations
- // with closure captures, so no need to come up with a
- // good error message. The reason this cannot happen
- // is because we only capture local variables in
- // closures, and those are never aliasable.
- self.tcx.sess.span_bug(
- span,
- "aliasability violation with closure");
- }
- BorrowViolation(euv::OverloadedOperator) |
- BorrowViolation(euv::AddrOf) |
- BorrowViolation(euv::AutoRef) |
- BorrowViolation(euv::RefBinding) |
- BorrowViolation(euv::MatchDiscriminant) => {
- "cannot borrow data mutably"
- }
-
- BorrowViolation(euv::ClosureInvocation) => {
- is_closure = true;
- "closure invocation"
- }
-
- BorrowViolation(euv::ForLoop) => {
- "`for` loop"
- }
- };
-
- match cause {
- mc::AliasableOther => {
- self.tcx.sess.span_err(
- span,
- format!("{} in an aliasable location",
- prefix).as_slice());
- }
- mc::AliasableClosure(id) => {
- self.tcx.sess.span_err(span,
- format!("{} in a captured outer \
- variable in an `Fn` closure", prefix).as_slice());
- span_help!(self.tcx.sess, self.tcx.map.span(id),
- "consider changing this closure to take self by mutable reference");
- }
- mc::AliasableStatic(..) |
- mc::AliasableStaticMut(..) => {
- self.tcx.sess.span_err(
- span,
- format!("{} in a static location", prefix).as_slice());
- }
- mc::AliasableBorrowed => {
- self.tcx.sess.span_err(
- span,
- format!("{} in a `&` reference", prefix).as_slice());
- }
- }
-
- if is_closure {
- self.tcx.sess.span_help(
- span,
- "closures behind references must be called via `&mut`");
- }
- }
-
- pub fn note_and_explain_bckerr(&self, err: BckError<'tcx>) {
- let code = err.code;
- match code {
- err_mutbl(..) => {
- match err.cmt.note {
- mc::NoteClosureEnv(upvar_id) | mc::NoteUpvarRef(upvar_id) => {
- // If this is an `Fn` closure, it simply can't mutate upvars.
- // If it's an `FnMut` closure, the original variable was declared immutable.
- // We need to determine which is the case here.
- let kind = match err.cmt.upvar().unwrap().cat {
- mc::cat_upvar(mc::Upvar { kind, .. }) => kind,
- _ => unreachable!()
- };
- if kind == ty::FnUnboxedClosureKind {
- self.tcx.sess.span_help(
- self.tcx.map.span(upvar_id.closure_expr_id),
- "consider changing this closure to take \
- self by mutable reference");
- }
- }
- _ => {}
- }
- }
-
- err_out_of_scope(super_scope, sub_scope) => {
- note_and_explain_region(
- self.tcx,
- "reference must be valid for ",
- sub_scope,
- "...");
- let suggestion = if is_statement_scope(self.tcx, super_scope) {
- Some("consider using a `let` binding to increase its lifetime")
- } else {
- None
- };
- let span = note_and_explain_region(
- self.tcx,
- "...but borrowed value is only valid for ",
- super_scope,
- "");
- match (span, suggestion) {
- (_, None) => {},
- (Some(span), Some(msg)) => self.tcx.sess.span_help(span, msg),
- (None, Some(msg)) => self.tcx.sess.help(msg),
- }
- }
-
- err_borrowed_pointer_too_short(loan_scope, ptr_scope) => {
- let descr = match opt_loan_path(&err.cmt) {
- Some(lp) => {
- format!("`{}`", self.loan_path_to_string(&*lp))
- }
- None => self.cmt_to_string(&*err.cmt),
- };
- note_and_explain_region(
- self.tcx,
- format!("{} would have to be valid for ",
- descr).as_slice(),
- loan_scope,
- "...");
- note_and_explain_region(
- self.tcx,
- format!("...but {} is only valid for ", descr).as_slice(),
- ptr_scope,
- "");
- }
- }
- }
-
- pub fn append_loan_path_to_string(&self,
- loan_path: &LoanPath<'tcx>,
- out: &mut String) {
- match loan_path.kind {
- LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) |
- LpVar(id) => {
- out.push_str(ty::local_var_name_str(self.tcx, id).get());
- }
-
- LpDowncast(ref lp_base, variant_def_id) => {
- out.push('(');
- self.append_loan_path_to_string(&**lp_base, out);
- out.push_str(DOWNCAST_PRINTED_OPERATOR);
- out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
- out.push(')');
- }
-
-
- LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => {
- self.append_autoderefd_loan_path_to_string(&**lp_base, out);
- match fname {
- mc::NamedField(fname) => {
- out.push('.');
- out.push_str(token::get_name(fname).get());
- }
- mc::PositionalField(idx) => {
- out.push('.');
- out.push_str(idx.to_string().as_slice());
- }
- }
- }
-
- LpExtend(ref lp_base, _, LpInterior(mc::InteriorElement(_))) => {
- self.append_autoderefd_loan_path_to_string(&**lp_base, out);
- out.push_str("[..]");
- }
-
- LpExtend(ref lp_base, _, LpDeref(_)) => {
- out.push('*');
- self.append_loan_path_to_string(&**lp_base, out);
- }
- }
- }
-
- pub fn append_autoderefd_loan_path_to_string(&self,
- loan_path: &LoanPath<'tcx>,
- out: &mut String) {
- match loan_path.kind {
- LpExtend(ref lp_base, _, LpDeref(_)) => {
- // For a path like `(*x).f` or `(*x)[3]`, autoderef
- // rules would normally allow users to omit the `*x`.
- // So just serialize such paths to `x.f` or x[3]` respectively.
- self.append_autoderefd_loan_path_to_string(&**lp_base, out)
- }
-
- LpDowncast(ref lp_base, variant_def_id) => {
- out.push('(');
- self.append_autoderefd_loan_path_to_string(&**lp_base, out);
- out.push(':');
- out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
- out.push(')');
- }
-
- LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => {
- self.append_loan_path_to_string(loan_path, out)
- }
- }
- }
-
- pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String {
- let mut result = String::new();
- self.append_loan_path_to_string(loan_path, &mut result);
- result
- }
-
- pub fn cmt_to_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
- self.mc().cmt_to_string(cmt)
- }
-}
-
-fn is_statement_scope(tcx: &ty::ctxt, region: ty::Region) -> bool {
- match region {
- ty::ReScope(scope) => {
- match tcx.map.find(scope.node_id()) {
- Some(ast_map::NodeStmt(_)) => true,
- _ => false
- }
- }
- _ => false
- }
-}
-
-impl BitwiseOperator for LoanDataFlowOperator {
- #[inline]
- fn join(&self, succ: uint, pred: uint) -> uint {
- succ | pred // loans from both preds are in scope
- }
-}
-
-impl DataFlowOperator for LoanDataFlowOperator {
- #[inline]
- fn initial_value(&self) -> bool {
- false // no loans in scope by default
- }
-}
-
-impl<'tcx> Repr<'tcx> for Loan<'tcx> {
- fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
- format!("Loan_{}({}, {}, {}-{}, {})",
- self.index,
- self.loan_path.repr(tcx),
- self.kind,
- self.gen_scope,
- self.kill_scope,
- self.restricted_paths.repr(tcx))
- }
-}
-
-impl<'tcx> Repr<'tcx> for LoanPath<'tcx> {
- fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
- match self.kind {
- LpVar(id) => {
- format!("$({})", tcx.map.node_to_string(id))
- }
-
- LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
- let s = tcx.map.node_to_string(var_id);
- format!("$({} captured by id={})", s, closure_expr_id)
- }
-
- LpDowncast(ref lp, variant_def_id) => {
- let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
- ty::item_path_str(tcx, variant_def_id)
- } else {
- variant_def_id.repr(tcx)
- };
- format!("({}{}{})", lp.repr(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
- }
-
- LpExtend(ref lp, _, LpDeref(_)) => {
- format!("{}.*", lp.repr(tcx))
- }
-
- LpExtend(ref lp, _, LpInterior(ref interior)) => {
- format!("{}.{}", lp.repr(tcx), interior.repr(tcx))
- }
- }
- }
-}
-
-impl<'tcx> UserString<'tcx> for LoanPath<'tcx> {
- fn user_string(&self, tcx: &ty::ctxt<'tcx>) -> String {
- match self.kind {
- LpVar(id) => {
- format!("$({})", tcx.map.node_to_user_string(id))
- }
-
- LpUpvar(ty::UpvarId{ var_id, closure_expr_id: _ }) => {
- let s = tcx.map.node_to_user_string(var_id);
- format!("$({} captured by closure)", s)
- }
-
- LpDowncast(ref lp, variant_def_id) => {
- let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
- ty::item_path_str(tcx, variant_def_id)
- } else {
- variant_def_id.repr(tcx)
- };
- format!("({}{}{})", lp.user_string(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
- }
-
- LpExtend(ref lp, _, LpDeref(_)) => {
- format!("{}.*", lp.user_string(tcx))
- }
-
- LpExtend(ref lp, _, LpInterior(ref interior)) => {
- format!("{}.{}", lp.user_string(tcx), interior.repr(tcx))
- }
- }
- }
-}
+++ /dev/null
-// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! Data structures used for tracking moves. Please see the extensive
-//! comments in the section "Moves and initialization" in `doc.rs`.
-
-pub use self::MoveKind::*;
-
-use std::cell::RefCell;
-use std::rc::Rc;
-use std::uint;
-use middle::borrowck::*;
-use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
-use middle::borrowck::LoanPathElem::{LpInterior};
-use middle::cfg;
-use middle::dataflow::DataFlowContext;
-use middle::dataflow::BitwiseOperator;
-use middle::dataflow::DataFlowOperator;
-use middle::expr_use_visitor as euv;
-use middle::mem_categorization as mc;
-use middle::ty;
-use syntax::ast;
-use syntax::ast_util;
-use syntax::codemap::Span;
-use util::nodemap::{FnvHashMap, NodeSet};
-use util::ppaux::Repr;
-
-#[path="fragments.rs"]
-pub mod fragments;
-
-pub struct MoveData<'tcx> {
- /// Move paths. See section "Move paths" in `doc.rs`.
- pub paths: RefCell<Vec<MovePath<'tcx>>>,
-
- /// Cache of loan path to move path index, for easy lookup.
- pub path_map: RefCell<FnvHashMap<Rc<LoanPath<'tcx>>, MovePathIndex>>,
-
- /// Each move or uninitialized variable gets an entry here.
- pub moves: RefCell<Vec<Move>>,
-
- /// Assignments to a variable, like `x = foo`. These are assigned
- /// bits for dataflow, since we must track them to ensure that
- /// immutable variables are assigned at most once along each path.
- pub var_assignments: RefCell<Vec<Assignment>>,
-
- /// Assignments to a path, like `x.f = foo`. These are not
- /// assigned dataflow bits, but we track them because they still
- /// kill move bits.
- pub path_assignments: RefCell<Vec<Assignment>>,
-
- /// Enum variant matched within a pattern on some match arm, like
- /// `SomeStruct{ f: Variant1(x, y) } => ...`
- pub variant_matches: RefCell<Vec<VariantMatch>>,
-
- /// Assignments to a variable or path, like `x = foo`, but not `x += foo`.
- pub assignee_ids: RefCell<NodeSet>,
-
- /// Path-fragments from moves in to or out of parts of structured data.
- pub fragments: RefCell<fragments::FragmentSets>,
-}
-
-pub struct FlowedMoveData<'a, 'tcx: 'a> {
- pub move_data: MoveData<'tcx>,
-
- pub dfcx_moves: MoveDataFlow<'a, 'tcx>,
-
- // We could (and maybe should, for efficiency) combine both move
- // and assign data flow into one, but this way it's easier to
- // distinguish the bits that correspond to moves and assignments.
- pub dfcx_assign: AssignDataFlow<'a, 'tcx>
-}
-
-/// Index into `MoveData.paths`, used like a pointer
-#[deriving(PartialEq, Eq, PartialOrd, Ord, Show)]
-pub struct MovePathIndex(uint);
-
-impl Copy for MovePathIndex {}
-
-impl MovePathIndex {
- fn get(&self) -> uint {
- let MovePathIndex(v) = *self; v
- }
-}
-
-impl Clone for MovePathIndex {
- fn clone(&self) -> MovePathIndex {
- MovePathIndex(self.get())
- }
-}
-
-#[allow(non_upper_case_globals)]
-static InvalidMovePathIndex: MovePathIndex =
- MovePathIndex(uint::MAX);
-
-/// Index into `MoveData.moves`, used like a pointer
-#[deriving(PartialEq)]
-pub struct MoveIndex(uint);
-
-impl Copy for MoveIndex {}
-
-impl MoveIndex {
- fn get(&self) -> uint {
- let MoveIndex(v) = *self; v
- }
-}
-
-#[allow(non_upper_case_globals)]
-static InvalidMoveIndex: MoveIndex =
- MoveIndex(uint::MAX);
-
-pub struct MovePath<'tcx> {
- /// Loan path corresponding to this move path
- pub loan_path: Rc<LoanPath<'tcx>>,
-
- /// Parent pointer, `InvalidMovePathIndex` if root
- pub parent: MovePathIndex,
-
- /// Head of linked list of moves to this path,
- /// `InvalidMoveIndex` if not moved
- pub first_move: MoveIndex,
-
- /// First node in linked list of children, `InvalidMovePathIndex` if leaf
- pub first_child: MovePathIndex,
-
- /// Next node in linked list of parent's children (siblings),
- /// `InvalidMovePathIndex` if none.
- pub next_sibling: MovePathIndex,
-}
-
-#[deriving(PartialEq, Show)]
-pub enum MoveKind {
- Declared, // When declared, variables start out "moved".
- MoveExpr, // Expression or binding that moves a variable
- MovePat, // By-move binding
- Captured // Closure creation that moves a value
-}
-
-impl Copy for MoveKind {}
-
-pub struct Move {
- /// Path being moved.
- pub path: MovePathIndex,
-
- /// id of node that is doing the move.
- pub id: ast::NodeId,
-
- /// Kind of move, for error messages.
- pub kind: MoveKind,
-
- /// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
- pub next_move: MoveIndex
-}
-
-impl Copy for Move {}
-
-pub struct Assignment {
- /// Path being assigned.
- pub path: MovePathIndex,
-
- /// id where assignment occurs
- pub id: ast::NodeId,
-
- /// span of node where assignment occurs
- pub span: Span,
-}
-
-impl Copy for Assignment {}
-
-pub struct VariantMatch {
- /// downcast to the variant.
- pub path: MovePathIndex,
-
- /// path being downcast to the variant.
- pub base_path: MovePathIndex,
-
- /// id where variant's pattern occurs
- pub id: ast::NodeId,
-
- /// says if variant established by move (and why), by copy, or by borrow.
- pub mode: euv::MatchMode
-}
-
-impl Copy for VariantMatch {}
-
-#[deriving(Clone)]
-pub struct MoveDataFlowOperator;
-
-impl Copy for MoveDataFlowOperator {}
-
-pub type MoveDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, MoveDataFlowOperator>;
-
-#[deriving(Clone)]
-pub struct AssignDataFlowOperator;
-
-impl Copy for AssignDataFlowOperator {}
-
-pub type AssignDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, AssignDataFlowOperator>;
-
-fn loan_path_is_precise(loan_path: &LoanPath) -> bool {
- match loan_path.kind {
- LpVar(_) | LpUpvar(_) => {
- true
- }
- LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
- // Paths involving element accesses do not refer to a unique
- // location, as there is no accurate tracking of the indices.
- false
- }
- LpDowncast(ref lp_base, _) |
- LpExtend(ref lp_base, _, _) => {
- loan_path_is_precise(&**lp_base)
- }
- }
-}
-
-impl Move {
- pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
- format!("Move{} path: {}, id: {}, kind: {} {}",
- "{",
- move_data.path_loan_path(self.path).repr(tcx),
- self.id,
- self.kind,
- "}")
- }
-}
-
-impl Assignment {
- pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
- format!("Assignment{} path: {}, id: {} {}",
- "{",
- move_data.path_loan_path(self.path).repr(tcx),
- self.id,
- "}")
- }
-}
-
-impl VariantMatch {
- pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
- format!("VariantMatch{} path: {}, id: {} {}",
- "{",
- move_data.path_loan_path(self.path).repr(tcx),
- self.id,
- "}")
- }
-}
-
-impl<'tcx> MoveData<'tcx> {
- pub fn new() -> MoveData<'tcx> {
- MoveData {
- paths: RefCell::new(Vec::new()),
- path_map: RefCell::new(FnvHashMap::new()),
- moves: RefCell::new(Vec::new()),
- path_assignments: RefCell::new(Vec::new()),
- var_assignments: RefCell::new(Vec::new()),
- variant_matches: RefCell::new(Vec::new()),
- assignee_ids: RefCell::new(NodeSet::new()),
- fragments: RefCell::new(fragments::FragmentSets::new()),
- }
- }
-
- pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath<'tcx>> {
- (*self.paths.borrow())[index.get()].loan_path.clone()
- }
-
- fn path_parent(&self, index: MovePathIndex) -> MovePathIndex {
- (*self.paths.borrow())[index.get()].parent
- }
-
- fn path_first_move(&self, index: MovePathIndex) -> MoveIndex {
- (*self.paths.borrow())[index.get()].first_move
- }
-
- /// Returns the index of first child, or `InvalidMovePathIndex` if
- /// `index` is leaf.
- fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex {
- (*self.paths.borrow())[index.get()].first_child
- }
-
- fn path_next_sibling(&self, index: MovePathIndex) -> MovePathIndex {
- (*self.paths.borrow())[index.get()].next_sibling
- }
-
- fn set_path_first_move(&self,
- index: MovePathIndex,
- first_move: MoveIndex) {
- (*self.paths.borrow_mut())[index.get()].first_move = first_move
- }
-
- fn set_path_first_child(&self,
- index: MovePathIndex,
- first_child: MovePathIndex) {
- (*self.paths.borrow_mut())[index.get()].first_child = first_child
- }
-
- fn move_next_move(&self, index: MoveIndex) -> MoveIndex {
- //! Type safe indexing operator
- (*self.moves.borrow())[index.get()].next_move
- }
-
- fn is_var_path(&self, index: MovePathIndex) -> bool {
- //! True if `index` refers to a variable
- self.path_parent(index) == InvalidMovePathIndex
- }
-
- /// Returns the existing move path index for `lp`, if any, and otherwise adds a new index for
- /// `lp` and any of its base paths that do not yet have an index.
- pub fn move_path(&self,
- tcx: &ty::ctxt<'tcx>,
- lp: Rc<LoanPath<'tcx>>) -> MovePathIndex {
- match self.path_map.borrow().get(&lp) {
- Some(&index) => {
- return index;
- }
- None => {}
- }
-
- let index = match lp.kind {
- LpVar(..) | LpUpvar(..) => {
- let index = MovePathIndex(self.paths.borrow().len());
-
- self.paths.borrow_mut().push(MovePath {
- loan_path: lp.clone(),
- parent: InvalidMovePathIndex,
- first_move: InvalidMoveIndex,
- first_child: InvalidMovePathIndex,
- next_sibling: InvalidMovePathIndex,
- });
-
- index
- }
-
- LpDowncast(ref base, _) |
- LpExtend(ref base, _, _) => {
- let parent_index = self.move_path(tcx, base.clone());
-
- let index = MovePathIndex(self.paths.borrow().len());
-
- let next_sibling = self.path_first_child(parent_index);
- self.set_path_first_child(parent_index, index);
-
- self.paths.borrow_mut().push(MovePath {
- loan_path: lp.clone(),
- parent: parent_index,
- first_move: InvalidMoveIndex,
- first_child: InvalidMovePathIndex,
- next_sibling: next_sibling,
- });
-
- index
- }
- };
-
- debug!("move_path(lp={}, index={})",
- lp.repr(tcx),
- index);
-
- assert_eq!(index.get(), self.paths.borrow().len() - 1);
- self.path_map.borrow_mut().insert(lp, index);
- return index;
- }
-
- fn existing_move_path(&self, lp: &Rc<LoanPath<'tcx>>)
- -> Option<MovePathIndex> {
- self.path_map.borrow().get(lp).cloned()
- }
-
- fn existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>)
- -> Vec<MovePathIndex> {
- let mut result = vec!();
- self.add_existing_base_paths(lp, &mut result);
- result
- }
-
- /// Adds any existing move path indices for `lp` and any base paths of `lp` to `result`, but
- /// does not add new move paths
- fn add_existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>,
- result: &mut Vec<MovePathIndex>) {
- match self.path_map.borrow().get(lp).cloned() {
- Some(index) => {
- self.each_base_path(index, |p| {
- result.push(p);
- true
- });
- }
- None => {
- match lp.kind {
- LpVar(..) | LpUpvar(..) => { }
- LpDowncast(ref b, _) |
- LpExtend(ref b, _, _) => {
- self.add_existing_base_paths(b, result);
- }
- }
- }
- }
-
- }
-
- /// Adds a new move entry for a move of `lp` that occurs at location `id` with kind `kind`.
- pub fn add_move(&self,
- tcx: &ty::ctxt<'tcx>,
- lp: Rc<LoanPath<'tcx>>,
- id: ast::NodeId,
- kind: MoveKind) {
- debug!("add_move(lp={}, id={}, kind={})",
- lp.repr(tcx),
- id,
- kind);
-
- let path_index = self.move_path(tcx, lp.clone());
- let move_index = MoveIndex(self.moves.borrow().len());
-
- self.fragments.borrow_mut().add_move(path_index);
-
- let next_move = self.path_first_move(path_index);
- self.set_path_first_move(path_index, move_index);
-
- self.moves.borrow_mut().push(Move {
- path: path_index,
- id: id,
- kind: kind,
- next_move: next_move
- });
- }
-
- /// Adds a new record for an assignment to `lp` that occurs at location `id` with the given
- /// `span`.
- pub fn add_assignment(&self,
- tcx: &ty::ctxt<'tcx>,
- lp: Rc<LoanPath<'tcx>>,
- assign_id: ast::NodeId,
- span: Span,
- assignee_id: ast::NodeId,
- mode: euv::MutateMode) {
- debug!("add_assignment(lp={}, assign_id={}, assignee_id={}",
- lp.repr(tcx), assign_id, assignee_id);
-
- let path_index = self.move_path(tcx, lp.clone());
-
- self.fragments.borrow_mut().add_assignment(path_index);
-
- match mode {
- euv::Init | euv::JustWrite => {
- self.assignee_ids.borrow_mut().insert(assignee_id);
- }
- euv::WriteAndRead => { }
- }
-
- let assignment = Assignment {
- path: path_index,
- id: assign_id,
- span: span,
- };
-
- if self.is_var_path(path_index) {
- debug!("add_assignment[var](lp={}, assignment={}, path_index={})",
- lp.repr(tcx), self.var_assignments.borrow().len(), path_index);
-
- self.var_assignments.borrow_mut().push(assignment);
- } else {
- debug!("add_assignment[path](lp={}, path_index={})",
- lp.repr(tcx), path_index);
-
- self.path_assignments.borrow_mut().push(assignment);
- }
- }
-
- /// Adds a new record for a match of `base_lp`, downcast to
- /// variant `lp`, that occurs at location `pattern_id`. (One
- /// should be able to recover the span info from the
- /// `pattern_id` and the ast_map, I think.)
- pub fn add_variant_match(&self,
- tcx: &ty::ctxt<'tcx>,
- lp: Rc<LoanPath<'tcx>>,
- pattern_id: ast::NodeId,
- base_lp: Rc<LoanPath<'tcx>>,
- mode: euv::MatchMode) {
- debug!("add_variant_match(lp={}, pattern_id={})",
- lp.repr(tcx), pattern_id);
-
- let path_index = self.move_path(tcx, lp.clone());
- let base_path_index = self.move_path(tcx, base_lp.clone());
-
- self.fragments.borrow_mut().add_assignment(path_index);
-
- let variant_match = VariantMatch {
- path: path_index,
- base_path: base_path_index,
- id: pattern_id,
- mode: mode,
- };
-
- self.variant_matches.borrow_mut().push(variant_match);
- }
-
- fn fixup_fragment_sets(&self, tcx: &ty::ctxt<'tcx>) {
- fragments::fixup_fragment_sets(self, tcx)
- }
-
- /// Adds the gen/kills for the various moves and
- /// assignments into the provided data flow contexts.
- /// Moves are generated by moves and killed by assignments and
- /// scoping. Assignments are generated by assignment to variables and
- /// killed by scoping. See `doc.rs` for more details.
- fn add_gen_kills(&self,
- tcx: &ty::ctxt<'tcx>,
- dfcx_moves: &mut MoveDataFlow,
- dfcx_assign: &mut AssignDataFlow) {
- for (i, the_move) in self.moves.borrow().iter().enumerate() {
- dfcx_moves.add_gen(the_move.id, i);
- }
-
- for (i, assignment) in self.var_assignments.borrow().iter().enumerate() {
- dfcx_assign.add_gen(assignment.id, i);
- self.kill_moves(assignment.path, assignment.id, dfcx_moves);
- }
-
- for assignment in self.path_assignments.borrow().iter() {
- self.kill_moves(assignment.path, assignment.id, dfcx_moves);
- }
-
- // Kill all moves related to a variable `x` when
- // it goes out of scope:
- for path in self.paths.borrow().iter() {
- match path.loan_path.kind {
- LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
- let kill_scope = path.loan_path.kill_scope(tcx);
- let path = self.path_map.borrow()[path.loan_path];
- self.kill_moves(path, kill_scope.node_id(), dfcx_moves);
- }
- LpExtend(..) => {}
- }
- }
-
- // Kill all assignments when the variable goes out of scope:
- for (assignment_index, assignment) in
- self.var_assignments.borrow().iter().enumerate() {
- let lp = self.path_loan_path(assignment.path);
- match lp.kind {
- LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
- let kill_scope = lp.kill_scope(tcx);
- dfcx_assign.add_kill(kill_scope.node_id(), assignment_index);
- }
- LpExtend(..) => {
- tcx.sess.bug("var assignment for non var path");
- }
- }
- }
- }
-
- fn each_base_path(&self, index: MovePathIndex, f: |MovePathIndex| -> bool)
- -> bool {
- let mut p = index;
- while p != InvalidMovePathIndex {
- if !f(p) {
- return false;
- }
- p = self.path_parent(p);
- }
- return true;
- }
-
- fn each_extending_path(&self,
- index: MovePathIndex,
- f: |MovePathIndex| -> bool)
- -> bool {
- if !f(index) {
- return false;
- }
-
- let mut p = self.path_first_child(index);
- while p != InvalidMovePathIndex {
- if !self.each_extending_path(p, |x| f(x)) {
- return false;
- }
- p = self.path_next_sibling(p);
- }
-
- return true;
- }
-
- fn each_applicable_move(&self,
- index0: MovePathIndex,
- f: |MoveIndex| -> bool)
- -> bool {
- let mut ret = true;
- self.each_extending_path(index0, |index| {
- let mut p = self.path_first_move(index);
- while p != InvalidMoveIndex {
- if !f(p) {
- ret = false;
- break;
- }
- p = self.move_next_move(p);
- }
- ret
- });
- ret
- }
-
- fn kill_moves(&self,
- path: MovePathIndex,
- kill_id: ast::NodeId,
- dfcx_moves: &mut MoveDataFlow) {
- // We can only perform kills for paths that refer to a unique location,
- // since otherwise we may kill a move from one location with an
- // assignment referring to another location.
-
- let loan_path = self.path_loan_path(path);
- if loan_path_is_precise(&*loan_path) {
- self.each_applicable_move(path, |move_index| {
- dfcx_moves.add_kill(kill_id, move_index.get());
- true
- });
- }
- }
-}
-
-impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
- pub fn new(move_data: MoveData<'tcx>,
- tcx: &'a ty::ctxt<'tcx>,
- cfg: &cfg::CFG,
- id_range: ast_util::IdRange,
- decl: &ast::FnDecl,
- body: &ast::Block)
- -> FlowedMoveData<'a, 'tcx> {
- let mut dfcx_moves =
- DataFlowContext::new(tcx,
- "flowed_move_data_moves",
- Some(decl),
- cfg,
- MoveDataFlowOperator,
- id_range,
- move_data.moves.borrow().len());
- let mut dfcx_assign =
- DataFlowContext::new(tcx,
- "flowed_move_data_assigns",
- Some(decl),
- cfg,
- AssignDataFlowOperator,
- id_range,
- move_data.var_assignments.borrow().len());
-
- move_data.fixup_fragment_sets(tcx);
-
- move_data.add_gen_kills(tcx,
- &mut dfcx_moves,
- &mut dfcx_assign);
-
- dfcx_moves.add_kills_from_flow_exits(cfg);
- dfcx_assign.add_kills_from_flow_exits(cfg);
-
- dfcx_moves.propagate(cfg, body);
- dfcx_assign.propagate(cfg, body);
-
- FlowedMoveData {
- move_data: move_data,
- dfcx_moves: dfcx_moves,
- dfcx_assign: dfcx_assign,
- }
- }
-
- pub fn kind_of_move_of_path(&self,
- id: ast::NodeId,
- loan_path: &Rc<LoanPath<'tcx>>)
- -> Option<MoveKind> {
- //! Returns the kind of a move of `loan_path` by `id`, if one exists.
-
- let mut ret = None;
- for loan_path_index in self.move_data.path_map.borrow().get(&*loan_path).iter() {
- self.dfcx_moves.each_gen_bit(id, |move_index| {
- let the_move = self.move_data.moves.borrow();
- let the_move = (*the_move)[move_index];
- if the_move.path == **loan_path_index {
- ret = Some(the_move.kind);
- false
- } else {
- true
- }
- });
- }
- ret
- }
-
- /// Iterates through each move of `loan_path` (or some base path of `loan_path`) that *may*
- /// have occurred on entry to `id` without an intervening assignment. In other words, any moves
- /// that would invalidate a reference to `loan_path` at location `id`.
- pub fn each_move_of(&self,
- id: ast::NodeId,
- loan_path: &Rc<LoanPath<'tcx>>,
- f: |&Move, &LoanPath<'tcx>| -> bool)
- -> bool {
- // Bad scenarios:
- //
- // 1. Move of `a.b.c`, use of `a.b.c`
- // 2. Move of `a.b.c`, use of `a.b.c.d`
- // 3. Move of `a.b.c`, use of `a` or `a.b`
- //
- // OK scenario:
- //
- // 4. move of `a.b.c`, use of `a.b.d`
-
- let base_indices = self.move_data.existing_base_paths(loan_path);
- if base_indices.is_empty() {
- return true;
- }
-
- let opt_loan_path_index = self.move_data.existing_move_path(loan_path);
-
- let mut ret = true;
-
- self.dfcx_moves.each_bit_on_entry(id, |index| {
- let the_move = self.move_data.moves.borrow();
- let the_move = &(*the_move)[index];
- let moved_path = the_move.path;
- if base_indices.iter().any(|x| x == &moved_path) {
- // Scenario 1 or 2: `loan_path` or some base path of
- // `loan_path` was moved.
- if !f(the_move, &*self.move_data.path_loan_path(moved_path)) {
- ret = false;
- }
- } else {
- for &loan_path_index in opt_loan_path_index.iter() {
- let cont = self.move_data.each_base_path(moved_path, |p| {
- if p == loan_path_index {
- // Scenario 3: some extension of `loan_path`
- // was moved
- f(the_move,
- &*self.move_data.path_loan_path(moved_path))
- } else {
- true
- }
- });
- if !cont { ret = false; break }
- }
- }
- ret
- })
- }
-
- /// Iterates through every assignment to `loan_path` that may have occurred on entry to `id`.
- /// `loan_path` must be a single variable.
- pub fn each_assignment_of(&self,
- id: ast::NodeId,
- loan_path: &Rc<LoanPath<'tcx>>,
- f: |&Assignment| -> bool)
- -> bool {
- let loan_path_index = {
- match self.move_data.existing_move_path(loan_path) {
- Some(i) => i,
- None => {
- // if there were any assignments, it'd have an index
- return true;
- }
- }
- };
-
- self.dfcx_assign.each_bit_on_entry(id, |index| {
- let assignment = self.move_data.var_assignments.borrow();
- let assignment = &(*assignment)[index];
- if assignment.path == loan_path_index && !f(assignment) {
- false
- } else {
- true
- }
- })
- }
-}
-
-impl BitwiseOperator for MoveDataFlowOperator {
- #[inline]
- fn join(&self, succ: uint, pred: uint) -> uint {
- succ | pred // moves from both preds are in scope
- }
-}
-
-impl DataFlowOperator for MoveDataFlowOperator {
- #[inline]
- fn initial_value(&self) -> bool {
- false // no loans in scope by default
- }
-}
-
-impl BitwiseOperator for AssignDataFlowOperator {
- #[inline]
- fn join(&self, succ: uint, pred: uint) -> uint {
- succ | pred // moves from both preds are in scope
- }
-}
-
-impl DataFlowOperator for AssignDataFlowOperator {
- #[inline]
- fn initial_value(&self) -> bool {
- false // no assignments in scope by default
- }
-}
--- /dev/null
+// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// ----------------------------------------------------------------------
+// Checking loans
+//
+// Phase 2 of check: we walk down the tree and check that:
+// 1. assignments are always made to mutable locations;
+// 2. loans made in overlapping scopes do not conflict
+// 3. assignments do not affect things loaned out as immutable
+// 4. moves do not affect things loaned out in any way
+use self::UseError::*;
+
+use borrowck::*;
+use borrowck::LoanPathElem::*;
+use borrowck::LoanPathKind::*;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::region;
+use rustc::middle::ty;
+use rustc::util::ppaux::Repr;
+use syntax::ast;
+use syntax::codemap::Span;
+
+use std::rc::Rc;
+
+// FIXME (#16118): These functions are intended to allow the borrow checker to
+// be less precise in its handling of Box while still allowing moves out of a
+// Box. They should be removed when OwnedPtr is removed from LoanPath.
+
+fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> {
+ //! Returns the base of the leftmost dereference of an OwnedPtr in
+ //! `loan_path`. If there is no dereference of an OwnedPtr in `loan_path`,
+ //! then it just returns `loan_path` itself.
+
+ return match helper(loan_path) {
+ Some(new_loan_path) => new_loan_path,
+ None => loan_path.clone()
+ };
+
+ fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> {
+ match loan_path.kind {
+ LpVar(_) | LpUpvar(_) => None,
+ LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
+ match helper(&**lp_base) {
+ v @ Some(_) => v,
+ None => Some(&**lp_base)
+ }
+ }
+ LpDowncast(ref lp_base, _) |
+ LpExtend(ref lp_base, _, _) => helper(&**lp_base)
+ }
+ }
+}
+
+fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Rc<LoanPath<'tcx>> {
+ //! The equivalent of `owned_ptr_base_path` for an &Rc<LoanPath> rather than
+ //! a &LoanPath.
+
+ return match helper(loan_path) {
+ Some(new_loan_path) => new_loan_path,
+ None => loan_path.clone()
+ };
+
+ fn helper<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Option<Rc<LoanPath<'tcx>>> {
+ match loan_path.kind {
+ LpVar(_) | LpUpvar(_) => None,
+ LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => {
+ match helper(lp_base) {
+ v @ Some(_) => v,
+ None => Some(lp_base.clone())
+ }
+ }
+ LpDowncast(ref lp_base, _) |
+ LpExtend(ref lp_base, _, _) => helper(lp_base)
+ }
+ }
+}
+
+struct CheckLoanCtxt<'a, 'tcx: 'a> {
+ bccx: &'a BorrowckCtxt<'a, 'tcx>,
+ dfcx_loans: &'a LoanDataFlow<'a, 'tcx>,
+ move_data: move_data::FlowedMoveData<'a, 'tcx>,
+ all_loans: &'a [Loan<'tcx>],
+ param_env: &'a ty::ParameterEnvironment<'tcx>,
+}
+
+impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
+ fn consume(&mut self,
+ consume_id: ast::NodeId,
+ consume_span: Span,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::ConsumeMode) {
+ debug!("consume(consume_id={}, cmt={}, mode={})",
+ consume_id, cmt.repr(self.tcx()), mode);
+
+ self.consume_common(consume_id, consume_span, cmt, mode);
+ }
+
+ fn matched_pat(&mut self,
+ _matched_pat: &ast::Pat,
+ _cmt: mc::cmt,
+ _mode: euv::MatchMode) { }
+
+ fn consume_pat(&mut self,
+ consume_pat: &ast::Pat,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::ConsumeMode) {
+ debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
+ consume_pat.repr(self.tcx()),
+ cmt.repr(self.tcx()),
+ mode);
+
+ self.consume_common(consume_pat.id, consume_pat.span, cmt, mode);
+ }
+
+ fn borrow(&mut self,
+ borrow_id: ast::NodeId,
+ borrow_span: Span,
+ cmt: mc::cmt<'tcx>,
+ loan_region: ty::Region,
+ bk: ty::BorrowKind,
+ loan_cause: euv::LoanCause)
+ {
+ debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
+ bk={}, loan_cause={})",
+ borrow_id, cmt.repr(self.tcx()), loan_region,
+ bk, loan_cause);
+
+ match opt_loan_path(&cmt) {
+ Some(lp) => {
+ let moved_value_use_kind = match loan_cause {
+ euv::ClosureCapture(_) => MovedInCapture,
+ _ => MovedInUse,
+ };
+ self.check_if_path_is_moved(borrow_id, borrow_span, moved_value_use_kind, &lp);
+ }
+ None => { }
+ }
+
+ self.check_for_conflicting_loans(region::CodeExtent::from_node_id(borrow_id));
+ }
+
+ fn mutate(&mut self,
+ assignment_id: ast::NodeId,
+ assignment_span: Span,
+ assignee_cmt: mc::cmt<'tcx>,
+ mode: euv::MutateMode)
+ {
+ debug!("mutate(assignment_id={}, assignee_cmt={})",
+ assignment_id, assignee_cmt.repr(self.tcx()));
+
+ match opt_loan_path(&assignee_cmt) {
+ Some(lp) => {
+ match mode {
+ euv::Init | euv::JustWrite => {
+ // In a case like `path = 1`, then path does not
+ // have to be *FULLY* initialized, but we still
+ // must be careful lest it contains derefs of
+ // pointers.
+ self.check_if_assigned_path_is_moved(assignee_cmt.id,
+ assignment_span,
+ MovedInUse,
+ &lp);
+ }
+ euv::WriteAndRead => {
+ // In a case like `path += 1`, then path must be
+ // fully initialized, since we will read it before
+ // we write it.
+ self.check_if_path_is_moved(assignee_cmt.id,
+ assignment_span,
+ MovedInUse,
+ &lp);
+ }
+ }
+ }
+ None => { }
+ }
+
+ self.check_assignment(assignment_id, assignment_span, assignee_cmt, mode);
+ }
+
+ fn decl_without_init(&mut self, _id: ast::NodeId, _span: Span) { }
+}
+
+pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ dfcx_loans: &LoanDataFlow<'b, 'tcx>,
+ move_data: move_data::FlowedMoveData<'c, 'tcx>,
+ all_loans: &[Loan<'tcx>],
+ fn_id: ast::NodeId,
+ decl: &ast::FnDecl,
+ body: &ast::Block) {
+ debug!("check_loans(body id={})", body.id);
+
+ let param_env = ty::ParameterEnvironment::for_item(bccx.tcx, fn_id);
+
+ let mut clcx = CheckLoanCtxt {
+ bccx: bccx,
+ dfcx_loans: dfcx_loans,
+ move_data: move_data,
+ all_loans: all_loans,
+ param_env: ¶m_env,
+ };
+
+ {
+ let mut euv = euv::ExprUseVisitor::new(&mut clcx,
+ bccx.tcx,
+ param_env.clone());
+ euv.walk_fn(decl, body);
+ }
+}
+
+#[deriving(PartialEq)]
+enum UseError<'tcx> {
+ UseOk,
+ UseWhileBorrowed(/*loan*/Rc<LoanPath<'tcx>>, /*loan*/Span)
+}
+
+fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
+ borrow_kind2: ty::BorrowKind)
+ -> bool {
+ borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow
+}
+
+impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
+ pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx }
+
+ pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan<'tcx>| -> bool)
+ -> bool {
+ //! Iterates over each loan that has been issued
+ //! on entrance to `scope`, regardless of whether it is
+ //! actually *in scope* at that point. Sometimes loans
+ //! are issued for future scopes and thus they may have been
+ //! *issued* but not yet be in effect.
+
+ self.dfcx_loans.each_bit_on_entry(scope.node_id(), |loan_index| {
+ let loan = &self.all_loans[loan_index];
+ op(loan)
+ })
+ }
+
+ pub fn each_in_scope_loan(&self,
+ scope: region::CodeExtent,
+ op: |&Loan<'tcx>| -> bool)
+ -> bool {
+ //! Like `each_issued_loan()`, but only considers loans that are
+ //! currently in scope.
+
+ let tcx = self.tcx();
+ self.each_issued_loan(scope, |loan| {
+ if tcx.region_maps.is_subscope_of(scope, loan.kill_scope) {
+ op(loan)
+ } else {
+ true
+ }
+ })
+ }
+
+ fn each_in_scope_loan_affecting_path(&self,
+ scope: region::CodeExtent,
+ loan_path: &LoanPath<'tcx>,
+ op: |&Loan<'tcx>| -> bool)
+ -> bool {
+ //! Iterates through all of the in-scope loans affecting `loan_path`,
+ //! calling `op`, and ceasing iteration if `false` is returned.
+
+ // First, we check for a loan restricting the path P being used. This
+ // accounts for borrows of P but also borrows of subpaths, like P.a.b.
+ // Consider the following example:
+ //
+ // let x = &mut a.b.c; // Restricts a, a.b, and a.b.c
+ // let y = a; // Conflicts with restriction
+
+ let loan_path = owned_ptr_base_path(loan_path);
+ let cont = self.each_in_scope_loan(scope, |loan| {
+ let mut ret = true;
+ for restr_path in loan.restricted_paths.iter() {
+ if **restr_path == *loan_path {
+ if !op(loan) {
+ ret = false;
+ break;
+ }
+ }
+ }
+ ret
+ });
+
+ if !cont {
+ return false;
+ }
+
+ // Next, we must check for *loans* (not restrictions) on the path P or
+ // any base path. This rejects examples like the following:
+ //
+ // let x = &mut a.b;
+ // let y = a.b.c;
+ //
+ // Limiting this search to *loans* and not *restrictions* means that
+ // examples like the following continue to work:
+ //
+ // let x = &mut a.b;
+ // let y = a.c;
+
+ let mut loan_path = loan_path;
+ loop {
+ match loan_path.kind {
+ LpVar(_) | LpUpvar(_) => {
+ break;
+ }
+ LpDowncast(ref lp_base, _) |
+ LpExtend(ref lp_base, _, _) => {
+ loan_path = &**lp_base;
+ }
+ }
+
+ let cont = self.each_in_scope_loan(scope, |loan| {
+ if *loan.loan_path == *loan_path {
+ op(loan)
+ } else {
+ true
+ }
+ });
+
+ if !cont {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ pub fn loans_generated_by(&self, scope: region::CodeExtent) -> Vec<uint> {
+ //! Returns a vector of the loans that are generated as
+ //! we enter `scope`.
+
+ let mut result = Vec::new();
+ self.dfcx_loans.each_gen_bit(scope.node_id(), |loan_index| {
+ result.push(loan_index);
+ true
+ });
+ return result;
+ }
+
+ pub fn check_for_conflicting_loans(&self, scope: region::CodeExtent) {
+ //! Checks to see whether any of the loans that are issued
+ //! on entrance to `scope` conflict with loans that have already been
+ //! issued when we enter `scope` (for example, we do not
+ //! permit two `&mut` borrows of the same variable).
+ //!
+ //! (Note that some loans can be *issued* without necessarily
+ //! taking effect yet.)
+
+ debug!("check_for_conflicting_loans(scope={})", scope);
+
+ let new_loan_indices = self.loans_generated_by(scope);
+ debug!("new_loan_indices = {}", new_loan_indices);
+
+ self.each_issued_loan(scope, |issued_loan| {
+ for &new_loan_index in new_loan_indices.iter() {
+ let new_loan = &self.all_loans[new_loan_index];
+ self.report_error_if_loans_conflict(issued_loan, new_loan);
+ }
+ true
+ });
+
+ for (i, &x) in new_loan_indices.iter().enumerate() {
+ let old_loan = &self.all_loans[x];
+ for &y in new_loan_indices.slice_from(i+1).iter() {
+ let new_loan = &self.all_loans[y];
+ self.report_error_if_loans_conflict(old_loan, new_loan);
+ }
+ }
+ }
+
+ pub fn report_error_if_loans_conflict(&self,
+ old_loan: &Loan<'tcx>,
+ new_loan: &Loan<'tcx>) {
+ //! Checks whether `old_loan` and `new_loan` can safely be issued
+ //! simultaneously.
+
+ debug!("report_error_if_loans_conflict(old_loan={}, new_loan={})",
+ old_loan.repr(self.tcx()),
+ new_loan.repr(self.tcx()));
+
+ // Should only be called for loans that are in scope at the same time.
+ assert!(self.tcx().region_maps.scopes_intersect(old_loan.kill_scope,
+ new_loan.kill_scope));
+
+ self.report_error_if_loan_conflicts_with_restriction(
+ old_loan, new_loan, old_loan, new_loan) &&
+ self.report_error_if_loan_conflicts_with_restriction(
+ new_loan, old_loan, old_loan, new_loan);
+ }
+
+ pub fn report_error_if_loan_conflicts_with_restriction(&self,
+ loan1: &Loan<'tcx>,
+ loan2: &Loan<'tcx>,
+ old_loan: &Loan<'tcx>,
+ new_loan: &Loan<'tcx>)
+ -> bool {
+ //! Checks whether the restrictions introduced by `loan1` would
+ //! prohibit `loan2`. Returns false if an error is reported.
+
+ debug!("report_error_if_loan_conflicts_with_restriction(\
+ loan1={}, loan2={})",
+ loan1.repr(self.tcx()),
+ loan2.repr(self.tcx()));
+
+ if compatible_borrow_kinds(loan1.kind, loan2.kind) {
+ return true;
+ }
+
+ let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path);
+ for restr_path in loan1.restricted_paths.iter() {
+ if *restr_path != loan2_base_path { continue; }
+
+ // If new_loan is something like `x.a`, and old_loan is something like `x.b`, we would
+ // normally generate a rather confusing message (in this case, for multiple mutable
+ // borrows):
+ //
+ // error: cannot borrow `x.b` as mutable more than once at a time
+ // note: previous borrow of `x.a` occurs here; the mutable borrow prevents
+ // subsequent moves, borrows, or modification of `x.a` until the borrow ends
+ //
+ // What we want to do instead is get the 'common ancestor' of the two borrow paths and
+ // use that for most of the message instead, giving is something like this:
+ //
+ // error: cannot borrow `x` as mutable more than once at a time
+ // note: previous borrow of `x` occurs here (through borrowing `x.a`); the mutable
+ // borrow prevents subsequent moves, borrows, or modification of `x` until the
+ // borrow ends
+
+ let common = new_loan.loan_path.common(&*old_loan.loan_path);
+ let (nl, ol, new_loan_msg, old_loan_msg) =
+ if new_loan.loan_path.has_fork(&*old_loan.loan_path) && common.is_some() {
+ let nl = self.bccx.loan_path_to_string(&common.unwrap());
+ let ol = nl.clone();
+ let new_loan_msg = format!(" (here through borrowing `{}`)",
+ self.bccx.loan_path_to_string(
+ &*new_loan.loan_path));
+ let old_loan_msg = format!(" (through borrowing `{}`)",
+ self.bccx.loan_path_to_string(
+ &*old_loan.loan_path));
+ (nl, ol, new_loan_msg, old_loan_msg)
+ } else {
+ (self.bccx.loan_path_to_string(&*new_loan.loan_path),
+ self.bccx.loan_path_to_string(&*old_loan.loan_path),
+ String::new(), String::new())
+ };
+
+ let ol_pronoun = if new_loan.loan_path == old_loan.loan_path {
+ "it".to_string()
+ } else {
+ format!("`{}`", ol)
+ };
+
+ match (new_loan.kind, old_loan.kind) {
+ (ty::MutBorrow, ty::MutBorrow) => {
+ self.bccx.span_err(
+ new_loan.span,
+ format!("cannot borrow `{}`{} as mutable \
+ more than once at a time",
+ nl, new_loan_msg).as_slice())
+ }
+
+ (ty::UniqueImmBorrow, _) => {
+ self.bccx.span_err(
+ new_loan.span,
+ format!("closure requires unique access to `{}` \
+ but {} is already borrowed{}",
+ nl, ol_pronoun, old_loan_msg).as_slice());
+ }
+
+ (_, ty::UniqueImmBorrow) => {
+ self.bccx.span_err(
+ new_loan.span,
+ format!("cannot borrow `{}`{} as {} because \
+ previous closure requires unique access",
+ nl, new_loan_msg, new_loan.kind.to_user_str()).as_slice());
+ }
+
+ (_, _) => {
+ self.bccx.span_err(
+ new_loan.span,
+ format!("cannot borrow `{}`{} as {} because \
+ {} is also borrowed as {}{}",
+ nl,
+ new_loan_msg,
+ new_loan.kind.to_user_str(),
+ ol_pronoun,
+ old_loan.kind.to_user_str(),
+ old_loan_msg).as_slice());
+ }
+ }
+
+ match new_loan.cause {
+ euv::ClosureCapture(span) => {
+ self.bccx.span_note(
+ span,
+ format!("borrow occurs due to use of `{}` in closure",
+ nl).as_slice());
+ }
+ _ => { }
+ }
+
+ let rule_summary = match old_loan.kind {
+ ty::MutBorrow => {
+ format!("the mutable borrow prevents subsequent \
+ moves, borrows, or modification of `{0}` \
+ until the borrow ends",
+ ol)
+ }
+
+ ty::ImmBorrow => {
+ format!("the immutable borrow prevents subsequent \
+ moves or mutable borrows of `{0}` \
+ until the borrow ends",
+ ol)
+ }
+
+ ty::UniqueImmBorrow => {
+ format!("the unique capture prevents subsequent \
+ moves or borrows of `{0}` \
+ until the borrow ends",
+ ol)
+ }
+ };
+
+ let borrow_summary = match old_loan.cause {
+ euv::ClosureCapture(_) => {
+ format!("previous borrow of `{}` occurs here{} due to \
+ use in closure",
+ ol, old_loan_msg)
+ }
+
+ euv::OverloadedOperator(..) |
+ euv::AddrOf(..) |
+ euv::AutoRef(..) |
+ euv::ClosureInvocation(..) |
+ euv::ForLoop(..) |
+ euv::RefBinding(..) |
+ euv::MatchDiscriminant(..) => {
+ format!("previous borrow of `{}` occurs here{}",
+ ol, old_loan_msg)
+ }
+ };
+
+ self.bccx.span_note(
+ old_loan.span,
+ format!("{}; {}", borrow_summary, rule_summary).as_slice());
+
+ let old_loan_span = self.tcx().map.span(old_loan.kill_scope.node_id());
+ self.bccx.span_end_note(old_loan_span,
+ "previous borrow ends here");
+
+ return false;
+ }
+
+ true
+ }
+
+ fn is_local_variable_or_arg(&self, cmt: mc::cmt<'tcx>) -> bool {
+ match cmt.cat {
+ mc::cat_local(_) => true,
+ _ => false
+ }
+ }
+
+ fn consume_common(&self,
+ id: ast::NodeId,
+ span: Span,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::ConsumeMode) {
+ match opt_loan_path(&cmt) {
+ Some(lp) => {
+ let moved_value_use_kind = match mode {
+ euv::Copy => {
+ self.check_for_copy_of_frozen_path(id, span, &*lp);
+ MovedInUse
+ }
+ euv::Move(_) => {
+ match self.move_data.kind_of_move_of_path(id, &lp) {
+ None => {
+ // Sometimes moves don't have a move kind;
+ // this either means that the original move
+ // was from something illegal to move,
+ // or was moved from referent of an unsafe
+ // pointer or something like that.
+ MovedInUse
+ }
+ Some(move_kind) => {
+ self.check_for_move_of_borrowed_path(id, span,
+ &*lp, move_kind);
+ if move_kind == move_data::Captured {
+ MovedInCapture
+ } else {
+ MovedInUse
+ }
+ }
+ }
+ }
+ };
+
+ self.check_if_path_is_moved(id, span, moved_value_use_kind, &lp);
+ }
+ None => { }
+ }
+ }
+
+ fn check_for_copy_of_frozen_path(&self,
+ id: ast::NodeId,
+ span: Span,
+ copy_path: &LoanPath<'tcx>) {
+ match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) {
+ UseOk => { }
+ UseWhileBorrowed(loan_path, loan_span) => {
+ self.bccx.span_err(
+ span,
+ format!("cannot use `{}` because it was mutably borrowed",
+ self.bccx.loan_path_to_string(copy_path).as_slice())
+ .as_slice());
+ self.bccx.span_note(
+ loan_span,
+ format!("borrow of `{}` occurs here",
+ self.bccx.loan_path_to_string(&*loan_path).as_slice())
+ .as_slice());
+ }
+ }
+ }
+
+ fn check_for_move_of_borrowed_path(&self,
+ id: ast::NodeId,
+ span: Span,
+ move_path: &LoanPath<'tcx>,
+ move_kind: move_data::MoveKind) {
+ // We want to detect if there are any loans at all, so we search for
+ // any loans incompatible with MutBorrrow, since all other kinds of
+ // loans are incompatible with that.
+ match self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow) {
+ UseOk => { }
+ UseWhileBorrowed(loan_path, loan_span) => {
+ let err_message = match move_kind {
+ move_data::Captured =>
+ format!("cannot move `{}` into closure because it is borrowed",
+ self.bccx.loan_path_to_string(move_path).as_slice()),
+ move_data::Declared |
+ move_data::MoveExpr |
+ move_data::MovePat =>
+ format!("cannot move out of `{}` because it is borrowed",
+ self.bccx.loan_path_to_string(move_path).as_slice())
+ };
+
+ self.bccx.span_err(span, err_message.as_slice());
+ self.bccx.span_note(
+ loan_span,
+ format!("borrow of `{}` occurs here",
+ self.bccx.loan_path_to_string(&*loan_path).as_slice())
+ .as_slice());
+ }
+ }
+ }
+
+ pub fn analyze_restrictions_on_use(&self,
+ expr_id: ast::NodeId,
+ use_path: &LoanPath<'tcx>,
+ borrow_kind: ty::BorrowKind)
+ -> UseError<'tcx> {
+ debug!("analyze_restrictions_on_use(expr_id={}, use_path={})",
+ self.tcx().map.node_to_string(expr_id),
+ use_path.repr(self.tcx()));
+
+ let mut ret = UseOk;
+
+ self.each_in_scope_loan_affecting_path(
+ region::CodeExtent::from_node_id(expr_id), use_path, |loan| {
+ if !compatible_borrow_kinds(loan.kind, borrow_kind) {
+ ret = UseWhileBorrowed(loan.loan_path.clone(), loan.span);
+ false
+ } else {
+ true
+ }
+ });
+
+ return ret;
+ }
+
+ /// Reports an error if `expr` (which should be a path)
+ /// is using a moved/uninitialized value
+ fn check_if_path_is_moved(&self,
+ id: ast::NodeId,
+ span: Span,
+ use_kind: MovedValueUseKind,
+ lp: &Rc<LoanPath<'tcx>>) {
+ debug!("check_if_path_is_moved(id={}, use_kind={}, lp={})",
+ id, use_kind, lp.repr(self.bccx.tcx));
+ let base_lp = owned_ptr_base_path_rc(lp);
+ self.move_data.each_move_of(id, &base_lp, |the_move, moved_lp| {
+ self.bccx.report_use_of_moved_value(
+ span,
+ use_kind,
+ &**lp,
+ the_move,
+ moved_lp,
+ self.param_env);
+ false
+ });
+ }
+
+ /// Reports an error if assigning to `lp` will use a
+ /// moved/uninitialized value. Mainly this is concerned with
+ /// detecting derefs of uninitialized pointers.
+ ///
+ /// For example:
+ ///
+ /// ```
+ /// let a: int;
+ /// a = 10; // ok, even though a is uninitialized
+ ///
+ /// struct Point { x: uint, y: uint }
+ /// let p: Point;
+ /// p.x = 22; // ok, even though `p` is uninitialized
+ ///
+ /// let p: ~Point;
+ /// (*p).x = 22; // not ok, p is uninitialized, can't deref
+ /// ```
+ fn check_if_assigned_path_is_moved(&self,
+ id: ast::NodeId,
+ span: Span,
+ use_kind: MovedValueUseKind,
+ lp: &Rc<LoanPath<'tcx>>)
+ {
+ match lp.kind {
+ LpVar(_) | LpUpvar(_) => {
+ // assigning to `x` does not require that `x` is initialized
+ }
+ LpDowncast(ref lp_base, _) => {
+ // assigning to `(P->Variant).f` is ok if assigning to `P` is ok
+ self.check_if_assigned_path_is_moved(id, span,
+ use_kind, lp_base);
+ }
+ LpExtend(ref lp_base, _, LpInterior(_)) => {
+ // assigning to `P.f` is ok if assigning to `P` is ok
+ self.check_if_assigned_path_is_moved(id, span,
+ use_kind, lp_base);
+ }
+ LpExtend(ref lp_base, _, LpDeref(_)) => {
+ // assigning to `(*P)` requires that `P` be initialized
+ self.check_if_path_is_moved(id, span,
+ use_kind, lp_base);
+ }
+ }
+ }
+
+ fn check_assignment(&self,
+ assignment_id: ast::NodeId,
+ assignment_span: Span,
+ assignee_cmt: mc::cmt<'tcx>,
+ mode: euv::MutateMode) {
+ debug!("check_assignment(assignee_cmt={})", assignee_cmt.repr(self.tcx()));
+
+ // Mutable values can be assigned, as long as they obey loans
+ // and aliasing restrictions:
+ if assignee_cmt.mutbl.is_mutable() {
+ if check_for_aliasable_mutable_writes(self, assignment_span, assignee_cmt.clone()) {
+ if mode != euv::Init {
+ check_for_assignment_to_borrowed_path(
+ self, assignment_id, assignment_span, assignee_cmt.clone());
+ mark_variable_as_used_mut(self, assignee_cmt);
+ }
+ }
+ return;
+ }
+
+ // Initializations are OK.
+ if mode == euv::Init {
+ return
+ }
+
+ // For immutable local variables, assignments are legal
+ // if they cannot already have been assigned
+ if self.is_local_variable_or_arg(assignee_cmt.clone()) {
+ assert!(assignee_cmt.mutbl.is_immutable()); // no "const" locals
+ let lp = opt_loan_path(&assignee_cmt).unwrap();
+ self.move_data.each_assignment_of(assignment_id, &lp, |assign| {
+ self.bccx.report_reassigned_immutable_variable(
+ assignment_span,
+ &*lp,
+ assign);
+ false
+ });
+ return;
+ }
+
+ // Otherwise, just a plain error.
+ match assignee_cmt.note {
+ mc::NoteClosureEnv(upvar_id) => {
+ // If this is an `Fn` closure, it simply can't mutate upvars.
+ // If it's an `FnMut` closure, the original variable was declared immutable.
+ // We need to determine which is the case here.
+ let kind = match assignee_cmt.upvar().unwrap().cat {
+ mc::cat_upvar(mc::Upvar { kind, .. }) => kind,
+ _ => unreachable!()
+ };
+ if kind == ty::FnUnboxedClosureKind {
+ self.bccx.span_err(
+ assignment_span,
+ format!("cannot assign to {}",
+ self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
+ self.bccx.span_help(
+ self.tcx().map.span(upvar_id.closure_expr_id),
+ "consider changing this closure to take self by mutable reference");
+ } else {
+ self.bccx.span_err(
+ assignment_span,
+ format!("cannot assign to {} {}",
+ assignee_cmt.mutbl.to_user_str(),
+ self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
+ }
+ }
+ _ => match opt_loan_path(&assignee_cmt) {
+ Some(lp) => {
+ self.bccx.span_err(
+ assignment_span,
+ format!("cannot assign to {} {} `{}`",
+ assignee_cmt.mutbl.to_user_str(),
+ self.bccx.cmt_to_string(&*assignee_cmt),
+ self.bccx.loan_path_to_string(&*lp)).as_slice());
+ }
+ None => {
+ self.bccx.span_err(
+ assignment_span,
+ format!("cannot assign to {} {}",
+ assignee_cmt.mutbl.to_user_str(),
+ self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
+ }
+ }
+ }
+ return;
+
+ fn mark_variable_as_used_mut<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
+ mut cmt: mc::cmt<'tcx>) {
+ //! If the mutability of the `cmt` being written is inherited
+ //! from a local variable, liveness will
+ //! not have been able to detect that this variable's mutability
+ //! is important, so we must add the variable to the
+ //! `used_mut_nodes` table here.
+
+ loop {
+ debug!("mark_variable_as_used_mut(cmt={})", cmt.repr(this.tcx()));
+ match cmt.cat.clone() {
+ mc::cat_upvar(mc::Upvar { id: ty::UpvarId { var_id: id, .. }, .. }) |
+ mc::cat_local(id) => {
+ this.tcx().used_mut_nodes.borrow_mut().insert(id);
+ return;
+ }
+
+ mc::cat_rvalue(..) |
+ mc::cat_static_item |
+ mc::cat_deref(_, _, mc::UnsafePtr(..)) |
+ mc::cat_deref(_, _, mc::Implicit(..)) => {
+ assert_eq!(cmt.mutbl, mc::McDeclared);
+ return;
+ }
+
+ mc::cat_deref(_, _, mc::BorrowedPtr(..)) => {
+ assert_eq!(cmt.mutbl, mc::McDeclared);
+ // We need to drill down to upvar if applicable
+ match cmt.upvar() {
+ Some(b) => cmt = b,
+ None => return
+ }
+ }
+
+ mc::cat_deref(b, _, mc::OwnedPtr) => {
+ assert_eq!(cmt.mutbl, mc::McInherited);
+ cmt = b;
+ }
+
+ mc::cat_downcast(b, _) |
+ mc::cat_interior(b, _) => {
+ assert_eq!(cmt.mutbl, mc::McInherited);
+ cmt = b;
+ }
+ }
+ }
+ }
+
+ fn check_for_aliasable_mutable_writes<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
+ span: Span,
+ cmt: mc::cmt<'tcx>) -> bool {
+ //! Safety checks related to writes to aliasable, mutable locations
+
+ let guarantor = cmt.guarantor();
+ debug!("check_for_aliasable_mutable_writes(cmt={}, guarantor={})",
+ cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
+ if let mc::cat_deref(ref b, _, mc::BorrowedPtr(ty::MutBorrow, _)) = guarantor.cat {
+ // Statically prohibit writes to `&mut` when aliasable
+ check_for_aliasability_violation(this, span, b.clone());
+ }
+
+ return true; // no errors reported
+ }
+
+ fn check_for_aliasability_violation<'a, 'tcx>(this: &CheckLoanCtxt<'a, 'tcx>,
+ span: Span,
+ cmt: mc::cmt<'tcx>)
+ -> bool {
+ match cmt.freely_aliasable(this.tcx()) {
+ None => {
+ return true;
+ }
+ Some(mc::AliasableStaticMut(..)) => {
+ return true;
+ }
+ Some(cause) => {
+ this.bccx.report_aliasability_violation(
+ span,
+ MutabilityViolation,
+ cause);
+ return false;
+ }
+ }
+ }
+
+ fn check_for_assignment_to_borrowed_path<'a, 'tcx>(
+ this: &CheckLoanCtxt<'a, 'tcx>,
+ assignment_id: ast::NodeId,
+ assignment_span: Span,
+ assignee_cmt: mc::cmt<'tcx>)
+ {
+ //! Check for assignments that violate the terms of an
+ //! outstanding loan.
+
+ let loan_path = match opt_loan_path(&assignee_cmt) {
+ Some(lp) => lp,
+ None => { return; /* no loan path, can't be any loans */ }
+ };
+
+ let scope = region::CodeExtent::from_node_id(assignment_id);
+ this.each_in_scope_loan_affecting_path(scope, &*loan_path, |loan| {
+ this.report_illegal_mutation(assignment_span, &*loan_path, loan);
+ false
+ });
+ }
+ }
+
+ pub fn report_illegal_mutation(&self,
+ span: Span,
+ loan_path: &LoanPath<'tcx>,
+ loan: &Loan) {
+ self.bccx.span_err(
+ span,
+ format!("cannot assign to `{}` because it is borrowed",
+ self.bccx.loan_path_to_string(loan_path)).as_slice());
+ self.bccx.span_note(
+ loan.span,
+ format!("borrow of `{}` occurs here",
+ self.bccx.loan_path_to_string(loan_path)).as_slice());
+ }
+}
--- /dev/null
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! # The Borrow Checker
+//!
+//! This pass has the job of enforcing memory safety. This is a subtle
+//! topic. This docs aim to explain both the practice and the theory
+//! behind the borrow checker. They start with a high-level overview of
+//! how it works, and then proceed to dive into the theoretical
+//! background. Finally, they go into detail on some of the more subtle
+//! aspects.
+//!
+//! # Table of contents
+//!
+//! These docs are long. Search for the section you are interested in.
+//!
+//! - Overview
+//! - Formal model
+//! - Borrowing and loans
+//! - Moves and initialization
+//! - Drop flags and structural fragments
+//! - Future work
+//!
+//! # Overview
+//!
+//! The borrow checker checks one function at a time. It operates in two
+//! passes. The first pass, called `gather_loans`, walks over the function
+//! and identifies all of the places where borrows (e.g., `&` expressions
+//! and `ref` bindings) and moves (copies or captures of a linear value)
+//! occur. It also tracks initialization sites. For each borrow and move,
+//! it checks various basic safety conditions at this time (for example,
+//! that the lifetime of the borrow doesn't exceed the lifetime of the
+//! value being borrowed, or that there is no move out of an `&T`
+//! referent).
+//!
+//! It then uses the dataflow module to propagate which of those borrows
+//! may be in scope at each point in the procedure. A loan is considered
+//! to come into scope at the expression that caused it and to go out of
+//! scope when the lifetime of the resulting reference expires.
+//!
+//! Once the in-scope loans are known for each point in the program, the
+//! borrow checker walks the IR again in a second pass called
+//! `check_loans`. This pass examines each statement and makes sure that
+//! it is safe with respect to the in-scope loans.
+//!
+//! # Formal model
+//!
+//! Throughout the docs we'll consider a simple subset of Rust in which
+//! you can only borrow from lvalues, defined like so:
+//!
+//! ```text
+//! LV = x | LV.f | *LV
+//! ```
+//!
+//! Here `x` represents some variable, `LV.f` is a field reference,
+//! and `*LV` is a pointer dereference. There is no auto-deref or other
+//! niceties. This means that if you have a type like:
+//!
+//! ```text
+//! struct S { f: uint }
+//! ```
+//!
+//! and a variable `a: Box<S>`, then the rust expression `a.f` would correspond
+//! to an `LV` of `(*a).f`.
+//!
+//! Here is the formal grammar for the types we'll consider:
+//!
+//! ```text
+//! TY = () | S<'LT...> | Box<TY> | & 'LT MQ TY
+//! MQ = mut | imm | const
+//! ```
+//!
+//! Most of these types should be pretty self explanatory. Here `S` is a
+//! struct name and we assume structs are declared like so:
+//!
+//! ```text
+//! SD = struct S<'LT...> { (f: TY)... }
+//! ```
+//!
+//! # Borrowing and loans
+//!
+//! ## An intuitive explanation
+//!
+//! ### Issuing loans
+//!
+//! Now, imagine we had a program like this:
+//!
+//! ```text
+//! struct Foo { f: uint, g: uint }
+//! ...
+//! 'a: {
+//! let mut x: Box<Foo> = ...;
+//! let y = &mut (*x).f;
+//! x = ...;
+//! }
+//! ```
+//!
+//! This is of course dangerous because mutating `x` will free the old
+//! value and hence invalidate `y`. The borrow checker aims to prevent
+//! this sort of thing.
+//!
+//! #### Loans and restrictions
+//!
+//! The way the borrow checker works is that it analyzes each borrow
+//! expression (in our simple model, that's stuff like `&LV`, though in
+//! real life there are a few other cases to consider). For each borrow
+//! expression, it computes a `Loan`, which is a data structure that
+//! records (1) the value being borrowed, (2) the mutability and scope of
+//! the borrow, and (3) a set of restrictions. In the code, `Loan` is a
+//! struct defined in `middle::borrowck`. Formally, we define `LOAN` as
+//! follows:
+//!
+//! ```text
+//! LOAN = (LV, LT, MQ, RESTRICTION*)
+//! RESTRICTION = (LV, ACTION*)
+//! ACTION = MUTATE | CLAIM | FREEZE
+//! ```
+//!
+//! Here the `LOAN` tuple defines the lvalue `LV` being borrowed; the
+//! lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a
+//! list of restrictions. The restrictions indicate actions which, if
+//! taken, could invalidate the loan and lead to type safety violations.
+//!
+//! Each `RESTRICTION` is a pair of a restrictive lvalue `LV` (which will
+//! either be the path that was borrowed or some prefix of the path that
+//! was borrowed) and a set of restricted actions. There are three kinds
+//! of actions that may be restricted for the path `LV`:
+//!
+//! - `MUTATE` means that `LV` cannot be assigned to;
+//! - `CLAIM` means that the `LV` cannot be borrowed mutably;
+//! - `FREEZE` means that the `LV` cannot be borrowed immutably;
+//!
+//! Finally, it is never possible to move from an lvalue that appears in a
+//! restriction. This implies that the "empty restriction" `(LV, [])`,
+//! which contains an empty set of actions, still has a purpose---it
+//! prevents moves from `LV`. I chose not to make `MOVE` a fourth kind of
+//! action because that would imply that sometimes moves are permitted
+//! from restrictived values, which is not the case.
+//!
+//! #### Example
+//!
+//! To give you a better feeling for what kind of restrictions derived
+//! from a loan, let's look at the loan `L` that would be issued as a
+//! result of the borrow `&mut (*x).f` in the example above:
+//!
+//! ```text
+//! L = ((*x).f, 'a, mut, RS) where
+//! RS = [((*x).f, [MUTATE, CLAIM, FREEZE]),
+//! (*x, [MUTATE, CLAIM, FREEZE]),
+//! (x, [MUTATE, CLAIM, FREEZE])]
+//! ```
+//!
+//! The loan states that the expression `(*x).f` has been loaned as
+//! mutable for the lifetime `'a`. Because the loan is mutable, that means
+//! that the value `(*x).f` may be mutated via the newly created reference
+//! (and *only* via that pointer). This is reflected in the
+//! restrictions `RS` that accompany the loan.
+//!
+//! The first restriction `((*x).f, [MUTATE, CLAIM, FREEZE])` states that
+//! the lender may not mutate, freeze, nor alias `(*x).f`. Mutation is
+//! illegal because `(*x).f` is only supposed to be mutated via the new
+//! reference, not by mutating the original path `(*x).f`. Freezing is
+//! illegal because the path now has an `&mut` alias; so even if we the
+//! lender were to consider `(*x).f` to be immutable, it might be mutated
+//! via this alias. They will be enforced for the lifetime `'a` of the
+//! loan. After the loan expires, the restrictions no longer apply.
+//!
+//! The second restriction on `*x` is interesting because it does not
+//! apply to the path that was lent (`(*x).f`) but rather to a prefix of
+//! the borrowed path. This is due to the rules of inherited mutability:
+//! if the user were to assign to (or freeze) `*x`, they would indirectly
+//! overwrite (or freeze) `(*x).f`, and thus invalidate the reference
+//! that was created. In general it holds that when a path is
+//! lent, restrictions are issued for all the owning prefixes of that
+//! path. In this case, the path `*x` owns the path `(*x).f` and,
+//! because `x` is an owned pointer, the path `x` owns the path `*x`.
+//! Therefore, borrowing `(*x).f` yields restrictions on both
+//! `*x` and `x`.
+//!
+//! ### Checking for illegal assignments, moves, and reborrows
+//!
+//! Once we have computed the loans introduced by each borrow, the borrow
+//! checker uses a data flow propagation to compute the full set of loans
+//! in scope at each expression and then uses that set to decide whether
+//! that expression is legal. Remember that the scope of loan is defined
+//! by its lifetime LT. We sometimes say that a loan which is in-scope at
+//! a particular point is an "outstanding loan", and the set of
+//! restrictions included in those loans as the "outstanding
+//! restrictions".
+//!
+//! The kinds of expressions which in-scope loans can render illegal are:
+//! - *assignments* (`lv = v`): illegal if there is an in-scope restriction
+//! against mutating `lv`;
+//! - *moves*: illegal if there is any in-scope restriction on `lv` at all;
+//! - *mutable borrows* (`&mut lv`): illegal there is an in-scope restriction
+//! against claiming `lv`;
+//! - *immutable borrows* (`&lv`): illegal there is an in-scope restriction
+//! against freezing `lv`.
+//!
+//! ## Formal rules
+//!
+//! Now that we hopefully have some kind of intuitive feeling for how the
+//! borrow checker works, let's look a bit more closely now at the precise
+//! conditions that it uses. For simplicity I will ignore const loans.
+//!
+//! I will present the rules in a modified form of standard inference
+//! rules, which looks as follows:
+//!
+//! ```text
+//! PREDICATE(X, Y, Z) // Rule-Name
+//! Condition 1
+//! Condition 2
+//! Condition 3
+//! ```
+//!
+//! The initial line states the predicate that is to be satisfied. The
+//! indented lines indicate the conditions that must be met for the
+//! predicate to be satisfied. The right-justified comment states the name
+//! of this rule: there are comments in the borrowck source referencing
+//! these names, so that you can cross reference to find the actual code
+//! that corresponds to the formal rule.
+//!
+//! ### Invariants
+//!
+//! I want to collect, at a high-level, the invariants the borrow checker
+//! maintains. I will give them names and refer to them throughout the
+//! text. Together these invariants are crucial for the overall soundness
+//! of the system.
+//!
+//! **Mutability requires uniqueness.** To mutate a path
+//!
+//! **Unique mutability.** There is only one *usable* mutable path to any
+//! given memory at any given time. This implies that when claiming memory
+//! with an expression like `p = &mut x`, the compiler must guarantee that
+//! the borrowed value `x` can no longer be mutated so long as `p` is
+//! live. (This is done via restrictions, read on.)
+//!
+//! **.**
+//!
+//!
+//! ### The `gather_loans` pass
+//!
+//! We start with the `gather_loans` pass, which walks the AST looking for
+//! borrows. For each borrow, there are three bits of information: the
+//! lvalue `LV` being borrowed and the mutability `MQ` and lifetime `LT`
+//! of the resulting pointer. Given those, `gather_loans` applies four
+//! validity tests:
+//!
+//! 1. `MUTABILITY(LV, MQ)`: The mutability of the reference is
+//! compatible with the mutability of `LV` (i.e., not borrowing immutable
+//! data as mutable).
+//!
+//! 2. `ALIASABLE(LV, MQ)`: The aliasability of the reference is
+//! compatible with the aliasability of `LV`. The goal is to prevent
+//! `&mut` borrows of aliasability data.
+//!
+//! 3. `LIFETIME(LV, LT, MQ)`: The lifetime of the borrow does not exceed
+//! the lifetime of the value being borrowed.
+//!
+//! 4. `RESTRICTIONS(LV, LT, ACTIONS) = RS`: This pass checks and computes the
+//! restrictions to maintain memory safety. These are the restrictions
+//! that will go into the final loan. We'll discuss in more detail below.
+//!
+//! ## Checking mutability
+//!
+//! Checking mutability is fairly straightforward. We just want to prevent
+//! immutable data from being borrowed as mutable. Note that it is ok to
+//! borrow mutable data as immutable, since that is simply a
+//! freeze. Formally we define a predicate `MUTABLE(LV, MQ)` which, if
+//! defined, means that "borrowing `LV` with mutability `MQ` is ok. The
+//! Rust code corresponding to this predicate is the function
+//! `check_mutability` in `middle::borrowck::gather_loans`.
+//!
+//! ### Checking mutability of variables
+//!
+//! *Code pointer:* Function `check_mutability()` in `gather_loans/mod.rs`,
+//! but also the code in `mem_categorization`.
+//!
+//! Let's begin with the rules for variables, which state that if a
+//! variable is declared as mutable, it may be borrowed any which way, but
+//! otherwise the variable must be borrowed as immutable or const:
+//!
+//! ```text
+//! MUTABILITY(X, MQ) // M-Var-Mut
+//! DECL(X) = mut
+//!
+//! MUTABILITY(X, MQ) // M-Var-Imm
+//! DECL(X) = imm
+//! MQ = imm | const
+//! ```
+//!
+//! ### Checking mutability of owned content
+//!
+//! Fields and owned pointers inherit their mutability from
+//! their base expressions, so both of their rules basically
+//! delegate the check to the base expression `LV`:
+//!
+//! ```text
+//! MUTABILITY(LV.f, MQ) // M-Field
+//! MUTABILITY(LV, MQ)
+//!
+//! MUTABILITY(*LV, MQ) // M-Deref-Unique
+//! TYPE(LV) = Box<Ty>
+//! MUTABILITY(LV, MQ)
+//! ```
+//!
+//! ### Checking mutability of immutable pointer types
+//!
+//! Immutable pointer types like `&T` can only
+//! be borrowed if MQ is immutable or const:
+//!
+//! ```text
+//! MUTABILITY(*LV, MQ) // M-Deref-Borrowed-Imm
+//! TYPE(LV) = &Ty
+//! MQ == imm | const
+//! ```
+//!
+//! ### Checking mutability of mutable pointer types
+//!
+//! `&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
+//!
+//! ```text
+//! MUTABILITY(*LV, MQ) // M-Deref-Borrowed-Mut
+//! TYPE(LV) = &mut Ty
+//! ```
+//!
+//! ## Checking aliasability
+//!
+//! The goal of the aliasability check is to ensure that we never permit
+//! `&mut` borrows of aliasable data. Formally we define a predicate
+//! `ALIASABLE(LV, MQ)` which if defined means that
+//! "borrowing `LV` with mutability `MQ` is ok". The
+//! Rust code corresponding to this predicate is the function
+//! `check_aliasability()` in `middle::borrowck::gather_loans`.
+//!
+//! ### Checking aliasability of variables
+//!
+//! Local variables are never aliasable as they are accessible only within
+//! the stack frame.
+//!
+//! ```text
+//! ALIASABLE(X, MQ) // M-Var-Mut
+//! ```
+//!
+//! ### Checking aliasable of owned content
+//!
+//! Owned content is aliasable if it is found in an aliasable location:
+//!
+//! ```text
+//! ALIASABLE(LV.f, MQ) // M-Field
+//! ALIASABLE(LV, MQ)
+//!
+//! ALIASABLE(*LV, MQ) // M-Deref-Unique
+//! ALIASABLE(LV, MQ)
+//! ```
+//!
+//! ### Checking mutability of immutable pointer types
+//!
+//! Immutable pointer types like `&T` are aliasable, and hence can only be
+//! borrowed immutably:
+//!
+//! ```text
+//! ALIASABLE(*LV, imm) // M-Deref-Borrowed-Imm
+//! TYPE(LV) = &Ty
+//! ```
+//!
+//! ### Checking mutability of mutable pointer types
+//!
+//! `&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
+//!
+//! ```text
+//! ALIASABLE(*LV, MQ) // M-Deref-Borrowed-Mut
+//! TYPE(LV) = &mut Ty
+//! ```
+//!
+//! ## Checking lifetime
+//!
+//! These rules aim to ensure that no data is borrowed for a scope that exceeds
+//! its lifetime. These two computations wind up being intimately related.
+//! Formally, we define a predicate `LIFETIME(LV, LT, MQ)`, which states that
+//! "the lvalue `LV` can be safely borrowed for the lifetime `LT` with mutability
+//! `MQ`". The Rust code corresponding to this predicate is the module
+//! `middle::borrowck::gather_loans::lifetime`.
+//!
+//! ### The Scope function
+//!
+//! Several of the rules refer to a helper function `SCOPE(LV)=LT`. The
+//! `SCOPE(LV)` yields the lifetime `LT` for which the lvalue `LV` is
+//! guaranteed to exist, presuming that no mutations occur.
+//!
+//! The scope of a local variable is the block where it is declared:
+//!
+//! ```text
+//! SCOPE(X) = block where X is declared
+//! ```
+//!
+//! The scope of a field is the scope of the struct:
+//!
+//! ```text
+//! SCOPE(LV.f) = SCOPE(LV)
+//! ```
+//!
+//! The scope of a unique referent is the scope of the pointer, since
+//! (barring mutation or moves) the pointer will not be freed until
+//! the pointer itself `LV` goes out of scope:
+//!
+//! ```text
+//! SCOPE(*LV) = SCOPE(LV) if LV has type Box<T>
+//! ```
+//!
+//! The scope of a borrowed referent is the scope associated with the
+//! pointer. This is a conservative approximation, since the data that
+//! the pointer points at may actually live longer:
+//!
+//! ```text
+//! SCOPE(*LV) = LT if LV has type &'LT T or &'LT mut T
+//! ```
+//!
+//! ### Checking lifetime of variables
+//!
+//! The rule for variables states that a variable can only be borrowed a
+//! lifetime `LT` that is a subregion of the variable's scope:
+//!
+//! ```text
+//! LIFETIME(X, LT, MQ) // L-Local
+//! LT <= SCOPE(X)
+//! ```
+//!
+//! ### Checking lifetime for owned content
+//!
+//! The lifetime of a field or owned pointer is the same as the lifetime
+//! of its owner:
+//!
+//! ```text
+//! LIFETIME(LV.f, LT, MQ) // L-Field
+//! LIFETIME(LV, LT, MQ)
+//!
+//! LIFETIME(*LV, LT, MQ) // L-Deref-Send
+//! TYPE(LV) = Box<Ty>
+//! LIFETIME(LV, LT, MQ)
+//! ```
+//!
+//! ### Checking lifetime for derefs of references
+//!
+//! References have a lifetime `LT'` associated with them. The
+//! data they point at has been guaranteed to be valid for at least this
+//! lifetime. Therefore, the borrow is valid so long as the lifetime `LT`
+//! of the borrow is shorter than the lifetime `LT'` of the pointer
+//! itself:
+//!
+//! ```text
+//! LIFETIME(*LV, LT, MQ) // L-Deref-Borrowed
+//! TYPE(LV) = <' Ty OR <' mut Ty
+//! LT <= LT'
+//! ```
+//!
+//! ## Computing the restrictions
+//!
+//! The final rules govern the computation of *restrictions*, meaning that
+//! we compute the set of actions that will be illegal for the life of the
+//! loan. The predicate is written `RESTRICTIONS(LV, LT, ACTIONS) =
+//! RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from
+//! occurring on `LV`, the restrictions `RESTRICTION*` must be respected
+//! for the lifetime of the loan".
+//!
+//! Note that there is an initial set of restrictions: these restrictions
+//! are computed based on the kind of borrow:
+//!
+//! ```text
+//! &mut LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM|FREEZE)
+//! &LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM)
+//! &const LV => RESTRICTIONS(LV, LT, [])
+//! ```
+//!
+//! The reasoning here is that a mutable borrow must be the only writer,
+//! therefore it prevents other writes (`MUTATE`), mutable borrows
+//! (`CLAIM`), and immutable borrows (`FREEZE`). An immutable borrow
+//! permits other immutable borrows but forbids writes and mutable borrows.
+//! Finally, a const borrow just wants to be sure that the value is not
+//! moved out from under it, so no actions are forbidden.
+//!
+//! ### Restrictions for loans of a local variable
+//!
+//! The simplest case is a borrow of a local variable `X`:
+//!
+//! ```text
+//! RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable
+//! ```
+//!
+//! In such cases we just record the actions that are not permitted.
+//!
+//! ### Restrictions for loans of fields
+//!
+//! Restricting a field is the same as restricting the owner of that
+//! field:
+//!
+//! ```text
+//! RESTRICTIONS(LV.f, LT, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
+//! RESTRICTIONS(LV, LT, ACTIONS) = RS
+//! ```
+//!
+//! The reasoning here is as follows. If the field must not be mutated,
+//! then you must not mutate the owner of the field either, since that
+//! would indirectly modify the field. Similarly, if the field cannot be
+//! frozen or aliased, we cannot allow the owner to be frozen or aliased,
+//! since doing so indirectly freezes/aliases the field. This is the
+//! origin of inherited mutability.
+//!
+//! ### Restrictions for loans of owned referents
+//!
+//! Because the mutability of owned referents is inherited, restricting an
+//! owned referent is similar to restricting a field, in that it implies
+//! restrictions on the pointer. However, owned pointers have an important
+//! twist: if the owner `LV` is mutated, that causes the owned referent
+//! `*LV` to be freed! So whenever an owned referent `*LV` is borrowed, we
+//! must prevent the owned pointer `LV` from being mutated, which means
+//! that we always add `MUTATE` and `CLAIM` to the restriction set imposed
+//! on `LV`:
+//!
+//! ```text
+//! RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
+//! TYPE(LV) = Box<Ty>
+//! RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS
+//! ```
+//!
+//! ### Restrictions for loans of immutable borrowed referents
+//!
+//! Immutable borrowed referents are freely aliasable, meaning that
+//! the compiler does not prevent you from copying the pointer. This
+//! implies that issuing restrictions is useless. We might prevent the
+//! user from acting on `*LV` itself, but there could be another path
+//! `*LV1` that refers to the exact same memory, and we would not be
+//! restricting that path. Therefore, the rule for `&Ty` pointers
+//! always returns an empty set of restrictions, and it only permits
+//! restricting `MUTATE` and `CLAIM` actions:
+//!
+//! ```text
+//! RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
+//! TYPE(LV) = <' Ty
+//! LT <= LT' // (1)
+//! ACTIONS subset of [MUTATE, CLAIM]
+//! ```
+//!
+//! The reason that we can restrict `MUTATE` and `CLAIM` actions even
+//! without a restrictions list is that it is never legal to mutate nor to
+//! borrow mutably the contents of a `&Ty` pointer. In other words,
+//! those restrictions are already inherent in the type.
+//!
+//! Clause (1) in the rule for `&Ty` deserves mention. Here I
+//! specify that the lifetime of the loan must be less than the lifetime
+//! of the `&Ty` pointer. In simple cases, this clause is redundant, since
+//! the `LIFETIME()` function will already enforce the required rule:
+//!
+//! ```
+//! fn foo(point: &'a Point) -> &'static f32 {
+//! &point.x // Error
+//! }
+//! ```
+//!
+//! The above example fails to compile both because of clause (1) above
+//! but also by the basic `LIFETIME()` check. However, in more advanced
+//! examples involving multiple nested pointers, clause (1) is needed:
+//!
+//! ```
+//! fn foo(point: &'a &'b mut Point) -> &'b f32 {
+//! &point.x // Error
+//! }
+//! ```
+//!
+//! The `LIFETIME` rule here would accept `'b` because, in fact, the
+//! *memory is* guaranteed to remain valid (i.e., not be freed) for the
+//! lifetime `'b`, since the `&mut` pointer is valid for `'b`. However, we
+//! are returning an immutable reference, so we need the memory to be both
+//! valid and immutable. Even though `point.x` is referenced by an `&mut`
+//! pointer, it can still be considered immutable so long as that `&mut`
+//! pointer is found in an aliased location. That means the memory is
+//! guaranteed to be *immutable* for the lifetime of the `&` pointer,
+//! which is only `'a`, not `'b`. Hence this example yields an error.
+//!
+//! As a final twist, consider the case of two nested *immutable*
+//! pointers, rather than a mutable pointer within an immutable one:
+//!
+//! ```
+//! fn foo(point: &'a &'b Point) -> &'b f32 {
+//! &point.x // OK
+//! }
+//! ```
+//!
+//! This function is legal. The reason for this is that the inner pointer
+//! (`*point : &'b Point`) is enough to guarantee the memory is immutable
+//! and valid for the lifetime `'b`. This is reflected in
+//! `RESTRICTIONS()` by the fact that we do not recurse (i.e., we impose
+//! no restrictions on `LV`, which in this particular case is the pointer
+//! `point : &'a &'b Point`).
+//!
+//! #### Why both `LIFETIME()` and `RESTRICTIONS()`?
+//!
+//! Given the previous text, it might seem that `LIFETIME` and
+//! `RESTRICTIONS` should be folded together into one check, but there is
+//! a reason that they are separated. They answer separate concerns.
+//! The rules pertaining to `LIFETIME` exist to ensure that we don't
+//! create a borrowed pointer that outlives the memory it points at. So
+//! `LIFETIME` prevents a function like this:
+//!
+//! ```
+//! fn get_1<'a>() -> &'a int {
+//! let x = 1;
+//! &x
+//! }
+//! ```
+//!
+//! Here we would be returning a pointer into the stack. Clearly bad.
+//!
+//! However, the `RESTRICTIONS` rules are more concerned with how memory
+//! is used. The example above doesn't generate an error according to
+//! `RESTRICTIONS` because, for local variables, we don't require that the
+//! loan lifetime be a subset of the local variable lifetime. The idea
+//! here is that we *can* guarantee that `x` is not (e.g.) mutated for the
+//! lifetime `'a`, even though `'a` exceeds the function body and thus
+//! involves unknown code in the caller -- after all, `x` ceases to exist
+//! after we return and hence the remaining code in `'a` cannot possibly
+//! mutate it. This distinction is important for type checking functions
+//! like this one:
+//!
+//! ```
+//! fn inc_and_get<'a>(p: &'a mut Point) -> &'a int {
+//! p.x += 1;
+//! &p.x
+//! }
+//! ```
+//!
+//! In this case, we take in a `&mut` and return a frozen borrowed pointer
+//! with the same lifetime. So long as the lifetime of the returned value
+//! doesn't exceed the lifetime of the `&mut` we receive as input, this is
+//! fine, though it may seem surprising at first (it surprised me when I
+//! first worked it through). After all, we're guaranteeing that `*p`
+//! won't be mutated for the lifetime `'a`, even though we can't "see" the
+//! entirety of the code during that lifetime, since some of it occurs in
+//! our caller. But we *do* know that nobody can mutate `*p` except
+//! through `p`. So if we don't mutate `*p` and we don't return `p`, then
+//! we know that the right to mutate `*p` has been lost to our caller --
+//! in terms of capability, the caller passed in the ability to mutate
+//! `*p`, and we never gave it back. (Note that we can't return `p` while
+//! `*p` is borrowed since that would be a move of `p`, as `&mut` pointers
+//! are affine.)
+//!
+//! ### Restrictions for loans of const aliasable referents
+//!
+//! Freeze pointers are read-only. There may be `&mut` or `&` aliases, and
+//! we can not prevent *anything* but moves in that case. So the
+//! `RESTRICTIONS` function is only defined if `ACTIONS` is the empty set.
+//! Because moves from a `&const` lvalue are never legal, it is not
+//! necessary to add any restrictions at all to the final result.
+//!
+//! ```text
+//! RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed
+//! TYPE(LV) = &const Ty
+//! ```
+//!
+//! ### Restrictions for loans of mutable borrowed referents
+//!
+//! Mutable borrowed pointers are guaranteed to be the only way to mutate
+//! their referent. This permits us to take greater license with them; for
+//! example, the referent can be frozen simply be ensuring that we do not
+//! use the original pointer to perform mutate. Similarly, we can allow
+//! the referent to be claimed, so long as the original pointer is unused
+//! while the new claimant is live.
+//!
+//! The rule for mutable borrowed pointers is as follows:
+//!
+//! ```text
+//! RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed
+//! TYPE(LV) = <' mut Ty
+//! LT <= LT' // (1)
+//! RESTRICTIONS(LV, LT, ACTIONS) = RS // (2)
+//! ```
+//!
+//! Let's examine the two numbered clauses:
+//!
+//! Clause (1) specifies that the lifetime of the loan (`LT`) cannot
+//! exceed the lifetime of the `&mut` pointer (`LT'`). The reason for this
+//! is that the `&mut` pointer is guaranteed to be the only legal way to
+//! mutate its referent -- but only for the lifetime `LT'`. After that
+//! lifetime, the loan on the referent expires and hence the data may be
+//! modified by its owner again. This implies that we are only able to
+//! guarantee that the referent will not be modified or aliased for a
+//! maximum of `LT'`.
+//!
+//! Here is a concrete example of a bug this rule prevents:
+//!
+//! ```
+//! // Test region-reborrow-from-shorter-mut-ref.rs:
+//! fn copy_pointer<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T {
+//! &mut **p // ERROR due to clause (1)
+//! }
+//! fn main() {
+//! let mut x = 1;
+//! let mut y = &mut x; // <-'b-----------------------------+
+//! // +-'a--------------------+ |
+//! // v v |
+//! let z = copy_borrowed_ptr(&mut y); // y is lent |
+//! *y += 1; // Here y==z, so both should not be usable... |
+//! *z += 1; // ...and yet they would be, but for clause 1. |
+//! } // <------------------------------------------------------+
+//! ```
+//!
+//! Clause (2) propagates the restrictions on the referent to the pointer
+//! itself. This is the same as with an owned pointer, though the
+//! reasoning is mildly different. The basic goal in all cases is to
+//! prevent the user from establishing another route to the same data. To
+//! see what I mean, let's examine various cases of what can go wrong and
+//! show how it is prevented.
+//!
+//! **Example danger 1: Moving the base pointer.** One of the simplest
+//! ways to violate the rules is to move the base pointer to a new name
+//! and access it via that new name, thus bypassing the restrictions on
+//! the old name. Here is an example:
+//!
+//! ```
+//! // src/test/compile-fail/borrowck-move-mut-base-ptr.rs
+//! fn foo(t0: &mut int) {
+//! let p: &int = &*t0; // Freezes `*t0`
+//! let t1 = t0; //~ ERROR cannot move out of `t0`
+//! *t1 = 22; // OK, not a write through `*t0`
+//! }
+//! ```
+//!
+//! Remember that `&mut` pointers are linear, and hence `let t1 = t0` is a
+//! move of `t0` -- or would be, if it were legal. Instead, we get an
+//! error, because clause (2) imposes restrictions on `LV` (`t0`, here),
+//! and any restrictions on a path make it impossible to move from that
+//! path.
+//!
+//! **Example danger 2: Claiming the base pointer.** Another possible
+//! danger is to mutably borrow the base path. This can lead to two bad
+//! scenarios. The most obvious is that the mutable borrow itself becomes
+//! another path to access the same data, as shown here:
+//!
+//! ```
+//! // src/test/compile-fail/borrowck-mut-borrow-of-mut-base-ptr.rs
+//! fn foo<'a>(mut t0: &'a mut int,
+//! mut t1: &'a mut int) {
+//! let p: &int = &*t0; // Freezes `*t0`
+//! let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
+//! **t2 += 1; // Mutates `*t0`
+//! }
+//! ```
+//!
+//! In this example, `**t2` is the same memory as `*t0`. Because `t2` is
+//! an `&mut` pointer, `**t2` is a unique path and hence it would be
+//! possible to mutate `**t2` even though that memory was supposed to be
+//! frozen by the creation of `p`. However, an error is reported -- the
+//! reason is that the freeze `&*t0` will restrict claims and mutation
+//! against `*t0` which, by clause 2, in turn prevents claims and mutation
+//! of `t0`. Hence the claim `&mut t0` is illegal.
+//!
+//! Another danger with an `&mut` pointer is that we could swap the `t0`
+//! value away to create a new path:
+//!
+//! ```
+//! // src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
+//! fn foo<'a>(mut t0: &'a mut int,
+//! mut t1: &'a mut int) {
+//! let p: &int = &*t0; // Freezes `*t0`
+//! swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
+//! *t1 = 22;
+//! }
+//! ```
+//!
+//! This is illegal for the same reason as above. Note that if we added
+//! back a swap operator -- as we used to have -- we would want to be very
+//! careful to ensure this example is still illegal.
+//!
+//! **Example danger 3: Freeze the base pointer.** In the case where the
+//! referent is claimed, even freezing the base pointer can be dangerous,
+//! as shown in the following example:
+//!
+//! ```
+//! // src/test/compile-fail/borrowck-borrow-of-mut-base-ptr.rs
+//! fn foo<'a>(mut t0: &'a mut int,
+//! mut t1: &'a mut int) {
+//! let p: &mut int = &mut *t0; // Claims `*t0`
+//! let mut t2 = &t0; //~ ERROR cannot borrow `t0`
+//! let q: &int = &*t2; // Freezes `*t0` but not through `*p`
+//! *p += 1; // violates type of `*q`
+//! }
+//! ```
+//!
+//! Here the problem is that `*t0` is claimed by `p`, and hence `p` wants
+//! to be the controlling pointer through which mutation or freezes occur.
+//! But `t2` would -- if it were legal -- have the type `& &mut int`, and
+//! hence would be a mutable pointer in an aliasable location, which is
+//! considered frozen (since no one can write to `**t2` as it is not a
+//! unique path). Therefore, we could reasonably create a frozen `&int`
+//! pointer pointing at `*t0` that coexists with the mutable pointer `p`,
+//! which is clearly unsound.
+//!
+//! However, it is not always unsafe to freeze the base pointer. In
+//! particular, if the referent is frozen, there is no harm in it:
+//!
+//! ```
+//! // src/test/run-pass/borrowck-borrow-of-mut-base-ptr-safe.rs
+//! fn foo<'a>(mut t0: &'a mut int,
+//! mut t1: &'a mut int) {
+//! let p: &int = &*t0; // Freezes `*t0`
+//! let mut t2 = &t0;
+//! let q: &int = &*t2; // Freezes `*t0`, but that's ok...
+//! let r: &int = &*t0; // ...after all, could do same thing directly.
+//! }
+//! ```
+//!
+//! In this case, creating the alias `t2` of `t0` is safe because the only
+//! thing `t2` can be used for is to further freeze `*t0`, which is
+//! already frozen. In particular, we cannot assign to `*t0` through the
+//! new alias `t2`, as demonstrated in this test case:
+//!
+//! ```
+//! // src/test/run-pass/borrowck-borrow-mut-base-ptr-in-aliasable-loc.rs
+//! fn foo(t0: & &mut int) {
+//! let t1 = t0;
+//! let p: &int = &**t0;
+//! **t1 = 22; //~ ERROR cannot assign
+//! }
+//! ```
+//!
+//! This distinction is reflected in the rules. When doing an `&mut`
+//! borrow -- as in the first example -- the set `ACTIONS` will be
+//! `CLAIM|MUTATE|FREEZE`, because claiming the referent implies that it
+//! cannot be claimed, mutated, or frozen by anyone else. These
+//! restrictions are propagated back to the base path and hence the base
+//! path is considered unfreezable.
+//!
+//! In contrast, when the referent is merely frozen -- as in the second
+//! example -- the set `ACTIONS` will be `CLAIM|MUTATE`, because freezing
+//! the referent implies that it cannot be claimed or mutated but permits
+//! others to freeze. Hence when these restrictions are propagated back to
+//! the base path, it will still be considered freezable.
+//!
+//!
+//!
+//! **FIXME #10520: Restrictions against mutating the base pointer.** When
+//! an `&mut` pointer is frozen or claimed, we currently pass along the
+//! restriction against MUTATE to the base pointer. I do not believe this
+//! restriction is needed. It dates from the days when we had a way to
+//! mutate that preserved the value being mutated (i.e., swap). Nowadays
+//! the only form of mutation is assignment, which destroys the pointer
+//! being mutated -- therefore, a mutation cannot create a new path to the
+//! same data. Rather, it removes an existing path. This implies that not
+//! only can we permit mutation, we can have mutation kill restrictions in
+//! the dataflow sense.
+//!
+//! **WARNING:** We do not currently have `const` borrows in the
+//! language. If they are added back in, we must ensure that they are
+//! consistent with all of these examples. The crucial question will be
+//! what sorts of actions are permitted with a `&const &mut` pointer. I
+//! would suggest that an `&mut` referent found in an `&const` location be
+//! prohibited from both freezes and claims. This would avoid the need to
+//! prevent `const` borrows of the base pointer when the referent is
+//! borrowed.
+//!
+//! # Moves and initialization
+//!
+//! The borrow checker is also in charge of ensuring that:
+//!
+//! - all memory which is accessed is initialized
+//! - immutable local variables are assigned at most once.
+//!
+//! These are two separate dataflow analyses built on the same
+//! framework. Let's look at checking that memory is initialized first;
+//! the checking of immutable local variable assignments works in a very
+//! similar way.
+//!
+//! To track the initialization of memory, we actually track all the
+//! points in the program that *create uninitialized memory*, meaning
+//! moves and the declaration of uninitialized variables. For each of
+//! these points, we create a bit in the dataflow set. Assignments to a
+//! variable `x` or path `a.b.c` kill the move/uninitialization bits for
+//! those paths and any subpaths (e.g., `x`, `x.y`, `a.b.c`, `*a.b.c`).
+//! Bits are unioned when two control-flow paths join. Thus, the
+//! presence of a bit indicates that the move may have occurred without an
+//! intervening assignment to the same memory. At each use of a variable,
+//! we examine the bits in scope, and check that none of them are
+//! moves/uninitializations of the variable that is being used.
+//!
+//! Let's look at a simple example:
+//!
+//! ```
+//! fn foo(a: Box<int>) {
+//! let b: Box<int>; // Gen bit 0.
+//!
+//! if cond { // Bits: 0
+//! use(&*a);
+//! b = a; // Gen bit 1, kill bit 0.
+//! use(&*b);
+//! } else {
+//! // Bits: 0
+//! }
+//! // Bits: 0,1
+//! use(&*a); // Error.
+//! use(&*b); // Error.
+//! }
+//!
+//! fn use(a: &int) { }
+//! ```
+//!
+//! In this example, the variable `b` is created uninitialized. In one
+//! branch of an `if`, we then move the variable `a` into `b`. Once we
+//! exit the `if`, therefore, it is an error to use `a` or `b` since both
+//! are only conditionally initialized. I have annotated the dataflow
+//! state using comments. There are two dataflow bits, with bit 0
+//! corresponding to the creation of `b` without an initializer, and bit 1
+//! corresponding to the move of `a`. The assignment `b = a` both
+//! generates bit 1, because it is a move of `a`, and kills bit 0, because
+//! `b` is now initialized. On the else branch, though, `b` is never
+//! initialized, and so bit 0 remains untouched. When the two flows of
+//! control join, we union the bits from both sides, resulting in both
+//! bits 0 and 1 being set. Thus any attempt to use `a` uncovers the bit 1
+//! from the "then" branch, showing that `a` may be moved, and any attempt
+//! to use `b` uncovers bit 0, from the "else" branch, showing that `b`
+//! may not be initialized.
+//!
+//! ## Initialization of immutable variables
+//!
+//! Initialization of immutable variables works in a very similar way,
+//! except that:
+//!
+//! 1. we generate bits for each assignment to a variable;
+//! 2. the bits are never killed except when the variable goes out of scope.
+//!
+//! Thus the presence of an assignment bit indicates that the assignment
+//! may have occurred. Note that assignments are only killed when the
+//! variable goes out of scope, as it is not relevant whether or not there
+//! has been a move in the meantime. Using these bits, we can declare that
+//! an assignment to an immutable variable is legal iff there is no other
+//! assignment bit to that same variable in scope.
+//!
+//! ## Why is the design made this way?
+//!
+//! It may seem surprising that we assign dataflow bits to *each move*
+//! rather than *each path being moved*. This is somewhat less efficient,
+//! since on each use, we must iterate through all moves and check whether
+//! any of them correspond to the path in question. Similar concerns apply
+//! to the analysis for double assignments to immutable variables. The
+//! main reason to do it this way is that it allows us to print better
+//! error messages, because when a use occurs, we can print out the
+//! precise move that may be in scope, rather than simply having to say
+//! "the variable may not be initialized".
+//!
+//! ## Data structures used in the move analysis
+//!
+//! The move analysis maintains several data structures that enable it to
+//! cross-reference moves and assignments to determine when they may be
+//! moving/assigning the same memory. These are all collected into the
+//! `MoveData` and `FlowedMoveData` structs. The former represents the set
+//! of move paths, moves, and assignments, and the latter adds in the
+//! results of a dataflow computation.
+//!
+//! ### Move paths
+//!
+//! The `MovePath` tree tracks every path that is moved or assigned to.
+//! These paths have the same form as the `LoanPath` data structure, which
+//! in turn is the "real world version of the lvalues `LV` that we
+//! introduced earlier. The difference between a `MovePath` and a `LoanPath`
+//! is that move paths are:
+//!
+//! 1. Canonicalized, so that we have exactly one copy of each, and
+//! we can refer to move paths by index;
+//! 2. Cross-referenced with other paths into a tree, so that given a move
+//! path we can efficiently find all parent move paths and all
+//! extensions (e.g., given the `a.b` move path, we can easily find the
+//! move path `a` and also the move paths `a.b.c`)
+//! 3. Cross-referenced with moves and assignments, so that we can
+//! easily find all moves and assignments to a given path.
+//!
+//! The mechanism that we use is to create a `MovePath` record for each
+//! move path. These are arranged in an array and are referenced using
+//! `MovePathIndex` values, which are newtype'd indices. The `MovePath`
+//! structs are arranged into a tree, representing using the standard
+//! Knuth representation where each node has a child 'pointer' and a "next
+//! sibling" 'pointer'. In addition, each `MovePath` has a parent
+//! 'pointer'. In this case, the 'pointers' are just `MovePathIndex`
+//! values.
+//!
+//! In this way, if we want to find all base paths of a given move path,
+//! we can just iterate up the parent pointers (see `each_base_path()` in
+//! the `move_data` module). If we want to find all extensions, we can
+//! iterate through the subtree (see `each_extending_path()`).
+//!
+//! ### Moves and assignments
+//!
+//! There are structs to represent moves (`Move`) and assignments
+//! (`Assignment`), and these are also placed into arrays and referenced
+//! by index. All moves of a particular path are arranged into a linked
+//! lists, beginning with `MovePath.first_move` and continuing through
+//! `Move.next_move`.
+//!
+//! We distinguish between "var" assignments, which are assignments to a
+//! variable like `x = foo`, and "path" assignments (`x.f = foo`). This
+//! is because we need to assign dataflows to the former, but not the
+//! latter, so as to check for double initialization of immutable
+//! variables.
+//!
+//! ### Gathering and checking moves
+//!
+//! Like loans, we distinguish two phases. The first, gathering, is where
+//! we uncover all the moves and assignments. As with loans, we do some
+//! basic sanity checking in this phase, so we'll report errors if you
+//! attempt to move out of a borrowed pointer etc. Then we do the dataflow
+//! (see `FlowedMoveData::new`). Finally, in the `check_loans.rs` code, we
+//! walk back over, identify all uses, assignments, and captures, and
+//! check that they are legal given the set of dataflow bits we have
+//! computed for that program point.
+//!
+//! # Drop flags and structural fragments
+//!
+//! In addition to the job of enforcing memory safety, the borrow checker
+//! code is also responsible for identifying the *structural fragments* of
+//! data in the function, to support out-of-band dynamic drop flags
+//! allocated on the stack. (For background, see [RFC PR #320].)
+//!
+//! [RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320
+//!
+//! Semantically, each piece of data that has a destructor may need a
+//! boolean flag to indicate whether or not its destructor has been run
+//! yet. However, in many cases there is no need to actually maintain such
+//! a flag: It can be apparent from the code itself that a given path is
+//! always initialized (or always deinitialized) when control reaches the
+//! end of its owner's scope, and thus we can unconditionally emit (or
+//! not) the destructor invocation for that path.
+//!
+//! A simple example of this is the following:
+//!
+//! ```rust
+//! struct D { p: int }
+//! impl D { fn new(x: int) -> D { ... }
+//! impl Drop for D { ... }
+//!
+//! fn foo(a: D, b: D, t: || -> bool) {
+//! let c: D;
+//! let d: D;
+//! if t() { c = b; }
+//! }
+//! ```
+//!
+//! At the end of the body of `foo`, the compiler knows that `a` is
+//! initialized, introducing a drop obligation (deallocating the boxed
+//! integer) for the end of `a`'s scope that is run unconditionally.
+//! Likewise the compiler knows that `d` is not initialized, and thus it
+//! leave out the drop code for `d`.
+//!
+//! The compiler cannot statically know the drop-state of `b` nor `c` at
+//! the end of their scope, since that depends on the value of
+//! `t`. Therefore, we need to insert boolean flags to track whether we
+//! need to drop `b` and `c`.
+//!
+//! However, the matter is not as simple as just mapping local variables
+//! to their corresponding drop flags when necessary. In particular, in
+//! addition to being able to move data out of local variables, Rust
+//! allows one to move values in and out of structured data.
+//!
+//! Consider the following:
+//!
+//! ```rust
+//! struct S { x: D, y: D, z: D }
+//!
+//! fn foo(a: S, mut b: S, t: || -> bool) {
+//! let mut c: S;
+//! let d: S;
+//! let e: S = a.clone();
+//! if t() {
+//! c = b;
+//! b.x = e.y;
+//! }
+//! if t() { c.y = D::new(4); }
+//! }
+//! ```
+//!
+//! As before, the drop obligations of `a` and `d` can be statically
+//! determined, and again the state of `b` and `c` depend on dynamic
+//! state. But additionally, the dynamic drop obligations introduced by
+//! `b` and `c` are not just per-local boolean flags. For example, if the
+//! first call to `t` returns `false` and the second call `true`, then at
+//! the end of their scope, `b` will be completely initialized, but only
+//! `c.y` in `c` will be initialized. If both calls to `t` return `true`,
+//! then at the end of their scope, `c` will be completely initialized,
+//! but only `b.x` will be initialized in `b`, and only `e.x` and `e.z`
+//! will be initialized in `e`.
+//!
+//! Note that we need to cover the `z` field in each case in some way,
+//! since it may (or may not) need to be dropped, even though `z` is never
+//! directly mentioned in the body of the `foo` function. We call a path
+//! like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes
+//! from the same structure `S` that declared the field `x` in `b.x`.
+//!
+//! In general we need to maintain boolean flags that match the
+//! `S`-structure of both `b` and `c`. In addition, we need to consult
+//! such a flag when doing an assignment (such as `c.y = D::new(4);`
+//! above), in order to know whether or not there is a previous value that
+//! needs to be dropped before we do the assignment.
+//!
+//! So for any given function, we need to determine what flags are needed
+//! to track its drop obligations. Our strategy for determining the set of
+//! flags is to represent the fragmentation of the structure explicitly:
+//! by starting initially from the paths that are explicitly mentioned in
+//! moves and assignments (such as `b.x` and `c.y` above), and then
+//! traversing the structure of the path's type to identify leftover
+//! *unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z`
+//! are leftover unmoved fragments. Each fragment represents a drop
+//! obligation that may need to be tracked. Paths that are only moved or
+//! assigned in their entirety (like `a` and `d`) are treated as a single
+//! drop obligation.
+//!
+//! The fragment construction process works by piggy-backing on the
+//! existing `move_data` module. We already have callbacks that visit each
+//! direct move and assignment; these form the basis for the sets of
+//! moved_leaf_paths and assigned_leaf_paths. From these leaves, we can
+//! walk up their parent chain to identify all of their parent paths.
+//! We need to identify the parents because of cases like the following:
+//!
+//! ```rust
+//! struct Pair<X,Y>{ x: X, y: Y }
+//! fn foo(dd_d_d: Pair<Pair<Pair<D, D>, D>, D>) {
+//! other_function(dd_d_d.x.y);
+//! }
+//! ```
+//!
+//! In this code, the move of the path `dd_d.x.y` leaves behind not only
+//! the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well.
+//!
+//! Once we have identified the directly-referenced leaves and their
+//! parents, we compute the left-over fragments, in the function
+//! `fragments::add_fragment_siblings`. As of this writing this works by
+//! looking at each directly-moved or assigned path P, and blindly
+//! gathering all sibling fields of P (as well as siblings for the parents
+//! of P, etc). After accumulating all such siblings, we filter out the
+//! entries added as siblings of P that turned out to be
+//! directly-referenced paths (or parents of directly referenced paths)
+//! themselves, thus leaving the never-referenced "left-overs" as the only
+//! thing left from the gathering step.
+//!
+//! ## Array structural fragments
+//!
+//! A special case of the structural fragments discussed above are
+//! the elements of an array that has been passed by value, such as
+//! the following:
+//!
+//! ```rust
+//! fn foo(a: [D, ..10], i: uint) -> D {
+//! a[i]
+//! }
+//! ```
+//!
+//! The above code moves a single element out of the input array `a`.
+//! The remainder of the array still needs to be dropped; i.e., it
+//! is a structural fragment. Note that after performing such a move,
+//! it is not legal to read from the array `a`. There are a number of
+//! ways to deal with this, but the important thing to note is that
+//! the semantics needs to distinguish in some manner between a
+//! fragment that is the *entire* array versus a fragment that represents
+//! all-but-one element of the array. A place where that distinction
+//! would arise is the following:
+//!
+//! ```rust
+//! fn foo(a: [D, ..10], b: [D, ..10], i: uint, t: bool) -> D {
+//! if t {
+//! a[i]
+//! } else {
+//! b[i]
+//! }
+//!
+//! // When control exits, we will need either to drop all of `a`
+//! // and all-but-one of `b`, or to drop all of `b` and all-but-one
+//! // of `a`.
+//! }
+//! ```
+//!
+//! There are a number of ways that the trans backend could choose to
+//! compile this (e.g. a `[bool, ..10]` array for each such moved array;
+//! or an `Option<uint>` for each moved array). From the viewpoint of the
+//! borrow-checker, the important thing is to record what kind of fragment
+//! is implied by the relevant moves.
+//!
+//! # Future work
+//!
+//! While writing up these docs, I encountered some rules I believe to be
+//! stricter than necessary:
+//!
+//! - I think restricting the `&mut` LV against moves and `ALIAS` is sufficient,
+//! `MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was
+//! a built-in operator, but as it is not, it is implied by `CLAIM`,
+//! and `CLAIM` is implied by `ALIAS`. The only net effect of this is an
+//! extra error message in some cases, though.
+//! - I have not described how closures interact. Current code is unsound.
+//! I am working on describing and implementing the fix.
+//! - If we wish, we can easily extend the move checking to allow finer-grained
+//! tracking of what is initialized and what is not, enabling code like
+//! this:
+//!
+//! a = x.f.g; // x.f.g is now uninitialized
+//! // here, x and x.f are not usable, but x.f.h *is*
+//! x.f.g = b; // x.f.g is not initialized
+//! // now x, x.f, x.f.g, x.f.h are all usable
+//!
+//! What needs to change here, most likely, is that the `moves` module
+//! should record not only what paths are moved, but what expressions
+//! are actual *uses*. For example, the reference to `x` in `x.f.g = b`
+//! is not a true *use* in the sense that it requires `x` to be fully
+//! initialized. This is in fact why the above code produces an error
+//! today: the reference to `x` in `x.f.g = b` is considered illegal
+//! because `x` is not fully initialized.
+//!
+//! There are also some possible refactorings:
+//!
+//! - It might be nice to replace all loan paths with the MovePath mechanism,
+//! since they allow lightweight comparison using an integer.
--- /dev/null
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Helper routines used for fragmenting structural paths due to moves for
+//! tracking drop obligations. Please see the extensive comments in the
+//! section "Structural fragments" in `doc.rs`.
+
+use self::Fragment::*;
+
+use borrowck::{LoanPath};
+use borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
+use borrowck::LoanPathElem::{LpDeref, LpInterior};
+use borrowck::move_data::{InvalidMovePathIndex};
+use borrowck::move_data::{MoveData, MovePathIndex};
+use rustc::session::config;
+use rustc::middle::ty;
+use rustc::middle::mem_categorization as mc;
+use rustc::util::ppaux::{Repr, UserString};
+use std::mem;
+use std::rc::Rc;
+use std::slice;
+use syntax::ast;
+use syntax::ast_map;
+use syntax::attr::AttrMetaMethods;
+use syntax::codemap::Span;
+
+#[deriving(PartialEq, Eq, PartialOrd, Ord)]
+enum Fragment {
+ // This represents the path described by the move path index
+ Just(MovePathIndex),
+
+ // This represents the collection of all but one of the elements
+ // from an array at the path described by the move path index.
+ // Note that attached MovePathIndex should have mem_categorization
+ // of InteriorElement (i.e. array dereference `[]`).
+ AllButOneFrom(MovePathIndex),
+}
+
+impl Fragment {
+ fn loan_path_repr<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String {
+ let repr = |mpi| move_data.path_loan_path(mpi).repr(tcx);
+ match *self {
+ Just(mpi) => repr(mpi),
+ AllButOneFrom(mpi) => format!("$(allbutone {})", repr(mpi)),
+ }
+ }
+
+ fn loan_path_user_string<'tcx>(&self,
+ move_data: &MoveData<'tcx>,
+ tcx: &ty::ctxt<'tcx>) -> String {
+ let user_string = |mpi| move_data.path_loan_path(mpi).user_string(tcx);
+ match *self {
+ Just(mpi) => user_string(mpi),
+ AllButOneFrom(mpi) => format!("$(allbutone {})", user_string(mpi)),
+ }
+ }
+}
+
+pub struct FragmentSets {
+ /// During move_data construction, `moved_leaf_paths` tracks paths
+ /// that have been used directly by being moved out of. When
+ /// move_data construction has been completed, `moved_leaf_paths`
+ /// tracks such paths that are *leaf fragments* (e.g. `a.j` if we
+ /// never move out any child like `a.j.x`); any parent paths
+ /// (e.g. `a` for the `a.j` example) are moved over to
+ /// `parents_of_fragments`.
+ moved_leaf_paths: Vec<MovePathIndex>,
+
+ /// `assigned_leaf_paths` tracks paths that have been used
+ /// directly by being overwritten, but is otherwise much like
+ /// `moved_leaf_paths`.
+ assigned_leaf_paths: Vec<MovePathIndex>,
+
+ /// `parents_of_fragments` tracks paths that are definitely
+ /// parents of paths that have been moved.
+ ///
+ /// FIXME(pnkfelix) probably do not want/need
+ /// `parents_of_fragments` at all, if we can avoid it.
+ ///
+ /// Update: I do not see a way to to avoid it. Maybe just remove
+ /// above fixme, or at least document why doing this may be hard.
+ parents_of_fragments: Vec<MovePathIndex>,
+
+ /// During move_data construction (specifically the
+ /// fixup_fragment_sets call), `unmoved_fragments` tracks paths
+ /// that have been "left behind" after a sibling has been moved or
+ /// assigned. When move_data construction has been completed,
+ /// `unmoved_fragments` tracks paths that were *only* results of
+ /// being left-behind, and never directly moved themselves.
+ unmoved_fragments: Vec<Fragment>,
+}
+
+impl FragmentSets {
+ pub fn new() -> FragmentSets {
+ FragmentSets {
+ unmoved_fragments: Vec::new(),
+ moved_leaf_paths: Vec::new(),
+ assigned_leaf_paths: Vec::new(),
+ parents_of_fragments: Vec::new(),
+ }
+ }
+
+ pub fn add_move(&mut self, path_index: MovePathIndex) {
+ self.moved_leaf_paths.push(path_index);
+ }
+
+ pub fn add_assignment(&mut self, path_index: MovePathIndex) {
+ self.assigned_leaf_paths.push(path_index);
+ }
+}
+
+pub fn instrument_move_fragments<'tcx>(this: &MoveData<'tcx>,
+ tcx: &ty::ctxt<'tcx>,
+ sp: Span,
+ id: ast::NodeId) {
+ let (span_err, print) = {
+ let attrs : &[ast::Attribute];
+ attrs = match tcx.map.find(id) {
+ Some(ast_map::NodeItem(ref item)) =>
+ item.attrs.as_slice(),
+ Some(ast_map::NodeImplItem(&ast::MethodImplItem(ref m))) =>
+ m.attrs.as_slice(),
+ Some(ast_map::NodeTraitItem(&ast::ProvidedMethod(ref m))) =>
+ m.attrs.as_slice(),
+ _ => [].as_slice(),
+ };
+
+ let span_err =
+ attrs.iter().any(|a| a.check_name("rustc_move_fragments"));
+ let print = tcx.sess.debugging_opt(config::PRINT_MOVE_FRAGMENTS);
+
+ (span_err, print)
+ };
+
+ if !span_err && !print { return; }
+
+ let instrument_all_paths = |kind, vec_rc: &Vec<MovePathIndex>| {
+ for (i, mpi) in vec_rc.iter().enumerate() {
+ let render = || this.path_loan_path(*mpi).user_string(tcx);
+ if span_err {
+ tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
+ }
+ if print {
+ println!("id:{} {}[{}] `{}`", id, kind, i, render());
+ }
+ }
+ };
+
+ let instrument_all_fragments = |kind, vec_rc: &Vec<Fragment>| {
+ for (i, f) in vec_rc.iter().enumerate() {
+ let render = || f.loan_path_user_string(this, tcx);
+ if span_err {
+ tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice());
+ }
+ if print {
+ println!("id:{} {}[{}] `{}`", id, kind, i, render());
+ }
+ }
+ };
+
+ let fragments = this.fragments.borrow();
+ instrument_all_paths("moved_leaf_path", &fragments.moved_leaf_paths);
+ instrument_all_fragments("unmoved_fragment", &fragments.unmoved_fragments);
+ instrument_all_paths("parent_of_fragments", &fragments.parents_of_fragments);
+ instrument_all_paths("assigned_leaf_path", &fragments.assigned_leaf_paths);
+}
+
+/// Normalizes the fragment sets in `this`; i.e., removes duplicate entries, constructs the set of
+/// parents, and constructs the left-over fragments.
+///
+/// Note: "left-over fragments" means paths that were not directly referenced in moves nor
+/// assignments, but must nonetheless be tracked as potential drop obligations.
+pub fn fixup_fragment_sets<'tcx>(this: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) {
+
+ let mut fragments = this.fragments.borrow_mut();
+
+ // Swap out contents of fragments so that we can modify the fields
+ // without borrowing the common fragments.
+ let mut unmoved = mem::replace(&mut fragments.unmoved_fragments, vec![]);
+ let mut parents = mem::replace(&mut fragments.parents_of_fragments, vec![]);
+ let mut moved = mem::replace(&mut fragments.moved_leaf_paths, vec![]);
+ let mut assigned = mem::replace(&mut fragments.assigned_leaf_paths, vec![]);
+
+ let path_lps = |mpis: &[MovePathIndex]| -> Vec<String> {
+ mpis.iter().map(|mpi| this.path_loan_path(*mpi).repr(tcx)).collect()
+ };
+
+ let frag_lps = |fs: &[Fragment]| -> Vec<String> {
+ fs.iter().map(|f| f.loan_path_repr(this, tcx)).collect()
+ };
+
+ // First, filter out duplicates
+ moved.sort();
+ moved.dedup();
+ debug!("fragments 1 moved: {}", path_lps(moved.as_slice()));
+
+ assigned.sort();
+ assigned.dedup();
+ debug!("fragments 1 assigned: {}", path_lps(assigned.as_slice()));
+
+ // Second, build parents from the moved and assigned.
+ for m in moved.iter() {
+ let mut p = this.path_parent(*m);
+ while p != InvalidMovePathIndex {
+ parents.push(p);
+ p = this.path_parent(p);
+ }
+ }
+ for a in assigned.iter() {
+ let mut p = this.path_parent(*a);
+ while p != InvalidMovePathIndex {
+ parents.push(p);
+ p = this.path_parent(p);
+ }
+ }
+
+ parents.sort();
+ parents.dedup();
+ debug!("fragments 2 parents: {}", path_lps(parents.as_slice()));
+
+ // Third, filter the moved and assigned fragments down to just the non-parents
+ moved.retain(|f| non_member(*f, parents.as_slice()));
+ debug!("fragments 3 moved: {}", path_lps(moved.as_slice()));
+
+ assigned.retain(|f| non_member(*f, parents.as_slice()));
+ debug!("fragments 3 assigned: {}", path_lps(assigned.as_slice()));
+
+ // Fourth, build the leftover from the moved, assigned, and parents.
+ for m in moved.iter() {
+ let lp = this.path_loan_path(*m);
+ add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+ }
+ for a in assigned.iter() {
+ let lp = this.path_loan_path(*a);
+ add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+ }
+ for p in parents.iter() {
+ let lp = this.path_loan_path(*p);
+ add_fragment_siblings(this, tcx, &mut unmoved, lp, None);
+ }
+
+ unmoved.sort();
+ unmoved.dedup();
+ debug!("fragments 4 unmoved: {}", frag_lps(unmoved.as_slice()));
+
+ // Fifth, filter the leftover fragments down to its core.
+ unmoved.retain(|f| match *f {
+ AllButOneFrom(_) => true,
+ Just(mpi) => non_member(mpi, parents.as_slice()) &&
+ non_member(mpi, moved.as_slice()) &&
+ non_member(mpi, assigned.as_slice())
+ });
+ debug!("fragments 5 unmoved: {}", frag_lps(unmoved.as_slice()));
+
+ // Swap contents back in.
+ fragments.unmoved_fragments = unmoved;
+ fragments.parents_of_fragments = parents;
+ fragments.moved_leaf_paths = moved;
+ fragments.assigned_leaf_paths = assigned;
+
+ return;
+
+ fn non_member(elem: MovePathIndex, set: &[MovePathIndex]) -> bool {
+ match set.binary_search_elem(&elem) {
+ slice::BinarySearchResult::Found(_) => false,
+ slice::BinarySearchResult::NotFound(_) => true,
+ }
+ }
+}
+
+/// Adds all of the precisely-tracked siblings of `lp` as potential move paths of interest. For
+/// example, if `lp` represents `s.x.j`, then adds moves paths for `s.x.i` and `s.x.k`, the
+/// siblings of `s.x.j`.
+fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>,
+ tcx: &ty::ctxt<'tcx>,
+ gathered_fragments: &mut Vec<Fragment>,
+ lp: Rc<LoanPath<'tcx>>,
+ origin_id: Option<ast::NodeId>) {
+ match lp.kind {
+ LpVar(_) | LpUpvar(..) => {} // Local variables have no siblings.
+
+ // Consuming a downcast is like consuming the original value, so propage inward.
+ LpDowncast(ref loan_parent, _) => {
+ add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
+ }
+
+ // *LV for OwnedPtr consumes the contents of the box (at
+ // least when it is non-copy...), so propagate inward.
+ LpExtend(ref loan_parent, _, LpDeref(mc::OwnedPtr)) => {
+ add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id);
+ }
+
+ // *LV for unsafe and borrowed pointers do not consume their loan path, so stop here.
+ LpExtend(_, _, LpDeref(mc::UnsafePtr(..))) |
+ LpExtend(_, _, LpDeref(mc::Implicit(..))) |
+ LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) => {}
+
+ // FIXME(pnkfelix): LV[j] should be tracked, at least in the
+ // sense of we will track the remaining drop obligation of the
+ // rest of the array.
+ //
+ // LV[j] is not tracked precisely
+ LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
+ let mp = this.move_path(tcx, lp.clone());
+ gathered_fragments.push(AllButOneFrom(mp));
+ }
+
+ // field access LV.x and tuple access LV#k are the cases
+ // we are interested in
+ LpExtend(ref loan_parent, mc,
+ LpInterior(mc::InteriorField(ref field_name))) => {
+ let enum_variant_info = match loan_parent.kind {
+ LpDowncast(ref loan_parent_2, variant_def_id) =>
+ Some((variant_def_id, loan_parent_2.clone())),
+ LpExtend(..) | LpVar(..) | LpUpvar(..) =>
+ None,
+ };
+ add_fragment_siblings_for_extension(
+ this,
+ tcx,
+ gathered_fragments,
+ loan_parent, mc, field_name, &lp, origin_id, enum_variant_info);
+ }
+ }
+}
+
+/// We have determined that `origin_lp` destructures to LpExtend(parent, original_field_name).
+/// Based on this, add move paths for all of the siblings of `origin_lp`.
+fn add_fragment_siblings_for_extension<'tcx>(this: &MoveData<'tcx>,
+ tcx: &ty::ctxt<'tcx>,
+ gathered_fragments: &mut Vec<Fragment>,
+ parent_lp: &Rc<LoanPath<'tcx>>,
+ mc: mc::MutabilityCategory,
+ origin_field_name: &mc::FieldName,
+ origin_lp: &Rc<LoanPath<'tcx>>,
+ origin_id: Option<ast::NodeId>,
+ enum_variant_info: Option<(ast::DefId,
+ Rc<LoanPath<'tcx>>)>) {
+ let parent_ty = parent_lp.to_type();
+
+ let add_fragment_sibling_local = |field_name, variant_did| {
+ add_fragment_sibling_core(
+ this, tcx, gathered_fragments, parent_lp.clone(), mc, field_name, origin_lp,
+ variant_did);
+ };
+
+ match (&parent_ty.sty, enum_variant_info) {
+ (&ty::ty_tup(ref v), None) => {
+ let tuple_idx = match *origin_field_name {
+ mc::PositionalField(tuple_idx) => tuple_idx,
+ mc::NamedField(_) =>
+ panic!("tuple type {} should not have named fields.",
+ parent_ty.repr(tcx)),
+ };
+ let tuple_len = v.len();
+ for i in range(0, tuple_len) {
+ if i == tuple_idx { continue }
+ let field_name = mc::PositionalField(i);
+ add_fragment_sibling_local(field_name, None);
+ }
+ }
+
+ (&ty::ty_struct(def_id, ref _substs), None) => {
+ let fields = ty::lookup_struct_fields(tcx, def_id);
+ match *origin_field_name {
+ mc::NamedField(ast_name) => {
+ for f in fields.iter() {
+ if f.name == ast_name {
+ continue;
+ }
+ let field_name = mc::NamedField(f.name);
+ add_fragment_sibling_local(field_name, None);
+ }
+ }
+ mc::PositionalField(tuple_idx) => {
+ for (i, _f) in fields.iter().enumerate() {
+ if i == tuple_idx {
+ continue
+ }
+ let field_name = mc::PositionalField(i);
+ add_fragment_sibling_local(field_name, None);
+ }
+ }
+ }
+ }
+
+ (&ty::ty_enum(enum_def_id, ref substs), ref enum_variant_info) => {
+ let variant_info = {
+ let mut variants = ty::substd_enum_variants(tcx, enum_def_id, substs);
+ match *enum_variant_info {
+ Some((variant_def_id, ref _lp2)) =>
+ variants.iter()
+ .find(|variant| variant.id == variant_def_id)
+ .expect("enum_variant_with_id(): no variant exists with that ID")
+ .clone(),
+ None => {
+ assert_eq!(variants.len(), 1);
+ variants.pop().unwrap()
+ }
+ }
+ };
+ match *origin_field_name {
+ mc::NamedField(ast_name) => {
+ let variant_arg_names = variant_info.arg_names.as_ref().unwrap();
+ for variant_arg_ident in variant_arg_names.iter() {
+ if variant_arg_ident.name == ast_name {
+ continue;
+ }
+ let field_name = mc::NamedField(variant_arg_ident.name);
+ add_fragment_sibling_local(field_name, Some(variant_info.id));
+ }
+ }
+ mc::PositionalField(tuple_idx) => {
+ let variant_arg_types = &variant_info.args;
+ for (i, _variant_arg_ty) in variant_arg_types.iter().enumerate() {
+ if tuple_idx == i {
+ continue;
+ }
+ let field_name = mc::PositionalField(i);
+ add_fragment_sibling_local(field_name, None);
+ }
+ }
+ }
+ }
+
+ ref sty_and_variant_info => {
+ let msg = format!("type {} ({}) is not fragmentable",
+ parent_ty.repr(tcx), sty_and_variant_info);
+ let opt_span = origin_id.and_then(|id|tcx.map.opt_span(id));
+ tcx.sess.opt_span_bug(opt_span, msg.as_slice())
+ }
+ }
+}
+
+/// Adds the single sibling `LpExtend(parent, new_field_name)` of `origin_lp` (the original
+/// loan-path).
+fn add_fragment_sibling_core<'tcx>(this: &MoveData<'tcx>,
+ tcx: &ty::ctxt<'tcx>,
+ gathered_fragments: &mut Vec<Fragment>,
+ parent: Rc<LoanPath<'tcx>>,
+ mc: mc::MutabilityCategory,
+ new_field_name: mc::FieldName,
+ origin_lp: &Rc<LoanPath<'tcx>>,
+ enum_variant_did: Option<ast::DefId>) -> MovePathIndex {
+ let opt_variant_did = match parent.kind {
+ LpDowncast(_, variant_did) => Some(variant_did),
+ LpVar(..) | LpUpvar(..) | LpExtend(..) => enum_variant_did,
+ };
+
+ let loan_path_elem = LpInterior(mc::InteriorField(new_field_name));
+ let new_lp_type = match new_field_name {
+ mc::NamedField(ast_name) =>
+ ty::named_element_ty(tcx, parent.to_type(), ast_name, opt_variant_did),
+ mc::PositionalField(idx) =>
+ ty::positional_element_ty(tcx, parent.to_type(), idx, opt_variant_did),
+ };
+ let new_lp_variant = LpExtend(parent, mc, loan_path_elem);
+ let new_lp = LoanPath::new(new_lp_variant, new_lp_type.unwrap());
+ debug!("add_fragment_sibling_core(new_lp={}, origin_lp={})",
+ new_lp.repr(tcx), origin_lp.repr(tcx));
+ let mp = this.move_path(tcx, Rc::new(new_lp));
+
+ // Do not worry about checking for duplicates here; we will sort
+ // and dedup after all are added.
+ gathered_fragments.push(Just(mp));
+
+ mp
+}
--- /dev/null
+// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Computes moves.
+
+use borrowck::*;
+use borrowck::LoanPathKind::*;
+use borrowck::gather_loans::move_error::MoveSpanAndPath;
+use borrowck::gather_loans::move_error::{MoveError, MoveErrorCollector};
+use borrowck::move_data::*;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::ty;
+use rustc::util::ppaux::Repr;
+use std::rc::Rc;
+use syntax::ast;
+use syntax::codemap::Span;
+
+struct GatherMoveInfo<'tcx> {
+ id: ast::NodeId,
+ kind: MoveKind,
+ cmt: mc::cmt<'tcx>,
+ span_path_opt: Option<MoveSpanAndPath>
+}
+
+pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ decl_id: ast::NodeId,
+ _decl_span: Span,
+ var_id: ast::NodeId) {
+ let ty = ty::node_id_to_type(bccx.tcx, var_id);
+ let loan_path = Rc::new(LoanPath::new(LpVar(var_id), ty));
+ move_data.add_move(bccx.tcx, loan_path, decl_id, Declared);
+}
+
+pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ move_error_collector: &MoveErrorCollector<'tcx>,
+ move_expr_id: ast::NodeId,
+ cmt: mc::cmt<'tcx>,
+ move_reason: euv::MoveReason) {
+ let kind = match move_reason {
+ euv::DirectRefMove | euv::PatBindingMove => MoveExpr,
+ euv::CaptureMove => Captured
+ };
+ let move_info = GatherMoveInfo {
+ id: move_expr_id,
+ kind: kind,
+ cmt: cmt,
+ span_path_opt: None,
+ };
+ gather_move(bccx, move_data, move_error_collector, move_info);
+}
+
+pub fn gather_match_variant<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ _move_error_collector: &MoveErrorCollector<'tcx>,
+ move_pat: &ast::Pat,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::MatchMode) {
+ let tcx = bccx.tcx;
+ debug!("gather_match_variant(move_pat={}, cmt={}, mode={})",
+ move_pat.id, cmt.repr(tcx), mode);
+
+ let opt_lp = opt_loan_path(&cmt);
+ match opt_lp {
+ Some(lp) => {
+ match lp.kind {
+ LpDowncast(ref base_lp, _) =>
+ move_data.add_variant_match(
+ tcx, lp.clone(), move_pat.id, base_lp.clone(), mode),
+ _ => panic!("should only call gather_match_variant \
+ for cat_downcast cmt"),
+ }
+ }
+ None => {
+ // We get None when input to match is non-path (e.g.
+ // temporary result like a function call). Since no
+ // loan-path is being matched, no need to record a
+ // downcast.
+ return;
+ }
+ }
+}
+
+pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ move_error_collector: &MoveErrorCollector<'tcx>,
+ move_pat: &ast::Pat,
+ cmt: mc::cmt<'tcx>) {
+ let pat_span_path_opt = match move_pat.node {
+ ast::PatIdent(_, ref path1, _) => {
+ Some(MoveSpanAndPath{span: move_pat.span,
+ ident: path1.node})
+ },
+ _ => None,
+ };
+ let move_info = GatherMoveInfo {
+ id: move_pat.id,
+ kind: MovePat,
+ cmt: cmt,
+ span_path_opt: pat_span_path_opt,
+ };
+ gather_move(bccx, move_data, move_error_collector, move_info);
+}
+
+fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ move_error_collector: &MoveErrorCollector<'tcx>,
+ move_info: GatherMoveInfo<'tcx>) {
+ debug!("gather_move(move_id={}, cmt={})",
+ move_info.id, move_info.cmt.repr(bccx.tcx));
+
+ let potentially_illegal_move =
+ check_and_get_illegal_move_origin(bccx, &move_info.cmt);
+ match potentially_illegal_move {
+ Some(illegal_move_origin) => {
+ debug!("illegal_move_origin={}", illegal_move_origin.repr(bccx.tcx));
+ let error = MoveError::with_move_info(illegal_move_origin,
+ move_info.span_path_opt);
+ move_error_collector.add_error(error);
+ return
+ }
+ None => ()
+ }
+
+ match opt_loan_path(&move_info.cmt) {
+ Some(loan_path) => {
+ move_data.add_move(bccx.tcx, loan_path,
+ move_info.id, move_info.kind);
+ }
+ None => {
+ // move from rvalue or unsafe pointer, hence ok
+ }
+ }
+}
+
+pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_data: &MoveData<'tcx>,
+ assignment_id: ast::NodeId,
+ assignment_span: Span,
+ assignee_loan_path: Rc<LoanPath<'tcx>>,
+ assignee_id: ast::NodeId,
+ mode: euv::MutateMode) {
+ move_data.add_assignment(bccx.tcx,
+ assignee_loan_path,
+ assignment_id,
+ assignment_span,
+ assignee_id,
+ mode);
+}
+
+fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ cmt: &mc::cmt<'tcx>)
+ -> Option<mc::cmt<'tcx>> {
+ match cmt.cat {
+ mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
+ mc::cat_deref(_, _, mc::Implicit(..)) |
+ mc::cat_deref(_, _, mc::UnsafePtr(..)) |
+ mc::cat_static_item => {
+ Some(cmt.clone())
+ }
+
+ mc::cat_rvalue(..) |
+ mc::cat_local(..) |
+ mc::cat_upvar(..) => {
+ None
+ }
+
+ mc::cat_downcast(ref b, _) |
+ mc::cat_interior(ref b, _) => {
+ match b.ty.sty {
+ ty::ty_struct(did, _) | ty::ty_enum(did, _) => {
+ if ty::has_dtor(bccx.tcx, did) {
+ Some(cmt.clone())
+ } else {
+ check_and_get_illegal_move_origin(bccx, b)
+ }
+ }
+ _ => {
+ check_and_get_illegal_move_origin(bccx, b)
+ }
+ }
+ }
+
+ mc::cat_deref(ref b, _, mc::OwnedPtr) => {
+ check_and_get_illegal_move_origin(bccx, b)
+ }
+ }
+}
--- /dev/null
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! This module implements the check that the lifetime of a borrow
+//! does not exceed the lifetime of the value being borrowed.
+
+use borrowck::*;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::region;
+use rustc::middle::ty;
+use rustc::util::ppaux::Repr;
+use syntax::ast;
+use syntax::codemap::Span;
+
+type R = Result<(),()>;
+
+pub fn guarantee_lifetime<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ item_scope: region::CodeExtent,
+ span: Span,
+ cause: euv::LoanCause,
+ cmt: mc::cmt<'tcx>,
+ loan_region: ty::Region,
+ _: ty::BorrowKind)
+ -> Result<(),()> {
+ //! Reports error if `loan_region` is larger than S
+ //! where S is `item_scope` if `cmt` is an upvar,
+ //! and is scope of `cmt` otherwise.
+ debug!("guarantee_lifetime(cmt={}, loan_region={})",
+ cmt.repr(bccx.tcx), loan_region.repr(bccx.tcx));
+ let ctxt = GuaranteeLifetimeContext {bccx: bccx,
+ item_scope: item_scope,
+ span: span,
+ cause: cause,
+ loan_region: loan_region,
+ cmt_original: cmt.clone()};
+ ctxt.check(&cmt, None)
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Private
+
+struct GuaranteeLifetimeContext<'a, 'tcx: 'a> {
+ bccx: &'a BorrowckCtxt<'a, 'tcx>,
+
+ // the scope of the function body for the enclosing item
+ item_scope: region::CodeExtent,
+
+ span: Span,
+ cause: euv::LoanCause,
+ loan_region: ty::Region,
+ cmt_original: mc::cmt<'tcx>
+}
+
+impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
+
+ fn check(&self, cmt: &mc::cmt<'tcx>, discr_scope: Option<ast::NodeId>) -> R {
+ //! Main routine. Walks down `cmt` until we find the
+ //! "guarantor". Reports an error if `self.loan_region` is
+ //! larger than scope of `cmt`.
+ debug!("guarantee_lifetime.check(cmt={}, loan_region={})",
+ cmt.repr(self.bccx.tcx),
+ self.loan_region.repr(self.bccx.tcx));
+
+ match cmt.cat {
+ mc::cat_rvalue(..) |
+ mc::cat_local(..) | // L-Local
+ mc::cat_upvar(..) |
+ mc::cat_deref(_, _, mc::BorrowedPtr(..)) | // L-Deref-Borrowed
+ mc::cat_deref(_, _, mc::Implicit(..)) |
+ mc::cat_deref(_, _, mc::UnsafePtr(..)) => {
+ self.check_scope(self.scope(cmt))
+ }
+
+ mc::cat_static_item => {
+ Ok(())
+ }
+
+ mc::cat_downcast(ref base, _) |
+ mc::cat_deref(ref base, _, mc::OwnedPtr) | // L-Deref-Send
+ mc::cat_interior(ref base, _) => { // L-Field
+ self.check(base, discr_scope)
+ }
+ }
+ }
+
+ fn check_scope(&self, max_scope: ty::Region) -> R {
+ //! Reports an error if `loan_region` is larger than `max_scope`
+
+ if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
+ Err(self.report_error(err_out_of_scope(max_scope, self.loan_region)))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn scope(&self, cmt: &mc::cmt) -> ty::Region {
+ //! Returns the maximal region scope for the which the
+ //! lvalue `cmt` is guaranteed to be valid without any
+ //! rooting etc, and presuming `cmt` is not mutated.
+
+ // See the SCOPE(LV) function in doc.rs
+
+ match cmt.cat {
+ mc::cat_rvalue(temp_scope) => {
+ temp_scope
+ }
+ mc::cat_upvar(..) => {
+ ty::ReScope(self.item_scope)
+ }
+ mc::cat_static_item => {
+ ty::ReStatic
+ }
+ mc::cat_local(local_id) => {
+ ty::ReScope(self.bccx.tcx.region_maps.var_scope(local_id))
+ }
+ mc::cat_deref(_, _, mc::UnsafePtr(..)) => {
+ ty::ReStatic
+ }
+ mc::cat_deref(_, _, mc::BorrowedPtr(_, r)) |
+ mc::cat_deref(_, _, mc::Implicit(_, r)) => {
+ r
+ }
+ mc::cat_downcast(ref cmt, _) |
+ mc::cat_deref(ref cmt, _, mc::OwnedPtr) |
+ mc::cat_interior(ref cmt, _) => {
+ self.scope(cmt)
+ }
+ }
+ }
+
+ fn report_error(&self, code: bckerr_code) {
+ self.bccx.report(BckError { cmt: self.cmt_original.clone(),
+ span: self.span,
+ cause: self.cause,
+ code: code });
+ }
+}
--- /dev/null
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// ----------------------------------------------------------------------
+// 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 borrowck::*;
+use borrowck::LoanPathKind::*;
+use borrowck::move_data::MoveData;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::region;
+use rustc::middle::ty;
+use rustc::util::ppaux::{Repr};
+use syntax::ast;
+use syntax::codemap::Span;
+use syntax::visit;
+use syntax::visit::Visitor;
+use syntax::ast::{Expr, FnDecl, Block, NodeId, Pat};
+
+mod lifetime;
+mod restrictions;
+mod gather_moves;
+mod move_error;
+
+pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ fn_id: NodeId,
+ decl: &ast::FnDecl,
+ body: &ast::Block)
+ -> (Vec<Loan<'tcx>>,
+ move_data::MoveData<'tcx>) {
+ let mut glcx = GatherLoanCtxt {
+ bccx: bccx,
+ all_loans: Vec::new(),
+ item_ub: region::CodeExtent::from_node_id(body.id),
+ move_data: MoveData::new(),
+ move_error_collector: move_error::MoveErrorCollector::new(),
+ };
+
+ let param_env = ty::ParameterEnvironment::for_item(bccx.tcx, fn_id);
+
+ {
+ let mut euv = euv::ExprUseVisitor::new(&mut glcx,
+ bccx.tcx,
+ param_env);
+ euv.walk_fn(decl, 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::CodeExtent,
+}
+
+impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
+ fn consume(&mut self,
+ consume_id: ast::NodeId,
+ _consume_span: Span,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::ConsumeMode) {
+ debug!("consume(consume_id={}, cmt={}, mode={})",
+ consume_id, cmt.repr(self.tcx()), mode);
+
+ match mode {
+ euv::Move(move_reason) => {
+ gather_moves::gather_move_from_expr(
+ self.bccx, &self.move_data, &self.move_error_collector,
+ consume_id, cmt, move_reason);
+ }
+ euv::Copy => { }
+ }
+ }
+
+ fn matched_pat(&mut self,
+ matched_pat: &ast::Pat,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::MatchMode) {
+ debug!("matched_pat(matched_pat={}, cmt={}, mode={})",
+ matched_pat.repr(self.tcx()),
+ cmt.repr(self.tcx()),
+ mode);
+
+ if let mc::cat_downcast(..) = cmt.cat {
+ gather_moves::gather_match_variant(
+ self.bccx, &self.move_data, &self.move_error_collector,
+ matched_pat, cmt, mode);
+ }
+ }
+
+ fn consume_pat(&mut self,
+ consume_pat: &ast::Pat,
+ cmt: mc::cmt<'tcx>,
+ mode: euv::ConsumeMode) {
+ debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
+ consume_pat.repr(self.tcx()),
+ cmt.repr(self.tcx()),
+ mode);
+
+ match mode {
+ euv::Copy => { return; }
+ euv::Move(_) => { }
+ }
+
+ gather_moves::gather_move_from_pat(
+ self.bccx, &self.move_data, &self.move_error_collector,
+ consume_pat, cmt);
+ }
+
+ fn borrow(&mut self,
+ borrow_id: ast::NodeId,
+ borrow_span: Span,
+ cmt: mc::cmt<'tcx>,
+ loan_region: ty::Region,
+ bk: ty::BorrowKind,
+ loan_cause: euv::LoanCause)
+ {
+ debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
+ bk={}, loan_cause={})",
+ borrow_id, cmt.repr(self.tcx()), loan_region,
+ bk, loan_cause);
+
+ self.guarantee_valid(borrow_id,
+ borrow_span,
+ cmt,
+ bk,
+ loan_region,
+ loan_cause);
+ }
+
+ fn mutate(&mut self,
+ assignment_id: ast::NodeId,
+ assignment_span: Span,
+ assignee_cmt: mc::cmt<'tcx>,
+ mode: euv::MutateMode)
+ {
+ debug!("mutate(assignment_id={}, assignee_cmt={})",
+ assignment_id, assignee_cmt.repr(self.tcx()));
+
+ match opt_loan_path(&assignee_cmt) {
+ Some(lp) => {
+ gather_moves::gather_assignment(self.bccx, &self.move_data,
+ assignment_id, assignment_span,
+ lp, assignee_cmt.id, mode);
+ }
+ 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.
+ }
+ }
+ }
+
+ fn decl_without_init(&mut self, id: ast::NodeId, span: Span) {
+ gather_moves::gather_decl(self.bccx, &self.move_data, id, span, id);
+ }
+}
+
+/// Implements the A-* rules in doc.rs.
+fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ borrow_span: Span,
+ loan_cause: euv::LoanCause,
+ cmt: mc::cmt<'tcx>,
+ req_kind: ty::BorrowKind)
+ -> Result<(),()> {
+
+ match (cmt.freely_aliasable(bccx.tcx), req_kind) {
+ (None, _) => {
+ /* Uniquely accessible path -- OK for `&` and `&mut` */
+ Ok(())
+ }
+ (Some(mc::AliasableStatic(safety)), ty::ImmBorrow) => {
+ // Borrow of an immutable static item:
+ match safety {
+ mc::InteriorUnsafe => {
+ // If the static item contains an Unsafe<T>, it has interior
+ // mutability. In such cases, another phase of the compiler
+ // will ensure that the type is `Sync` and then trans will
+ // not put it in rodata, so this is ok to allow.
+ Ok(())
+ }
+ mc::InteriorSafe => {
+ // Immutable static can be borrowed, no problem.
+ Ok(())
+ }
+ }
+ }
+ (Some(mc::AliasableStaticMut(..)), _) => {
+ // Even touching a static mut is considered unsafe. We assume the
+ // user knows what they're doing in these cases.
+ Ok(())
+ }
+ (Some(alias_cause), ty::UniqueImmBorrow) |
+ (Some(alias_cause), ty::MutBorrow) => {
+ bccx.report_aliasability_violation(
+ borrow_span,
+ BorrowViolation(loan_cause),
+ alias_cause);
+ Err(())
+ }
+ (_, _) => {
+ Ok(())
+ }
+ }
+}
+
+impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
+ pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx }
+
+ /// 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: ast::NodeId,
+ borrow_span: Span,
+ cmt: mc::cmt<'tcx>,
+ req_kind: ty::BorrowKind,
+ loan_region: ty::Region,
+ cause: euv::LoanCause) {
+ debug!("guarantee_valid(borrow_id={}, cmt={}, \
+ req_mutbl={}, loan_region={})",
+ borrow_id,
+ cmt.repr(self.tcx()),
+ 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.clone(), loan_region,
+ req_kind).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, cause,
+ cmt.clone(), 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, cause,
+ cmt.clone(), 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.clone(), loan_region);
+
+ debug!("guarantee_valid(): restrictions={}", restr);
+
+ // Create the loan record (if needed).
+ let loan = match restr {
+ restrictions::Safe => {
+ // No restrictions---no loan record necessary
+ return;
+ }
+
+ restrictions::SafeIf(loan_path, restricted_paths) => {
+ let loan_scope = match loan_region {
+ ty::ReScope(scope) => scope,
+
+ ty::ReFree(ref fr) => fr.scope,
+
+ ty::ReStatic => {
+ // If we get here, an error must have been
+ // reported in
+ // `lifetime::guarantee_lifetime()`, because
+ // the only legal ways to have a borrow with a
+ // static lifetime should not require
+ // restrictions. To avoid reporting derived
+ // errors, we just return here without adding
+ // any loans.
+ return;
+ }
+
+ ty::ReEmpty |
+ ty::ReLateBound(..) |
+ ty::ReEarlyBound(..) |
+ ty::ReInfer(..) => {
+ self.tcx().sess.span_bug(
+ cmt.span,
+ format!("invalid borrow lifetime: {}",
+ loan_region).as_slice());
+ }
+ };
+ debug!("loan_scope = {}", loan_scope);
+
+ let borrow_scope = region::CodeExtent::from_node_id(borrow_id);
+ 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: loan_path,
+ kind: req_kind,
+ gen_scope: gen_scope,
+ kill_scope: kill_scope,
+ span: borrow_span,
+ restricted_paths: restricted_paths,
+ cause: cause,
+ }
+ }
+ };
+
+ debug!("guarantee_valid(borrow_id={}), loan={}",
+ borrow_id, loan.repr(self.tcx()));
+
+ // 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(#6268) 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 = {
+ // let all_loans = &mut *self.all_loans; // FIXME(#5074)
+ // Loan {
+ // index: all_loans.len(),
+ // loan_path: loan_path,
+ // cmt: cmt,
+ // mutbl: ConstMutability,
+ // gen_scope: borrow_id,
+ // kill_scope: kill_scope,
+ // span: borrow_span,
+ // restrictions: restrictions
+ // }
+ // }
+
+ fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ borrow_span: Span,
+ cause: euv::LoanCause,
+ cmt: mc::cmt<'tcx>,
+ req_kind: ty::BorrowKind)
+ -> Result<(),()> {
+ //! Implements the M-* rules in doc.rs.
+
+ 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: cause,
+ cmt: cmt,
+ code: err_mutbl }))
+ } else {
+ Ok(())
+ }
+ }
+ }
+ }
+ }
+
+ 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.
+
+ match loan_path.kind {
+ LpVar(local_id) |
+ LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => {
+ self.tcx().used_mut_nodes.borrow_mut().insert(local_id);
+ }
+ LpDowncast(ref base, _) |
+ LpExtend(ref base, mc::McInherited, _) |
+ LpExtend(ref base, mc::McDeclared, _) => {
+ self.mark_loan_path_as_mutated(&**base);
+ }
+ LpExtend(_, mc::McImmutable, _) => {
+ // Nothing to do.
+ }
+ }
+ }
+
+ pub fn compute_gen_scope(&self,
+ borrow_scope: region::CodeExtent,
+ loan_scope: region::CodeExtent)
+ -> region::CodeExtent {
+ //! 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.tcx.region_maps.is_subscope_of(borrow_scope, loan_scope) {
+ borrow_scope
+ } else {
+ loan_scope
+ }
+ }
+
+ pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath<'tcx>)
+ -> region::CodeExtent {
+ //! 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:
+ //!
+ //! fn counter<'a>(v: &'a mut Foo) -> &'a mut uint {
+ //! &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.tcx);
+ let rm = &self.bccx.tcx.region_maps;
+ if rm.is_subscope_of(lexical_scope, loan_scope) {
+ lexical_scope
+ } else {
+ assert!(self.bccx.tcx.region_maps.is_subscope_of(loan_scope, lexical_scope));
+ loan_scope
+ }
+ }
+
+ pub fn report_potential_errors(&self) {
+ self.move_error_collector.report_potential_errors(self.bccx);
+ }
+}
+
+/// Context used while gathering loans on static initializers
+///
+/// This visitor walks static initializer's expressions and makes
+/// sure the loans being taken are sound.
+struct StaticInitializerCtxt<'a, 'tcx: 'a> {
+ bccx: &'a BorrowckCtxt<'a, 'tcx>
+}
+
+impl<'a, 'tcx, 'v> Visitor<'v> for StaticInitializerCtxt<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &Expr) {
+ if let ast::ExprAddrOf(mutbl, ref base) = ex.node {
+ let base_cmt = self.bccx.cat_expr(&**base);
+ let borrow_kind = ty::BorrowKind::from_mutbl(mutbl);
+ // Check that we don't allow borrows of unsafe static items.
+ if check_aliasability(self.bccx, ex.span, euv::AddrOf,
+ base_cmt, borrow_kind).is_err() {
+ return; // reported an error, no sense in reporting more.
+ }
+ }
+
+ visit::walk_expr(self, ex);
+ }
+}
+
+pub fn gather_loans_in_static_initializer(bccx: &mut BorrowckCtxt, expr: &ast::Expr) {
+
+ debug!("gather_loans_in_static_initializer(expr={})", expr.repr(bccx.tcx));
+
+ let mut sicx = StaticInitializerCtxt {
+ bccx: bccx
+ };
+
+ sicx.visit_expr(expr);
+}
--- /dev/null
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use borrowck::BorrowckCtxt;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::ty;
+use rustc::util::ppaux::UserString;
+use std::cell::RefCell;
+use syntax::ast;
+use syntax::codemap;
+use syntax::print::pprust;
+
+pub struct MoveErrorCollector<'tcx> {
+ errors: RefCell<Vec<MoveError<'tcx>>>
+}
+
+impl<'tcx> MoveErrorCollector<'tcx> {
+ pub fn new() -> MoveErrorCollector<'tcx> {
+ MoveErrorCollector {
+ errors: RefCell::new(Vec::new())
+ }
+ }
+
+ pub fn add_error(&self, error: MoveError<'tcx>) {
+ self.errors.borrow_mut().push(error);
+ }
+
+ pub fn report_potential_errors<'a>(&self, bccx: &BorrowckCtxt<'a, 'tcx>) {
+ report_move_errors(bccx, self.errors.borrow().deref())
+ }
+}
+
+pub struct MoveError<'tcx> {
+ move_from: mc::cmt<'tcx>,
+ move_to: Option<MoveSpanAndPath>
+}
+
+impl<'tcx> MoveError<'tcx> {
+ pub fn with_move_info(move_from: mc::cmt<'tcx>,
+ move_to: Option<MoveSpanAndPath>)
+ -> MoveError<'tcx> {
+ MoveError {
+ move_from: move_from,
+ move_to: move_to,
+ }
+ }
+}
+
+#[deriving(Clone)]
+pub struct MoveSpanAndPath {
+ pub span: codemap::Span,
+ pub ident: ast::Ident
+}
+
+pub struct GroupedMoveErrors<'tcx> {
+ move_from: mc::cmt<'tcx>,
+ move_to_places: Vec<MoveSpanAndPath>
+}
+
+fn report_move_errors<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ errors: &Vec<MoveError<'tcx>>) {
+ let grouped_errors = group_errors_with_same_origin(errors);
+ for error in grouped_errors.iter() {
+ report_cannot_move_out_of(bccx, error.move_from.clone());
+ let mut is_first_note = true;
+ for move_to in error.move_to_places.iter() {
+ note_move_destination(bccx, move_to.span,
+ &move_to.ident, is_first_note);
+ is_first_note = false;
+ }
+ }
+}
+
+fn group_errors_with_same_origin<'tcx>(errors: &Vec<MoveError<'tcx>>)
+ -> Vec<GroupedMoveErrors<'tcx>> {
+ let mut grouped_errors = Vec::new();
+ for error in errors.iter() {
+ append_to_grouped_errors(&mut grouped_errors, error)
+ }
+ return grouped_errors;
+
+ fn append_to_grouped_errors<'tcx>(grouped_errors: &mut Vec<GroupedMoveErrors<'tcx>>,
+ error: &MoveError<'tcx>) {
+ let move_from_id = error.move_from.id;
+ debug!("append_to_grouped_errors(move_from_id={})", move_from_id);
+ let move_to = if error.move_to.is_some() {
+ vec!(error.move_to.clone().unwrap())
+ } else {
+ Vec::new()
+ };
+ for ge in grouped_errors.iter_mut() {
+ if move_from_id == ge.move_from.id && error.move_to.is_some() {
+ debug!("appending move_to to list");
+ ge.move_to_places.extend(move_to.into_iter());
+ return
+ }
+ }
+ debug!("found a new move from location");
+ grouped_errors.push(GroupedMoveErrors {
+ move_from: error.move_from.clone(),
+ move_to_places: move_to
+ })
+ }
+}
+
+fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ move_from: mc::cmt<'tcx>) {
+ match move_from.cat {
+ mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
+ mc::cat_deref(_, _, mc::Implicit(..)) |
+ mc::cat_deref(_, _, mc::UnsafePtr(..)) |
+ mc::cat_static_item => {
+ bccx.span_err(
+ move_from.span,
+ format!("cannot move out of {}",
+ bccx.cmt_to_string(&*move_from)).as_slice());
+ }
+
+ mc::cat_downcast(ref b, _) |
+ mc::cat_interior(ref b, _) => {
+ match b.ty.sty {
+ ty::ty_struct(did, _)
+ | ty::ty_enum(did, _) if ty::has_dtor(bccx.tcx, did) => {
+ bccx.span_err(
+ move_from.span,
+ format!("cannot move out of type `{}`, \
+ which defines the `Drop` trait",
+ b.ty.user_string(bccx.tcx)).as_slice());
+ },
+ _ => panic!("this path should not cause illegal move")
+ }
+ }
+ _ => panic!("this path should not cause illegal move")
+ }
+}
+
+fn note_move_destination(bccx: &BorrowckCtxt,
+ move_to_span: codemap::Span,
+ pat_ident: &ast::Ident,
+ is_first_note: bool) {
+ let pat_name = pprust::ident_to_string(pat_ident);
+ if is_first_note {
+ bccx.span_note(
+ move_to_span,
+ "attempting to move value to here");
+ bccx.span_help(
+ move_to_span,
+ format!("to prevent the move, \
+ use `ref {0}` or `ref mut {0}` to capture value by \
+ reference",
+ pat_name).as_slice());
+ } else {
+ bccx.span_note(move_to_span,
+ format!("and here (use `ref {0}` or `ref mut {0}`)",
+ pat_name).as_slice());
+ }
+}
--- /dev/null
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Computes the restrictions that result from a borrow.
+
+pub use self::RestrictionResult::*;
+
+use borrowck::*;
+use borrowck::LoanPathElem::*;
+use borrowck::LoanPathKind::*;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::ty;
+use rustc::util::ppaux::Repr;
+use syntax::codemap::Span;
+
+use std::rc::Rc;
+
+#[deriving(Show)]
+pub enum RestrictionResult<'tcx> {
+ Safe,
+ SafeIf(Rc<LoanPath<'tcx>>, Vec<Rc<LoanPath<'tcx>>>)
+}
+
+pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
+ span: Span,
+ cause: euv::LoanCause,
+ cmt: mc::cmt<'tcx>,
+ loan_region: ty::Region)
+ -> RestrictionResult<'tcx> {
+ let ctxt = RestrictionsContext {
+ bccx: bccx,
+ span: span,
+ cause: cause,
+ loan_region: loan_region,
+ };
+
+ ctxt.restrict(cmt)
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Private
+
+struct RestrictionsContext<'a, 'tcx: 'a> {
+ bccx: &'a BorrowckCtxt<'a, 'tcx>,
+ span: Span,
+ loan_region: ty::Region,
+ cause: euv::LoanCause,
+}
+
+impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
+ fn restrict(&self,
+ cmt: mc::cmt<'tcx>) -> RestrictionResult<'tcx> {
+ debug!("restrict(cmt={})", cmt.repr(self.bccx.tcx));
+
+ let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
+
+ match cmt.cat.clone() {
+ mc::cat_rvalue(..) => {
+ // Effectively, rvalues are stored into a
+ // non-aliasable temporary on the stack. Since they
+ // are inherently non-aliasable, they can only be
+ // accessed later through the borrow itself and hence
+ // must inherently comply with its terms.
+ Safe
+ }
+
+ mc::cat_local(local_id) => {
+ // R-Variable, locally declared
+ let lp = new_lp(LpVar(local_id));
+ SafeIf(lp.clone(), vec![lp])
+ }
+
+ mc::cat_upvar(mc::Upvar { id, .. }) => {
+ // R-Variable, captured into closure
+ let lp = new_lp(LpUpvar(id));
+ SafeIf(lp.clone(), vec![lp])
+ }
+
+ mc::cat_downcast(cmt_base, _) => {
+ // When we borrow the interior of an enum, we have to
+ // ensure the enum itself is not mutated, because that
+ // could cause the type of the memory to change.
+ self.restrict(cmt_base)
+ }
+
+ mc::cat_interior(cmt_base, i) => {
+ // R-Field
+ //
+ // Overwriting the base would not change the type of
+ // the memory, so no additional restrictions are
+ // needed.
+ let result = self.restrict(cmt_base);
+ self.extend(result, &cmt, LpInterior(i))
+ }
+
+ mc::cat_static_item(..) => {
+ Safe
+ }
+
+ mc::cat_deref(cmt_base, _, pk) => {
+ match pk {
+ mc::OwnedPtr => {
+ // R-Deref-Send-Pointer
+ //
+ // When we borrow the interior of an owned pointer, we
+ // cannot permit the base to be mutated, because that
+ // would cause the unique pointer to be freed.
+ //
+ // Eventually we should make these non-special and
+ // just rely on Deref<T> implementation.
+ let result = self.restrict(cmt_base);
+ self.extend(result, &cmt, LpDeref(pk))
+ }
+ mc::Implicit(bk, lt) | mc::BorrowedPtr(bk, lt) => {
+ // R-Deref-[Mut-]Borrowed
+ if !self.bccx.is_subregion_of(self.loan_region, lt) {
+ self.bccx.report(
+ BckError {
+ span: self.span,
+ cause: self.cause,
+ cmt: cmt_base,
+ code: err_borrowed_pointer_too_short(
+ self.loan_region, lt)});
+ return Safe;
+ }
+
+ match bk {
+ ty::ImmBorrow => Safe,
+ ty::MutBorrow | ty::UniqueImmBorrow => {
+ // R-Deref-Mut-Borrowed
+ //
+ // The referent can be aliased after the
+ // references lifetime ends (by a newly-unfrozen
+ // borrow).
+ let result = self.restrict(cmt_base);
+ self.extend(result, &cmt, LpDeref(pk))
+ }
+ }
+ }
+ // Borrowck is not relevant for unsafe pointers
+ mc::UnsafePtr(..) => Safe
+ }
+ }
+ }
+ }
+
+ fn extend(&self,
+ result: RestrictionResult<'tcx>,
+ cmt: &mc::cmt<'tcx>,
+ elem: LoanPathElem) -> RestrictionResult<'tcx> {
+ match result {
+ Safe => Safe,
+ SafeIf(base_lp, mut base_vec) => {
+ let v = LpExtend(base_lp, cmt.mutbl, elem);
+ let lp = Rc::new(LoanPath::new(v, cmt.ty));
+ base_vec.push(lp.clone());
+ SafeIf(lp, base_vec)
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! See doc.rs for a thorough explanation of the borrow checker
+
+#![allow(non_camel_case_types)]
+
+pub use self::LoanPathKind::*;
+pub use self::LoanPathElem::*;
+pub use self::bckerr_code::*;
+pub use self::AliasableViolationKind::*;
+pub use self::MovedValueUseKind::*;
+
+use rustc::middle::cfg;
+use rustc::middle::dataflow::DataFlowContext;
+use rustc::middle::dataflow::BitwiseOperator;
+use rustc::middle::dataflow::DataFlowOperator;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::region;
+use rustc::middle::ty::{mod, Ty};
+use rustc::util::ppaux::{note_and_explain_region, Repr, UserString};
+use std::rc::Rc;
+use std::string::String;
+use syntax::ast;
+use syntax::ast_map;
+use syntax::ast_map::blocks::{FnLikeNode, FnParts};
+use syntax::ast_util;
+use syntax::codemap::Span;
+use syntax::parse::token;
+use syntax::visit;
+use syntax::visit::{Visitor, FnKind};
+use syntax::ast::{FnDecl, Block, NodeId};
+
+macro_rules! if_ok(
+ ($inp: expr) => (
+ match $inp {
+ Ok(v) => { v }
+ Err(e) => { return Err(e); }
+ }
+ )
+)
+
+pub mod doc;
+
+pub mod check_loans;
+
+pub mod gather_loans;
+
+pub mod move_data;
+
+#[deriving(Clone)]
+pub struct LoanDataFlowOperator;
+
+impl Copy for LoanDataFlowOperator {}
+
+pub type LoanDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, LoanDataFlowOperator>;
+
+impl<'a, 'tcx, 'v> Visitor<'v> for BorrowckCtxt<'a, 'tcx> {
+ fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v FnDecl,
+ b: &'v Block, s: Span, id: ast::NodeId) {
+ borrowck_fn(self, fk, fd, b, s, id);
+ }
+
+ fn visit_item(&mut self, item: &ast::Item) {
+ borrowck_item(self, item);
+ }
+}
+
+pub fn check_crate(tcx: &ty::ctxt) {
+ let mut bccx = BorrowckCtxt {
+ tcx: tcx,
+ stats: BorrowStats {
+ loaned_paths_same: 0,
+ loaned_paths_imm: 0,
+ stable_paths: 0,
+ guaranteed_paths: 0
+ }
+ };
+
+ visit::walk_crate(&mut bccx, tcx.map.krate());
+
+ if tcx.sess.borrowck_stats() {
+ println!("--- borrowck stats ---");
+ println!("paths requiring guarantees: {}",
+ bccx.stats.guaranteed_paths);
+ println!("paths requiring loans : {}",
+ make_stat(&bccx, bccx.stats.loaned_paths_same));
+ println!("paths requiring imm loans : {}",
+ make_stat(&bccx, bccx.stats.loaned_paths_imm));
+ println!("stable paths : {}",
+ make_stat(&bccx, bccx.stats.stable_paths));
+ }
+
+ fn make_stat(bccx: &BorrowckCtxt, stat: uint) -> String {
+ let total = bccx.stats.guaranteed_paths as f64;
+ let perc = if total == 0.0 { 0.0 } else { stat as f64 * 100.0 / total };
+ format!("{} ({:.0}%)", stat, perc)
+ }
+}
+
+fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) {
+ // Gather loans for items. Note that we don't need
+ // to check loans for single expressions. The check
+ // loan step is intended for things that have a data
+ // flow dependent conditions.
+ match item.node {
+ ast::ItemStatic(_, _, ref ex) |
+ ast::ItemConst(_, ref ex) => {
+ gather_loans::gather_loans_in_static_initializer(this, &**ex);
+ }
+ _ => {
+ visit::walk_item(this, item);
+ }
+ }
+}
+
+/// Collection of conclusions determined via borrow checker analyses.
+pub struct AnalysisData<'a, 'tcx: 'a> {
+ pub all_loans: Vec<Loan<'tcx>>,
+ pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>,
+ pub move_data: move_data::FlowedMoveData<'a, 'tcx>,
+}
+
+fn borrowck_fn(this: &mut BorrowckCtxt,
+ fk: FnKind,
+ decl: &ast::FnDecl,
+ body: &ast::Block,
+ sp: Span,
+ id: ast::NodeId) {
+ debug!("borrowck_fn(id={})", id);
+ let cfg = cfg::CFG::new(this.tcx, body);
+ let AnalysisData { all_loans,
+ loans: loan_dfcx,
+ move_data:flowed_moves } =
+ build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id);
+
+ move_data::fragments::instrument_move_fragments(&flowed_moves.move_data,
+ this.tcx, sp, id);
+
+ check_loans::check_loans(this,
+ &loan_dfcx,
+ flowed_moves,
+ all_loans.as_slice(),
+ id,
+ decl,
+ body);
+
+ visit::walk_fn(this, fk, decl, body, sp);
+}
+
+fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>,
+ fk: FnKind,
+ decl: &ast::FnDecl,
+ cfg: &cfg::CFG,
+ body: &ast::Block,
+ sp: Span,
+ id: ast::NodeId) -> AnalysisData<'a, 'tcx> {
+ // Check the body of fn items.
+ let id_range = ast_util::compute_id_range_for_fn_body(fk, decl, body, sp, id);
+ let (all_loans, move_data) =
+ gather_loans::gather_loans_in_fn(this, id, decl, body);
+
+ let mut loan_dfcx =
+ DataFlowContext::new(this.tcx,
+ "borrowck",
+ Some(decl),
+ cfg,
+ LoanDataFlowOperator,
+ id_range,
+ all_loans.len());
+ for (loan_idx, loan) in all_loans.iter().enumerate() {
+ loan_dfcx.add_gen(loan.gen_scope.node_id(), loan_idx);
+ loan_dfcx.add_kill(loan.kill_scope.node_id(), loan_idx);
+ }
+ loan_dfcx.add_kills_from_flow_exits(cfg);
+ loan_dfcx.propagate(cfg, body);
+
+ let flowed_moves = move_data::FlowedMoveData::new(move_data,
+ this.tcx,
+ cfg,
+ id_range,
+ decl,
+ body);
+
+ AnalysisData { all_loans: all_loans,
+ loans: loan_dfcx,
+ move_data:flowed_moves }
+}
+
+/// This and a `ty::ctxt` is all you need to run the dataflow analyses
+/// used in the borrow checker.
+pub struct FnPartsWithCFG<'a> {
+ pub fn_parts: FnParts<'a>,
+ pub cfg: &'a cfg::CFG,
+}
+
+impl<'a> FnPartsWithCFG<'a> {
+ pub fn from_fn_like(f: &'a FnLikeNode,
+ g: &'a cfg::CFG) -> FnPartsWithCFG<'a> {
+ FnPartsWithCFG { fn_parts: f.to_fn_parts(), cfg: g }
+ }
+}
+
+/// Accessor for introspective clients inspecting `AnalysisData` and
+/// the `BorrowckCtxt` itself , e.g. the flowgraph visualizer.
+pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
+ tcx: &'a ty::ctxt<'tcx>,
+ input: FnPartsWithCFG<'a>) -> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'a, 'tcx>) {
+
+ let mut bccx = BorrowckCtxt {
+ tcx: tcx,
+ stats: BorrowStats {
+ loaned_paths_same: 0,
+ loaned_paths_imm: 0,
+ stable_paths: 0,
+ guaranteed_paths: 0
+ }
+ };
+
+ let p = input.fn_parts;
+
+ let dataflow_data = build_borrowck_dataflow_data(&mut bccx,
+ p.kind,
+ &*p.decl,
+ input.cfg,
+ &*p.body,
+ p.span,
+ p.id);
+
+ (bccx, dataflow_data)
+}
+
+// ----------------------------------------------------------------------
+// Type definitions
+
+pub struct BorrowckCtxt<'a, 'tcx: 'a> {
+ tcx: &'a ty::ctxt<'tcx>,
+
+ // Statistics:
+ stats: BorrowStats
+}
+
+struct BorrowStats {
+ loaned_paths_same: uint,
+ loaned_paths_imm: uint,
+ stable_paths: uint,
+ guaranteed_paths: uint
+}
+
+pub type BckResult<'tcx, T> = Result<T, BckError<'tcx>>;
+
+///////////////////////////////////////////////////////////////////////////
+// Loans and loan paths
+
+/// Record of a loan that was issued.
+pub struct Loan<'tcx> {
+ index: uint,
+ loan_path: Rc<LoanPath<'tcx>>,
+ kind: ty::BorrowKind,
+ restricted_paths: Vec<Rc<LoanPath<'tcx>>>,
+
+ /// gen_scope indicates where loan is introduced. 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. See also
+ /// `GatherLoanCtxt::compute_gen_scope`.
+ gen_scope: region::CodeExtent,
+
+ /// kill_scope indicates when the loan goes 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. See also `GatherLoanCtxt::compute_kill_scope`.
+ kill_scope: region::CodeExtent,
+ span: Span,
+ cause: euv::LoanCause,
+}
+
+impl<'tcx> Loan<'tcx> {
+ pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
+ self.loan_path.clone()
+ }
+}
+
+#[deriving(Eq, Hash, Show)]
+pub struct LoanPath<'tcx> {
+ kind: LoanPathKind<'tcx>,
+ ty: ty::Ty<'tcx>,
+}
+
+impl<'tcx> PartialEq for LoanPath<'tcx> {
+ fn eq(&self, that: &LoanPath<'tcx>) -> bool {
+ let r = self.kind == that.kind;
+ debug_assert!(self.ty == that.ty || !r,
+ "Somehow loan paths are equal though their tys are not.");
+ r
+ }
+}
+
+#[deriving(PartialEq, Eq, Hash, Show)]
+pub enum LoanPathKind<'tcx> {
+ LpVar(ast::NodeId), // `x` in doc.rs
+ LpUpvar(ty::UpvarId), // `x` captured by-value into closure
+ LpDowncast(Rc<LoanPath<'tcx>>, ast::DefId), // `x` downcast to particular enum variant
+ LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem)
+}
+
+impl<'tcx> LoanPath<'tcx> {
+ fn new(kind: LoanPathKind<'tcx>, ty: ty::Ty<'tcx>) -> LoanPath<'tcx> {
+ LoanPath { kind: kind, ty: ty }
+ }
+
+ fn to_type(&self) -> ty::Ty<'tcx> { self.ty }
+}
+
+// FIXME (pnkfelix): See discussion here
+// https://github.com/pnkfelix/rust/commit/
+// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
+static DOWNCAST_PRINTED_OPERATOR : &'static str = " as ";
+
+#[deriving(PartialEq, Eq, Hash, Show)]
+pub enum LoanPathElem {
+ LpDeref(mc::PointerKind), // `*LV` in doc.rs
+ LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
+}
+
+impl Copy for LoanPathElem {}
+
+pub fn closure_to_block(closure_id: ast::NodeId,
+ tcx: &ty::ctxt) -> ast::NodeId {
+ match tcx.map.get(closure_id) {
+ ast_map::NodeExpr(expr) => match expr.node {
+ ast::ExprProc(_, ref block) |
+ ast::ExprClosure(_, _, _, ref block) => {
+ block.id
+ }
+ _ => {
+ panic!("encountered non-closure id: {}", closure_id)
+ }
+ },
+ _ => panic!("encountered non-expr id: {}", closure_id)
+ }
+}
+
+impl<'tcx> LoanPath<'tcx> {
+ pub fn kill_scope(&self, tcx: &ty::ctxt<'tcx>) -> region::CodeExtent {
+ match self.kind {
+ LpVar(local_id) => tcx.region_maps.var_scope(local_id),
+ LpUpvar(upvar_id) => {
+ let block_id = closure_to_block(upvar_id.closure_expr_id, tcx);
+ region::CodeExtent::from_node_id(block_id)
+ }
+ LpDowncast(ref base, _) |
+ LpExtend(ref base, _, _) => base.kill_scope(tcx),
+ }
+ }
+
+ fn has_fork(&self, other: &LoanPath<'tcx>) -> bool {
+ match (&self.kind, &other.kind) {
+ (&LpExtend(ref base, _, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) =>
+ if id == id2 {
+ base.has_fork(&**base2)
+ } else {
+ true
+ },
+ (&LpExtend(ref base, _, LpDeref(_)), _) => base.has_fork(other),
+ (_, &LpExtend(ref base, _, LpDeref(_))) => self.has_fork(&**base),
+ _ => false,
+ }
+ }
+
+ fn depth(&self) -> uint {
+ match self.kind {
+ LpExtend(ref base, _, LpDeref(_)) => base.depth(),
+ LpExtend(ref base, _, LpInterior(_)) => base.depth() + 1,
+ _ => 0,
+ }
+ }
+
+ fn common(&self, other: &LoanPath<'tcx>) -> Option<LoanPath<'tcx>> {
+ match (&self.kind, &other.kind) {
+ (&LpExtend(ref base, a, LpInterior(id)),
+ &LpExtend(ref base2, _, LpInterior(id2))) => {
+ if id == id2 {
+ base.common(&**base2).map(|x| {
+ let xd = x.depth();
+ if base.depth() == xd && base2.depth() == xd {
+ assert_eq!(base.ty, base2.ty);
+ assert_eq!(self.ty, other.ty);
+ LoanPath {
+ kind: LpExtend(Rc::new(x), a, LpInterior(id)),
+ ty: self.ty,
+ }
+ } else {
+ x
+ }
+ })
+ } else {
+ base.common(&**base2)
+ }
+ }
+ (&LpExtend(ref base, _, LpDeref(_)), _) => base.common(other),
+ (_, &LpExtend(ref other, _, LpDeref(_))) => self.common(&**other),
+ (&LpVar(id), &LpVar(id2)) => {
+ if id == id2 {
+ assert_eq!(self.ty, other.ty);
+ Some(LoanPath { kind: LpVar(id), ty: self.ty })
+ } else {
+ None
+ }
+ }
+ (&LpUpvar(id), &LpUpvar(id2)) => {
+ if id == id2 {
+ assert_eq!(self.ty, other.ty);
+ Some(LoanPath { kind: LpUpvar(id), ty: self.ty })
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }
+}
+
+pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
+ //! Computes the `LoanPath` (if any) for a `cmt`.
+ //! Note that this logic is somewhat duplicated in
+ //! the method `compute()` found in `gather_loans::restrictions`,
+ //! which allows it to share common loan path pieces as it
+ //! traverses the CMT.
+
+ let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
+
+ match cmt.cat {
+ mc::cat_rvalue(..) |
+ mc::cat_static_item => {
+ None
+ }
+
+ mc::cat_local(id) => {
+ Some(new_lp(LpVar(id)))
+ }
+
+ mc::cat_upvar(mc::Upvar { id, .. }) => {
+ Some(new_lp(LpUpvar(id)))
+ }
+
+ mc::cat_deref(ref cmt_base, _, pk) => {
+ opt_loan_path(cmt_base).map(|lp| {
+ new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
+ })
+ }
+
+ mc::cat_interior(ref cmt_base, ik) => {
+ opt_loan_path(cmt_base).map(|lp| {
+ new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
+ })
+ }
+
+ mc::cat_downcast(ref cmt_base, variant_def_id) =>
+ opt_loan_path(cmt_base)
+ .map(|lp| {
+ new_lp(LpDowncast(lp, variant_def_id))
+ }),
+
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Errors
+
+// Errors that can occur
+#[deriving(PartialEq)]
+#[allow(missing_copy_implementations)]
+pub enum bckerr_code {
+ err_mutbl,
+ err_out_of_scope(ty::Region, ty::Region), // superscope, subscope
+ err_borrowed_pointer_too_short(ty::Region, ty::Region), // loan, ptr
+}
+
+// Combination of an error code and the categorization of the expression
+// that caused it
+#[deriving(PartialEq)]
+pub struct BckError<'tcx> {
+ span: Span,
+ cause: euv::LoanCause,
+ cmt: mc::cmt<'tcx>,
+ code: bckerr_code
+}
+
+pub enum AliasableViolationKind {
+ MutabilityViolation,
+ BorrowViolation(euv::LoanCause)
+}
+
+impl Copy for AliasableViolationKind {}
+
+#[deriving(Show)]
+pub enum MovedValueUseKind {
+ MovedInUse,
+ MovedInCapture,
+}
+
+impl Copy for MovedValueUseKind {}
+
+///////////////////////////////////////////////////////////////////////////
+// Misc
+
+impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
+ pub fn is_subregion_of(&self, r_sub: ty::Region, r_sup: ty::Region)
+ -> bool {
+ self.tcx.region_maps.is_subregion_of(r_sub, r_sup)
+ }
+
+ pub fn mc(&self) -> mc::MemCategorizationContext<'a, ty::ctxt<'tcx>> {
+ mc::MemCategorizationContext::new(self.tcx)
+ }
+
+ pub fn cat_expr(&self, expr: &ast::Expr) -> mc::cmt<'tcx> {
+ match self.mc().cat_expr(expr) {
+ Ok(c) => c,
+ Err(()) => {
+ self.tcx.sess.span_bug(expr.span, "error in mem categorization");
+ }
+ }
+ }
+
+ pub fn report(&self, err: BckError<'tcx>) {
+ self.span_err(
+ err.span,
+ self.bckerr_to_string(&err).as_slice());
+ self.note_and_explain_bckerr(err);
+ }
+
+ pub fn report_use_of_moved_value(&self,
+ use_span: Span,
+ use_kind: MovedValueUseKind,
+ lp: &LoanPath<'tcx>,
+ the_move: &move_data::Move,
+ moved_lp: &LoanPath<'tcx>,
+ param_env: &ty::ParameterEnvironment<'tcx>) {
+ let verb = match use_kind {
+ MovedInUse => "use",
+ MovedInCapture => "capture",
+ };
+
+ let (ol, moved_lp_msg) = match the_move.kind {
+ move_data::Declared => {
+ self.tcx.sess.span_err(
+ use_span,
+ format!("{} of possibly uninitialized variable: `{}`",
+ verb,
+ self.loan_path_to_string(lp)).as_slice());
+ (self.loan_path_to_string(moved_lp),
+ String::new())
+ }
+ _ => {
+ // If moved_lp is something like `x.a`, and lp is something like `x.b`, we would
+ // normally generate a rather confusing message:
+ //
+ // error: use of moved value: `x.b`
+ // note: `x.a` moved here...
+ //
+ // What we want to do instead is get the 'common ancestor' of the two moves and
+ // use that for most of the message instead, giving is something like this:
+ //
+ // error: use of moved value: `x`
+ // note: `x` moved here (through moving `x.a`)...
+
+ let common = moved_lp.common(lp);
+ let has_common = common.is_some();
+ let has_fork = moved_lp.has_fork(lp);
+ let (nl, ol, moved_lp_msg) =
+ if has_fork && has_common {
+ let nl = self.loan_path_to_string(&common.unwrap());
+ let ol = nl.clone();
+ let moved_lp_msg = format!(" (through moving `{}`)",
+ self.loan_path_to_string(moved_lp));
+ (nl, ol, moved_lp_msg)
+ } else {
+ (self.loan_path_to_string(lp),
+ self.loan_path_to_string(moved_lp),
+ String::new())
+ };
+
+ let partial = moved_lp.depth() > lp.depth();
+ let msg = if !has_fork && partial { "partially " }
+ else if has_fork && !has_common { "collaterally "}
+ else { "" };
+ self.tcx.sess.span_err(
+ use_span,
+ format!("{} of {}moved value: `{}`",
+ verb,
+ msg,
+ nl).as_slice());
+ (ol, moved_lp_msg)
+ }
+ };
+
+ match the_move.kind {
+ move_data::Declared => {}
+
+ move_data::MoveExpr => {
+ let (expr_ty, expr_span) = match self.tcx
+ .map
+ .find(the_move.id) {
+ Some(ast_map::NodeExpr(expr)) => {
+ (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
+ }
+ r => {
+ self.tcx.sess.bug(format!("MoveExpr({}) maps to \
+ {}, not Expr",
+ the_move.id,
+ r).as_slice())
+ }
+ };
+ let (suggestion, _) = move_suggestion(self.tcx, param_env, expr_ty,
+ ("moved by default", ""));
+ self.tcx.sess.span_note(
+ expr_span,
+ format!("`{}` moved here{} because it has type `{}`, which is {}",
+ ol,
+ moved_lp_msg,
+ expr_ty.user_string(self.tcx),
+ suggestion).as_slice());
+ }
+
+ move_data::MovePat => {
+ let pat_ty = ty::node_id_to_type(self.tcx, the_move.id);
+ let span = self.tcx.map.span(the_move.id);
+ self.tcx.sess.span_note(span,
+ format!("`{}` moved here{} because it has type `{}`, \
+ which is moved by default",
+ ol,
+ moved_lp_msg,
+ pat_ty.user_string(self.tcx)).as_slice());
+ self.tcx.sess.span_help(span,
+ "use `ref` to override");
+ }
+
+ move_data::Captured => {
+ let (expr_ty, expr_span) = match self.tcx
+ .map
+ .find(the_move.id) {
+ Some(ast_map::NodeExpr(expr)) => {
+ (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
+ }
+ r => {
+ self.tcx.sess.bug(format!("Captured({}) maps to \
+ {}, not Expr",
+ the_move.id,
+ r).as_slice())
+ }
+ };
+ let (suggestion, help) = move_suggestion(self.tcx,
+ param_env,
+ expr_ty,
+ ("moved by default", "make a copy and \
+ capture that instead to override"));
+ self.tcx.sess.span_note(
+ expr_span,
+ format!("`{}` moved into closure environment here{} because it \
+ has type `{}`, which is {}",
+ ol,
+ moved_lp_msg,
+ expr_ty.user_string(self.tcx),
+ suggestion).as_slice());
+ self.tcx.sess.span_help(expr_span, help);
+ }
+ }
+
+ fn move_suggestion<'tcx>(tcx: &ty::ctxt<'tcx>,
+ param_env: &ty::ParameterEnvironment<'tcx>,
+ ty: Ty<'tcx>,
+ default_msgs: (&'static str, &'static str))
+ -> (&'static str, &'static str) {
+ match ty.sty {
+ ty::ty_closure(box ty::ClosureTy {
+ store: ty::RegionTraitStore(..),
+ ..
+ }) =>
+ ("a non-copyable stack closure",
+ "capture it in a new closure, e.g. `|x| f(x)`, to override"),
+ _ if ty::type_moves_by_default(tcx, ty, param_env) =>
+ ("non-copyable",
+ "perhaps you meant to use `clone()`?"),
+ _ => default_msgs,
+ }
+ }
+ }
+
+ pub fn report_reassigned_immutable_variable(&self,
+ span: Span,
+ lp: &LoanPath<'tcx>,
+ assign:
+ &move_data::Assignment) {
+ self.tcx.sess.span_err(
+ span,
+ format!("re-assignment of immutable variable `{}`",
+ self.loan_path_to_string(lp)).as_slice());
+ self.tcx.sess.span_note(assign.span, "prior assignment occurs here");
+ }
+
+ pub fn span_err(&self, s: Span, m: &str) {
+ self.tcx.sess.span_err(s, m);
+ }
+
+ pub fn span_note(&self, s: Span, m: &str) {
+ self.tcx.sess.span_note(s, m);
+ }
+
+ pub fn span_end_note(&self, s: Span, m: &str) {
+ self.tcx.sess.span_end_note(s, m);
+ }
+
+ pub fn span_help(&self, s: Span, m: &str) {
+ self.tcx.sess.span_help(s, m);
+ }
+
+ pub fn bckerr_to_string(&self, err: &BckError<'tcx>) -> String {
+ match err.code {
+ err_mutbl => {
+ let descr = match err.cmt.note {
+ mc::NoteClosureEnv(_) | mc::NoteUpvarRef(_) => {
+ self.cmt_to_string(&*err.cmt)
+ }
+ _ => match opt_loan_path(&err.cmt) {
+ None => {
+ format!("{} {}",
+ err.cmt.mutbl.to_user_str(),
+ self.cmt_to_string(&*err.cmt))
+ }
+ Some(lp) => {
+ format!("{} {} `{}`",
+ err.cmt.mutbl.to_user_str(),
+ self.cmt_to_string(&*err.cmt),
+ self.loan_path_to_string(&*lp))
+ }
+ }
+ };
+
+ match err.cause {
+ euv::ClosureCapture(_) => {
+ format!("closure cannot assign to {}", descr)
+ }
+ euv::OverloadedOperator |
+ euv::AddrOf |
+ euv::RefBinding |
+ euv::AutoRef |
+ euv::ForLoop |
+ euv::MatchDiscriminant => {
+ format!("cannot borrow {} as mutable", descr)
+ }
+ euv::ClosureInvocation => {
+ self.tcx.sess.span_bug(err.span,
+ "err_mutbl with a closure invocation");
+ }
+ }
+ }
+ err_out_of_scope(..) => {
+ let msg = match opt_loan_path(&err.cmt) {
+ None => "borrowed value".to_string(),
+ Some(lp) => {
+ format!("`{}`", self.loan_path_to_string(&*lp))
+ }
+ };
+ format!("{} does not live long enough", msg)
+ }
+ err_borrowed_pointer_too_short(..) => {
+ let descr = match opt_loan_path(&err.cmt) {
+ Some(lp) => {
+ format!("`{}`", self.loan_path_to_string(&*lp))
+ }
+ None => self.cmt_to_string(&*err.cmt),
+ };
+
+ format!("lifetime of {} is too short to guarantee \
+ its contents can be safely reborrowed",
+ descr)
+ }
+ }
+ }
+
+ pub fn report_aliasability_violation(&self,
+ span: Span,
+ kind: AliasableViolationKind,
+ cause: mc::AliasableReason) {
+ let mut is_closure = false;
+ let prefix = match kind {
+ MutabilityViolation => {
+ "cannot assign to data"
+ }
+ BorrowViolation(euv::ClosureCapture(_)) => {
+ // I don't think we can get aliasability violations
+ // with closure captures, so no need to come up with a
+ // good error message. The reason this cannot happen
+ // is because we only capture local variables in
+ // closures, and those are never aliasable.
+ self.tcx.sess.span_bug(
+ span,
+ "aliasability violation with closure");
+ }
+ BorrowViolation(euv::OverloadedOperator) |
+ BorrowViolation(euv::AddrOf) |
+ BorrowViolation(euv::AutoRef) |
+ BorrowViolation(euv::RefBinding) |
+ BorrowViolation(euv::MatchDiscriminant) => {
+ "cannot borrow data mutably"
+ }
+
+ BorrowViolation(euv::ClosureInvocation) => {
+ is_closure = true;
+ "closure invocation"
+ }
+
+ BorrowViolation(euv::ForLoop) => {
+ "`for` loop"
+ }
+ };
+
+ match cause {
+ mc::AliasableOther => {
+ self.tcx.sess.span_err(
+ span,
+ format!("{} in an aliasable location",
+ prefix).as_slice());
+ }
+ mc::AliasableClosure(id) => {
+ self.tcx.sess.span_err(span,
+ format!("{} in a captured outer \
+ variable in an `Fn` closure", prefix).as_slice());
+ span_help!(self.tcx.sess, self.tcx.map.span(id),
+ "consider changing this closure to take self by mutable reference");
+ }
+ mc::AliasableStatic(..) |
+ mc::AliasableStaticMut(..) => {
+ self.tcx.sess.span_err(
+ span,
+ format!("{} in a static location", prefix).as_slice());
+ }
+ mc::AliasableBorrowed => {
+ self.tcx.sess.span_err(
+ span,
+ format!("{} in a `&` reference", prefix).as_slice());
+ }
+ }
+
+ if is_closure {
+ self.tcx.sess.span_help(
+ span,
+ "closures behind references must be called via `&mut`");
+ }
+ }
+
+ pub fn note_and_explain_bckerr(&self, err: BckError<'tcx>) {
+ let code = err.code;
+ match code {
+ err_mutbl(..) => {
+ match err.cmt.note {
+ mc::NoteClosureEnv(upvar_id) | mc::NoteUpvarRef(upvar_id) => {
+ // If this is an `Fn` closure, it simply can't mutate upvars.
+ // If it's an `FnMut` closure, the original variable was declared immutable.
+ // We need to determine which is the case here.
+ let kind = match err.cmt.upvar().unwrap().cat {
+ mc::cat_upvar(mc::Upvar { kind, .. }) => kind,
+ _ => unreachable!()
+ };
+ if kind == ty::FnUnboxedClosureKind {
+ self.tcx.sess.span_help(
+ self.tcx.map.span(upvar_id.closure_expr_id),
+ "consider changing this closure to take \
+ self by mutable reference");
+ }
+ }
+ _ => {}
+ }
+ }
+
+ err_out_of_scope(super_scope, sub_scope) => {
+ note_and_explain_region(
+ self.tcx,
+ "reference must be valid for ",
+ sub_scope,
+ "...");
+ let suggestion = if is_statement_scope(self.tcx, super_scope) {
+ Some("consider using a `let` binding to increase its lifetime")
+ } else {
+ None
+ };
+ let span = note_and_explain_region(
+ self.tcx,
+ "...but borrowed value is only valid for ",
+ super_scope,
+ "");
+ match (span, suggestion) {
+ (_, None) => {},
+ (Some(span), Some(msg)) => self.tcx.sess.span_help(span, msg),
+ (None, Some(msg)) => self.tcx.sess.help(msg),
+ }
+ }
+
+ err_borrowed_pointer_too_short(loan_scope, ptr_scope) => {
+ let descr = match opt_loan_path(&err.cmt) {
+ Some(lp) => {
+ format!("`{}`", self.loan_path_to_string(&*lp))
+ }
+ None => self.cmt_to_string(&*err.cmt),
+ };
+ note_and_explain_region(
+ self.tcx,
+ format!("{} would have to be valid for ",
+ descr).as_slice(),
+ loan_scope,
+ "...");
+ note_and_explain_region(
+ self.tcx,
+ format!("...but {} is only valid for ", descr).as_slice(),
+ ptr_scope,
+ "");
+ }
+ }
+ }
+
+ pub fn append_loan_path_to_string(&self,
+ loan_path: &LoanPath<'tcx>,
+ out: &mut String) {
+ match loan_path.kind {
+ LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) |
+ LpVar(id) => {
+ out.push_str(ty::local_var_name_str(self.tcx, id).get());
+ }
+
+ LpDowncast(ref lp_base, variant_def_id) => {
+ out.push('(');
+ self.append_loan_path_to_string(&**lp_base, out);
+ out.push_str(DOWNCAST_PRINTED_OPERATOR);
+ out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
+ out.push(')');
+ }
+
+
+ LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => {
+ self.append_autoderefd_loan_path_to_string(&**lp_base, out);
+ match fname {
+ mc::NamedField(fname) => {
+ out.push('.');
+ out.push_str(token::get_name(fname).get());
+ }
+ mc::PositionalField(idx) => {
+ out.push('.');
+ out.push_str(idx.to_string().as_slice());
+ }
+ }
+ }
+
+ LpExtend(ref lp_base, _, LpInterior(mc::InteriorElement(_))) => {
+ self.append_autoderefd_loan_path_to_string(&**lp_base, out);
+ out.push_str("[..]");
+ }
+
+ LpExtend(ref lp_base, _, LpDeref(_)) => {
+ out.push('*');
+ self.append_loan_path_to_string(&**lp_base, out);
+ }
+ }
+ }
+
+ pub fn append_autoderefd_loan_path_to_string(&self,
+ loan_path: &LoanPath<'tcx>,
+ out: &mut String) {
+ match loan_path.kind {
+ LpExtend(ref lp_base, _, LpDeref(_)) => {
+ // For a path like `(*x).f` or `(*x)[3]`, autoderef
+ // rules would normally allow users to omit the `*x`.
+ // So just serialize such paths to `x.f` or x[3]` respectively.
+ self.append_autoderefd_loan_path_to_string(&**lp_base, out)
+ }
+
+ LpDowncast(ref lp_base, variant_def_id) => {
+ out.push('(');
+ self.append_autoderefd_loan_path_to_string(&**lp_base, out);
+ out.push(':');
+ out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice());
+ out.push(')');
+ }
+
+ LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => {
+ self.append_loan_path_to_string(loan_path, out)
+ }
+ }
+ }
+
+ pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String {
+ let mut result = String::new();
+ self.append_loan_path_to_string(loan_path, &mut result);
+ result
+ }
+
+ pub fn cmt_to_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
+ self.mc().cmt_to_string(cmt)
+ }
+}
+
+fn is_statement_scope(tcx: &ty::ctxt, region: ty::Region) -> bool {
+ match region {
+ ty::ReScope(scope) => {
+ match tcx.map.find(scope.node_id()) {
+ Some(ast_map::NodeStmt(_)) => true,
+ _ => false
+ }
+ }
+ _ => false
+ }
+}
+
+impl BitwiseOperator for LoanDataFlowOperator {
+ #[inline]
+ fn join(&self, succ: uint, pred: uint) -> uint {
+ succ | pred // loans from both preds are in scope
+ }
+}
+
+impl DataFlowOperator for LoanDataFlowOperator {
+ #[inline]
+ fn initial_value(&self) -> bool {
+ false // no loans in scope by default
+ }
+}
+
+impl<'tcx> Repr<'tcx> for Loan<'tcx> {
+ fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
+ format!("Loan_{}({}, {}, {}-{}, {})",
+ self.index,
+ self.loan_path.repr(tcx),
+ self.kind,
+ self.gen_scope,
+ self.kill_scope,
+ self.restricted_paths.repr(tcx))
+ }
+}
+
+impl<'tcx> Repr<'tcx> for LoanPath<'tcx> {
+ fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String {
+ match self.kind {
+ LpVar(id) => {
+ format!("$({})", tcx.map.node_to_string(id))
+ }
+
+ LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
+ let s = tcx.map.node_to_string(var_id);
+ format!("$({} captured by id={})", s, closure_expr_id)
+ }
+
+ LpDowncast(ref lp, variant_def_id) => {
+ let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
+ ty::item_path_str(tcx, variant_def_id)
+ } else {
+ variant_def_id.repr(tcx)
+ };
+ format!("({}{}{})", lp.repr(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
+ }
+
+ LpExtend(ref lp, _, LpDeref(_)) => {
+ format!("{}.*", lp.repr(tcx))
+ }
+
+ LpExtend(ref lp, _, LpInterior(ref interior)) => {
+ format!("{}.{}", lp.repr(tcx), interior.repr(tcx))
+ }
+ }
+ }
+}
+
+impl<'tcx> UserString<'tcx> for LoanPath<'tcx> {
+ fn user_string(&self, tcx: &ty::ctxt<'tcx>) -> String {
+ match self.kind {
+ LpVar(id) => {
+ format!("$({})", tcx.map.node_to_user_string(id))
+ }
+
+ LpUpvar(ty::UpvarId{ var_id, closure_expr_id: _ }) => {
+ let s = tcx.map.node_to_user_string(var_id);
+ format!("$({} captured by closure)", s)
+ }
+
+ LpDowncast(ref lp, variant_def_id) => {
+ let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE {
+ ty::item_path_str(tcx, variant_def_id)
+ } else {
+ variant_def_id.repr(tcx)
+ };
+ format!("({}{}{})", lp.user_string(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str)
+ }
+
+ LpExtend(ref lp, _, LpDeref(_)) => {
+ format!("{}.*", lp.user_string(tcx))
+ }
+
+ LpExtend(ref lp, _, LpInterior(ref interior)) => {
+ format!("{}.{}", lp.user_string(tcx), interior.repr(tcx))
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Data structures used for tracking moves. Please see the extensive
+//! comments in the section "Moves and initialization" in `doc.rs`.
+
+pub use self::MoveKind::*;
+
+use borrowck::*;
+use borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend};
+use borrowck::LoanPathElem::{LpInterior};
+use rustc::middle::cfg;
+use rustc::middle::dataflow::DataFlowContext;
+use rustc::middle::dataflow::BitwiseOperator;
+use rustc::middle::dataflow::DataFlowOperator;
+use rustc::middle::expr_use_visitor as euv;
+use rustc::middle::mem_categorization as mc;
+use rustc::middle::ty;
+use rustc::util::nodemap::{FnvHashMap, NodeSet};
+use rustc::util::ppaux::Repr;
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::uint;
+use syntax::ast;
+use syntax::ast_util;
+use syntax::codemap::Span;
+
+#[path="fragments.rs"]
+pub mod fragments;
+
+pub struct MoveData<'tcx> {
+ /// Move paths. See section "Move paths" in `doc.rs`.
+ pub paths: RefCell<Vec<MovePath<'tcx>>>,
+
+ /// Cache of loan path to move path index, for easy lookup.
+ pub path_map: RefCell<FnvHashMap<Rc<LoanPath<'tcx>>, MovePathIndex>>,
+
+ /// Each move or uninitialized variable gets an entry here.
+ pub moves: RefCell<Vec<Move>>,
+
+ /// Assignments to a variable, like `x = foo`. These are assigned
+ /// bits for dataflow, since we must track them to ensure that
+ /// immutable variables are assigned at most once along each path.
+ pub var_assignments: RefCell<Vec<Assignment>>,
+
+ /// Assignments to a path, like `x.f = foo`. These are not
+ /// assigned dataflow bits, but we track them because they still
+ /// kill move bits.
+ pub path_assignments: RefCell<Vec<Assignment>>,
+
+ /// Enum variant matched within a pattern on some match arm, like
+ /// `SomeStruct{ f: Variant1(x, y) } => ...`
+ pub variant_matches: RefCell<Vec<VariantMatch>>,
+
+ /// Assignments to a variable or path, like `x = foo`, but not `x += foo`.
+ pub assignee_ids: RefCell<NodeSet>,
+
+ /// Path-fragments from moves in to or out of parts of structured data.
+ pub fragments: RefCell<fragments::FragmentSets>,
+}
+
+pub struct FlowedMoveData<'a, 'tcx: 'a> {
+ pub move_data: MoveData<'tcx>,
+
+ pub dfcx_moves: MoveDataFlow<'a, 'tcx>,
+
+ // We could (and maybe should, for efficiency) combine both move
+ // and assign data flow into one, but this way it's easier to
+ // distinguish the bits that correspond to moves and assignments.
+ pub dfcx_assign: AssignDataFlow<'a, 'tcx>
+}
+
+/// Index into `MoveData.paths`, used like a pointer
+#[deriving(PartialEq, Eq, PartialOrd, Ord, Show)]
+pub struct MovePathIndex(uint);
+
+impl Copy for MovePathIndex {}
+
+impl MovePathIndex {
+ fn get(&self) -> uint {
+ let MovePathIndex(v) = *self; v
+ }
+}
+
+impl Clone for MovePathIndex {
+ fn clone(&self) -> MovePathIndex {
+ MovePathIndex(self.get())
+ }
+}
+
+#[allow(non_upper_case_globals)]
+static InvalidMovePathIndex: MovePathIndex =
+ MovePathIndex(uint::MAX);
+
+/// Index into `MoveData.moves`, used like a pointer
+#[deriving(PartialEq)]
+pub struct MoveIndex(uint);
+
+impl Copy for MoveIndex {}
+
+impl MoveIndex {
+ fn get(&self) -> uint {
+ let MoveIndex(v) = *self; v
+ }
+}
+
+#[allow(non_upper_case_globals)]
+static InvalidMoveIndex: MoveIndex =
+ MoveIndex(uint::MAX);
+
+pub struct MovePath<'tcx> {
+ /// Loan path corresponding to this move path
+ pub loan_path: Rc<LoanPath<'tcx>>,
+
+ /// Parent pointer, `InvalidMovePathIndex` if root
+ pub parent: MovePathIndex,
+
+ /// Head of linked list of moves to this path,
+ /// `InvalidMoveIndex` if not moved
+ pub first_move: MoveIndex,
+
+ /// First node in linked list of children, `InvalidMovePathIndex` if leaf
+ pub first_child: MovePathIndex,
+
+ /// Next node in linked list of parent's children (siblings),
+ /// `InvalidMovePathIndex` if none.
+ pub next_sibling: MovePathIndex,
+}
+
+#[deriving(PartialEq, Show)]
+pub enum MoveKind {
+ Declared, // When declared, variables start out "moved".
+ MoveExpr, // Expression or binding that moves a variable
+ MovePat, // By-move binding
+ Captured // Closure creation that moves a value
+}
+
+impl Copy for MoveKind {}
+
+pub struct Move {
+ /// Path being moved.
+ pub path: MovePathIndex,
+
+ /// id of node that is doing the move.
+ pub id: ast::NodeId,
+
+ /// Kind of move, for error messages.
+ pub kind: MoveKind,
+
+ /// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
+ pub next_move: MoveIndex
+}
+
+impl Copy for Move {}
+
+pub struct Assignment {
+ /// Path being assigned.
+ pub path: MovePathIndex,
+
+ /// id where assignment occurs
+ pub id: ast::NodeId,
+
+ /// span of node where assignment occurs
+ pub span: Span,
+}
+
+impl Copy for Assignment {}
+
+pub struct VariantMatch {
+ /// downcast to the variant.
+ pub path: MovePathIndex,
+
+ /// path being downcast to the variant.
+ pub base_path: MovePathIndex,
+
+ /// id where variant's pattern occurs
+ pub id: ast::NodeId,
+
+ /// says if variant established by move (and why), by copy, or by borrow.
+ pub mode: euv::MatchMode
+}
+
+impl Copy for VariantMatch {}
+
+#[deriving(Clone)]
+pub struct MoveDataFlowOperator;
+
+impl Copy for MoveDataFlowOperator {}
+
+pub type MoveDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, MoveDataFlowOperator>;
+
+#[deriving(Clone)]
+pub struct AssignDataFlowOperator;
+
+impl Copy for AssignDataFlowOperator {}
+
+pub type AssignDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, AssignDataFlowOperator>;
+
+fn loan_path_is_precise(loan_path: &LoanPath) -> bool {
+ match loan_path.kind {
+ LpVar(_) | LpUpvar(_) => {
+ true
+ }
+ LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {
+ // Paths involving element accesses do not refer to a unique
+ // location, as there is no accurate tracking of the indices.
+ false
+ }
+ LpDowncast(ref lp_base, _) |
+ LpExtend(ref lp_base, _, _) => {
+ loan_path_is_precise(&**lp_base)
+ }
+ }
+}
+
+impl<'tcx> MoveData<'tcx> {
+ pub fn new() -> MoveData<'tcx> {
+ MoveData {
+ paths: RefCell::new(Vec::new()),
+ path_map: RefCell::new(FnvHashMap::new()),
+ moves: RefCell::new(Vec::new()),
+ path_assignments: RefCell::new(Vec::new()),
+ var_assignments: RefCell::new(Vec::new()),
+ variant_matches: RefCell::new(Vec::new()),
+ assignee_ids: RefCell::new(NodeSet::new()),
+ fragments: RefCell::new(fragments::FragmentSets::new()),
+ }
+ }
+
+ pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath<'tcx>> {
+ (*self.paths.borrow())[index.get()].loan_path.clone()
+ }
+
+ fn path_parent(&self, index: MovePathIndex) -> MovePathIndex {
+ (*self.paths.borrow())[index.get()].parent
+ }
+
+ fn path_first_move(&self, index: MovePathIndex) -> MoveIndex {
+ (*self.paths.borrow())[index.get()].first_move
+ }
+
+ /// Returns the index of first child, or `InvalidMovePathIndex` if
+ /// `index` is leaf.
+ fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex {
+ (*self.paths.borrow())[index.get()].first_child
+ }
+
+ fn path_next_sibling(&self, index: MovePathIndex) -> MovePathIndex {
+ (*self.paths.borrow())[index.get()].next_sibling
+ }
+
+ fn set_path_first_move(&self,
+ index: MovePathIndex,
+ first_move: MoveIndex) {
+ (*self.paths.borrow_mut())[index.get()].first_move = first_move
+ }
+
+ fn set_path_first_child(&self,
+ index: MovePathIndex,
+ first_child: MovePathIndex) {
+ (*self.paths.borrow_mut())[index.get()].first_child = first_child
+ }
+
+ fn move_next_move(&self, index: MoveIndex) -> MoveIndex {
+ //! Type safe indexing operator
+ (*self.moves.borrow())[index.get()].next_move
+ }
+
+ fn is_var_path(&self, index: MovePathIndex) -> bool {
+ //! True if `index` refers to a variable
+ self.path_parent(index) == InvalidMovePathIndex
+ }
+
+ /// Returns the existing move path index for `lp`, if any, and otherwise adds a new index for
+ /// `lp` and any of its base paths that do not yet have an index.
+ pub fn move_path(&self,
+ tcx: &ty::ctxt<'tcx>,
+ lp: Rc<LoanPath<'tcx>>) -> MovePathIndex {
+ match self.path_map.borrow().get(&lp) {
+ Some(&index) => {
+ return index;
+ }
+ None => {}
+ }
+
+ let index = match lp.kind {
+ LpVar(..) | LpUpvar(..) => {
+ let index = MovePathIndex(self.paths.borrow().len());
+
+ self.paths.borrow_mut().push(MovePath {
+ loan_path: lp.clone(),
+ parent: InvalidMovePathIndex,
+ first_move: InvalidMoveIndex,
+ first_child: InvalidMovePathIndex,
+ next_sibling: InvalidMovePathIndex,
+ });
+
+ index
+ }
+
+ LpDowncast(ref base, _) |
+ LpExtend(ref base, _, _) => {
+ let parent_index = self.move_path(tcx, base.clone());
+
+ let index = MovePathIndex(self.paths.borrow().len());
+
+ let next_sibling = self.path_first_child(parent_index);
+ self.set_path_first_child(parent_index, index);
+
+ self.paths.borrow_mut().push(MovePath {
+ loan_path: lp.clone(),
+ parent: parent_index,
+ first_move: InvalidMoveIndex,
+ first_child: InvalidMovePathIndex,
+ next_sibling: next_sibling,
+ });
+
+ index
+ }
+ };
+
+ debug!("move_path(lp={}, index={})",
+ lp.repr(tcx),
+ index);
+
+ assert_eq!(index.get(), self.paths.borrow().len() - 1);
+ self.path_map.borrow_mut().insert(lp, index);
+ return index;
+ }
+
+ fn existing_move_path(&self, lp: &Rc<LoanPath<'tcx>>)
+ -> Option<MovePathIndex> {
+ self.path_map.borrow().get(lp).cloned()
+ }
+
+ fn existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>)
+ -> Vec<MovePathIndex> {
+ let mut result = vec!();
+ self.add_existing_base_paths(lp, &mut result);
+ result
+ }
+
+ /// Adds any existing move path indices for `lp` and any base paths of `lp` to `result`, but
+ /// does not add new move paths
+ fn add_existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>,
+ result: &mut Vec<MovePathIndex>) {
+ match self.path_map.borrow().get(lp).cloned() {
+ Some(index) => {
+ self.each_base_path(index, |p| {
+ result.push(p);
+ true
+ });
+ }
+ None => {
+ match lp.kind {
+ LpVar(..) | LpUpvar(..) => { }
+ LpDowncast(ref b, _) |
+ LpExtend(ref b, _, _) => {
+ self.add_existing_base_paths(b, result);
+ }
+ }
+ }
+ }
+
+ }
+
+ /// Adds a new move entry for a move of `lp` that occurs at location `id` with kind `kind`.
+ pub fn add_move(&self,
+ tcx: &ty::ctxt<'tcx>,
+ lp: Rc<LoanPath<'tcx>>,
+ id: ast::NodeId,
+ kind: MoveKind) {
+ debug!("add_move(lp={}, id={}, kind={})",
+ lp.repr(tcx),
+ id,
+ kind);
+
+ let path_index = self.move_path(tcx, lp.clone());
+ let move_index = MoveIndex(self.moves.borrow().len());
+
+ self.fragments.borrow_mut().add_move(path_index);
+
+ let next_move = self.path_first_move(path_index);
+ self.set_path_first_move(path_index, move_index);
+
+ self.moves.borrow_mut().push(Move {
+ path: path_index,
+ id: id,
+ kind: kind,
+ next_move: next_move
+ });
+ }
+
+ /// Adds a new record for an assignment to `lp` that occurs at location `id` with the given
+ /// `span`.
+ pub fn add_assignment(&self,
+ tcx: &ty::ctxt<'tcx>,
+ lp: Rc<LoanPath<'tcx>>,
+ assign_id: ast::NodeId,
+ span: Span,
+ assignee_id: ast::NodeId,
+ mode: euv::MutateMode) {
+ debug!("add_assignment(lp={}, assign_id={}, assignee_id={}",
+ lp.repr(tcx), assign_id, assignee_id);
+
+ let path_index = self.move_path(tcx, lp.clone());
+
+ self.fragments.borrow_mut().add_assignment(path_index);
+
+ match mode {
+ euv::Init | euv::JustWrite => {
+ self.assignee_ids.borrow_mut().insert(assignee_id);
+ }
+ euv::WriteAndRead => { }
+ }
+
+ let assignment = Assignment {
+ path: path_index,
+ id: assign_id,
+ span: span,
+ };
+
+ if self.is_var_path(path_index) {
+ debug!("add_assignment[var](lp={}, assignment={}, path_index={})",
+ lp.repr(tcx), self.var_assignments.borrow().len(), path_index);
+
+ self.var_assignments.borrow_mut().push(assignment);
+ } else {
+ debug!("add_assignment[path](lp={}, path_index={})",
+ lp.repr(tcx), path_index);
+
+ self.path_assignments.borrow_mut().push(assignment);
+ }
+ }
+
+ /// Adds a new record for a match of `base_lp`, downcast to
+ /// variant `lp`, that occurs at location `pattern_id`. (One
+ /// should be able to recover the span info from the
+ /// `pattern_id` and the ast_map, I think.)
+ pub fn add_variant_match(&self,
+ tcx: &ty::ctxt<'tcx>,
+ lp: Rc<LoanPath<'tcx>>,
+ pattern_id: ast::NodeId,
+ base_lp: Rc<LoanPath<'tcx>>,
+ mode: euv::MatchMode) {
+ debug!("add_variant_match(lp={}, pattern_id={})",
+ lp.repr(tcx), pattern_id);
+
+ let path_index = self.move_path(tcx, lp.clone());
+ let base_path_index = self.move_path(tcx, base_lp.clone());
+
+ self.fragments.borrow_mut().add_assignment(path_index);
+
+ let variant_match = VariantMatch {
+ path: path_index,
+ base_path: base_path_index,
+ id: pattern_id,
+ mode: mode,
+ };
+
+ self.variant_matches.borrow_mut().push(variant_match);
+ }
+
+ fn fixup_fragment_sets(&self, tcx: &ty::ctxt<'tcx>) {
+ fragments::fixup_fragment_sets(self, tcx)
+ }
+
+ /// Adds the gen/kills for the various moves and
+ /// assignments into the provided data flow contexts.
+ /// Moves are generated by moves and killed by assignments and
+ /// scoping. Assignments are generated by assignment to variables and
+ /// killed by scoping. See `doc.rs` for more details.
+ fn add_gen_kills(&self,
+ tcx: &ty::ctxt<'tcx>,
+ dfcx_moves: &mut MoveDataFlow,
+ dfcx_assign: &mut AssignDataFlow) {
+ for (i, the_move) in self.moves.borrow().iter().enumerate() {
+ dfcx_moves.add_gen(the_move.id, i);
+ }
+
+ for (i, assignment) in self.var_assignments.borrow().iter().enumerate() {
+ dfcx_assign.add_gen(assignment.id, i);
+ self.kill_moves(assignment.path, assignment.id, dfcx_moves);
+ }
+
+ for assignment in self.path_assignments.borrow().iter() {
+ self.kill_moves(assignment.path, assignment.id, dfcx_moves);
+ }
+
+ // Kill all moves related to a variable `x` when
+ // it goes out of scope:
+ for path in self.paths.borrow().iter() {
+ match path.loan_path.kind {
+ LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
+ let kill_scope = path.loan_path.kill_scope(tcx);
+ let path = self.path_map.borrow()[path.loan_path];
+ self.kill_moves(path, kill_scope.node_id(), dfcx_moves);
+ }
+ LpExtend(..) => {}
+ }
+ }
+
+ // Kill all assignments when the variable goes out of scope:
+ for (assignment_index, assignment) in
+ self.var_assignments.borrow().iter().enumerate() {
+ let lp = self.path_loan_path(assignment.path);
+ match lp.kind {
+ LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
+ let kill_scope = lp.kill_scope(tcx);
+ dfcx_assign.add_kill(kill_scope.node_id(), assignment_index);
+ }
+ LpExtend(..) => {
+ tcx.sess.bug("var assignment for non var path");
+ }
+ }
+ }
+ }
+
+ fn each_base_path(&self, index: MovePathIndex, f: |MovePathIndex| -> bool)
+ -> bool {
+ let mut p = index;
+ while p != InvalidMovePathIndex {
+ if !f(p) {
+ return false;
+ }
+ p = self.path_parent(p);
+ }
+ return true;
+ }
+
+ fn each_extending_path(&self,
+ index: MovePathIndex,
+ f: |MovePathIndex| -> bool)
+ -> bool {
+ if !f(index) {
+ return false;
+ }
+
+ let mut p = self.path_first_child(index);
+ while p != InvalidMovePathIndex {
+ if !self.each_extending_path(p, |x| f(x)) {
+ return false;
+ }
+ p = self.path_next_sibling(p);
+ }
+
+ return true;
+ }
+
+ fn each_applicable_move(&self,
+ index0: MovePathIndex,
+ f: |MoveIndex| -> bool)
+ -> bool {
+ let mut ret = true;
+ self.each_extending_path(index0, |index| {
+ let mut p = self.path_first_move(index);
+ while p != InvalidMoveIndex {
+ if !f(p) {
+ ret = false;
+ break;
+ }
+ p = self.move_next_move(p);
+ }
+ ret
+ });
+ ret
+ }
+
+ fn kill_moves(&self,
+ path: MovePathIndex,
+ kill_id: ast::NodeId,
+ dfcx_moves: &mut MoveDataFlow) {
+ // We can only perform kills for paths that refer to a unique location,
+ // since otherwise we may kill a move from one location with an
+ // assignment referring to another location.
+
+ let loan_path = self.path_loan_path(path);
+ if loan_path_is_precise(&*loan_path) {
+ self.each_applicable_move(path, |move_index| {
+ dfcx_moves.add_kill(kill_id, move_index.get());
+ true
+ });
+ }
+ }
+}
+
+impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> {
+ pub fn new(move_data: MoveData<'tcx>,
+ tcx: &'a ty::ctxt<'tcx>,
+ cfg: &cfg::CFG,
+ id_range: ast_util::IdRange,
+ decl: &ast::FnDecl,
+ body: &ast::Block)
+ -> FlowedMoveData<'a, 'tcx> {
+ let mut dfcx_moves =
+ DataFlowContext::new(tcx,
+ "flowed_move_data_moves",
+ Some(decl),
+ cfg,
+ MoveDataFlowOperator,
+ id_range,
+ move_data.moves.borrow().len());
+ let mut dfcx_assign =
+ DataFlowContext::new(tcx,
+ "flowed_move_data_assigns",
+ Some(decl),
+ cfg,
+ AssignDataFlowOperator,
+ id_range,
+ move_data.var_assignments.borrow().len());
+
+ move_data.fixup_fragment_sets(tcx);
+
+ move_data.add_gen_kills(tcx,
+ &mut dfcx_moves,
+ &mut dfcx_assign);
+
+ dfcx_moves.add_kills_from_flow_exits(cfg);
+ dfcx_assign.add_kills_from_flow_exits(cfg);
+
+ dfcx_moves.propagate(cfg, body);
+ dfcx_assign.propagate(cfg, body);
+
+ FlowedMoveData {
+ move_data: move_data,
+ dfcx_moves: dfcx_moves,
+ dfcx_assign: dfcx_assign,
+ }
+ }
+
+ pub fn kind_of_move_of_path(&self,
+ id: ast::NodeId,
+ loan_path: &Rc<LoanPath<'tcx>>)
+ -> Option<MoveKind> {
+ //! Returns the kind of a move of `loan_path` by `id`, if one exists.
+
+ let mut ret = None;
+ for loan_path_index in self.move_data.path_map.borrow().get(&*loan_path).iter() {
+ self.dfcx_moves.each_gen_bit(id, |move_index| {
+ let the_move = self.move_data.moves.borrow();
+ let the_move = (*the_move)[move_index];
+ if the_move.path == **loan_path_index {
+ ret = Some(the_move.kind);
+ false
+ } else {
+ true
+ }
+ });
+ }
+ ret
+ }
+
+ /// Iterates through each move of `loan_path` (or some base path of `loan_path`) that *may*
+ /// have occurred on entry to `id` without an intervening assignment. In other words, any moves
+ /// that would invalidate a reference to `loan_path` at location `id`.
+ pub fn each_move_of(&self,
+ id: ast::NodeId,
+ loan_path: &Rc<LoanPath<'tcx>>,
+ f: |&Move, &LoanPath<'tcx>| -> bool)
+ -> bool {
+ // Bad scenarios:
+ //
+ // 1. Move of `a.b.c`, use of `a.b.c`
+ // 2. Move of `a.b.c`, use of `a.b.c.d`
+ // 3. Move of `a.b.c`, use of `a` or `a.b`
+ //
+ // OK scenario:
+ //
+ // 4. move of `a.b.c`, use of `a.b.d`
+
+ let base_indices = self.move_data.existing_base_paths(loan_path);
+ if base_indices.is_empty() {
+ return true;
+ }
+
+ let opt_loan_path_index = self.move_data.existing_move_path(loan_path);
+
+ let mut ret = true;
+
+ self.dfcx_moves.each_bit_on_entry(id, |index| {
+ let the_move = self.move_data.moves.borrow();
+ let the_move = &(*the_move)[index];
+ let moved_path = the_move.path;
+ if base_indices.iter().any(|x| x == &moved_path) {
+ // Scenario 1 or 2: `loan_path` or some base path of
+ // `loan_path` was moved.
+ if !f(the_move, &*self.move_data.path_loan_path(moved_path)) {
+ ret = false;
+ }
+ } else {
+ for &loan_path_index in opt_loan_path_index.iter() {
+ let cont = self.move_data.each_base_path(moved_path, |p| {
+ if p == loan_path_index {
+ // Scenario 3: some extension of `loan_path`
+ // was moved
+ f(the_move,
+ &*self.move_data.path_loan_path(moved_path))
+ } else {
+ true
+ }
+ });
+ if !cont { ret = false; break }
+ }
+ }
+ ret
+ })
+ }
+
+ /// Iterates through every assignment to `loan_path` that may have occurred on entry to `id`.
+ /// `loan_path` must be a single variable.
+ pub fn each_assignment_of(&self,
+ id: ast::NodeId,
+ loan_path: &Rc<LoanPath<'tcx>>,
+ f: |&Assignment| -> bool)
+ -> bool {
+ let loan_path_index = {
+ match self.move_data.existing_move_path(loan_path) {
+ Some(i) => i,
+ None => {
+ // if there were any assignments, it'd have an index
+ return true;
+ }
+ }
+ };
+
+ self.dfcx_assign.each_bit_on_entry(id, |index| {
+ let assignment = self.move_data.var_assignments.borrow();
+ let assignment = &(*assignment)[index];
+ if assignment.path == loan_path_index && !f(assignment) {
+ false
+ } else {
+ true
+ }
+ })
+ }
+}
+
+impl BitwiseOperator for MoveDataFlowOperator {
+ #[inline]
+ fn join(&self, succ: uint, pred: uint) -> uint {
+ succ | pred // moves from both preds are in scope
+ }
+}
+
+impl DataFlowOperator for MoveDataFlowOperator {
+ #[inline]
+ fn initial_value(&self) -> bool {
+ false // no loans in scope by default
+ }
+}
+
+impl BitwiseOperator for AssignDataFlowOperator {
+ #[inline]
+ fn join(&self, succ: uint, pred: uint) -> uint {
+ succ | pred // moves from both preds are in scope
+ }
+}
+
+impl DataFlowOperator for AssignDataFlowOperator {
+ #[inline]
+ fn initial_value(&self) -> bool {
+ false // no assignments in scope by default
+ }
+}
--- /dev/null
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! This module provides linkage between rustc::middle::graph and
+//! libgraphviz traits, specialized to attaching borrowck analysis
+//! data to rendered labels.
+
+pub use self::Variant::*;
+
+pub use rustc::middle::cfg::graphviz::{Node, Edge};
+use rustc::middle::cfg::graphviz as cfg_dot;
+
+use borrowck;
+use borrowck::{BorrowckCtxt, LoanPath};
+use dot;
+use rustc::middle::cfg::{CFGIndex};
+use rustc::middle::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
+use rustc::middle::dataflow;
+use std::rc::Rc;
+
+#[deriving(Show)]
+pub enum Variant {
+ Loans,
+ Moves,
+ Assigns,
+}
+
+impl Copy for Variant {}
+
+impl Variant {
+ pub fn short_name(&self) -> &'static str {
+ match *self {
+ Loans => "loans",
+ Moves => "moves",
+ Assigns => "assigns",
+ }
+ }
+}
+
+pub struct DataflowLabeller<'a, 'tcx: 'a> {
+ pub inner: cfg_dot::LabelledCFG<'a, 'tcx>,
+ pub variants: Vec<Variant>,
+ pub borrowck_ctxt: &'a BorrowckCtxt<'a, 'tcx>,
+ pub analysis_data: &'a borrowck::AnalysisData<'a, 'tcx>,
+}
+
+impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> {
+ fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
+ let id = n.val1().data.id;
+ debug!("dataflow_for({}, id={}) {}", e, id, self.variants);
+ let mut sets = "".to_string();
+ let mut seen_one = false;
+ for &variant in self.variants.iter() {
+ if seen_one { sets.push_str(" "); } else { seen_one = true; }
+ sets.push_str(variant.short_name());
+ sets.push_str(": ");
+ sets.push_str(self.dataflow_for_variant(e, n, variant).as_slice());
+ }
+ sets
+ }
+
+ fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node, v: Variant) -> String {
+ let cfgidx = n.val0();
+ match v {
+ Loans => self.dataflow_loans_for(e, cfgidx),
+ Moves => self.dataflow_moves_for(e, cfgidx),
+ Assigns => self.dataflow_assigns_for(e, cfgidx),
+ }
+ }
+
+ fn build_set<O:DataFlowOperator>(&self,
+ e: EntryOrExit,
+ cfgidx: CFGIndex,
+ dfcx: &DataFlowContext<'a, 'tcx, O>,
+ to_lp: |uint| -> Rc<LoanPath<'tcx>>) -> String {
+ let mut saw_some = false;
+ let mut set = "{".to_string();
+ dfcx.each_bit_for_node(e, cfgidx, |index| {
+ let lp = to_lp(index);
+ if saw_some {
+ set.push_str(", ");
+ }
+ let loan_str = self.borrowck_ctxt.loan_path_to_string(&*lp);
+ set.push_str(loan_str.as_slice());
+ saw_some = true;
+ true
+ });
+ set.push_str("}");
+ set
+ }
+
+ fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+ let dfcx = &self.analysis_data.loans;
+ let loan_index_to_path = |loan_index| {
+ let all_loans = &self.analysis_data.all_loans;
+ all_loans[loan_index].loan_path()
+ };
+ self.build_set(e, cfgidx, dfcx, loan_index_to_path)
+ }
+
+ fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+ let dfcx = &self.analysis_data.move_data.dfcx_moves;
+ let move_index_to_path = |move_index| {
+ let move_data = &self.analysis_data.move_data.move_data;
+ let moves = move_data.moves.borrow();
+ let the_move = &(*moves)[move_index];
+ move_data.path_loan_path(the_move.path)
+ };
+ self.build_set(e, cfgidx, dfcx, move_index_to_path)
+ }
+
+ fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+ let dfcx = &self.analysis_data.move_data.dfcx_assign;
+ let assign_index_to_path = |assign_index| {
+ let move_data = &self.analysis_data.move_data.move_data;
+ let assignments = move_data.var_assignments.borrow();
+ let assignment = &(*assignments)[assign_index];
+ move_data.path_loan_path(assignment.path)
+ };
+ self.build_set(e, cfgidx, dfcx, assign_index_to_path)
+ }
+}
+
+impl<'a, 'tcx> dot::Labeller<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a, 'tcx> {
+ fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() }
+ fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) }
+ fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> {
+ let prefix = self.dataflow_for(dataflow::Entry, n);
+ let suffix = self.dataflow_for(dataflow::Exit, n);
+ let inner_label = self.inner.node_label(n);
+ inner_label
+ .prefix_line(dot::LabelStr(prefix.into_cow()))
+ .suffix_line(dot::LabelStr(suffix.into_cow()))
+ }
+ fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
+}
+
+impl<'a, 'tcx> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a, 'tcx> {
+ fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() }
+ fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() }
+ fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) }
+ fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) }
+}
--- /dev/null
+// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_name = "rustc_borrowck"]
+#![experimental]
+#![crate_type = "dylib"]
+#![crate_type = "rlib"]
+#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
+ html_favicon_url = "http://www.rust-lang.org/favicon.ico",
+ html_root_url = "http://doc.rust-lang.org/nightly/")]
+
+#![feature(default_type_params, globs, if_let, import_shadowing, macro_rules, phase, quote)]
+#![feature(slicing_syntax, tuple_indexing, unsafe_destructor)]
+#![feature(rustc_diagnostic_macros)]
+#![allow(non_camel_case_types)]
+
+#[phase(plugin, link)] extern crate log;
+#[phase(plugin, link)] extern crate syntax;
+
+// for "clarity", rename the graphviz crate to dot; graphviz within `borrowck`
+// refers to the borrowck-specific graphviz adapter traits.
+extern crate "graphviz" as dot;
+extern crate rustc;
+
+pub use borrowck::check_crate;
+pub use borrowck::build_borrowck_dataflow_data_for_fn;
+pub use borrowck::FnPartsWithCFG;
+
+mod borrowck;
+
+pub mod graphviz;
+
use rustc::plugin::registry::Registry;
use rustc::plugin;
use rustc::util::common::time;
+use rustc_borrowck as borrowck;
use rustc_trans::back::link;
use rustc_trans::back::write;
use rustc_trans::save;
middle::liveness::check_crate(&ty_cx));
time(time_passes, "borrow checking", (), |_|
- middle::borrowck::check_crate(&ty_cx));
+ borrowck::check_crate(&ty_cx));
time(time_passes, "rvalue checking", (), |_|
middle::check_rvalues::check_crate(&ty_cx, krate));
extern crate graphviz;
extern crate libc;
extern crate rustc;
-extern crate rustc_typeck;
extern crate rustc_back;
+extern crate rustc_borrowck;
extern crate rustc_trans;
+extern crate rustc_typeck;
#[phase(plugin, link)] extern crate log;
#[phase(plugin, link)] extern crate syntax;
extern crate serialize;
use driver;
use rustc::middle::ty;
-use rustc::middle::borrowck::{mod, FnPartsWithCFG};
-use rustc::middle::borrowck::graphviz as borrowck_dot;
use rustc::middle::cfg;
use rustc::middle::cfg::graphviz::LabelledCFG;
use rustc::session::Session;
use rustc::session::config::{mod, Input};
use rustc::util::ppaux;
+use rustc_borrowck as borrowck;
+use rustc_borrowck::graphviz as borrowck_dot;
use syntax::ast;
use syntax::ast_map::{mod, blocks, NodePrinter};
return Ok(())
}
blocks::FnLikeCode(fn_like) => {
- let fn_parts = FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
+ let fn_parts = borrowck::FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
let (bccx, analysis_data) =
borrowck::build_borrowck_dataflow_data_for_fn(ty_cx, fn_parts);