// 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 or the MIT license // , 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 */ use core::prelude::*; use mc = middle::mem_categorization; use middle::ty; use middle::typeck; use middle::moves; use middle::dataflow::DataFlowContext; use middle::dataflow::DataFlowOperator; use util::common::stmt_set; use util::ppaux::{note_and_explain_region, Repr, UserString}; use core::hashmap::{HashSet, HashMap}; use core::io; use core::ops::{BitOr, BitAnd}; use core::result::{Result}; use core::str; use syntax::ast; use syntax::ast_map; use syntax::visit; use syntax::codemap::span; macro_rules! if_ok( ($inp: expr) => ( match $inp { Ok(v) => { v } Err(e) => { return Err(e); } } ) ) pub mod doc; pub mod check_loans; #[path="gather_loans/mod.rs"] pub mod gather_loans; pub mod move_data; pub struct LoanDataFlowOperator; pub type LoanDataFlow = DataFlowContext; pub fn check_crate( tcx: ty::ctxt, method_map: typeck::method_map, moves_map: moves::MovesMap, moved_variables_set: moves::MovedVariablesSet, capture_map: moves::CaptureMap, crate: @ast::crate) -> (root_map, write_guard_map) { let bccx = @BorrowckCtxt { tcx: tcx, method_map: method_map, moves_map: moves_map, moved_variables_set: moved_variables_set, capture_map: capture_map, root_map: root_map(), loan_map: @mut HashMap::new(), write_guard_map: @mut HashSet::new(), stmt_map: @mut HashSet::new(), stats: @mut BorrowStats { loaned_paths_same: 0, loaned_paths_imm: 0, stable_paths: 0, req_pure_paths: 0, guaranteed_paths: 0, } }; let v = visit::mk_vt(@visit::Visitor {visit_fn: borrowck_fn, ..*visit::default_visitor()}); visit::visit_crate(crate, bccx, v); if tcx.sess.borrowck_stats() { io::println("--- borrowck stats ---"); io::println(fmt!("paths requiring guarantees: %u", bccx.stats.guaranteed_paths)); io::println(fmt!("paths requiring loans : %s", make_stat(bccx, bccx.stats.loaned_paths_same))); io::println(fmt!("paths requiring imm loans : %s", make_stat(bccx, bccx.stats.loaned_paths_imm))); io::println(fmt!("stable paths : %s", make_stat(bccx, bccx.stats.stable_paths))); io::println(fmt!("paths requiring purity : %s", make_stat(bccx, bccx.stats.req_pure_paths))); } return (bccx.root_map, bccx.write_guard_map); fn make_stat(bccx: &BorrowckCtxt, stat: uint) -> ~str { let stat_f = stat as float; let total = bccx.stats.guaranteed_paths as float; fmt!("%u (%.0f%%)", stat , stat_f * 100f / total) } } fn borrowck_fn(fk: &visit::fn_kind, decl: &ast::fn_decl, body: &ast::blk, sp: span, id: ast::node_id, this: @BorrowckCtxt, v: visit::vt<@BorrowckCtxt>) { match fk { &visit::fk_anon(*) | &visit::fk_fn_block(*) => { // Closures are checked as part of their containing fn item. } &visit::fk_item_fn(*) | &visit::fk_method(*) => { debug!("borrowck_fn(id=%?)", id); // Check the body of fn items. let (id_range, all_loans, move_data) = gather_loans::gather_loans(this, body); let mut loan_dfcx = DataFlowContext::new(this.tcx, this.method_map, LoanDataFlowOperator, id_range, all_loans.len()); for all_loans.eachi |loan_idx, loan| { loan_dfcx.add_gen(loan.gen_scope, loan_idx); loan_dfcx.add_kill(loan.kill_scope, loan_idx); } loan_dfcx.propagate(body); let flowed_moves = move_data::FlowedMoveData::new(move_data, this.tcx, this.method_map, id_range, body); check_loans::check_loans(this, &loan_dfcx, flowed_moves, *all_loans, body); } } visit::visit_fn(fk, decl, body, sp, id, this, v); } // ---------------------------------------------------------------------- // Type definitions pub struct BorrowckCtxt { tcx: ty::ctxt, method_map: typeck::method_map, moves_map: moves::MovesMap, moved_variables_set: moves::MovedVariablesSet, capture_map: moves::CaptureMap, root_map: root_map, loan_map: LoanMap, write_guard_map: write_guard_map, stmt_map: stmt_set, // Statistics: stats: @mut BorrowStats } pub struct BorrowStats { loaned_paths_same: uint, loaned_paths_imm: uint, stable_paths: uint, req_pure_paths: uint, guaranteed_paths: uint } pub type LoanMap = @mut HashMap; // The keys to the root map combine the `id` of the deref expression // with the number of types that it is *autodereferenced*. So, for // example, imagine I have a variable `x: @@@T` and an expression // `(*x).f`. This will have 3 derefs, one explicit and then two // autoderefs. These are the relevant `root_map_key` values that could // appear: // // {id:*x, derefs:0} --> roots `x` (type: @@@T, due to explicit deref) // {id:*x, derefs:1} --> roots `*x` (type: @@T, due to autoderef #1) // {id:*x, derefs:2} --> roots `**x` (type: @T, due to autoderef #2) // // Note that there is no entry with derefs:3---the type of that expression // is T, which is not a box. // // Note that implicit dereferences also occur with indexing of `@[]`, // `@str`, etc. The same rules apply. So, for example, given a // variable `x` of type `@[@[...]]`, if I have an instance of the // expression `x[0]` which is then auto-slice'd, there would be two // potential entries in the root map, both with the id of the `x[0]` // expression. The entry with `derefs==0` refers to the deref of `x` // used as part of evaluating `x[0]`. The entry with `derefs==1` // refers to the deref of the `x[0]` that occurs as part of the // auto-slice. #[deriving(Eq, IterBytes)] pub struct root_map_key { id: ast::node_id, derefs: uint } // A set containing IDs of expressions of gc'd type that need to have a write // guard. pub type write_guard_map = @mut HashSet; pub type BckResult = Result; #[deriving(Eq)] pub enum PartialTotal { Partial, // Loan affects some portion Total // Loan affects entire path } /////////////////////////////////////////////////////////////////////////// // Loans and loan paths /// Record of a loan that was issued. pub struct Loan { index: uint, loan_path: @LoanPath, cmt: mc::cmt, mutbl: ast::mutability, restrictions: ~[Restriction], gen_scope: ast::node_id, kill_scope: ast::node_id, span: span, } #[deriving(Eq, IterBytes)] pub enum LoanPath { LpVar(ast::node_id), // `x` in doc.rs LpExtend(@LoanPath, mc::MutabilityCategory, LoanPathElem) } #[deriving(Eq, IterBytes)] pub enum LoanPathElem { LpDeref, // `*LV` in doc.rs LpInterior(mc::InteriorKind) // `LV.f` in doc.rs } pub impl LoanPath { fn node_id(&self) -> ast::node_id { match *self { LpVar(local_id) => local_id, LpExtend(base, _, _) => base.node_id() } } } pub fn opt_loan_path(cmt: mc::cmt) -> Option<@LoanPath> { //! 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. match cmt.cat { mc::cat_rvalue | mc::cat_static_item | mc::cat_copied_upvar(_) | mc::cat_implicit_self => { None } mc::cat_local(id) | mc::cat_arg(id) | mc::cat_self(id) => { Some(@LpVar(id)) } mc::cat_deref(cmt_base, _, _) => { opt_loan_path(cmt_base).map( |&lp| @LpExtend(lp, cmt.mutbl, LpDeref)) } mc::cat_interior(cmt_base, ik) => { opt_loan_path(cmt_base).map( |&lp| @LpExtend(lp, cmt.mutbl, LpInterior(ik))) } mc::cat_downcast(cmt_base) | mc::cat_stack_upvar(cmt_base) | mc::cat_discr(cmt_base, _) => { opt_loan_path(cmt_base) } } } /////////////////////////////////////////////////////////////////////////// // Restrictions // // Borrowing an lvalue often results in *restrictions* that limit what // can be done with this lvalue during the scope of the loan: // // - `RESTR_MUTATE`: The lvalue may not be modified. // - `RESTR_CLAIM`: `&mut` borrows of the lvalue are forbidden. // - `RESTR_FREEZE`: `&` borrows of the lvalue are forbidden. // - `RESTR_ALIAS`: All borrows of the lvalue are forbidden. // // In addition, no value which is restricted may be moved. Therefore, // restrictions are meaningful even if the RestrictionSet is empty, // because the restriction against moves is implied. pub struct Restriction { loan_path: @LoanPath, set: RestrictionSet } #[deriving(Eq)] pub struct RestrictionSet { bits: u32 } pub static RESTR_EMPTY: RestrictionSet = RestrictionSet {bits: 0b0000}; pub static RESTR_MUTATE: RestrictionSet = RestrictionSet {bits: 0b0001}; pub static RESTR_CLAIM: RestrictionSet = RestrictionSet {bits: 0b0010}; pub static RESTR_FREEZE: RestrictionSet = RestrictionSet {bits: 0b0100}; pub static RESTR_ALIAS: RestrictionSet = RestrictionSet {bits: 0b1000}; pub impl RestrictionSet { fn intersects(&self, restr: RestrictionSet) -> bool { (self.bits & restr.bits) != 0 } fn contains_all(&self, restr: RestrictionSet) -> bool { (self.bits & restr.bits) == restr.bits } } impl BitOr for RestrictionSet { fn bitor(&self, rhs: &RestrictionSet) -> RestrictionSet { RestrictionSet {bits: self.bits | rhs.bits} } } impl BitAnd for RestrictionSet { fn bitand(&self, rhs: &RestrictionSet) -> RestrictionSet { RestrictionSet {bits: self.bits & rhs.bits} } } /////////////////////////////////////////////////////////////////////////// // Rooting of managed boxes // // When we borrow the interior of a managed box, it is sometimes // necessary to *root* the box, meaning to stash a copy of the box // somewhere that the garbage collector will find it. This ensures // that the box is not collected for the lifetime of the borrow. // // As part of this rooting, we sometimes also freeze the box at // runtime, meaning that we dynamically detect when the box is // borrowed in incompatible ways. // // Both of these actions are driven through the `root_map`, which maps // from a node to the dynamic rooting action that should be taken when // that node executes. The node is identified through a // `root_map_key`, which pairs a node-id and a deref count---the // problem is that sometimes the box that needs to be rooted is only // uncovered after a certain number of auto-derefs. pub struct RootInfo { scope: ast::node_id, freeze: Option // Some() if we should freeze box at runtime } pub type root_map = @mut HashMap; pub fn root_map() -> root_map { return @mut HashMap::new(); } pub enum DynaFreezeKind { DynaImm, DynaMut } impl ToStr for DynaFreezeKind { fn to_str(&self) -> ~str { match *self { DynaMut => ~"mutable", DynaImm => ~"immutable" } } } /////////////////////////////////////////////////////////////////////////// // Errors // Errors that can occur #[deriving(Eq)] pub enum bckerr_code { err_mutbl(ast::mutability), err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope err_out_of_scope(ty::Region, ty::Region), // superscope, subscope err_freeze_aliasable_const } // Combination of an error code and the categorization of the expression // that caused it #[deriving(Eq)] pub struct BckError { span: span, cmt: mc::cmt, code: bckerr_code } pub enum AliasableViolationKind { MutabilityViolation, BorrowViolation } pub enum MovedValueUseKind { MovedInUse, MovedInCapture, } /////////////////////////////////////////////////////////////////////////// // Misc pub impl BorrowckCtxt { 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) } fn is_subscope_of(&self, r_sub: ast::node_id, r_sup: ast::node_id) -> bool { self.tcx.region_maps.is_subscope_of(r_sub, r_sup) } fn is_move(&self, id: ast::node_id) -> bool { self.moves_map.contains(&id) } fn cat_expr(&self, expr: @ast::expr) -> mc::cmt { mc::cat_expr(self.tcx, self.method_map, expr) } fn cat_expr_unadjusted(&self, expr: @ast::expr) -> mc::cmt { mc::cat_expr_unadjusted(self.tcx, self.method_map, expr) } fn cat_expr_autoderefd(&self, expr: @ast::expr, adj: @ty::AutoAdjustment) -> mc::cmt { match *adj { ty::AutoAddEnv(*) => { // no autoderefs mc::cat_expr_unadjusted(self.tcx, self.method_map, expr) } ty::AutoDerefRef( ty::AutoDerefRef { autoderefs: autoderefs, _}) => { mc::cat_expr_autoderefd(self.tcx, self.method_map, expr, autoderefs) } } } fn cat_def(&self, id: ast::node_id, span: span, ty: ty::t, def: ast::def) -> mc::cmt { mc::cat_def(self.tcx, self.method_map, id, span, ty, def) } fn cat_discr(&self, cmt: mc::cmt, match_id: ast::node_id) -> mc::cmt { @mc::cmt_ {cat:mc::cat_discr(cmt, match_id), mutbl:cmt.mutbl.inherit(), ..*cmt} } fn mc_ctxt(&self) -> mc::mem_categorization_ctxt { mc::mem_categorization_ctxt {tcx: self.tcx, method_map: self.method_map} } fn cat_pattern(&self, cmt: mc::cmt, pat: @ast::pat, op: &fn(mc::cmt, @ast::pat)) { let mc = self.mc_ctxt(); mc.cat_pattern(cmt, pat, op); } fn report(&self, err: BckError) { self.span_err( err.span, self.bckerr_to_str(err)); self.note_and_explain_bckerr(err); } fn report_use_of_moved_value(&self, use_span: span, use_kind: MovedValueUseKind, lp: @LoanPath, move: &move_data::Move, moved_lp: @LoanPath) { let verb = match use_kind { MovedInUse => "use", MovedInCapture => "capture", }; match move.kind { move_data::Declared => { self.tcx.sess.span_err( use_span, fmt!("%s of possibly uninitialized value: `%s`", verb, self.loan_path_to_str(lp))); } _ => { let partially = if lp == moved_lp {""} else {"partially "}; self.tcx.sess.span_err( use_span, fmt!("%s of %smoved value: `%s`", verb, partially, self.loan_path_to_str(lp))); } } match move.kind { move_data::Declared => {} move_data::MoveExpr(expr) => { let expr_ty = ty::expr_ty_adjusted(self.tcx, expr); self.tcx.sess.span_note( expr.span, fmt!("`%s` moved here because it has type `%s`, \ which is moved by default (use `copy` to override)", self.loan_path_to_str(moved_lp), expr_ty.user_string(self.tcx))); } move_data::MovePat(pat) => { let pat_ty = ty::node_id_to_type(self.tcx, pat.id); self.tcx.sess.span_note( pat.span, fmt!("`%s` moved here because it has type `%s`, \ which is moved by default (use `ref` to override)", self.loan_path_to_str(moved_lp), pat_ty.user_string(self.tcx))); } move_data::Captured(expr) => { self.tcx.sess.span_note( expr.span, fmt!("`%s` moved into closure environment here \ because its type is moved by default \ (make a copy and capture that instead to override)", self.loan_path_to_str(moved_lp))); } } } fn report_reassigned_immutable_variable(&self, span: span, lp: @LoanPath, assign: &move_data::Assignment) { self.tcx.sess.span_err( span, fmt!("re-assignment of immutable variable `%s`", self.loan_path_to_str(lp))); self.tcx.sess.span_note( assign.span, fmt!("prior assignment occurs here")); } fn span_err(&self, s: span, m: &str) { self.tcx.sess.span_err(s, m); } fn span_note(&self, s: span, m: &str) { self.tcx.sess.span_note(s, m); } fn bckerr_to_str(&self, err: BckError) -> ~str { match err.code { err_mutbl(lk) => { fmt!("cannot borrow %s %s as %s", err.cmt.mutbl.to_user_str(), self.cmt_to_str(err.cmt), self.mut_to_str(lk)) } err_out_of_root_scope(*) => { fmt!("cannot root managed value long enough") } err_out_of_scope(*) => { fmt!("borrowed value does not live long enough") } err_freeze_aliasable_const => { // Means that the user borrowed a ~T or enum value // residing in &const or @const pointer. Terrible // error message, but then &const and @const are // supposed to be going away. fmt!("unsafe borrow of aliasable, const value") } } } fn report_aliasability_violation(&self, span: span, kind: AliasableViolationKind, cause: mc::AliasableReason) { let prefix = match kind { MutabilityViolation => "cannot assign to an `&mut`", BorrowViolation => "cannot borrow an `&mut`" }; match cause { mc::AliasableOther => { self.tcx.sess.span_err( span, fmt!("%s in an aliasable location", prefix)); } mc::AliasableManaged(ast::m_mutbl) => { // FIXME(#6269) reborrow @mut to &mut self.tcx.sess.span_err( span, fmt!("%s in a `@mut` pointer; \ try borrowing as `&mut` first", prefix)); } mc::AliasableManaged(m) => { self.tcx.sess.span_err( span, fmt!("%s in a `@%s` pointer; \ try an `@mut` instead", prefix, self.mut_to_keyword(m))); } mc::AliasableBorrowed(m) => { self.tcx.sess.span_err( span, fmt!("%s in a `&%s` pointer; \ try an `&mut` instead", prefix, self.mut_to_keyword(m))); } } } fn note_and_explain_bckerr(&self, err: BckError) { let code = err.code; match code { err_mutbl(*) | err_freeze_aliasable_const(*) => {} err_out_of_root_scope(super_scope, sub_scope) => { note_and_explain_region( self.tcx, "managed value would have to be rooted for ", sub_scope, "..."); note_and_explain_region( self.tcx, "...but can only be rooted for ", super_scope, ""); } err_out_of_scope(super_scope, sub_scope) => { note_and_explain_region( self.tcx, "borrowed pointer must be valid for ", sub_scope, "..."); note_and_explain_region( self.tcx, "...but borrowed value is only valid for ", super_scope, ""); } } } fn append_loan_path_to_str_from_interior(&self, loan_path: &LoanPath, out: &mut ~str) { match *loan_path { LpExtend(_, _, LpDeref) => { str::push_char(out, '('); self.append_loan_path_to_str(loan_path, out); str::push_char(out, ')'); } LpExtend(_, _, LpInterior(_)) | LpVar(_) => { self.append_loan_path_to_str(loan_path, out); } } } fn append_loan_path_to_str(&self, loan_path: &LoanPath, out: &mut ~str) { match *loan_path { LpVar(id) => { match self.tcx.items.find(&id) { Some(&ast_map::node_local(ident)) => { str::push_str(out, *self.tcx.sess.intr().get(ident)); } r => { self.tcx.sess.bug( fmt!("Loan path LpVar(%?) maps to %?, not local", id, r)); } } } LpExtend(lp_base, _, LpInterior(mc::InteriorField(fname))) => { self.append_loan_path_to_str_from_interior(lp_base, out); match fname { mc::NamedField(fname) => { str::push_char(out, '.'); str::push_str(out, *self.tcx.sess.intr().get(fname)); } mc::PositionalField(idx) => { str::push_char(out, '#'); // invent a notation here str::push_str(out, idx.to_str()); } } } LpExtend(lp_base, _, LpInterior(mc::InteriorElement(_))) => { self.append_loan_path_to_str_from_interior(lp_base, out); str::push_str(out, "[]"); } LpExtend(lp_base, _, LpDeref) => { str::push_char(out, '*'); self.append_loan_path_to_str(lp_base, out); } } } fn loan_path_to_str(&self, loan_path: &LoanPath) -> ~str { let mut result = ~""; self.append_loan_path_to_str(loan_path, &mut result); result } fn cmt_to_str(&self, cmt: mc::cmt) -> ~str { let mc = &mc::mem_categorization_ctxt {tcx: self.tcx, method_map: self.method_map}; mc.cmt_to_str(cmt) } fn mut_to_str(&self, mutbl: ast::mutability) -> ~str { let mc = &mc::mem_categorization_ctxt {tcx: self.tcx, method_map: self.method_map}; mc.mut_to_str(mutbl) } fn mut_to_keyword(&self, mutbl: ast::mutability) -> &'static str { match mutbl { ast::m_imm => "", ast::m_const => "const", ast::m_mutbl => "mut" } } } impl DataFlowOperator for LoanDataFlowOperator { #[inline(always)] fn initial_value(&self) -> bool { false // no loans in scope by default } #[inline(always)] fn join(&self, succ: uint, pred: uint) -> uint { succ | pred // loans from both preds are in scope } #[inline(always)] fn walk_closures(&self) -> bool { true } } impl Repr for Loan { fn repr(&self, tcx: ty::ctxt) -> ~str { fmt!("Loan_%?(%s, %?, %?-%?, %s)", self.index, self.loan_path.repr(tcx), self.mutbl, self.gen_scope, self.kill_scope, self.restrictions.repr(tcx)) } } impl Repr for Restriction { fn repr(&self, tcx: ty::ctxt) -> ~str { fmt!("Restriction(%s, %x)", self.loan_path.repr(tcx), self.set.bits as uint) } } impl Repr for LoanPath { fn repr(&self, tcx: ty::ctxt) -> ~str { match self { &LpVar(id) => { fmt!("$(%?)", id) } &LpExtend(lp, _, LpDeref) => { fmt!("%s.*", lp.repr(tcx)) } &LpExtend(lp, _, LpInterior(ref interior)) => { fmt!("%s.%s", lp.repr(tcx), interior.repr(tcx)) } } } }