1 // Copyright 2012-2013 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 // ----------------------------------------------------------------------
14 // Phase 2 of check: we walk down the tree and check that:
15 // 1. assignments are always made to mutable locations;
16 // 2. loans made in overlapping scopes do not conflict
17 // 3. assignments do not affect things loaned out as immutable
18 // 4. moves do not affect things loaned out in any way
21 use middle::borrowck::*;
22 use euv = middle::expr_use_visitor;
23 use mc = middle::mem_categorization;
26 use syntax::codemap::Span;
27 use util::ppaux::Repr;
31 struct CheckLoanCtxt<'a> {
32 bccx: &'a BorrowckCtxt<'a>,
33 dfcx_loans: &'a LoanDataFlow<'a>,
34 move_data: move_data::FlowedMoveData<'a>,
35 all_loans: &'a [Loan],
38 impl<'a> euv::Delegate for CheckLoanCtxt<'a> {
40 consume_id: ast::NodeId,
43 mode: euv::ConsumeMode) {
44 debug!("consume(consume_id={}, cmt={}, mode={})",
45 consume_id, cmt.repr(self.tcx()), mode);
47 self.consume_common(consume_id, consume_span, cmt, mode);
50 fn consume_pat(&mut self,
51 consume_pat: &ast::Pat,
53 mode: euv::ConsumeMode) {
54 debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
55 consume_pat.repr(self.tcx()),
59 self.consume_common(consume_pat.id, consume_pat.span, cmt, mode);
63 borrow_id: ast::NodeId,
66 loan_region: ty::Region,
68 loan_cause: euv::LoanCause)
70 debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
71 bk={}, loan_cause={:?})",
72 borrow_id, cmt.repr(self.tcx()), loan_region,
75 match opt_loan_path(&cmt) {
77 let moved_value_use_kind = match loan_cause {
78 euv::ClosureCapture(_) => MovedInCapture,
81 self.check_if_path_is_moved(borrow_id, borrow_span, moved_value_use_kind, &lp);
86 self.check_for_conflicting_loans(borrow_id);
90 assignment_id: ast::NodeId,
91 assignment_span: Span,
92 assignee_cmt: mc::cmt,
93 mode: euv::MutateMode)
95 debug!("mutate(assignment_id={}, assignee_cmt={})",
96 assignment_id, assignee_cmt.repr(self.tcx()));
98 match opt_loan_path(&assignee_cmt) {
101 euv::Init | euv::JustWrite => {
102 // In a case like `path = 1`, then path does not
103 // have to be *FULLY* initialized, but we still
104 // must be careful lest it contains derefs of
106 self.check_if_assigned_path_is_moved(assignee_cmt.id,
111 euv::WriteAndRead => {
112 // In a case like `path += 1`, then path must be
113 // fully initialized, since we will read it before
115 self.check_if_path_is_moved(assignee_cmt.id,
125 self.check_assignment(assignment_id, assignment_span, assignee_cmt, mode);
128 fn decl_without_init(&mut self, _id: ast::NodeId, _span: Span) { }
131 pub fn check_loans(bccx: &BorrowckCtxt,
132 dfcx_loans: &LoanDataFlow,
133 move_data: move_data::FlowedMoveData,
137 debug!("check_loans(body id={:?})", body.id);
139 let mut clcx = CheckLoanCtxt {
141 dfcx_loans: dfcx_loans,
142 move_data: move_data,
143 all_loans: all_loans,
147 let mut euv = euv::ExprUseVisitor::new(&mut clcx, bccx.tcx);
148 euv.walk_fn(decl, body);
152 #[deriving(PartialEq)]
155 UseWhileBorrowed(/*loan*/Rc<LoanPath>, /*loan*/Span)
158 fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
159 borrow_kind2: ty::BorrowKind)
161 borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow
164 impl<'a> CheckLoanCtxt<'a> {
165 pub fn tcx(&self) -> &'a ty::ctxt { self.bccx.tcx }
167 pub fn each_issued_loan(&self, scope_id: ast::NodeId, op: |&Loan| -> bool)
169 //! Iterates over each loan that has been issued
170 //! on entrance to `scope_id`, regardless of whether it is
171 //! actually *in scope* at that point. Sometimes loans
172 //! are issued for future scopes and thus they may have been
173 //! *issued* but not yet be in effect.
175 self.dfcx_loans.each_bit_on_entry(scope_id, |loan_index| {
176 let loan = &self.all_loans[loan_index];
181 pub fn each_in_scope_loan(&self,
182 scope_id: ast::NodeId,
185 //! Like `each_issued_loan()`, but only considers loans that are
186 //! currently in scope.
188 let tcx = self.tcx();
189 self.each_issued_loan(scope_id, |loan| {
190 if tcx.region_maps.is_subscope_of(scope_id, loan.kill_scope) {
198 fn each_in_scope_loan_affecting_path(&self,
199 scope_id: ast::NodeId,
200 loan_path: &LoanPath,
203 //! Iterates through all of the in-scope loans affecting `loan_path`,
204 //! calling `op`, and ceasing iteration if `false` is returned.
206 // First, we check for a loan restricting the path P being used. This
207 // accounts for borrows of P but also borrows of subpaths, like P.a.b.
208 // Consider the following example:
210 // let x = &mut a.b.c; // Restricts a, a.b, and a.b.c
211 // let y = a; // Conflicts with restriction
213 let cont = self.each_in_scope_loan(scope_id, |loan| {
215 for restr_path in loan.restricted_paths.iter() {
216 if **restr_path == *loan_path {
230 // Next, we must check for *loans* (not restrictions) on the path P or
231 // any base path. This rejects examples like the following:
236 // Limiting this search to *loans* and not *restrictions* means that
237 // examples like the following continue to work:
242 let mut loan_path = loan_path;
245 LpVar(_) | LpUpvar(_) => {
248 LpExtend(ref lp_base, _, _) => {
249 loan_path = &**lp_base;
253 let cont = self.each_in_scope_loan(scope_id, |loan| {
254 if *loan.loan_path == *loan_path {
269 pub fn loans_generated_by(&self, scope_id: ast::NodeId) -> Vec<uint> {
270 //! Returns a vector of the loans that are generated as
271 //! we encounter `scope_id`.
273 let mut result = Vec::new();
274 self.dfcx_loans.each_gen_bit(scope_id, |loan_index| {
275 result.push(loan_index);
281 pub fn check_for_conflicting_loans(&self, scope_id: ast::NodeId) {
282 //! Checks to see whether any of the loans that are issued
283 //! by `scope_id` conflict with loans that have already been
284 //! issued when we enter `scope_id` (for example, we do not
285 //! permit two `&mut` borrows of the same variable).
287 debug!("check_for_conflicting_loans(scope_id={:?})", scope_id);
289 let new_loan_indices = self.loans_generated_by(scope_id);
290 debug!("new_loan_indices = {:?}", new_loan_indices);
292 self.each_issued_loan(scope_id, |issued_loan| {
293 for &new_loan_index in new_loan_indices.iter() {
294 let new_loan = &self.all_loans[new_loan_index];
295 self.report_error_if_loans_conflict(issued_loan, new_loan);
300 for (i, &x) in new_loan_indices.iter().enumerate() {
301 let old_loan = &self.all_loans[x];
302 for &y in new_loan_indices.slice_from(i+1).iter() {
303 let new_loan = &self.all_loans[y];
304 self.report_error_if_loans_conflict(old_loan, new_loan);
309 pub fn report_error_if_loans_conflict(&self,
312 //! Checks whether `old_loan` and `new_loan` can safely be issued
315 debug!("report_error_if_loans_conflict(old_loan={}, new_loan={})",
316 old_loan.repr(self.tcx()),
317 new_loan.repr(self.tcx()));
319 // Should only be called for loans that are in scope at the same time.
320 assert!(self.tcx().region_maps.scopes_intersect(old_loan.kill_scope,
321 new_loan.kill_scope));
323 self.report_error_if_loan_conflicts_with_restriction(
324 old_loan, new_loan, old_loan, new_loan) &&
325 self.report_error_if_loan_conflicts_with_restriction(
326 new_loan, old_loan, old_loan, new_loan);
329 pub fn report_error_if_loan_conflicts_with_restriction(&self,
335 //! Checks whether the restrictions introduced by `loan1` would
336 //! prohibit `loan2`. Returns false if an error is reported.
338 debug!("report_error_if_loan_conflicts_with_restriction(\
339 loan1={}, loan2={})",
340 loan1.repr(self.tcx()),
341 loan2.repr(self.tcx()));
343 if compatible_borrow_kinds(loan1.kind, loan2.kind) {
347 for restr_path in loan1.restricted_paths.iter() {
348 if *restr_path != loan2.loan_path { continue; }
350 let old_pronoun = if new_loan.loan_path == old_loan.loan_path {
354 self.bccx.loan_path_to_string(&*old_loan.loan_path))
357 match (new_loan.kind, old_loan.kind) {
358 (ty::MutBorrow, ty::MutBorrow) => {
361 format!("cannot borrow `{}` as mutable \
362 more than once at a time",
363 self.bccx.loan_path_to_string(
364 &*new_loan.loan_path)).as_slice());
367 (ty::UniqueImmBorrow, _) => {
370 format!("closure requires unique access to `{}` \
371 but {} is already borrowed",
372 self.bccx.loan_path_to_string(&*new_loan.loan_path),
373 old_pronoun).as_slice());
376 (_, ty::UniqueImmBorrow) => {
379 format!("cannot borrow `{}` as {} because \
380 previous closure requires unique access",
381 self.bccx.loan_path_to_string(&*new_loan.loan_path),
382 new_loan.kind.to_user_str()).as_slice());
388 format!("cannot borrow `{}` as {} because \
389 {} is also borrowed as {}",
390 self.bccx.loan_path_to_string(&*new_loan.loan_path),
391 new_loan.kind.to_user_str(),
393 old_loan.kind.to_user_str()).as_slice());
397 match new_loan.cause {
398 euv::ClosureCapture(span) => {
401 format!("borrow occurs due to use of `{}` in closure",
402 self.bccx.loan_path_to_string(
403 &*new_loan.loan_path)).as_slice());
408 let rule_summary = match old_loan.kind {
410 format!("the mutable borrow prevents subsequent \
411 moves, borrows, or modification of `{0}` \
412 until the borrow ends",
413 self.bccx.loan_path_to_string(
414 &*old_loan.loan_path))
418 format!("the immutable borrow prevents subsequent \
419 moves or mutable borrows of `{0}` \
420 until the borrow ends",
421 self.bccx.loan_path_to_string(&*old_loan.loan_path))
424 ty::UniqueImmBorrow => {
425 format!("the unique capture prevents subsequent \
426 moves or borrows of `{0}` \
427 until the borrow ends",
428 self.bccx.loan_path_to_string(&*old_loan.loan_path))
432 let borrow_summary = match old_loan.cause {
433 euv::ClosureCapture(_) => {
434 format!("previous borrow of `{}` occurs here due to \
436 self.bccx.loan_path_to_string(&*old_loan.loan_path))
439 euv::OverloadedOperator(..) |
442 euv::ClosureInvocation(..) |
443 euv::RefBinding(..) => {
444 format!("previous borrow of `{}` occurs here",
445 self.bccx.loan_path_to_string(&*old_loan.loan_path))
451 format!("{}; {}", borrow_summary, rule_summary).as_slice());
453 let old_loan_span = self.tcx().map.span(old_loan.kill_scope);
454 self.bccx.span_end_note(old_loan_span,
455 "previous borrow ends here");
463 pub fn is_local_variable_or_arg(&self, cmt: mc::cmt) -> bool {
465 mc::cat_local(_) | mc::cat_arg(_) => true,
470 fn consume_common(&self,
474 mode: euv::ConsumeMode) {
475 match opt_loan_path(&cmt) {
477 let moved_value_use_kind = match mode {
479 self.check_for_copy_of_frozen_path(id, span, &*lp);
483 match self.move_data.kind_of_move_of_path(id, &lp) {
485 // Sometimes moves don't have a move kind;
486 // this either means that the original move
487 // was from something illegal to move,
488 // or was moved from referent of an unsafe
489 // pointer or something like that.
493 self.check_for_move_of_borrowed_path(id, span,
495 if move_kind == move_data::Captured {
505 self.check_if_path_is_moved(id, span, moved_value_use_kind, &lp);
511 fn check_for_copy_of_frozen_path(&self,
514 copy_path: &LoanPath) {
515 match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) {
517 UseWhileBorrowed(loan_path, loan_span) => {
520 format!("cannot use `{}` because it was mutably borrowed",
521 self.bccx.loan_path_to_string(copy_path).as_slice())
525 format!("borrow of `{}` occurs here",
526 self.bccx.loan_path_to_string(&*loan_path).as_slice())
532 fn check_for_move_of_borrowed_path(&self,
535 move_path: &LoanPath,
536 move_kind: move_data::MoveKind) {
537 // We want to detect if there are any loans at all, so we search for
538 // any loans incompatible with MutBorrrow, since all other kinds of
539 // loans are incompatible with that.
540 match self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow) {
542 UseWhileBorrowed(loan_path, loan_span) => {
543 let err_message = match move_kind {
544 move_data::Captured =>
545 format!("cannot move `{}` into closure because it is borrowed",
546 self.bccx.loan_path_to_string(move_path).as_slice()),
547 move_data::Declared |
548 move_data::MoveExpr |
549 move_data::MovePat =>
550 format!("cannot move out of `{}` because it is borrowed",
551 self.bccx.loan_path_to_string(move_path).as_slice())
554 self.bccx.span_err(span, err_message.as_slice());
557 format!("borrow of `{}` occurs here",
558 self.bccx.loan_path_to_string(&*loan_path).as_slice())
564 pub fn analyze_restrictions_on_use(&self,
565 expr_id: ast::NodeId,
567 borrow_kind: ty::BorrowKind)
569 debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={})",
570 self.tcx().map.node_to_string(expr_id),
571 use_path.repr(self.tcx()));
575 self.each_in_scope_loan_affecting_path(expr_id, use_path, |loan| {
576 if !compatible_borrow_kinds(loan.kind, borrow_kind) {
577 ret = UseWhileBorrowed(loan.loan_path.clone(), loan.span);
587 fn check_if_path_is_moved(&self,
590 use_kind: MovedValueUseKind,
593 * Reports an error if `expr` (which should be a path)
594 * is using a moved/uninitialized value
597 debug!("check_if_path_is_moved(id={:?}, use_kind={:?}, lp={})",
598 id, use_kind, lp.repr(self.bccx.tcx));
599 self.move_data.each_move_of(id, lp, |move, moved_lp| {
600 self.bccx.report_use_of_moved_value(
610 fn check_if_assigned_path_is_moved(&self,
613 use_kind: MovedValueUseKind,
617 * Reports an error if assigning to `lp` will use a
618 * moved/uninitialized value. Mainly this is concerned with
619 * detecting derefs of uninitialized pointers.
624 * a = 10; // ok, even though a is uninitialized
626 * struct Point { x: uint, y: uint }
628 * p.x = 22; // ok, even though `p` is uninitialized
631 * (*p).x = 22; // not ok, p is uninitialized, can't deref
635 LpVar(_) | LpUpvar(_) => {
636 // assigning to `x` does not require that `x` is initialized
638 LpExtend(ref lp_base, _, LpInterior(_)) => {
639 // assigning to `P.f` is ok if assigning to `P` is ok
640 self.check_if_assigned_path_is_moved(id, span,
643 LpExtend(ref lp_base, _, LpDeref(_)) => {
644 // assigning to `(*P)` requires that `P` be initialized
645 self.check_if_path_is_moved(id, span,
651 fn check_assignment(&self,
652 assignment_id: ast::NodeId,
653 assignment_span: Span,
654 assignee_cmt: mc::cmt,
655 mode: euv::MutateMode) {
656 debug!("check_assignment(assignee_cmt={})", assignee_cmt.repr(self.tcx()));
658 // Mutable values can be assigned, as long as they obey loans
659 // and aliasing restrictions:
660 if assignee_cmt.mutbl.is_mutable() {
661 if check_for_aliasable_mutable_writes(self, assignment_span, assignee_cmt.clone()) {
662 if mode != euv::Init {
663 check_for_assignment_to_borrowed_path(
664 self, assignment_id, assignment_span, assignee_cmt.clone());
665 mark_variable_as_used_mut(self, assignee_cmt);
671 // For immutable local variables, assignments are legal
672 // if they cannot already have been assigned
673 if self.is_local_variable_or_arg(assignee_cmt.clone()) {
674 assert!(assignee_cmt.mutbl.is_immutable()); // no "const" locals
675 let lp = opt_loan_path(&assignee_cmt).unwrap();
676 self.move_data.each_assignment_of(assignment_id, &lp, |assign| {
677 self.bccx.report_reassigned_immutable_variable(
686 // Otherwise, just a plain error.
687 match opt_loan_path(&assignee_cmt) {
691 format!("cannot assign to {} {} `{}`",
692 assignee_cmt.mutbl.to_user_str(),
693 self.bccx.cmt_to_string(&*assignee_cmt),
694 self.bccx.loan_path_to_string(&*lp)).as_slice());
699 format!("cannot assign to {} {}",
700 assignee_cmt.mutbl.to_user_str(),
701 self.bccx.cmt_to_string(&*assignee_cmt)).as_slice());
706 fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
708 //! If the mutability of the `cmt` being written is inherited
709 //! from a local variable, liveness will
710 //! not have been able to detect that this variable's mutability
711 //! is important, so we must add the variable to the
712 //! `used_mut_nodes` table here.
716 debug!("mark_writes_through_upvars_as_used_mut(cmt={})",
717 cmt.repr(this.tcx()));
718 match cmt.cat.clone() {
719 mc::cat_local(id) | mc::cat_arg(id) => {
720 this.tcx().used_mut_nodes.borrow_mut().insert(id);
724 mc::cat_upvar(..) => {
728 mc::cat_deref(_, _, mc::GcPtr) => {
729 assert_eq!(cmt.mutbl, mc::McImmutable);
734 mc::cat_static_item |
735 mc::cat_copied_upvar(..) |
736 mc::cat_deref(_, _, mc::UnsafePtr(..)) |
737 mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
738 mc::cat_deref(_, _, mc::Implicit(..)) => {
739 assert_eq!(cmt.mutbl, mc::McDeclared);
743 mc::cat_discr(b, _) |
744 mc::cat_deref(b, _, mc::OwnedPtr) => {
745 assert_eq!(cmt.mutbl, mc::McInherited);
749 mc::cat_downcast(b) |
750 mc::cat_interior(b, _) => {
751 assert_eq!(cmt.mutbl, mc::McInherited);
758 fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
760 cmt: mc::cmt) -> bool {
761 //! Safety checks related to writes to aliasable, mutable locations
763 let guarantor = cmt.guarantor();
764 debug!("check_for_aliasable_mutable_writes(cmt={}, guarantor={})",
765 cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
766 match guarantor.cat {
767 mc::cat_deref(ref b, _, mc::BorrowedPtr(ty::MutBorrow, _)) => {
768 // Statically prohibit writes to `&mut` when aliasable
770 check_for_aliasability_violation(this, span, b.clone());
776 return true; // no errors reported
779 fn check_for_aliasability_violation(this: &CheckLoanCtxt,
783 match cmt.freely_aliasable(this.tcx()) {
787 Some(mc::AliasableStaticMut(..)) => {
791 this.bccx.report_aliasability_violation(
800 fn check_for_assignment_to_borrowed_path(
801 this: &CheckLoanCtxt,
802 assignment_id: ast::NodeId,
803 assignment_span: Span,
804 assignee_cmt: mc::cmt)
806 //! Check for assignments that violate the terms of an
807 //! outstanding loan.
809 let loan_path = match opt_loan_path(&assignee_cmt) {
811 None => { return; /* no loan path, can't be any loans */ }
814 this.each_in_scope_loan_affecting_path(assignment_id, &*loan_path, |loan| {
815 this.report_illegal_mutation(assignment_span, &*loan_path, loan);
821 pub fn report_illegal_mutation(&self,
823 loan_path: &LoanPath,
827 format!("cannot assign to `{}` because it is borrowed",
828 self.bccx.loan_path_to_string(loan_path)).as_slice());
831 format!("borrow of `{}` occurs here",
832 self.bccx.loan_path_to_string(loan_path)).as_slice());