1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 /*! See doc.rs for a thorough explanation of the borrow checker */
13 #![allow(non_camel_case_types)]
16 use middle::dataflow::DataFlowContext;
17 use middle::dataflow::BitwiseOperator;
18 use middle::dataflow::DataFlowOperator;
20 use middle::expr_use_visitor as euv;
21 use middle::mem_categorization as mc;
23 use util::ppaux::{note_and_explain_region, Repr, UserString};
26 use std::string::String;
29 use syntax::ast_map::blocks::{FnLikeNode, FnParts};
31 use syntax::codemap::Span;
32 use syntax::parse::token;
34 use syntax::visit::{Visitor, FnKind};
35 use syntax::ast::{FnDecl, Block, NodeId};
41 Err(e) => { return Err(e); }
57 pub struct LoanDataFlowOperator;
59 pub type LoanDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, LoanDataFlowOperator>;
61 impl<'a, 'tcx, 'v> Visitor<'v> for BorrowckCtxt<'a, 'tcx> {
62 fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v FnDecl,
63 b: &'v Block, s: Span, n: NodeId) {
64 borrowck_fn(self, fk, fd, b, s, n);
67 fn visit_item(&mut self, item: &ast::Item) {
68 borrowck_item(self, item);
72 pub fn check_crate(tcx: &ty::ctxt) {
73 let mut bccx = BorrowckCtxt {
83 visit::walk_crate(&mut bccx, tcx.map.krate());
85 if tcx.sess.borrowck_stats() {
86 println!("--- borrowck stats ---");
87 println!("paths requiring guarantees: {}",
88 bccx.stats.guaranteed_paths);
89 println!("paths requiring loans : {}",
90 make_stat(&bccx, bccx.stats.loaned_paths_same));
91 println!("paths requiring imm loans : {}",
92 make_stat(&bccx, bccx.stats.loaned_paths_imm));
93 println!("stable paths : {}",
94 make_stat(&bccx, bccx.stats.stable_paths));
97 fn make_stat(bccx: &BorrowckCtxt, stat: uint) -> String {
98 let total = bccx.stats.guaranteed_paths as f64;
99 let perc = if total == 0.0 { 0.0 } else { stat as f64 * 100.0 / total };
100 format!("{} ({:.0f}%)", stat, perc)
104 fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) {
105 // Gather loans for items. Note that we don't need
106 // to check loans for single expressions. The check
107 // loan step is intended for things that have a data
108 // flow dependent conditions.
110 ast::ItemStatic(_, _, ref ex) => {
111 gather_loans::gather_loans_in_static_initializer(this, &**ex);
114 visit::walk_item(this, item);
119 /// Collection of conclusions determined via borrow checker analyses.
120 pub struct AnalysisData<'a, 'tcx: 'a> {
121 pub all_loans: Vec<Loan>,
122 pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>,
123 pub move_data: move_data::FlowedMoveData<'a, 'tcx>,
126 fn borrowck_fn(this: &mut BorrowckCtxt,
132 debug!("borrowck_fn(id={})", id);
133 let cfg = cfg::CFG::new(this.tcx, body);
134 let AnalysisData { all_loans,
136 move_data:flowed_moves } =
137 build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id);
139 check_loans::check_loans(this, &loan_dfcx, flowed_moves,
140 all_loans.as_slice(), decl, body);
142 visit::walk_fn(this, fk, decl, body, sp);
145 fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>,
151 id: ast::NodeId) -> AnalysisData<'a, 'tcx> {
152 // Check the body of fn items.
153 let id_range = ast_util::compute_id_range_for_fn_body(fk, decl, body, sp, id);
154 let (all_loans, move_data) =
155 gather_loans::gather_loans_in_fn(this, decl, body);
158 DataFlowContext::new(this.tcx,
162 LoanDataFlowOperator,
165 for (loan_idx, loan) in all_loans.iter().enumerate() {
166 loan_dfcx.add_gen(loan.gen_scope, loan_idx);
167 loan_dfcx.add_kill(loan.kill_scope, loan_idx);
169 loan_dfcx.add_kills_from_flow_exits(cfg);
170 loan_dfcx.propagate(cfg, body);
172 let flowed_moves = move_data::FlowedMoveData::new(move_data,
179 AnalysisData { all_loans: all_loans,
181 move_data:flowed_moves }
184 /// This and a `ty::ctxt` is all you need to run the dataflow analyses
185 /// used in the borrow checker.
186 pub struct FnPartsWithCFG<'a> {
187 pub fn_parts: FnParts<'a>,
188 pub cfg: &'a cfg::CFG,
191 impl<'a> FnPartsWithCFG<'a> {
192 pub fn from_fn_like(f: &'a FnLikeNode,
193 g: &'a cfg::CFG) -> FnPartsWithCFG<'a> {
194 FnPartsWithCFG { fn_parts: f.to_fn_parts(), cfg: g }
198 /// Accessor for introspective clients inspecting `AnalysisData` and
199 /// the `BorrowckCtxt` itself , e.g. the flowgraph visualizer.
200 pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
201 tcx: &'a ty::ctxt<'tcx>,
202 input: FnPartsWithCFG<'a>) -> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'a, 'tcx>) {
204 let mut bccx = BorrowckCtxt {
207 loaned_paths_same: 0,
214 let p = input.fn_parts;
216 let dataflow_data = build_borrowck_dataflow_data(&mut bccx,
224 (bccx, dataflow_data)
227 // ----------------------------------------------------------------------
230 pub struct BorrowckCtxt<'a, 'tcx: 'a> {
231 tcx: &'a ty::ctxt<'tcx>,
238 loaned_paths_same: uint,
239 loaned_paths_imm: uint,
241 guaranteed_paths: uint
244 pub type BckResult<T> = Result<T, BckError>;
246 #[deriving(PartialEq)]
247 pub enum PartialTotal {
248 Partial, // Loan affects some portion
249 Total // Loan affects entire path
252 ///////////////////////////////////////////////////////////////////////////
253 // Loans and loan paths
255 /// Record of a loan that was issued.
258 loan_path: Rc<LoanPath>,
259 kind: ty::BorrowKind,
260 restricted_paths: Vec<Rc<LoanPath>>,
261 gen_scope: ast::NodeId,
262 kill_scope: ast::NodeId,
264 cause: euv::LoanCause,
268 pub fn loan_path(&self) -> Rc<LoanPath> {
269 self.loan_path.clone()
273 #[deriving(PartialEq, Eq, Hash)]
275 LpVar(ast::NodeId), // `x` in doc.rs
276 LpUpvar(ty::UpvarId), // `x` captured by-value into closure
277 LpExtend(Rc<LoanPath>, mc::MutabilityCategory, LoanPathElem)
280 #[deriving(PartialEq, Eq, Hash)]
281 pub enum LoanPathElem {
282 LpDeref(mc::PointerKind), // `*LV` in doc.rs
283 LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
286 pub fn closure_to_block(closure_id: ast::NodeId,
287 tcx: &ty::ctxt) -> ast::NodeId {
288 match tcx.map.get(closure_id) {
289 ast_map::NodeExpr(expr) => match expr.node {
290 ast::ExprProc(_, ref block) |
291 ast::ExprFnBlock(_, _, ref block) |
292 ast::ExprUnboxedFn(_, _, _, ref block) => { block.id }
293 _ => fail!("encountered non-closure id: {}", closure_id)
295 _ => fail!("encountered non-expr id: {}", closure_id)
300 pub fn kill_scope(&self, tcx: &ty::ctxt) -> ast::NodeId {
302 LpVar(local_id) => tcx.region_maps.var_scope(local_id),
304 closure_to_block(upvar_id.closure_expr_id, tcx),
305 LpExtend(ref base, _, _) => base.kill_scope(tcx),
310 pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
311 //! Computes the `LoanPath` (if any) for a `cmt`.
312 //! Note that this logic is somewhat duplicated in
313 //! the method `compute()` found in `gather_loans::restrictions`,
314 //! which allows it to share common loan path pieces as it
315 //! traverses the CMT.
319 mc::cat_static_item |
320 mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
324 mc::cat_local(id) => {
325 Some(Rc::new(LpVar(id)))
328 mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _) |
329 mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id,
331 capturing_proc: proc_id }) => {
332 let upvar_id = ty::UpvarId{ var_id: id, closure_expr_id: proc_id };
333 Some(Rc::new(LpUpvar(upvar_id)))
336 mc::cat_deref(ref cmt_base, _, pk) => {
337 opt_loan_path(cmt_base).map(|lp| {
338 Rc::new(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
342 mc::cat_interior(ref cmt_base, ik) => {
343 opt_loan_path(cmt_base).map(|lp| {
344 Rc::new(LpExtend(lp, cmt.mutbl, LpInterior(ik)))
348 mc::cat_downcast(ref cmt_base) |
349 mc::cat_discr(ref cmt_base, _) => {
350 opt_loan_path(cmt_base)
355 ///////////////////////////////////////////////////////////////////////////
358 // Errors that can occur
359 #[deriving(PartialEq)]
360 pub enum bckerr_code {
362 err_out_of_scope(ty::Region, ty::Region), // superscope, subscope
363 err_borrowed_pointer_too_short(ty::Region, ty::Region), // loan, ptr
366 // Combination of an error code and the categorization of the expression
368 #[deriving(PartialEq)]
369 pub struct BckError {
371 cause: euv::LoanCause,
376 pub enum AliasableViolationKind {
378 BorrowViolation(euv::LoanCause)
381 pub enum MovedValueUseKind {
386 ///////////////////////////////////////////////////////////////////////////
389 impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
390 pub fn is_subregion_of(&self, r_sub: ty::Region, r_sup: ty::Region)
392 self.tcx.region_maps.is_subregion_of(r_sub, r_sup)
395 pub fn is_subscope_of(&self, r_sub: ast::NodeId, r_sup: ast::NodeId)
397 self.tcx.region_maps.is_subscope_of(r_sub, r_sup)
400 pub fn mc(&self) -> mc::MemCategorizationContext<'a, ty::ctxt<'tcx>> {
401 mc::MemCategorizationContext::new(self.tcx)
404 pub fn cat_expr(&self, expr: &ast::Expr) -> mc::cmt {
405 match self.mc().cat_expr(expr) {
408 self.tcx.sess.span_bug(expr.span, "error in mem categorization");
413 pub fn cat_expr_unadjusted(&self, expr: &ast::Expr) -> mc::cmt {
414 match self.mc().cat_expr_unadjusted(expr) {
417 self.tcx.sess.span_bug(expr.span, "error in mem categorization");
422 pub fn cat_expr_autoderefd(&self,
424 adj: &ty::AutoAdjustment)
429 autoderefs: autoderefs, ..}) => {
430 self.mc().cat_expr_autoderefd(expr, autoderefs)
432 ty::AutoAddEnv(..) => {
434 self.mc().cat_expr_unadjusted(expr)
441 self.tcx.sess.span_bug(expr.span,
442 "error in mem categorization");
447 pub fn cat_def(&self,
453 match self.mc().cat_def(id, span, ty, def) {
456 self.tcx.sess.span_bug(span, "error in mem categorization");
461 pub fn cat_captured_var(&self,
462 closure_id: ast::NodeId,
466 // Create the cmt for the variable being borrowed, from the
467 // caller's perspective
468 let var_id = upvar_def.def_id().node;
469 let var_ty = ty::node_id_to_type(self.tcx, var_id);
470 self.cat_def(closure_id, closure_span, var_ty, upvar_def)
473 pub fn cat_discr(&self, cmt: mc::cmt, match_id: ast::NodeId) -> mc::cmt {
475 cat: mc::cat_discr(cmt.clone(), match_id),
476 mutbl: cmt.mutbl.inherit(),
481 pub fn cat_pattern(&self,
484 op: |mc::cmt, &ast::Pat|) {
485 let r = self.mc().cat_pattern(cmt, pat, |_,x,y| op(x,y));
489 pub fn report(&self, err: BckError) {
492 self.bckerr_to_string(&err).as_slice());
493 self.note_and_explain_bckerr(err);
496 pub fn report_use_of_moved_value(&self,
498 use_kind: MovedValueUseKind,
500 move: &move_data::Move,
501 moved_lp: &LoanPath) {
502 let verb = match use_kind {
504 MovedInCapture => "capture",
508 move_data::Declared => {
509 self.tcx.sess.span_err(
511 format!("{} of possibly uninitialized variable: `{}`",
513 self.loan_path_to_string(lp)).as_slice());
516 let partially = if lp == moved_lp {""} else {"partially "};
517 self.tcx.sess.span_err(
519 format!("{} of {}moved value: `{}`",
522 self.loan_path_to_string(lp)).as_slice());
527 move_data::Declared => {}
529 move_data::MoveExpr => {
530 let (expr_ty, expr_span) = match self.tcx.map.find(move.id) {
531 Some(ast_map::NodeExpr(expr)) => {
532 (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
535 self.tcx.sess.bug(format!("MoveExpr({:?}) maps to \
541 let suggestion = move_suggestion(self.tcx, expr_ty,
542 "moved by default (use `copy` to override)");
543 self.tcx.sess.span_note(
545 format!("`{}` moved here because it has type `{}`, which is {}",
546 self.loan_path_to_string(moved_lp),
547 expr_ty.user_string(self.tcx),
548 suggestion).as_slice());
551 move_data::MovePat => {
552 let pat_ty = ty::node_id_to_type(self.tcx, move.id);
553 self.tcx.sess.span_note(self.tcx.map.span(move.id),
554 format!("`{}` moved here because it has type `{}`, \
555 which is moved by default (use `ref` to \
557 self.loan_path_to_string(moved_lp),
558 pat_ty.user_string(self.tcx)).as_slice());
561 move_data::Captured => {
562 let (expr_ty, expr_span) = match self.tcx.map.find(move.id) {
563 Some(ast_map::NodeExpr(expr)) => {
564 (ty::expr_ty_adjusted(self.tcx, &*expr), expr.span)
567 self.tcx.sess.bug(format!("Captured({:?}) maps to \
573 let suggestion = move_suggestion(self.tcx, expr_ty,
574 "moved by default (make a copy and \
575 capture that instead to override)");
576 self.tcx.sess.span_note(
578 format!("`{}` moved into closure environment here because it \
579 has type `{}`, which is {}",
580 self.loan_path_to_string(moved_lp),
581 expr_ty.user_string(self.tcx),
582 suggestion).as_slice());
586 fn move_suggestion(tcx: &ty::ctxt, ty: ty::t, default_msg: &'static str)
588 match ty::get(ty).sty {
589 ty::ty_closure(box ty::ClosureTy {
590 store: ty::RegionTraitStore(..),
593 "a non-copyable stack closure (capture it in a new closure, \
594 e.g. `|x| f(x)`, to override)",
595 _ if ty::type_moves_by_default(tcx, ty) =>
596 "non-copyable (perhaps you meant to use clone()?)",
602 pub fn report_reassigned_immutable_variable(&self,
606 &move_data::Assignment) {
607 self.tcx.sess.span_err(
609 format!("re-assignment of immutable variable `{}`",
610 self.loan_path_to_string(lp)).as_slice());
611 self.tcx.sess.span_note(assign.span, "prior assignment occurs here");
614 pub fn span_err(&self, s: Span, m: &str) {
615 self.tcx.sess.span_err(s, m);
618 pub fn span_note(&self, s: Span, m: &str) {
619 self.tcx.sess.span_note(s, m);
622 pub fn span_end_note(&self, s: Span, m: &str) {
623 self.tcx.sess.span_end_note(s, m);
626 pub fn bckerr_to_string(&self, err: &BckError) -> String {
629 let descr = match opt_loan_path(&err.cmt) {
632 err.cmt.mutbl.to_user_str(),
633 self.cmt_to_string(&*err.cmt))
636 format!("{} {} `{}`",
637 err.cmt.mutbl.to_user_str(),
638 self.cmt_to_string(&*err.cmt),
639 self.loan_path_to_string(&*lp))
644 euv::ClosureCapture(_) => {
645 format!("closure cannot assign to {}", descr)
647 euv::OverloadedOperator |
652 format!("cannot borrow {} as mutable", descr)
654 euv::ClosureInvocation => {
655 self.tcx.sess.span_bug(err.span,
656 "err_mutbl with a closure invocation");
660 err_out_of_scope(..) => {
661 let msg = match opt_loan_path(&err.cmt) {
662 None => "borrowed value".to_string(),
664 format!("`{}`", self.loan_path_to_string(&*lp))
667 format!("{} does not live long enough", msg)
669 err_borrowed_pointer_too_short(..) => {
670 let descr = match opt_loan_path(&err.cmt) {
672 format!("`{}`", self.loan_path_to_string(&*lp))
674 None => self.cmt_to_string(&*err.cmt),
677 format!("lifetime of {} is too short to guarantee \
678 its contents can be safely reborrowed",
684 pub fn report_aliasability_violation(&self,
686 kind: AliasableViolationKind,
687 cause: mc::AliasableReason) {
688 let prefix = match kind {
689 MutabilityViolation => {
690 "cannot assign to data"
692 BorrowViolation(euv::ClosureCapture(_)) => {
693 // I don't think we can get aliasability violations
694 // with closure captures, so no need to come up with a
695 // good error message. The reason this cannot happen
696 // is because we only capture local variables in
697 // closures, and those are never aliasable.
698 self.tcx.sess.span_bug(
700 "aliasability violation with closure");
702 BorrowViolation(euv::OverloadedOperator) |
703 BorrowViolation(euv::AddrOf) |
704 BorrowViolation(euv::AutoRef) |
705 BorrowViolation(euv::RefBinding) => {
706 "cannot borrow data mutably"
709 BorrowViolation(euv::ClosureInvocation) => {
713 BorrowViolation(euv::ForLoop) => {
719 mc::AliasableOther => {
720 self.tcx.sess.span_err(
722 format!("{} in an aliasable location",
725 mc::AliasableStatic(..) |
726 mc::AliasableStaticMut(..) => {
727 self.tcx.sess.span_err(
729 format!("{} in a static location", prefix).as_slice());
731 mc::AliasableManaged => {
732 self.tcx.sess.span_err(
734 format!("{} in a `@` pointer", prefix).as_slice());
736 mc::AliasableBorrowed => {
737 self.tcx.sess.span_err(
739 format!("{} in a `&` reference", prefix).as_slice());
744 pub fn note_and_explain_bckerr(&self, err: BckError) {
749 err_out_of_scope(super_scope, sub_scope) => {
750 note_and_explain_region(
752 "reference must be valid for ",
755 let suggestion = if is_statement_scope(self.tcx, super_scope) {
756 "; consider using a `let` binding to increase its lifetime"
760 note_and_explain_region(
762 "...but borrowed value is only valid for ",
767 err_borrowed_pointer_too_short(loan_scope, ptr_scope) => {
768 let descr = match opt_loan_path(&err.cmt) {
770 format!("`{}`", self.loan_path_to_string(&*lp))
772 None => self.cmt_to_string(&*err.cmt),
774 note_and_explain_region(
776 format!("{} would have to be valid for ",
780 note_and_explain_region(
782 format!("...but {} is only valid for ", descr).as_slice(),
789 pub fn append_loan_path_to_string(&self,
790 loan_path: &LoanPath,
793 LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) |
795 out.push_str(ty::local_var_name_str(self.tcx, id).get());
798 LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => {
799 self.append_autoderefd_loan_path_to_string(&**lp_base, out);
801 mc::NamedField(fname) => {
803 out.push_str(token::get_name(fname).get());
805 mc::PositionalField(idx) => {
807 out.push_str(idx.to_string().as_slice());
812 LpExtend(ref lp_base, _, LpInterior(mc::InteriorElement(_))) => {
813 self.append_autoderefd_loan_path_to_string(&**lp_base, out);
814 out.push_str("[..]");
817 LpExtend(ref lp_base, _, LpDeref(_)) => {
819 self.append_loan_path_to_string(&**lp_base, out);
824 pub fn append_autoderefd_loan_path_to_string(&self,
825 loan_path: &LoanPath,
828 LpExtend(ref lp_base, _, LpDeref(_)) => {
829 // For a path like `(*x).f` or `(*x)[3]`, autoderef
830 // rules would normally allow users to omit the `*x`.
831 // So just serialize such paths to `x.f` or x[3]` respectively.
832 self.append_autoderefd_loan_path_to_string(&**lp_base, out)
835 LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => {
836 self.append_loan_path_to_string(loan_path, out)
841 pub fn loan_path_to_string(&self, loan_path: &LoanPath) -> String {
842 let mut result = String::new();
843 self.append_loan_path_to_string(loan_path, &mut result);
847 pub fn cmt_to_string(&self, cmt: &mc::cmt_) -> String {
848 self.mc().cmt_to_string(cmt)
852 fn is_statement_scope(tcx: &ty::ctxt, region: ty::Region) -> bool {
854 ty::ReScope(node_id) => {
855 match tcx.map.find(node_id) {
856 Some(ast_map::NodeStmt(_)) => true,
864 impl BitwiseOperator for LoanDataFlowOperator {
866 fn join(&self, succ: uint, pred: uint) -> uint {
867 succ | pred // loans from both preds are in scope
871 impl DataFlowOperator for LoanDataFlowOperator {
873 fn initial_value(&self) -> bool {
874 false // no loans in scope by default
879 fn repr(&self, tcx: &ty::ctxt) -> String {
880 format!("Loan_{:?}({}, {:?}, {:?}-{:?}, {})",
882 self.loan_path.repr(tcx),
886 self.restricted_paths.repr(tcx))
890 impl Repr for LoanPath {
891 fn repr(&self, tcx: &ty::ctxt) -> String {
894 format!("$({})", tcx.map.node_to_string(id))
897 &LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
898 let s = tcx.map.node_to_string(var_id);
899 format!("$({} captured by id={})", s, closure_expr_id)
902 &LpExtend(ref lp, _, LpDeref(_)) => {
903 format!("{}.*", lp.repr(tcx))
906 &LpExtend(ref lp, _, LpInterior(ref interior)) => {
907 format!("{}.{}", lp.repr(tcx), interior.repr(tcx))