rm -rf librustc_ast_borrowck
This commit is contained in:
parent
a37fe2de69
commit
a094926096
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Rust Project Developers"]
|
||||
name = "rustc_ast_borrowck"
|
||||
version = "0.0.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "rustc_ast_borrowck"
|
||||
path = "lib.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
syntax_pos = { path = "../libsyntax_pos" }
|
||||
# for "clarity", rename the graphviz crate to dot; graphviz within `borrowck`
|
||||
# refers to the borrowck-specific graphviz adapter traits.
|
||||
dot = { path = "../libgraphviz", package = "graphviz" }
|
||||
rustc = { path = "../librustc" }
|
||||
rustc_data_structures = { path = "../librustc_data_structures" }
|
File diff suppressed because it is too large
Load Diff
@ -1,680 +0,0 @@
|
||||
// ----------------------------------------------------------------------
|
||||
// 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 crate::borrowck::*;
|
||||
use crate::borrowck::InteriorKind::{InteriorElement, InteriorField};
|
||||
use rustc::middle::expr_use_visitor as euv;
|
||||
use rustc::middle::expr_use_visitor::MutateMode;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt, RegionKind};
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use rustc::hir::Node;
|
||||
use log::debug;
|
||||
|
||||
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 Unique 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 Unique in
|
||||
//! `loan_path`. If there is no dereference of an Unique 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,
|
||||
};
|
||||
|
||||
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::Unique)) => {
|
||||
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::Unique)) => {
|
||||
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> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
dfcx_loans: &'a LoanDataFlow<'tcx>,
|
||||
move_data: &'a move_data::FlowedMoveData<'tcx>,
|
||||
all_loans: &'a [Loan<'tcx>],
|
||||
movable_generator: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
|
||||
fn consume(&mut self,
|
||||
consume_id: hir::HirId,
|
||||
_: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume(consume_id={}, cmt={:?})", consume_id, cmt);
|
||||
|
||||
self.consume_common(consume_id.local_id, cmt, mode);
|
||||
}
|
||||
|
||||
fn matched_pat(&mut self,
|
||||
_matched_pat: &hir::Pat,
|
||||
_cmt: &mc::cmt_<'_>,
|
||||
_mode: euv::MatchMode) { }
|
||||
|
||||
fn consume_pat(&mut self,
|
||||
consume_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume_pat(consume_pat={:?}, cmt={:?})", consume_pat, cmt);
|
||||
|
||||
self.consume_common(consume_pat.hir_id.local_id, cmt, mode);
|
||||
}
|
||||
|
||||
fn borrow(&mut self,
|
||||
borrow_id: hir::HirId,
|
||||
borrow_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
bk: ty::BorrowKind,
|
||||
loan_cause: euv::LoanCause)
|
||||
{
|
||||
debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
|
||||
bk={:?}, loan_cause={:?})",
|
||||
borrow_id, cmt, loan_region,
|
||||
bk, loan_cause);
|
||||
|
||||
if let Some(lp) = opt_loan_path(cmt) {
|
||||
self.check_if_path_is_moved(borrow_id.local_id, &lp);
|
||||
}
|
||||
|
||||
self.check_for_conflicting_loans(borrow_id.local_id);
|
||||
|
||||
self.check_for_loans_across_yields(cmt, loan_region, borrow_span);
|
||||
}
|
||||
|
||||
fn mutate(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
_: Span,
|
||||
assignee_cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::MutateMode)
|
||||
{
|
||||
debug!("mutate(assignment_id={}, assignee_cmt={:?})",
|
||||
assignment_id, assignee_cmt);
|
||||
|
||||
if let Some(lp) = opt_loan_path(assignee_cmt) {
|
||||
match mode {
|
||||
MutateMode::Init | MutateMode::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.hir_id.local_id, &lp);
|
||||
}
|
||||
MutateMode::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.hir_id.local_id,
|
||||
&lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_assignment(assignment_id.local_id, assignee_cmt);
|
||||
}
|
||||
|
||||
fn decl_without_init(&mut self, _id: hir::HirId, _span: Span) { }
|
||||
}
|
||||
|
||||
pub fn check_loans<'a, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
dfcx_loans: &LoanDataFlow<'tcx>,
|
||||
move_data: &move_data::FlowedMoveData<'tcx>,
|
||||
all_loans: &[Loan<'tcx>],
|
||||
body: &hir::Body,
|
||||
) {
|
||||
debug!("check_loans(body id={})", body.value.hir_id);
|
||||
|
||||
let def_id = bccx.tcx.hir().body_owner_def_id(body.id());
|
||||
|
||||
let hir_id = bccx.tcx.hir().as_local_hir_id(def_id).unwrap();
|
||||
let movable_generator = !match bccx.tcx.hir().get(hir_id) {
|
||||
Node::Expr(&hir::Expr {
|
||||
kind: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)),
|
||||
..
|
||||
}) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let param_env = bccx.tcx.param_env(def_id);
|
||||
let mut clcx = CheckLoanCtxt {
|
||||
bccx,
|
||||
dfcx_loans,
|
||||
move_data,
|
||||
all_loans,
|
||||
movable_generator,
|
||||
};
|
||||
let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
|
||||
euv::ExprUseVisitor::new(&mut clcx,
|
||||
bccx.tcx,
|
||||
def_id,
|
||||
param_env,
|
||||
&bccx.region_scope_tree,
|
||||
bccx.tables,
|
||||
Some(rvalue_promotable_map))
|
||||
.consume_body(body);
|
||||
}
|
||||
|
||||
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) -> TyCtxt<'tcx> { self.bccx.tcx }
|
||||
|
||||
pub fn each_issued_loan<F>(&self, node: hir::ItemLocalId, mut op: F) -> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> bool,
|
||||
{
|
||||
//! Iterates over each loan that has been issued
|
||||
//! on entrance to `node`, 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(node, |loan_index| {
|
||||
let loan = &self.all_loans[loan_index];
|
||||
op(loan)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn each_in_scope_loan<F>(&self, scope: region::Scope, mut op: F) -> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> bool,
|
||||
{
|
||||
//! Like `each_issued_loan()`, but only considers loans that are
|
||||
//! currently in scope.
|
||||
|
||||
self.each_issued_loan(scope.item_local_id(), |loan| {
|
||||
if self.bccx.region_scope_tree.is_subscope_of(scope, loan.kill_scope) {
|
||||
op(loan)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn each_in_scope_loan_affecting_path<F>(&self,
|
||||
scope: region::Scope,
|
||||
loan_path: &LoanPath<'tcx>,
|
||||
mut op: F)
|
||||
-> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> 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 {
|
||||
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, node: hir::ItemLocalId) -> Vec<usize> {
|
||||
//! Returns a vector of the loans that are generated as
|
||||
//! we enter `node`.
|
||||
|
||||
let mut result = Vec::new();
|
||||
self.dfcx_loans.each_gen_bit(node, |loan_index| {
|
||||
result.push(loan_index);
|
||||
true
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn check_for_loans_across_yields(&self,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
borrow_span: Span) {
|
||||
pub fn borrow_of_local_data(cmt: &mc::cmt_<'_>) -> bool {
|
||||
match cmt.cat {
|
||||
// Borrows of static items is allowed
|
||||
Categorization::StaticItem => false,
|
||||
// Reborrow of already borrowed data is ignored
|
||||
// Any errors will be caught on the initial borrow
|
||||
Categorization::Deref(..) => false,
|
||||
|
||||
// By-ref upvars has Derefs so they will get ignored.
|
||||
// Generators counts as FnOnce so this leaves only
|
||||
// by-move upvars, which is local data for generators
|
||||
Categorization::Upvar(..) => true,
|
||||
|
||||
Categorization::ThreadLocal(region) |
|
||||
Categorization::Rvalue(region) => {
|
||||
// Rvalues promoted to 'static are no longer local
|
||||
if let RegionKind::ReStatic = *region {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Borrow of local data must be checked
|
||||
Categorization::Local(..) => true,
|
||||
|
||||
// For interior references and downcasts, find out if the base is local
|
||||
Categorization::Downcast(ref cmt_base, _) |
|
||||
Categorization::Interior(ref cmt_base, _) => borrow_of_local_data(&cmt_base),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.movable_generator {
|
||||
return;
|
||||
}
|
||||
|
||||
if !borrow_of_local_data(cmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope = match *loan_region {
|
||||
// A concrete region in which we will look for a yield expression
|
||||
RegionKind::ReScope(scope) => scope,
|
||||
|
||||
// There cannot be yields inside an empty region
|
||||
RegionKind::ReEmpty => return,
|
||||
|
||||
// Local data cannot have these lifetimes
|
||||
RegionKind::ReEarlyBound(..) |
|
||||
RegionKind::ReLateBound(..) |
|
||||
RegionKind::ReFree(..) |
|
||||
RegionKind::ReStatic => {
|
||||
self.bccx
|
||||
.tcx
|
||||
.sess.delay_span_bug(borrow_span,
|
||||
&format!("unexpected region for local data {:?}",
|
||||
loan_region));
|
||||
return
|
||||
}
|
||||
|
||||
// These cannot exist in borrowck
|
||||
RegionKind::ReVar(..) |
|
||||
RegionKind::RePlaceholder(..) |
|
||||
RegionKind::ReClosureBound(..) |
|
||||
RegionKind::ReErased => span_bug!(borrow_span,
|
||||
"unexpected region in borrowck {:?}",
|
||||
loan_region),
|
||||
};
|
||||
|
||||
let body_id = self.bccx.body.value.hir_id.local_id;
|
||||
|
||||
if self.bccx.region_scope_tree.containing_body(scope) != Some(body_id) {
|
||||
// We are borrowing local data longer than its storage.
|
||||
// This should result in other borrowck errors.
|
||||
self.bccx.tcx.sess.delay_span_bug(borrow_span,
|
||||
"borrowing local data longer than its storage");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(_) = self.bccx.region_scope_tree
|
||||
.yield_in_scope_for_expr(scope, cmt.hir_id, self.bccx.body)
|
||||
{
|
||||
self.bccx.signal_error();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_for_conflicting_loans(&self, node: hir::ItemLocalId) {
|
||||
//! Checks to see whether any of the loans that are issued
|
||||
//! on entrance to `node` conflict with loans that have already been
|
||||
//! issued when we enter `node` (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(node={:?})", node);
|
||||
|
||||
let new_loan_indices = self.loans_generated_by(node);
|
||||
debug!("new_loan_indices = {:?}", new_loan_indices);
|
||||
|
||||
for &new_loan_index in &new_loan_indices {
|
||||
self.each_issued_loan(node, |issued_loan| {
|
||||
let new_loan = &self.all_loans[new_loan_index];
|
||||
// Only report an error for the first issued loan that conflicts
|
||||
// to avoid O(n^2) errors.
|
||||
self.report_error_if_loans_conflict(issued_loan, new_loan)
|
||||
});
|
||||
}
|
||||
|
||||
for (i, &x) in new_loan_indices.iter().enumerate() {
|
||||
let old_loan = &self.all_loans[x];
|
||||
for &y in &new_loan_indices[(i+1) ..] {
|
||||
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>,
|
||||
) -> bool {
|
||||
//! Checks whether `old_loan` and `new_loan` can safely be issued
|
||||
//! simultaneously.
|
||||
|
||||
debug!("report_error_if_loans_conflict(old_loan={:?}, new_loan={:?})",
|
||||
old_loan,
|
||||
new_loan);
|
||||
|
||||
// Should only be called for loans that are in scope at the same time.
|
||||
assert!(self.bccx.region_scope_tree.scopes_intersect(old_loan.kill_scope,
|
||||
new_loan.kill_scope));
|
||||
|
||||
self.report_error_if_loan_conflicts_with_restriction(
|
||||
old_loan, new_loan)
|
||||
&& self.report_error_if_loan_conflicts_with_restriction(
|
||||
new_loan, old_loan)
|
||||
}
|
||||
|
||||
pub fn report_error_if_loan_conflicts_with_restriction(
|
||||
&self,
|
||||
loan1: &Loan<'tcx>,
|
||||
loan2: &Loan<'tcx>,
|
||||
) -> bool {
|
||||
//! Checks whether the restrictions introduced by `loan1` would
|
||||
//! prohibit `loan2`.
|
||||
debug!("report_error_if_loan_conflicts_with_restriction(\
|
||||
loan1={:?}, loan2={:?})",
|
||||
loan1,
|
||||
loan2);
|
||||
|
||||
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 {
|
||||
if *restr_path != loan2_base_path { continue; }
|
||||
|
||||
self.bccx.signal_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn consume_common(
|
||||
&self,
|
||||
id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode,
|
||||
) {
|
||||
if let Some(lp) = opt_loan_path(cmt) {
|
||||
match mode {
|
||||
euv::Copy => {
|
||||
self.check_for_copy_of_frozen_path(id, &lp);
|
||||
}
|
||||
euv::Move(_) => {
|
||||
// Sometimes moves aren't from a move path;
|
||||
// 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.
|
||||
if self.move_data.is_move_path(id, &lp) {
|
||||
self.check_for_move_of_borrowed_path(id, &lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_if_path_is_moved(id, &lp);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_for_copy_of_frozen_path(&self,
|
||||
id: hir::ItemLocalId,
|
||||
copy_path: &LoanPath<'tcx>) {
|
||||
self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow);
|
||||
}
|
||||
|
||||
fn check_for_move_of_borrowed_path(&self,
|
||||
id: hir::ItemLocalId,
|
||||
move_path: &LoanPath<'tcx>) {
|
||||
// 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.
|
||||
self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow);
|
||||
}
|
||||
|
||||
fn analyze_restrictions_on_use(&self,
|
||||
expr_id: hir::ItemLocalId,
|
||||
use_path: &LoanPath<'tcx>,
|
||||
borrow_kind: ty::BorrowKind) {
|
||||
debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={:?})",
|
||||
expr_id, use_path);
|
||||
|
||||
let scope = region::Scope {
|
||||
id: expr_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
self.each_in_scope_loan_affecting_path(
|
||||
scope, use_path, |loan| {
|
||||
if !compatible_borrow_kinds(loan.kind, borrow_kind) {
|
||||
self.bccx.signal_error();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Reports an error if `expr` (which should be a path)
|
||||
/// is using a moved/uninitialized value
|
||||
fn check_if_path_is_moved(&self,
|
||||
id: hir::ItemLocalId,
|
||||
lp: &Rc<LoanPath<'tcx>>) {
|
||||
debug!("check_if_path_is_moved(id={:?}, lp={:?})", id, lp);
|
||||
|
||||
// FIXME: if you find yourself tempted to cut and paste
|
||||
// the body below and then specializing the error reporting,
|
||||
// consider refactoring this instead!
|
||||
|
||||
let base_lp = owned_ptr_base_path_rc(lp);
|
||||
self.move_data.each_move_of(id, &base_lp, |_, _| {
|
||||
self.bccx.signal_error();
|
||||
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: i32;
|
||||
/// a = 10; // ok, even though a is uninitialized
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// struct Point { x: u32, y: u32 }
|
||||
/// let mut p: Point;
|
||||
/// p.x = 22; // ok, even though `p` is uninitialized
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail,E0381
|
||||
/// # struct Point { x: u32, y: u32 }
|
||||
/// let mut p: Box<Point>;
|
||||
/// (*p).x = 22; // not ok, p is uninitialized, can't deref
|
||||
/// ```
|
||||
fn check_if_assigned_path_is_moved(&self,
|
||||
id: hir::ItemLocalId,
|
||||
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, lp_base);
|
||||
}
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorField(_))) => {
|
||||
match lp_base.to_type().kind {
|
||||
ty::Adt(def, _) if def.has_dtor(self.tcx()) => {
|
||||
// In the case where the owner implements drop, then
|
||||
// the path must be initialized to prevent a case of
|
||||
// partial reinitialization
|
||||
//
|
||||
// FIXME: could refactor via hypothetical
|
||||
// generalized check_if_path_is_moved
|
||||
let loan_path = owned_ptr_base_path_rc(lp_base);
|
||||
self.move_data.each_move_of(id, &loan_path, |_, _| {
|
||||
self.bccx
|
||||
.signal_error();
|
||||
false
|
||||
});
|
||||
return;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
// assigning to `P.f` is ok if assigning to `P` is ok
|
||||
self.check_if_assigned_path_is_moved(id, lp_base);
|
||||
}
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) |
|
||||
LpExtend(ref lp_base, _, LpDeref(_)) => {
|
||||
// assigning to `P[i]` requires `P` is initialized
|
||||
// assigning to `(*P)` requires `P` is initialized
|
||||
self.check_if_path_is_moved(id, lp_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_assignment(&self,
|
||||
assignment_id: hir::ItemLocalId,
|
||||
assignee_cmt: &mc::cmt_<'tcx>) {
|
||||
debug!("check_assignment(assignee_cmt={:?})", assignee_cmt);
|
||||
|
||||
// Check that we don't invalidate any outstanding loans
|
||||
if let Some(loan_path) = opt_loan_path(assignee_cmt) {
|
||||
let scope = region::Scope {
|
||||
id: assignment_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
self.each_in_scope_loan_affecting_path(scope, &loan_path, |_| {
|
||||
self.bccx.signal_error();
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
// Check for reassignments to (immutable) local variables. This
|
||||
// needs to be done here instead of in check_loans because we
|
||||
// depend on move data.
|
||||
if let Categorization::Local(_) = assignee_cmt.cat {
|
||||
let lp = opt_loan_path(assignee_cmt).unwrap();
|
||||
self.move_data.each_assignment_of(assignment_id, &lp, |_| {
|
||||
if !assignee_cmt.mutbl.is_mutable() {
|
||||
self.bccx.signal_error();
|
||||
}
|
||||
false
|
||||
});
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
//! Computes moves.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::borrowck::move_data::*;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::mem_categorization::InteriorOffsetKind as Kind;
|
||||
use rustc::ty::{self, Ty};
|
||||
|
||||
use std::rc::Rc;
|
||||
use syntax_pos::Span;
|
||||
use log::debug;
|
||||
|
||||
struct GatherMoveInfo<'c, 'tcx> {
|
||||
id: hir::ItemLocalId,
|
||||
cmt: &'c mc::cmt_<'tcx>,
|
||||
}
|
||||
|
||||
pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
var_id: hir::HirId,
|
||||
var_ty: Ty<'tcx>) {
|
||||
let loan_path = Rc::new(LoanPath::new(LpVar(var_id), var_ty));
|
||||
move_data.add_move(bccx.tcx, loan_path, var_id.local_id);
|
||||
}
|
||||
|
||||
pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_expr_id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>) {
|
||||
let move_info = GatherMoveInfo {
|
||||
id: move_expr_id,
|
||||
cmt,
|
||||
};
|
||||
gather_move(bccx, move_data, move_info);
|
||||
}
|
||||
|
||||
pub fn gather_move_from_pat<'a, 'c, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_pat: &hir::Pat,
|
||||
cmt: &'c mc::cmt_<'tcx>,
|
||||
) {
|
||||
let move_info = GatherMoveInfo {
|
||||
id: move_pat.hir_id.local_id,
|
||||
cmt,
|
||||
};
|
||||
|
||||
debug!("gather_move_from_pat: move_pat={:?}", move_pat);
|
||||
|
||||
gather_move(bccx, move_data, move_info);
|
||||
}
|
||||
|
||||
fn gather_move<'a, 'c, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_info: GatherMoveInfo<'c, 'tcx>,
|
||||
) {
|
||||
debug!("gather_move(move_id={:?}, cmt={:?})",
|
||||
move_info.id, move_info.cmt);
|
||||
|
||||
let potentially_illegal_move = check_and_get_illegal_move_origin(bccx, move_info.cmt);
|
||||
if let Some(_) = potentially_illegal_move {
|
||||
bccx.signal_error();
|
||||
return;
|
||||
}
|
||||
|
||||
match opt_loan_path(&move_info.cmt) {
|
||||
Some(loan_path) => {
|
||||
move_data.add_move(bccx.tcx, loan_path,
|
||||
move_info.id);
|
||||
}
|
||||
None => {
|
||||
// move from rvalue or raw pointer, hence ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
assignment_id: hir::ItemLocalId,
|
||||
assignment_span: Span,
|
||||
assignee_loan_path: Rc<LoanPath<'tcx>>) {
|
||||
move_data.add_assignment(bccx.tcx,
|
||||
assignee_loan_path,
|
||||
assignment_id,
|
||||
assignment_span);
|
||||
}
|
||||
|
||||
// (keep in sync with move_error::report_cannot_move_out_of )
|
||||
fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>)
|
||||
-> Option<mc::cmt_<'tcx>> {
|
||||
match cmt.cat {
|
||||
Categorization::Deref(_, mc::BorrowedPtr(..)) |
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::StaticItem => {
|
||||
Some(cmt.clone())
|
||||
}
|
||||
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::Local(..) |
|
||||
Categorization::Upvar(..) => {
|
||||
None
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref b, _) |
|
||||
Categorization::Interior(ref b, mc::InteriorField(_)) |
|
||||
Categorization::Interior(ref b, mc::InteriorElement(Kind::Pattern)) => {
|
||||
match b.ty.kind {
|
||||
ty::Adt(def, _) => {
|
||||
if def.has_dtor(bccx.tcx) {
|
||||
Some(cmt.clone())
|
||||
} else {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
ty::Slice(..) => Some(cmt.clone()),
|
||||
_ => {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Categorization::Interior(_, mc::InteriorElement(Kind::Index)) => {
|
||||
// Forbid move of arr[i] for arr: [T; 3]; see RFC 533.
|
||||
Some(cmt.clone())
|
||||
}
|
||||
|
||||
Categorization::Deref(ref b, mc::Unique) => {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
//! This module implements the check that the lifetime of a borrow
|
||||
//! does not exceed the lifetime of the value being borrowed.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use rustc::hir::HirId;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty;
|
||||
|
||||
use log::debug;
|
||||
|
||||
type R = Result<(),()>;
|
||||
|
||||
pub fn guarantee_lifetime<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
item_scope: region::Scope,
|
||||
cmt: &'a mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>)
|
||||
-> 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, loan_region);
|
||||
let ctxt = GuaranteeLifetimeContext { bccx, item_scope, loan_region };
|
||||
ctxt.check(cmt, None)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
||||
struct GuaranteeLifetimeContext<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
|
||||
// the scope of the function body for the enclosing item
|
||||
item_scope: region::Scope,
|
||||
|
||||
loan_region: ty::Region<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
|
||||
fn check(&self, cmt: &mc::cmt_<'tcx>, discr_scope: Option<HirId>) -> 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,
|
||||
self.loan_region);
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::Local(..) | // L-Local
|
||||
Categorization::Upvar(..) |
|
||||
Categorization::Deref(_, mc::BorrowedPtr(..)) | // L-Deref-Borrowed
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) => {
|
||||
self.check_scope(self.scope(cmt))
|
||||
}
|
||||
|
||||
Categorization::StaticItem => {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref base, _) |
|
||||
Categorization::Deref(ref base, mc::Unique) | // L-Deref-Send
|
||||
Categorization::Interior(ref base, _) => { // L-Field
|
||||
self.check(base, discr_scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_scope(&self, max_scope: ty::Region<'tcx>) -> 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.bccx.signal_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn scope(&self, cmt: &mc::cmt_<'tcx>) -> ty::Region<'tcx> {
|
||||
//! Returns the maximal region scope for the which the
|
||||
//! place `cmt` is guaranteed to be valid without any
|
||||
//! rooting etc, and presuming `cmt` is not mutated.
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::ThreadLocal(temp_scope) |
|
||||
Categorization::Rvalue(temp_scope) => {
|
||||
temp_scope
|
||||
}
|
||||
Categorization::Upvar(..) => {
|
||||
self.bccx.tcx.mk_region(ty::ReScope(self.item_scope))
|
||||
}
|
||||
Categorization::Local(hir_id) => {
|
||||
self.bccx.tcx.mk_region(ty::ReScope(
|
||||
self.bccx.region_scope_tree.var_scope(hir_id.local_id)))
|
||||
}
|
||||
Categorization::StaticItem |
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) => {
|
||||
self.bccx.tcx.lifetimes.re_static
|
||||
}
|
||||
Categorization::Deref(_, mc::BorrowedPtr(_, r)) => {
|
||||
r
|
||||
}
|
||||
Categorization::Downcast(ref cmt, _) |
|
||||
Categorization::Deref(ref cmt, mc::Unique) |
|
||||
Categorization::Interior(ref cmt, _) => {
|
||||
self.scope(cmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,433 +0,0 @@
|
||||
// ----------------------------------------------------------------------
|
||||
// Gathering loans
|
||||
//
|
||||
// The borrow check proceeds in two phases. In phase one, we gather the full
|
||||
// set of loans that are required at any point. These are sorted according to
|
||||
// their associated scopes. In phase two, checking loans, we will then make
|
||||
// sure that all of these loans are honored.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::borrowck::move_data::MoveData;
|
||||
use rustc::middle::expr_use_visitor as euv;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use log::debug;
|
||||
|
||||
use restrictions::RestrictionResult;
|
||||
|
||||
mod lifetime;
|
||||
mod restrictions;
|
||||
mod gather_moves;
|
||||
|
||||
pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
body: hir::BodyId)
|
||||
-> (Vec<Loan<'tcx>>, move_data::MoveData<'tcx>) {
|
||||
let def_id = bccx.tcx.hir().body_owner_def_id(body);
|
||||
let param_env = bccx.tcx.param_env(def_id);
|
||||
let mut glcx = GatherLoanCtxt {
|
||||
bccx,
|
||||
all_loans: Vec::new(),
|
||||
item_ub: region::Scope {
|
||||
id: bccx.tcx.hir().body(body).value.hir_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
},
|
||||
move_data: MoveData::default(),
|
||||
};
|
||||
|
||||
let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
|
||||
euv::ExprUseVisitor::new(&mut glcx,
|
||||
bccx.tcx,
|
||||
def_id,
|
||||
param_env,
|
||||
&bccx.region_scope_tree,
|
||||
bccx.tables,
|
||||
Some(rvalue_promotable_map))
|
||||
.consume_body(bccx.body);
|
||||
|
||||
let GatherLoanCtxt { all_loans, move_data, .. } = glcx;
|
||||
(all_loans, move_data)
|
||||
}
|
||||
|
||||
struct GatherLoanCtxt<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: move_data::MoveData<'tcx>,
|
||||
all_loans: Vec<Loan<'tcx>>,
|
||||
/// `item_ub` is used as an upper-bound on the lifetime whenever we
|
||||
/// ask for the scope of an expression categorized as an upvar.
|
||||
item_ub: region::Scope,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
|
||||
fn consume(&mut self,
|
||||
consume_id: hir::HirId,
|
||||
_consume_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume(consume_id={}, cmt={:?}, mode={:?})",
|
||||
consume_id, cmt, mode);
|
||||
|
||||
match mode {
|
||||
euv::Move(_) => {
|
||||
gather_moves::gather_move_from_expr(
|
||||
self.bccx, &self.move_data,
|
||||
consume_id.local_id, cmt);
|
||||
}
|
||||
euv::Copy => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn matched_pat(&mut self,
|
||||
matched_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::MatchMode) {
|
||||
debug!("matched_pat(matched_pat={:?}, cmt={:?}, mode={:?})",
|
||||
matched_pat,
|
||||
cmt,
|
||||
mode);
|
||||
}
|
||||
|
||||
fn consume_pat(&mut self,
|
||||
consume_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})",
|
||||
consume_pat,
|
||||
cmt,
|
||||
mode);
|
||||
|
||||
match mode {
|
||||
euv::Copy => { return; }
|
||||
euv::Move(_) => { }
|
||||
}
|
||||
|
||||
gather_moves::gather_move_from_pat(
|
||||
self.bccx, &self.move_data,
|
||||
consume_pat, cmt);
|
||||
}
|
||||
|
||||
fn borrow(&mut self,
|
||||
borrow_id: hir::HirId,
|
||||
_: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
bk: ty::BorrowKind,
|
||||
loan_cause: euv::LoanCause)
|
||||
{
|
||||
debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
|
||||
bk={:?}, loan_cause={:?})",
|
||||
borrow_id, cmt, loan_region,
|
||||
bk, loan_cause);
|
||||
|
||||
self.guarantee_valid(borrow_id.local_id,
|
||||
cmt,
|
||||
bk,
|
||||
loan_region);
|
||||
}
|
||||
|
||||
fn mutate(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
assignment_span: Span,
|
||||
assignee_cmt: &mc::cmt_<'tcx>,
|
||||
_: euv::MutateMode)
|
||||
{
|
||||
self.guarantee_assignment_valid(assignment_id,
|
||||
assignment_span,
|
||||
assignee_cmt);
|
||||
}
|
||||
|
||||
fn decl_without_init(&mut self, id: hir::HirId, _span: Span) {
|
||||
let ty = self.bccx
|
||||
.tables
|
||||
.node_type(id);
|
||||
gather_moves::gather_decl(self.bccx, &self.move_data, id, ty);
|
||||
}
|
||||
|
||||
fn nested_body(&mut self, body_id: hir::BodyId) {
|
||||
debug!("nested_body(body_id={:?})", body_id);
|
||||
// rust-lang/rust#58776: MIR and AST borrow check disagree on where
|
||||
// certain closure errors are reported. As such migrate borrowck has to
|
||||
// operate at the level of items, rather than bodies. Check if the
|
||||
// contained closure had any errors and set `signalled_any_error` if it
|
||||
// has.
|
||||
let bccx = self.bccx;
|
||||
if bccx.tcx.migrate_borrowck() {
|
||||
if let SignalledError::NoErrorsSeen = bccx.signalled_any_error.get() {
|
||||
let closure_def_id = bccx.tcx.hir().body_owner_def_id(body_id);
|
||||
debug!("checking closure: {:?}", closure_def_id);
|
||||
|
||||
bccx.signalled_any_error.set(bccx.tcx.borrowck(closure_def_id).signalled_any_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the A-* rules in README.md.
|
||||
fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind)
|
||||
-> Result<(),()> {
|
||||
|
||||
let aliasability = cmt.freely_aliasable();
|
||||
debug!("check_aliasability aliasability={:?} req_kind={:?}",
|
||||
aliasability, req_kind);
|
||||
|
||||
match (aliasability, req_kind) {
|
||||
(mc::Aliasability::NonAliasable, _) => {
|
||||
/* Uniquely accessible path -- OK for `&` and `&mut` */
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(mc::AliasableStatic), ty::ImmBorrow) => {
|
||||
// Borrow of an immutable static item.
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(mc::AliasableStaticMut), _) => {
|
||||
// Even touching a static mut is considered unsafe. We assume the
|
||||
// user knows what they're doing in these cases.
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(_), ty::UniqueImmBorrow) |
|
||||
(mc::Aliasability::FreelyAliasable(_), ty::MutBorrow) => {
|
||||
bccx.signal_error();
|
||||
Err(())
|
||||
}
|
||||
(..) => {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the M-* rules in README.md.
|
||||
fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind)
|
||||
-> Result<(),()> {
|
||||
debug!("check_mutability(cmt={:?} req_kind={:?}", cmt, req_kind);
|
||||
match req_kind {
|
||||
ty::UniqueImmBorrow | ty::ImmBorrow => {
|
||||
match cmt.mutbl {
|
||||
// I am intentionally leaving this here to help
|
||||
// refactoring if, in the future, we should add new
|
||||
// kinds of mutability.
|
||||
mc::McImmutable | mc::McDeclared | mc::McInherited => {
|
||||
// both imm and mut data can be lent as imm;
|
||||
// for mutable data, this is a freeze
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ty::MutBorrow => {
|
||||
// Only mutable data can be lent as mutable.
|
||||
if !cmt.mutbl.is_mutable() {
|
||||
Err(bccx.signal_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
|
||||
pub fn tcx(&self) -> TyCtxt<'tcx> { self.bccx.tcx }
|
||||
|
||||
/// Guarantees that `cmt` is assignable, or reports an error.
|
||||
fn guarantee_assignment_valid(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
assignment_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>) {
|
||||
|
||||
let opt_lp = opt_loan_path(cmt);
|
||||
debug!("guarantee_assignment_valid(assignment_id={}, cmt={:?}) opt_lp={:?}",
|
||||
assignment_id, cmt, opt_lp);
|
||||
|
||||
if let Categorization::Local(..) = cmt.cat {
|
||||
// Only re-assignments to locals require it to be
|
||||
// mutable - this is checked in check_loans.
|
||||
} else {
|
||||
// Check that we don't allow assignments to non-mutable data.
|
||||
if check_mutability(self.bccx, cmt, ty::MutBorrow).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we don't allow assignments to aliasable data
|
||||
if check_aliasability(self.bccx, cmt, ty::MutBorrow).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
match opt_lp {
|
||||
Some(lp) => {
|
||||
gather_moves::gather_assignment(self.bccx, &self.move_data,
|
||||
assignment_id.local_id,
|
||||
assignment_span,
|
||||
lp);
|
||||
}
|
||||
None => {
|
||||
// This can occur with e.g., `*foo() = 5`. In such
|
||||
// cases, there is no need to check for conflicts
|
||||
// with moves etc, just ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarantees that `addr_of(cmt)` will be valid for the duration of `static_scope_r`, or
|
||||
/// reports an error. This may entail taking out loans, which will be added to the
|
||||
/// `req_loan_map`.
|
||||
fn guarantee_valid(&mut self,
|
||||
borrow_id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind,
|
||||
loan_region: ty::Region<'tcx>) {
|
||||
debug!("guarantee_valid(borrow_id={:?}, cmt={:?}, \
|
||||
req_mutbl={:?}, loan_region={:?})",
|
||||
borrow_id,
|
||||
cmt,
|
||||
req_kind,
|
||||
loan_region);
|
||||
|
||||
// a loan for the empty region can never be dereferenced, so
|
||||
// it is always safe
|
||||
if *loan_region == ty::ReEmpty {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the lifetime of the borrow does not exceed
|
||||
// the lifetime of the data being borrowed.
|
||||
if lifetime::guarantee_lifetime(self.bccx, self.item_ub, cmt, loan_region).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Check that we don't allow mutable borrows of non-mutable data.
|
||||
if check_mutability(self.bccx, cmt, req_kind).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Check that we don't allow mutable borrows of aliasable data.
|
||||
if check_aliasability(self.bccx, cmt, req_kind).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Compute the restrictions that are required to enforce the
|
||||
// loan is safe.
|
||||
let restr = restrictions::compute_restrictions(self.bccx, &cmt, loan_region);
|
||||
|
||||
debug!("guarantee_valid(): restrictions={:?}", restr);
|
||||
|
||||
// Create the loan record (if needed).
|
||||
let loan = match restr {
|
||||
RestrictionResult::Safe => {
|
||||
// No restrictions---no loan record necessary
|
||||
return;
|
||||
}
|
||||
|
||||
RestrictionResult::SafeIf(loan_path, restricted_paths) => {
|
||||
let loan_scope = match *loan_region {
|
||||
ty::ReScope(scope) => scope,
|
||||
|
||||
ty::ReEarlyBound(ref br) => {
|
||||
self.bccx.region_scope_tree.early_free_scope(self.tcx(), br)
|
||||
}
|
||||
|
||||
ty::ReFree(ref fr) => {
|
||||
self.bccx.region_scope_tree.free_scope(self.tcx(), fr)
|
||||
}
|
||||
|
||||
ty::ReStatic => self.item_ub,
|
||||
|
||||
ty::ReEmpty |
|
||||
ty::ReClosureBound(..) |
|
||||
ty::ReLateBound(..) |
|
||||
ty::ReVar(..) |
|
||||
ty::RePlaceholder(..) |
|
||||
ty::ReErased => {
|
||||
span_bug!(
|
||||
cmt.span,
|
||||
"invalid borrow lifetime: {:?}",
|
||||
loan_region);
|
||||
}
|
||||
};
|
||||
debug!("loan_scope = {:?}", loan_scope);
|
||||
|
||||
let borrow_scope = region::Scope {
|
||||
id: borrow_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
let gen_scope = self.compute_gen_scope(borrow_scope, loan_scope);
|
||||
debug!("gen_scope = {:?}", gen_scope);
|
||||
|
||||
let kill_scope = self.compute_kill_scope(loan_scope, &loan_path);
|
||||
debug!("kill_scope = {:?}", kill_scope);
|
||||
|
||||
Loan {
|
||||
index: self.all_loans.len(),
|
||||
loan_path,
|
||||
kind: req_kind,
|
||||
gen_scope,
|
||||
kill_scope,
|
||||
restricted_paths,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!("guarantee_valid(borrow_id={:?}), loan={:?}",
|
||||
borrow_id, loan);
|
||||
|
||||
// let loan_path = loan.loan_path;
|
||||
// let loan_gen_scope = loan.gen_scope;
|
||||
// let loan_kill_scope = loan.kill_scope;
|
||||
self.all_loans.push(loan);
|
||||
}
|
||||
|
||||
pub fn compute_gen_scope(&self,
|
||||
borrow_scope: region::Scope,
|
||||
loan_scope: region::Scope)
|
||||
-> region::Scope {
|
||||
//! Determine when to introduce the loan. Typically the loan
|
||||
//! is introduced at the point of the borrow, but in some cases,
|
||||
//! notably method arguments, the loan may be introduced only
|
||||
//! later, once it comes into scope.
|
||||
|
||||
if self.bccx.region_scope_tree.is_subscope_of(borrow_scope, loan_scope) {
|
||||
borrow_scope
|
||||
} else {
|
||||
loan_scope
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_kill_scope(&self, loan_scope: region::Scope, lp: &LoanPath<'tcx>)
|
||||
-> region::Scope {
|
||||
//! Determine when the loan restrictions go out of scope.
|
||||
//! This is either when the lifetime expires or when the
|
||||
//! local variable which roots the loan-path goes out of scope,
|
||||
//! whichever happens faster.
|
||||
//!
|
||||
//! It may seem surprising that we might have a loan region
|
||||
//! larger than the variable which roots the loan-path; this can
|
||||
//! come about when variables of `&mut` type are re-borrowed,
|
||||
//! as in this example:
|
||||
//!
|
||||
//! struct Foo { counter: u32 }
|
||||
//!
|
||||
//! fn counter<'a>(v: &'a mut Foo) -> &'a mut u32 {
|
||||
//! &mut v.counter
|
||||
//! }
|
||||
//!
|
||||
//! In this case, the reference (`'a`) outlives the
|
||||
//! variable `v` that hosts it. Note that this doesn't come up
|
||||
//! with immutable `&` pointers, because borrows of such pointers
|
||||
//! do not require restrictions and hence do not cause a loan.
|
||||
|
||||
let lexical_scope = lp.kill_scope(self.bccx);
|
||||
if self.bccx.region_scope_tree.is_subscope_of(lexical_scope, loan_scope) {
|
||||
lexical_scope
|
||||
} else {
|
||||
assert!(self.bccx.region_scope_tree.is_subscope_of(loan_scope, lexical_scope));
|
||||
loan_scope
|
||||
}
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
//! Computes the restrictions that result from a borrow.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::ty;
|
||||
use log::debug;
|
||||
|
||||
use crate::borrowck::ToInteriorKind;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RestrictionResult<'tcx> {
|
||||
Safe,
|
||||
SafeIf(Rc<LoanPath<'tcx>>, Vec<Rc<LoanPath<'tcx>>>)
|
||||
}
|
||||
|
||||
pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>)
|
||||
-> RestrictionResult<'tcx> {
|
||||
let ctxt = RestrictionsContext { bccx, loan_region };
|
||||
|
||||
ctxt.restrict(cmt)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
||||
struct RestrictionsContext<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
|
||||
fn restrict(&self,
|
||||
cmt: &mc::cmt_<'tcx>) -> RestrictionResult<'tcx> {
|
||||
debug!("restrict(cmt={:?})", cmt);
|
||||
|
||||
let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
|
||||
|
||||
match cmt.cat.clone() {
|
||||
Categorization::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.
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::ThreadLocal(..) => {
|
||||
// Thread-locals are statics that have a scope, with
|
||||
// no underlying structure to provide restrictions.
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::Local(local_id) => {
|
||||
// R-Variable, locally declared
|
||||
let lp = new_lp(LpVar(local_id));
|
||||
RestrictionResult::SafeIf(lp.clone(), vec![lp])
|
||||
}
|
||||
|
||||
Categorization::Upvar(mc::Upvar { id, .. }) => {
|
||||
// R-Variable, captured into closure
|
||||
let lp = new_lp(LpUpvar(id));
|
||||
RestrictionResult::SafeIf(lp.clone(), vec![lp])
|
||||
}
|
||||
|
||||
Categorization::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)
|
||||
}
|
||||
|
||||
Categorization::Interior(cmt_base, interior) => {
|
||||
// R-Field
|
||||
//
|
||||
// Overwriting the base would not change the type of
|
||||
// the memory, so no additional restrictions are
|
||||
// needed.
|
||||
let opt_variant_id = match cmt_base.cat {
|
||||
Categorization::Downcast(_, variant_id) => Some(variant_id),
|
||||
_ => None
|
||||
};
|
||||
let interior = interior.cleaned();
|
||||
let base_ty = cmt_base.ty;
|
||||
let result = self.restrict(&cmt_base);
|
||||
// Borrowing one union field automatically borrows all its fields.
|
||||
match base_ty.kind {
|
||||
ty::Adt(adt_def, _) if adt_def.is_union() => match result {
|
||||
RestrictionResult::Safe => RestrictionResult::Safe,
|
||||
RestrictionResult::SafeIf(base_lp, mut base_vec) => {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field = InteriorKind::InteriorField(
|
||||
mc::FieldIndex(i, field.ident.name)
|
||||
);
|
||||
let field_ty = if field == interior {
|
||||
cmt.ty
|
||||
} else {
|
||||
self.bccx.tcx.types.err // Doesn't matter
|
||||
};
|
||||
let sibling_lp_kind = LpExtend(base_lp.clone(), cmt.mutbl,
|
||||
LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty));
|
||||
base_vec.push(sibling_lp);
|
||||
}
|
||||
|
||||
let lp = new_lp(LpExtend(base_lp, cmt.mutbl,
|
||||
LpInterior(opt_variant_id, interior)));
|
||||
RestrictionResult::SafeIf(lp, base_vec)
|
||||
}
|
||||
},
|
||||
_ => self.extend(result, &cmt, LpInterior(opt_variant_id, interior))
|
||||
}
|
||||
}
|
||||
|
||||
Categorization::StaticItem => {
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::Deref(cmt_base, pk) => {
|
||||
match pk {
|
||||
mc::Unique => {
|
||||
// R-Deref-Send-Pointer
|
||||
//
|
||||
// When we borrow the interior of a box, 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::BorrowedPtr(bk, lt) => {
|
||||
// R-Deref-[Mut-]Borrowed
|
||||
if !self.bccx.is_subregion_of(self.loan_region, lt) {
|
||||
self.bccx.signal_error();
|
||||
return RestrictionResult::Safe;
|
||||
}
|
||||
|
||||
match bk {
|
||||
ty::ImmBorrow => RestrictionResult::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 raw pointers
|
||||
mc::UnsafePtr(..) => RestrictionResult::Safe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend(&self,
|
||||
result: RestrictionResult<'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
elem: LoanPathElem<'tcx>) -> RestrictionResult<'tcx> {
|
||||
match result {
|
||||
RestrictionResult::Safe => RestrictionResult::Safe,
|
||||
RestrictionResult::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());
|
||||
RestrictionResult::SafeIf(lp, base_vec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,621 +0,0 @@
|
||||
//! See The Book chapter on the borrow checker for more details.
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub use LoanPathKind::*;
|
||||
pub use LoanPathElem::*;
|
||||
|
||||
use InteriorKind::*;
|
||||
|
||||
use rustc::hir::HirId;
|
||||
use rustc::hir::Node;
|
||||
use rustc::middle::borrowck::{BorrowCheckResult, SignalledError};
|
||||
use rustc::hir::def_id::{DefId, LocalDefId};
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::middle::free_region::RegionRelations;
|
||||
use rustc::ty::{self, Ty, TyCtxt};
|
||||
use rustc::ty::query::Providers;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use log::debug;
|
||||
|
||||
use rustc::hir;
|
||||
|
||||
use crate::cfg;
|
||||
use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom};
|
||||
|
||||
pub mod check_loans;
|
||||
|
||||
pub mod gather_loans;
|
||||
|
||||
pub mod move_data;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LoanDataFlowOperator;
|
||||
|
||||
pub type LoanDataFlow<'tcx> = DataFlowContext<'tcx, LoanDataFlowOperator>;
|
||||
|
||||
pub fn check_crate(tcx: TyCtxt<'_>) {
|
||||
tcx.par_body_owners(|body_owner_def_id| {
|
||||
tcx.ensure().borrowck(body_owner_def_id);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers<'_>) {
|
||||
*providers = Providers {
|
||||
borrowck,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
||||
/// Collection of conclusions determined via borrow checker analyses.
|
||||
pub struct AnalysisData<'tcx> {
|
||||
pub all_loans: Vec<Loan<'tcx>>,
|
||||
pub loans: DataFlowContext<'tcx, LoanDataFlowOperator>,
|
||||
pub move_data: move_data::FlowedMoveData<'tcx>,
|
||||
}
|
||||
|
||||
fn borrowck(tcx: TyCtxt<'_>, owner_def_id: DefId) -> &BorrowCheckResult {
|
||||
assert!(tcx.use_ast_borrowck() || tcx.migrate_borrowck());
|
||||
|
||||
debug!("borrowck(body_owner_def_id={:?})", owner_def_id);
|
||||
|
||||
let signalled_error = tcx.check_match(owner_def_id);
|
||||
if let SignalledError::SawSomeError = signalled_error {
|
||||
return tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: SignalledError::SawSomeError,
|
||||
})
|
||||
}
|
||||
|
||||
let owner_id = tcx.hir().as_local_hir_id(owner_def_id).unwrap();
|
||||
|
||||
match tcx.hir().get(owner_id) {
|
||||
Node::Ctor(..) => {
|
||||
// We get invoked with anything that has MIR, but some of
|
||||
// those things (notably the synthesized constructors from
|
||||
// tuple structs/variants) do not have an associated body
|
||||
// and do not need borrowchecking.
|
||||
return tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: SignalledError::NoErrorsSeen,
|
||||
})
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
|
||||
let body_id = tcx.hir().body_owned_by(owner_id);
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
let region_scope_tree = tcx.region_scope_tree(owner_def_id);
|
||||
let body = tcx.hir().body(body_id);
|
||||
let mut bccx = BorrowckCtxt {
|
||||
tcx,
|
||||
tables,
|
||||
region_scope_tree,
|
||||
owner_def_id,
|
||||
body,
|
||||
signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
|
||||
};
|
||||
|
||||
// Eventually, borrowck will always read the MIR, but at the
|
||||
// moment we do not. So, for now, we always force MIR to be
|
||||
// constructed for a given fn, since this may result in errors
|
||||
// being reported and we want that to happen.
|
||||
//
|
||||
// Note that `mir_validated` is a "stealable" result; the
|
||||
// thief, `optimized_mir()`, forces borrowck, so we know that
|
||||
// is not yet stolen.
|
||||
tcx.ensure().mir_validated(owner_def_id);
|
||||
|
||||
// option dance because you can't capture an uninitialized variable
|
||||
// by mut-ref.
|
||||
let mut cfg = None;
|
||||
if let Some(AnalysisData { all_loans,
|
||||
loans: loan_dfcx,
|
||||
move_data: flowed_moves }) =
|
||||
build_borrowck_dataflow_data(&mut bccx, false, body_id,
|
||||
|bccx| {
|
||||
cfg = Some(cfg::CFG::new(bccx.tcx, &body));
|
||||
cfg.as_mut().unwrap()
|
||||
})
|
||||
{
|
||||
check_loans::check_loans(&mut bccx, &loan_dfcx, &flowed_moves, &all_loans, body);
|
||||
}
|
||||
|
||||
tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: bccx.signalled_any_error.into_inner(),
|
||||
})
|
||||
}
|
||||
|
||||
fn build_borrowck_dataflow_data<'a, 'c, 'tcx, F>(this: &mut BorrowckCtxt<'a, 'tcx>,
|
||||
force_analysis: bool,
|
||||
body_id: hir::BodyId,
|
||||
get_cfg: F)
|
||||
-> Option<AnalysisData<'tcx>>
|
||||
where F: FnOnce(&mut BorrowckCtxt<'a, 'tcx>) -> &'c cfg::CFG
|
||||
{
|
||||
// Check the body of fn items.
|
||||
let (all_loans, move_data) =
|
||||
gather_loans::gather_loans_in_fn(this, body_id);
|
||||
|
||||
if !force_analysis && move_data.is_empty() && all_loans.is_empty() {
|
||||
// large arrays of data inserted as constants can take a lot of
|
||||
// time and memory to borrow-check - see issue #36799. However,
|
||||
// they don't have places, so no borrow-check is actually needed.
|
||||
// Recognize that case and skip borrow-checking.
|
||||
debug!("skipping loan propagation for {:?} because of no loans", body_id);
|
||||
return None;
|
||||
} else {
|
||||
debug!("propagating loans in {:?}", body_id);
|
||||
}
|
||||
|
||||
let cfg = get_cfg(this);
|
||||
let mut loan_dfcx =
|
||||
DataFlowContext::new(this.tcx,
|
||||
"borrowck",
|
||||
Some(this.body),
|
||||
cfg,
|
||||
LoanDataFlowOperator,
|
||||
all_loans.len());
|
||||
for (loan_idx, loan) in all_loans.iter().enumerate() {
|
||||
loan_dfcx.add_gen(loan.gen_scope.item_local_id(), loan_idx);
|
||||
loan_dfcx.add_kill(KillFrom::ScopeEnd,
|
||||
loan.kill_scope.item_local_id(),
|
||||
loan_idx);
|
||||
}
|
||||
loan_dfcx.add_kills_from_flow_exits(cfg);
|
||||
loan_dfcx.propagate(cfg, this.body);
|
||||
|
||||
let flowed_moves = move_data::FlowedMoveData::new(move_data,
|
||||
this,
|
||||
cfg,
|
||||
this.body);
|
||||
|
||||
Some(AnalysisData { all_loans,
|
||||
loans: loan_dfcx,
|
||||
move_data:flowed_moves })
|
||||
}
|
||||
|
||||
/// 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: TyCtxt<'tcx>,
|
||||
body_id: hir::BodyId,
|
||||
cfg: &cfg::CFG)
|
||||
-> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'tcx>)
|
||||
{
|
||||
let owner_id = tcx.hir().body_owner(body_id);
|
||||
let owner_def_id = tcx.hir().local_def_id(owner_id);
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
let region_scope_tree = tcx.region_scope_tree(owner_def_id);
|
||||
let body = tcx.hir().body(body_id);
|
||||
let mut bccx = BorrowckCtxt {
|
||||
tcx,
|
||||
tables,
|
||||
region_scope_tree,
|
||||
owner_def_id,
|
||||
body,
|
||||
signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
|
||||
};
|
||||
|
||||
let dataflow_data = build_borrowck_dataflow_data(&mut bccx, true, body_id, |_| cfg);
|
||||
(bccx, dataflow_data.unwrap())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Type definitions
|
||||
|
||||
pub struct BorrowckCtxt<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
// tables for the current thing we are checking; set to
|
||||
// Some in `borrowck_fn` and cleared later
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
|
||||
region_scope_tree: &'tcx region::ScopeTree,
|
||||
|
||||
owner_def_id: DefId,
|
||||
|
||||
body: &'tcx hir::Body,
|
||||
|
||||
signalled_any_error: Cell<SignalledError>,
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'tcx: 'a> BorrowckCtxt<'a, 'tcx> {
|
||||
fn signal_error(&self) {
|
||||
self.signalled_any_error.set(SignalledError::SawSomeError);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Loans and loan paths
|
||||
|
||||
/// Record of a loan that was issued.
|
||||
pub struct Loan<'tcx> {
|
||||
index: usize,
|
||||
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::Scope,
|
||||
|
||||
/// 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::Scope,
|
||||
}
|
||||
|
||||
impl<'tcx> Loan<'tcx> {
|
||||
pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
|
||||
self.loan_path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
pub struct LoanPath<'tcx> {
|
||||
kind: LoanPathKind<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> PartialEq for LoanPath<'tcx> {
|
||||
fn eq(&self, that: &LoanPath<'tcx>) -> bool {
|
||||
self.kind == that.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Hash for LoanPath<'tcx> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub enum LoanPathKind<'tcx> {
|
||||
LpVar(hir::HirId), // `x` in README.md
|
||||
LpUpvar(ty::UpvarId), // `x` captured by-value into closure
|
||||
LpDowncast(Rc<LoanPath<'tcx>>, DefId), // `x` downcast to particular enum variant
|
||||
LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem<'tcx>)
|
||||
}
|
||||
|
||||
impl<'tcx> LoanPath<'tcx> {
|
||||
fn new(kind: LoanPathKind<'tcx>, ty: Ty<'tcx>) -> LoanPath<'tcx> {
|
||||
LoanPath { kind: kind, ty: ty }
|
||||
}
|
||||
|
||||
fn to_type(&self) -> Ty<'tcx> { self.ty }
|
||||
}
|
||||
|
||||
// FIXME (pnkfelix): See discussion here
|
||||
// https://github.com/pnkfelix/rust/commit/
|
||||
// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
|
||||
const DOWNCAST_PRINTED_OPERATOR: &'static str = " as ";
|
||||
|
||||
// A local, "cleaned" version of `mc::InteriorKind` that drops
|
||||
// information that is not relevant to loan-path analysis. (In
|
||||
// particular, the distinction between how precisely an array-element
|
||||
// is tracked is irrelevant here.)
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum InteriorKind {
|
||||
InteriorField(mc::FieldIndex),
|
||||
InteriorElement,
|
||||
}
|
||||
|
||||
trait ToInteriorKind { fn cleaned(self) -> InteriorKind; }
|
||||
impl ToInteriorKind for mc::InteriorKind {
|
||||
fn cleaned(self) -> InteriorKind {
|
||||
match self {
|
||||
mc::InteriorField(name) => InteriorField(name),
|
||||
mc::InteriorElement(_) => InteriorElement,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This can be:
|
||||
// - a pointer dereference (`*P` in README.md)
|
||||
// - a field reference, with an optional definition of the containing
|
||||
// enum variant (`P.f` in README.md)
|
||||
// `DefId` is present when the field is part of struct that is in
|
||||
// a variant of an enum. For instance in:
|
||||
// `enum E { X { foo: u32 }, Y { foo: u32 }}`
|
||||
// each `foo` is qualified by the definitition id of the variant (`X` or `Y`).
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum LoanPathElem<'tcx> {
|
||||
LpDeref(mc::PointerKind<'tcx>),
|
||||
LpInterior(Option<DefId>, InteriorKind),
|
||||
}
|
||||
|
||||
fn closure_to_block(closure_id: LocalDefId, tcx: TyCtxt<'_>) -> HirId {
|
||||
let closure_id = tcx.hir().local_def_id_to_hir_id(closure_id);
|
||||
match tcx.hir().get(closure_id) {
|
||||
Node::Expr(expr) => match expr.kind {
|
||||
hir::ExprKind::Closure(.., body_id, _, _) => {
|
||||
body_id.hir_id
|
||||
}
|
||||
_ => {
|
||||
bug!("encountered non-closure id: {}", closure_id)
|
||||
}
|
||||
},
|
||||
_ => bug!("encountered non-expr id: {}", closure_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LoanPath<'tcx> {
|
||||
pub fn kill_scope(&self, bccx: &BorrowckCtxt<'a, 'tcx>) -> region::Scope {
|
||||
match self.kind {
|
||||
LpVar(hir_id) => {
|
||||
bccx.region_scope_tree.var_scope(hir_id.local_id)
|
||||
}
|
||||
LpUpvar(upvar_id) => {
|
||||
let block_id = closure_to_block(upvar_id.closure_expr_id, bccx.tcx);
|
||||
region::Scope { id: block_id.local_id, data: region::ScopeData::Node }
|
||||
}
|
||||
LpDowncast(ref base, _) |
|
||||
LpExtend(ref base, ..) => base.kill_scope(bccx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid "cannot borrow immutable field `self.x` as mutable" as that implies that a field *can* be
|
||||
// mutable independently of the struct it belongs to. (#35937)
|
||||
pub fn opt_loan_path_is_field<'tcx>(cmt: &mc::cmt_<'tcx>) -> (Option<Rc<LoanPath<'tcx>>>, bool) {
|
||||
let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::StaticItem => {
|
||||
(None, false)
|
||||
}
|
||||
|
||||
Categorization::Local(id) => {
|
||||
(Some(new_lp(LpVar(id))), false)
|
||||
}
|
||||
|
||||
Categorization::Upvar(mc::Upvar { id, .. }) => {
|
||||
(Some(new_lp(LpUpvar(id))), false)
|
||||
}
|
||||
|
||||
Categorization::Deref(ref cmt_base, pk) => {
|
||||
let lp = opt_loan_path_is_field(cmt_base);
|
||||
(lp.0.map(|lp| {
|
||||
new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
|
||||
}), lp.1)
|
||||
}
|
||||
|
||||
Categorization::Interior(ref cmt_base, ik) => {
|
||||
(opt_loan_path(cmt_base).map(|lp| {
|
||||
let opt_variant_id = match cmt_base.cat {
|
||||
Categorization::Downcast(_, did) => Some(did),
|
||||
_ => None
|
||||
};
|
||||
new_lp(LpExtend(lp, cmt.mutbl, LpInterior(opt_variant_id, ik.cleaned())))
|
||||
}), true)
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref cmt_base, variant_def_id) => {
|
||||
let lp = opt_loan_path_is_field(cmt_base);
|
||||
(lp.0.map(|lp| {
|
||||
new_lp(LpDowncast(lp, variant_def_id))
|
||||
}), lp.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn opt_loan_path<'tcx>(cmt: &mc::cmt_<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
|
||||
opt_loan_path_is_field(cmt).0
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
|
||||
impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
|
||||
pub fn is_subregion_of(&self,
|
||||
r_sub: ty::Region<'tcx>,
|
||||
r_sup: ty::Region<'tcx>)
|
||||
-> bool
|
||||
{
|
||||
let region_rels = RegionRelations::new(self.tcx,
|
||||
self.owner_def_id,
|
||||
&self.region_scope_tree,
|
||||
&self.tables.free_region_map);
|
||||
region_rels.is_subregion_of(r_sub, r_sup)
|
||||
}
|
||||
|
||||
pub fn append_loan_path_to_string(&self,
|
||||
loan_path: &LoanPath<'tcx>,
|
||||
out: &mut String) {
|
||||
match loan_path.kind {
|
||||
LpUpvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id: id }, closure_expr_id: _ }) => {
|
||||
out.push_str(&self.tcx.hir().name(id).as_str());
|
||||
}
|
||||
LpVar(id) => {
|
||||
out.push_str(&self.tcx.hir().name(id).as_str());
|
||||
}
|
||||
|
||||
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(&self.tcx.def_path_str(variant_def_id));
|
||||
out.push(')');
|
||||
}
|
||||
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorField(mc::FieldIndex(_, info)))) => {
|
||||
self.append_autoderefd_loan_path_to_string(&lp_base, out);
|
||||
out.push('.');
|
||||
out.push_str(&info.as_str());
|
||||
}
|
||||
|
||||
LpExtend(ref lp_base, _, LpInterior(_, 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_str(DOWNCAST_PRINTED_OPERATOR);
|
||||
out.push_str(&self.tcx.def_path_str(variant_def_id));
|
||||
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_cow_str(&self, cmt: &mc::cmt_<'tcx>) -> Cow<'static, str> {
|
||||
cmt.descriptive_string(self.tcx)
|
||||
}
|
||||
|
||||
pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
|
||||
match opt_loan_path(cmt) {
|
||||
Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)),
|
||||
None => self.cmt_to_cow_str(cmt).into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitwiseOperator for LoanDataFlowOperator {
|
||||
#[inline]
|
||||
fn join(&self, succ: usize, pred: usize) -> usize {
|
||||
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 fmt::Debug for InteriorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
InteriorField(mc::FieldIndex(_, info)) => write!(f, "{}", info),
|
||||
InteriorElement => write!(f, "[]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for Loan<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Loan_{}({:?}, {:?}, {:?}-{:?}, {:?})",
|
||||
self.index,
|
||||
self.loan_path,
|
||||
self.kind,
|
||||
self.gen_scope,
|
||||
self.kill_scope,
|
||||
self.restricted_paths)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for LoanPath<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
LpVar(id) => {
|
||||
write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().node_to_string(id)))
|
||||
}
|
||||
|
||||
LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath {hir_id: var_id}, closure_expr_id }) => {
|
||||
let s = ty::tls::with(|tcx| {
|
||||
tcx.hir().node_to_string(var_id)
|
||||
});
|
||||
write!(f, "$({} captured by id={:?})", s, closure_expr_id)
|
||||
}
|
||||
|
||||
LpDowncast(ref lp, variant_def_id) => {
|
||||
let variant_str = if variant_def_id.is_local() {
|
||||
ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
|
||||
} else {
|
||||
format!("{:?}", variant_def_id)
|
||||
};
|
||||
write!(f, "({:?}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpDeref(_)) => {
|
||||
write!(f, "{:?}.*", lp)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
|
||||
write!(f, "{:?}.{:?}", lp, interior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Display for LoanPath<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
LpVar(id) => {
|
||||
write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().hir_to_user_string(id)))
|
||||
}
|
||||
|
||||
LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath { hir_id }, closure_expr_id: _ }) => {
|
||||
let s = ty::tls::with(|tcx| {
|
||||
tcx.hir().node_to_string(hir_id)
|
||||
});
|
||||
write!(f, "$({} captured by closure)", s)
|
||||
}
|
||||
|
||||
LpDowncast(ref lp, variant_def_id) => {
|
||||
let variant_str = if variant_def_id.is_local() {
|
||||
ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
|
||||
} else {
|
||||
format!("{:?}", variant_def_id)
|
||||
};
|
||||
write!(f, "({}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpDeref(_)) => {
|
||||
write!(f, "{}.*", lp)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
|
||||
write!(f, "{}.{:?}", lp, interior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,730 +0,0 @@
|
||||
//! Data structures used for tracking moves. Please see the extensive
|
||||
//! comments in the section "Moves and initialization" in `README.md`.
|
||||
|
||||
use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom};
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::cfg;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
use rustc::util::nodemap::FxHashMap;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::usize;
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use log::debug;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MoveData<'tcx> {
|
||||
/// Move paths. See section "Move paths" in `README.md`.
|
||||
pub paths: RefCell<Vec<MovePath<'tcx>>>,
|
||||
|
||||
/// Cache of loan path to move path index, for easy lookup.
|
||||
pub path_map: RefCell<FxHashMap<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>>,
|
||||
}
|
||||
|
||||
pub struct FlowedMoveData<'tcx> {
|
||||
pub move_data: MoveData<'tcx>,
|
||||
|
||||
pub dfcx_moves: MoveDataFlow<'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<'tcx>,
|
||||
}
|
||||
|
||||
/// Index into `MoveData.paths`, used like a pointer
|
||||
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct MovePathIndex(usize);
|
||||
|
||||
impl MovePathIndex {
|
||||
fn get(&self) -> usize {
|
||||
let MovePathIndex(v) = *self; v
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for MovePathIndex {
|
||||
fn clone(&self) -> MovePathIndex {
|
||||
MovePathIndex(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const InvalidMovePathIndex: MovePathIndex = MovePathIndex(usize::MAX);
|
||||
|
||||
/// Index into `MoveData.moves`, used like a pointer
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct MoveIndex(usize);
|
||||
|
||||
impl MoveIndex {
|
||||
fn get(&self) -> usize {
|
||||
let MoveIndex(v) = *self; v
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const InvalidMoveIndex: MoveIndex = MoveIndex(usize::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,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Move {
|
||||
/// Path being moved.
|
||||
pub path: MovePathIndex,
|
||||
|
||||
/// ID of node that is doing the move.
|
||||
pub id: hir::ItemLocalId,
|
||||
|
||||
/// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
|
||||
pub next_move: MoveIndex
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Assignment {
|
||||
/// Path being assigned.
|
||||
pub path: MovePathIndex,
|
||||
|
||||
/// ID where assignment occurs
|
||||
pub id: hir::ItemLocalId,
|
||||
|
||||
/// span of node where assignment occurs
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MoveDataFlowOperator;
|
||||
|
||||
pub type MoveDataFlow<'tcx> = DataFlowContext<'tcx, MoveDataFlowOperator>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AssignDataFlowOperator;
|
||||
|
||||
pub type AssignDataFlow<'tcx> = DataFlowContext<'tcx, AssignDataFlowOperator>;
|
||||
|
||||
fn loan_path_is_precise(loan_path: &LoanPath<'_>) -> bool {
|
||||
match loan_path.kind {
|
||||
LpVar(_) | LpUpvar(_) => {
|
||||
true
|
||||
}
|
||||
LpExtend(.., LpInterior(_, InteriorKind::InteriorElement)) => {
|
||||
// Paths involving element accesses a[i] do not refer to a unique
|
||||
// location, as there is no accurate tracking of the indices.
|
||||
//
|
||||
// (Paths involving element accesses via slice pattern bindings
|
||||
// can in principle be tracked precisely, but that is future
|
||||
// work. For now, continue claiming that they are imprecise.)
|
||||
false
|
||||
}
|
||||
LpDowncast(ref lp_base, _) |
|
||||
LpExtend(ref lp_base, ..) => {
|
||||
loan_path_is_precise(&lp_base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveData<'tcx> {
|
||||
/// Returns `true` if there are no trackable assignments or moves
|
||||
/// in this move data -- that means that there is nothing that
|
||||
/// could cause a borrow error.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.moves.borrow().is_empty() &&
|
||||
self.path_assignments.borrow().is_empty() &&
|
||||
self.var_assignments.borrow().is_empty()
|
||||
}
|
||||
|
||||
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: TyCtxt<'tcx>, lp: Rc<LoanPath<'tcx>>) -> MovePathIndex {
|
||||
if let Some(&index) = self.path_map.borrow().get(&lp) {
|
||||
return index;
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
index
|
||||
}
|
||||
};
|
||||
|
||||
debug!("move_path(lp={:?}, index={:?})",
|
||||
lp,
|
||||
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: TyCtxt<'tcx>,
|
||||
orig_lp: Rc<LoanPath<'tcx>>,
|
||||
id: hir::ItemLocalId,
|
||||
) {
|
||||
// Moving one union field automatically moves all its fields. Also move siblings of
|
||||
// all parent union fields, moves do not propagate upwards automatically.
|
||||
let mut lp = orig_lp.clone();
|
||||
while let LpExtend(ref base_lp, mutbl, lp_elem) = lp.clone().kind {
|
||||
if let (&ty::Adt(adt_def, _), LpInterior(opt_variant_id, interior))
|
||||
= (&base_lp.ty.kind, lp_elem) {
|
||||
if adt_def.is_union() {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field =
|
||||
InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name));
|
||||
if field != interior {
|
||||
let sibling_lp_kind =
|
||||
LpExtend(base_lp.clone(), mutbl, LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, tcx.types.err));
|
||||
self.add_move_helper(tcx, sibling_lp, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lp = base_lp.clone();
|
||||
}
|
||||
|
||||
self.add_move_helper(tcx, orig_lp, id);
|
||||
}
|
||||
|
||||
fn add_move_helper(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
id: hir::ItemLocalId,
|
||||
) {
|
||||
debug!("add_move(lp={:?}, id={:?})", lp, id);
|
||||
|
||||
let path_index = self.move_path(tcx, lp);
|
||||
let move_index = MoveIndex(self.moves.borrow().len());
|
||||
|
||||
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,
|
||||
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: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
assign_id: hir::ItemLocalId,
|
||||
span: Span,
|
||||
) {
|
||||
// Assigning to one union field automatically assigns to all its fields.
|
||||
if let LpExtend(ref base_lp, mutbl, LpInterior(opt_variant_id, interior)) = lp.kind {
|
||||
if let ty::Adt(adt_def, _) = base_lp.ty.kind {
|
||||
if adt_def.is_union() {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field =
|
||||
InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name));
|
||||
let field_ty = if field == interior {
|
||||
lp.ty
|
||||
} else {
|
||||
tcx.types.err // Doesn't matter
|
||||
};
|
||||
let sibling_lp_kind = LpExtend(base_lp.clone(), mutbl,
|
||||
LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty));
|
||||
self.add_assignment_helper(tcx, sibling_lp, assign_id,
|
||||
span);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.add_assignment_helper(tcx, lp, assign_id, span);
|
||||
}
|
||||
|
||||
fn add_assignment_helper(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
assign_id: hir::ItemLocalId,
|
||||
span: Span,
|
||||
) {
|
||||
debug!("add_assignment(lp={:?}, assign_id={:?}", lp, assign_id);
|
||||
|
||||
let path_index = self.move_path(tcx, lp.clone());
|
||||
|
||||
let assignment = Assignment {
|
||||
path: path_index,
|
||||
id: assign_id,
|
||||
span,
|
||||
};
|
||||
|
||||
if self.is_var_path(path_index) {
|
||||
debug!("add_assignment[var](lp={:?}, assignment={}, path_index={:?})",
|
||||
lp, self.var_assignments.borrow().len(), path_index);
|
||||
|
||||
self.var_assignments.borrow_mut().push(assignment);
|
||||
} else {
|
||||
debug!("add_assignment[path](lp={:?}, path_index={:?})",
|
||||
lp, path_index);
|
||||
|
||||
self.path_assignments.borrow_mut().push(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 `README.md` for more details.
|
||||
fn add_gen_kills(
|
||||
&self,
|
||||
bccx: &BorrowckCtxt<'_, '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,
|
||||
KillFrom::Execution, dfcx_moves);
|
||||
}
|
||||
|
||||
for assignment in self.path_assignments.borrow().iter() {
|
||||
self.kill_moves(assignment.path, assignment.id,
|
||||
KillFrom::Execution, 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(bccx);
|
||||
let path = *self.path_map.borrow().get(&path.loan_path).unwrap();
|
||||
self.kill_moves(path, kill_scope.item_local_id(),
|
||||
KillFrom::ScopeEnd, 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(bccx);
|
||||
dfcx_assign.add_kill(KillFrom::ScopeEnd,
|
||||
kill_scope.item_local_id(),
|
||||
assignment_index);
|
||||
}
|
||||
LpExtend(..) => {
|
||||
bug!("var assignment for non var path");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn each_base_path<F>(&self, index: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
let mut p = index;
|
||||
while p != InvalidMovePathIndex {
|
||||
if !f(p) {
|
||||
return false;
|
||||
}
|
||||
p = self.path_parent(p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME(#19596) This is a workaround, but there should be better way to do this
|
||||
fn each_extending_path_<F>(&self, index: MovePathIndex, f: &mut F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
if !(*f)(index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut p = self.path_first_child(index);
|
||||
while p != InvalidMovePathIndex {
|
||||
if !self.each_extending_path_(p, f) {
|
||||
return false;
|
||||
}
|
||||
p = self.path_next_sibling(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn each_extending_path<F>(&self, index: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
self.each_extending_path_(index, &mut f)
|
||||
}
|
||||
|
||||
fn each_applicable_move<F>(&self, index0: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MoveIndex) -> 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: hir::ItemLocalId,
|
||||
kill_kind: KillFrom,
|
||||
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| {
|
||||
debug!("kill_moves add_kill {:?} kill_id={:?} move_index={}",
|
||||
kill_kind, kill_id, move_index.get());
|
||||
dfcx_moves.add_kill(kill_kind, kill_id, move_index.get());
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FlowedMoveData<'tcx> {
|
||||
pub fn new(
|
||||
move_data: MoveData<'tcx>,
|
||||
bccx: &BorrowckCtxt<'_, 'tcx>,
|
||||
cfg: &cfg::CFG,
|
||||
body: &hir::Body,
|
||||
) -> FlowedMoveData<'tcx> {
|
||||
let tcx = bccx.tcx;
|
||||
|
||||
let mut dfcx_moves =
|
||||
DataFlowContext::new(tcx,
|
||||
"flowed_move_data_moves",
|
||||
Some(body),
|
||||
cfg,
|
||||
MoveDataFlowOperator,
|
||||
move_data.moves.borrow().len());
|
||||
let mut dfcx_assign =
|
||||
DataFlowContext::new(tcx,
|
||||
"flowed_move_data_assigns",
|
||||
Some(body),
|
||||
cfg,
|
||||
AssignDataFlowOperator,
|
||||
move_data.var_assignments.borrow().len());
|
||||
|
||||
move_data.add_gen_kills(bccx,
|
||||
&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,
|
||||
dfcx_moves,
|
||||
dfcx_assign,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_move_path(&self, id: hir::ItemLocalId, loan_path: &Rc<LoanPath<'tcx>>) -> bool {
|
||||
//! Returns the kind of a move of `loan_path` by `id`, if one exists.
|
||||
|
||||
let mut ret = false;
|
||||
if let Some(loan_path_index) = self.move_data.path_map.borrow().get(&*loan_path) {
|
||||
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 = true;
|
||||
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<F>(&self,
|
||||
id: hir::ItemLocalId,
|
||||
loan_path: &Rc<LoanPath<'tcx>>,
|
||||
mut f: F)
|
||||
-> bool where
|
||||
F: FnMut(&Move, &LoanPath<'tcx>) -> 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 {
|
||||
if let Some(loan_path_index) = opt_loan_path_index {
|
||||
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; }
|
||||
}
|
||||
}
|
||||
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<F>(&self,
|
||||
id: hir::ItemLocalId,
|
||||
loan_path: &Rc<LoanPath<'tcx>>,
|
||||
mut f: F)
|
||||
-> bool where
|
||||
F: FnMut(&Assignment) -> 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: usize, pred: usize) -> usize {
|
||||
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: usize, pred: usize) -> usize {
|
||||
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
|
||||
}
|
||||
}
|
@ -1,545 +0,0 @@
|
||||
use crate::cfg::*;
|
||||
|
||||
use rustc::hir::{self, PatKind};
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::hir::ptr::P;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
|
||||
use rustc_data_structures::graph::implementation as graph;
|
||||
|
||||
struct CFGBuilder<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
owner_def_id: DefId,
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
graph: CFGGraph,
|
||||
fn_exit: CFGIndex,
|
||||
loop_scopes: Vec<LoopScope>,
|
||||
breakable_block_scopes: Vec<BlockScope>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct BlockScope {
|
||||
block_expr_id: hir::ItemLocalId, // ID of breakable block expr node
|
||||
break_index: CFGIndex, // where to go on `break`
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct LoopScope {
|
||||
loop_id: hir::ItemLocalId, // ID of `loop`/`while` node
|
||||
continue_index: CFGIndex, // where to go on a `loop`
|
||||
break_index: CFGIndex, // where to go on a `break`
|
||||
}
|
||||
|
||||
pub(super) fn construct(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG {
|
||||
let mut graph = graph::Graph::new();
|
||||
let entry = graph.add_node(CFGNodeData::Entry);
|
||||
|
||||
// `fn_exit` is target of return exprs, which lies somewhere
|
||||
// outside input `body`. (Distinguishing `fn_exit` and `body_exit`
|
||||
// also resolves chicken-and-egg problem that arises if you try to
|
||||
// have return exprs jump to `body_exit` during construction.)
|
||||
let fn_exit = graph.add_node(CFGNodeData::Exit);
|
||||
let body_exit;
|
||||
|
||||
// Find the tables for this body.
|
||||
let owner_def_id = tcx.hir().body_owner_def_id(body.id());
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
|
||||
let mut cfg_builder = CFGBuilder {
|
||||
tcx,
|
||||
owner_def_id,
|
||||
tables,
|
||||
graph,
|
||||
fn_exit,
|
||||
loop_scopes: Vec::new(),
|
||||
breakable_block_scopes: Vec::new(),
|
||||
};
|
||||
body_exit = cfg_builder.expr(&body.value, entry);
|
||||
cfg_builder.add_contained_edge(body_exit, fn_exit);
|
||||
let CFGBuilder { graph, .. } = cfg_builder;
|
||||
CFG {
|
||||
owner_def_id,
|
||||
graph,
|
||||
entry,
|
||||
exit: fn_exit,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
||||
fn block(&mut self, blk: &hir::Block, pred: CFGIndex) -> CFGIndex {
|
||||
if blk.targeted_by_break {
|
||||
let expr_exit = self.add_ast_node(blk.hir_id.local_id, &[]);
|
||||
|
||||
self.breakable_block_scopes.push(BlockScope {
|
||||
block_expr_id: blk.hir_id.local_id,
|
||||
break_index: expr_exit,
|
||||
});
|
||||
|
||||
let mut stmts_exit = pred;
|
||||
for stmt in &blk.stmts {
|
||||
stmts_exit = self.stmt(stmt, stmts_exit);
|
||||
}
|
||||
let blk_expr_exit = self.opt_expr(&blk.expr, stmts_exit);
|
||||
self.add_contained_edge(blk_expr_exit, expr_exit);
|
||||
|
||||
self.breakable_block_scopes.pop();
|
||||
|
||||
expr_exit
|
||||
} else {
|
||||
let mut stmts_exit = pred;
|
||||
for stmt in &blk.stmts {
|
||||
stmts_exit = self.stmt(stmt, stmts_exit);
|
||||
}
|
||||
|
||||
let expr_exit = self.opt_expr(&blk.expr, stmts_exit);
|
||||
|
||||
self.add_ast_node(blk.hir_id.local_id, &[expr_exit])
|
||||
}
|
||||
}
|
||||
|
||||
fn stmt(&mut self, stmt: &hir::Stmt, pred: CFGIndex) -> CFGIndex {
|
||||
let exit = match stmt.kind {
|
||||
hir::StmtKind::Local(ref local) => {
|
||||
let init_exit = self.opt_expr(&local.init, pred);
|
||||
self.pat(&local.pat, init_exit)
|
||||
}
|
||||
hir::StmtKind::Item(_) => pred,
|
||||
hir::StmtKind::Expr(ref expr) |
|
||||
hir::StmtKind::Semi(ref expr) => {
|
||||
self.expr(&expr, pred)
|
||||
}
|
||||
};
|
||||
self.add_ast_node(stmt.hir_id.local_id, &[exit])
|
||||
}
|
||||
|
||||
fn pat(&mut self, pat: &hir::Pat, pred: CFGIndex) -> CFGIndex {
|
||||
match pat.kind {
|
||||
PatKind::Binding(.., None) |
|
||||
PatKind::Path(_) |
|
||||
PatKind::Lit(..) |
|
||||
PatKind::Range(..) |
|
||||
PatKind::Wild => self.add_ast_node(pat.hir_id.local_id, &[pred]),
|
||||
|
||||
PatKind::Box(ref subpat) |
|
||||
PatKind::Ref(ref subpat, _) |
|
||||
PatKind::Binding(.., Some(ref subpat)) => {
|
||||
let subpat_exit = self.pat(&subpat, pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[subpat_exit])
|
||||
}
|
||||
|
||||
PatKind::TupleStruct(_, ref subpats, _) |
|
||||
PatKind::Tuple(ref subpats, _) => {
|
||||
let pats_exit = self.pats_all(subpats.iter(), pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[pats_exit])
|
||||
}
|
||||
|
||||
PatKind::Struct(_, ref subpats, _) => {
|
||||
let pats_exit = self.pats_all(subpats.iter().map(|f| &f.pat), pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[pats_exit])
|
||||
}
|
||||
|
||||
PatKind::Or(ref pats) => {
|
||||
let branches: Vec<_> = pats.iter().map(|p| self.pat(p, pred)).collect();
|
||||
self.add_ast_node(pat.hir_id.local_id, &branches)
|
||||
}
|
||||
|
||||
PatKind::Slice(ref pre, ref vec, ref post) => {
|
||||
let pre_exit = self.pats_all(pre.iter(), pred);
|
||||
let vec_exit = self.pats_all(vec.iter(), pre_exit);
|
||||
let post_exit = self.pats_all(post.iter(), vec_exit);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[post_exit])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles case where all of the patterns must match.
|
||||
fn pats_all<'b, I: Iterator<Item = &'b P<hir::Pat>>>(
|
||||
&mut self,
|
||||
pats: I,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
pats.fold(pred, |pred, pat| self.pat(&pat, pred))
|
||||
}
|
||||
|
||||
fn expr(&mut self, expr: &hir::Expr, pred: CFGIndex) -> CFGIndex {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Block(ref blk, _) => {
|
||||
let blk_exit = self.block(&blk, pred);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[blk_exit])
|
||||
}
|
||||
|
||||
hir::ExprKind::Loop(ref body, _, _) => {
|
||||
//
|
||||
// [pred]
|
||||
// |
|
||||
// v 1
|
||||
// [loopback] <---+
|
||||
// | 4 |
|
||||
// v 3 |
|
||||
// [body] ------+
|
||||
//
|
||||
// [expr] 2
|
||||
//
|
||||
// Note that `break` and `loop` statements
|
||||
// may cause additional edges.
|
||||
|
||||
let loopback = self.add_dummy_node(&[pred]); // 1
|
||||
let expr_exit = self.add_ast_node(expr.hir_id.local_id, &[]); // 2
|
||||
self.loop_scopes.push(LoopScope {
|
||||
loop_id: expr.hir_id.local_id,
|
||||
continue_index: loopback,
|
||||
break_index: expr_exit,
|
||||
});
|
||||
let body_exit = self.block(&body, loopback); // 3
|
||||
self.add_contained_edge(body_exit, loopback); // 4
|
||||
self.loop_scopes.pop();
|
||||
expr_exit
|
||||
}
|
||||
|
||||
hir::ExprKind::Match(ref discr, ref arms, _) => {
|
||||
self.match_(expr.hir_id.local_id, &discr, &arms, pred)
|
||||
}
|
||||
|
||||
hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => {
|
||||
//
|
||||
// [pred]
|
||||
// |
|
||||
// v 1
|
||||
// [l]
|
||||
// |
|
||||
// / \
|
||||
// / \
|
||||
// v 2 *
|
||||
// [r] |
|
||||
// | |
|
||||
// v 3 v 4
|
||||
// [..exit..]
|
||||
//
|
||||
let l_exit = self.expr(&l, pred); // 1
|
||||
let r_exit = self.expr(&r, l_exit); // 2
|
||||
self.add_ast_node(expr.hir_id.local_id, &[l_exit, r_exit]) // 3,4
|
||||
}
|
||||
|
||||
hir::ExprKind::Ret(ref v) => {
|
||||
let v_exit = self.opt_expr(v, pred);
|
||||
let b = self.add_ast_node(expr.hir_id.local_id, &[v_exit]);
|
||||
self.add_returning_edge(expr, b);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Break(destination, ref opt_expr) => {
|
||||
let v = self.opt_expr(opt_expr, pred);
|
||||
let (target_scope, break_dest) =
|
||||
self.find_scope_edge(expr, destination, ScopeCfKind::Break);
|
||||
let b = self.add_ast_node(expr.hir_id.local_id, &[v]);
|
||||
self.add_exiting_edge(expr, b, target_scope, break_dest);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Continue(destination) => {
|
||||
let (target_scope, cont_dest) =
|
||||
self.find_scope_edge(expr, destination, ScopeCfKind::Continue);
|
||||
let a = self.add_ast_node(expr.hir_id.local_id, &[pred]);
|
||||
self.add_exiting_edge(expr, a, target_scope, cont_dest);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Array(ref elems) => {
|
||||
self.straightline(expr, pred, elems.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Call(ref func, ref args) => {
|
||||
self.call(expr, pred, &func, args.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::MethodCall(.., ref args) => {
|
||||
self.call(expr, pred, &args[0], args[1..].iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Index(ref l, ref r) |
|
||||
hir::ExprKind::Binary(_, ref l, ref r) if self.tables.is_method_call(expr) => {
|
||||
self.call(expr, pred, &l, Some(&**r).into_iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::Unary(_, ref e) if self.tables.is_method_call(expr) => {
|
||||
self.call(expr, pred, &e, None::<hir::Expr>.iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::Tup(ref exprs) => {
|
||||
self.straightline(expr, pred, exprs.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Struct(_, ref fields, ref base) => {
|
||||
let field_cfg = self.straightline(expr, pred, fields.iter().map(|f| &*f.expr));
|
||||
self.opt_expr(base, field_cfg)
|
||||
}
|
||||
|
||||
hir::ExprKind::Assign(ref l, ref r) |
|
||||
hir::ExprKind::AssignOp(_, ref l, ref r) => {
|
||||
self.straightline(expr, pred, [r, l].iter().map(|&e| &**e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Index(ref l, ref r) |
|
||||
hir::ExprKind::Binary(_, ref l, ref r) => { // N.B., && and || handled earlier
|
||||
self.straightline(expr, pred, [l, r].iter().map(|&e| &**e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Box(ref e) |
|
||||
hir::ExprKind::AddrOf(_, ref e) |
|
||||
hir::ExprKind::Cast(ref e, _) |
|
||||
hir::ExprKind::Type(ref e, _) |
|
||||
hir::ExprKind::DropTemps(ref e) |
|
||||
hir::ExprKind::Unary(_, ref e) |
|
||||
hir::ExprKind::Field(ref e, _) |
|
||||
hir::ExprKind::Yield(ref e, _) |
|
||||
hir::ExprKind::Repeat(ref e, _) => {
|
||||
self.straightline(expr, pred, Some(&**e).into_iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::InlineAsm(_, ref outputs, ref inputs) => {
|
||||
let post_outputs = self.exprs(outputs.iter().map(|e| &*e), pred);
|
||||
let post_inputs = self.exprs(inputs.iter().map(|e| &*e), post_outputs);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[post_inputs])
|
||||
}
|
||||
|
||||
hir::ExprKind::Closure(..) |
|
||||
hir::ExprKind::Lit(..) |
|
||||
hir::ExprKind::Path(_) |
|
||||
hir::ExprKind::Err => {
|
||||
self.straightline(expr, pred, None::<hir::Expr>.iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
call_expr: &hir::Expr,
|
||||
pred: CFGIndex,
|
||||
func_or_rcvr: &hir::Expr,
|
||||
args: I,
|
||||
) -> CFGIndex {
|
||||
let func_or_rcvr_exit = self.expr(func_or_rcvr, pred);
|
||||
let ret = self.straightline(call_expr, func_or_rcvr_exit, args);
|
||||
let m = self.tcx.hir().get_module_parent(call_expr.hir_id);
|
||||
if self.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(call_expr)) {
|
||||
self.add_unreachable_node()
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs graph for `exprs` evaluated in order.
|
||||
fn exprs<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
exprs: I,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
exprs.fold(pred, |p, e| self.expr(e, p))
|
||||
}
|
||||
|
||||
/// Constructs graph for `opt_expr` evaluated, if `Some`.
|
||||
fn opt_expr(
|
||||
&mut self,
|
||||
opt_expr: &Option<P<hir::Expr>>,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
opt_expr.iter().fold(pred, |p, e| self.expr(&e, p))
|
||||
}
|
||||
|
||||
/// Handles case of an expression that evaluates `subexprs` in order.
|
||||
fn straightline<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
expr: &hir::Expr,
|
||||
pred: CFGIndex,
|
||||
subexprs: I,
|
||||
) -> CFGIndex {
|
||||
let subexprs_exit = self.exprs(subexprs, pred);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[subexprs_exit])
|
||||
}
|
||||
|
||||
fn match_(&mut self, id: hir::ItemLocalId, discr: &hir::Expr,
|
||||
arms: &[hir::Arm], pred: CFGIndex) -> CFGIndex {
|
||||
// The CFG for match expressions is quite complex, so no ASCII
|
||||
// art for it (yet).
|
||||
//
|
||||
// The CFG generated below matches roughly what MIR contains.
|
||||
// Each pattern and guard is visited in parallel, with
|
||||
// arms containing multiple patterns generating multiple nodes
|
||||
// for the same guard expression. The guard expressions chain
|
||||
// into each other from top to bottom, with a specific
|
||||
// exception to allow some additional valid programs
|
||||
// (explained below). MIR differs slightly in that the
|
||||
// pattern matching may continue after a guard but the visible
|
||||
// behaviour should be the same.
|
||||
//
|
||||
// What is going on is explained in further comments.
|
||||
|
||||
// Visit the discriminant expression.
|
||||
let discr_exit = self.expr(discr, pred);
|
||||
|
||||
// Add a node for the exit of the match expression as a whole.
|
||||
let expr_exit = self.add_ast_node(id, &[]);
|
||||
|
||||
// Keep track of the previous guard expressions.
|
||||
let mut prev_guard = None;
|
||||
let match_scope = region::Scope { id, data: region::ScopeData::Node };
|
||||
|
||||
for arm in arms {
|
||||
// Add an exit node for when we've visited all the
|
||||
// patterns and the guard (if there is one) in the arm.
|
||||
let bindings_exit = self.add_dummy_node(&[]);
|
||||
|
||||
for pat in arm.top_pats_hack() {
|
||||
// Visit the pattern, coming from the discriminant exit
|
||||
let mut pat_exit = self.pat(&pat, discr_exit);
|
||||
|
||||
// If there is a guard expression, handle it here.
|
||||
if let Some(ref guard) = arm.guard {
|
||||
// Add a dummy node for the previous guard
|
||||
// expression to target.
|
||||
let guard_start = self.add_dummy_node(&[pat_exit]);
|
||||
// Visit the guard expression.
|
||||
let guard_exit = match guard {
|
||||
hir::Guard::If(ref e) => (&**e, self.expr(e, guard_start)),
|
||||
};
|
||||
// #47295: We used to have very special case code
|
||||
// here for when a pair of arms are both formed
|
||||
// solely from constants, and if so, not add these
|
||||
// edges. But this was not actually sound without
|
||||
// other constraints that we stopped enforcing at
|
||||
// some point.
|
||||
if let Some((prev_guard, prev_index)) = prev_guard.take() {
|
||||
self.add_exiting_edge(prev_guard, prev_index, match_scope, guard_start);
|
||||
}
|
||||
|
||||
// Push the guard onto the list of previous guards.
|
||||
prev_guard = Some(guard_exit);
|
||||
|
||||
// Update the exit node for the pattern.
|
||||
pat_exit = guard_exit.1;
|
||||
}
|
||||
|
||||
// Add an edge from the exit of this pattern to the exit of the arm.
|
||||
self.add_contained_edge(pat_exit, bindings_exit);
|
||||
}
|
||||
|
||||
// Visit the body of this arm.
|
||||
let body_exit = self.expr(&arm.body, bindings_exit);
|
||||
|
||||
let arm_exit = self.add_ast_node(arm.hir_id.local_id, &[body_exit]);
|
||||
|
||||
// Link the body to the exit of the expression.
|
||||
self.add_contained_edge(arm_exit, expr_exit);
|
||||
}
|
||||
|
||||
expr_exit
|
||||
}
|
||||
|
||||
fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::Dummy, preds)
|
||||
}
|
||||
|
||||
fn add_ast_node(&mut self, id: hir::ItemLocalId, preds: &[CFGIndex]) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::AST(id), preds)
|
||||
}
|
||||
|
||||
fn add_unreachable_node(&mut self) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::Unreachable, &[])
|
||||
}
|
||||
|
||||
fn add_node(&mut self, data: CFGNodeData, preds: &[CFGIndex]) -> CFGIndex {
|
||||
let node = self.graph.add_node(data);
|
||||
for &pred in preds {
|
||||
self.add_contained_edge(pred, node);
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
fn add_contained_edge(
|
||||
&mut self,
|
||||
source: CFGIndex,
|
||||
target: CFGIndex,
|
||||
) {
|
||||
let data = CFGEdgeData {exiting_scopes: vec![] };
|
||||
self.graph.add_edge(source, target, data);
|
||||
}
|
||||
|
||||
fn add_exiting_edge(
|
||||
&mut self,
|
||||
from_expr: &hir::Expr,
|
||||
from_index: CFGIndex,
|
||||
target_scope: region::Scope,
|
||||
to_index: CFGIndex,
|
||||
) {
|
||||
let mut data = CFGEdgeData { exiting_scopes: vec![] };
|
||||
let mut scope = region::Scope {
|
||||
id: from_expr.hir_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
let region_scope_tree = self.tcx.region_scope_tree(self.owner_def_id);
|
||||
while scope != target_scope {
|
||||
data.exiting_scopes.push(scope.item_local_id());
|
||||
scope = region_scope_tree.encl_scope(scope);
|
||||
}
|
||||
self.graph.add_edge(from_index, to_index, data);
|
||||
}
|
||||
|
||||
fn add_returning_edge(
|
||||
&mut self,
|
||||
_from_expr: &hir::Expr,
|
||||
from_index: CFGIndex,
|
||||
) {
|
||||
let data = CFGEdgeData {
|
||||
exiting_scopes: self.loop_scopes.iter()
|
||||
.rev()
|
||||
.map(|&LoopScope { loop_id: id, .. }| id)
|
||||
.collect()
|
||||
};
|
||||
self.graph.add_edge(from_index, self.fn_exit, data);
|
||||
}
|
||||
|
||||
fn find_scope_edge(
|
||||
&self,
|
||||
expr: &hir::Expr,
|
||||
destination: hir::Destination,
|
||||
scope_cf_kind: ScopeCfKind,
|
||||
) -> (region::Scope, CFGIndex) {
|
||||
match destination.target_id {
|
||||
Ok(loop_id) => {
|
||||
for b in &self.breakable_block_scopes {
|
||||
if b.block_expr_id == loop_id.local_id {
|
||||
let scope = region::Scope {
|
||||
id: loop_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
return (scope, match scope_cf_kind {
|
||||
ScopeCfKind::Break => b.break_index,
|
||||
ScopeCfKind::Continue => bug!("can't continue to block"),
|
||||
});
|
||||
}
|
||||
}
|
||||
for l in &self.loop_scopes {
|
||||
if l.loop_id == loop_id.local_id {
|
||||
let scope = region::Scope {
|
||||
id: loop_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
return (scope, match scope_cf_kind {
|
||||
ScopeCfKind::Break => l.break_index,
|
||||
ScopeCfKind::Continue => l.continue_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
span_bug!(expr.span, "no scope for ID {}", loop_id);
|
||||
}
|
||||
Err(err) => span_bug!(expr.span, "scope error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
enum ScopeCfKind {
|
||||
Break,
|
||||
Continue,
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/// This module provides linkage between `rustc::middle::graph` and
|
||||
/// libgraphviz traits.
|
||||
|
||||
use crate::cfg;
|
||||
use rustc::hir;
|
||||
use rustc::ty::TyCtxt;
|
||||
|
||||
pub(crate) type Node<'a> = (cfg::CFGIndex, &'a cfg::CFGNode);
|
||||
pub(crate) type Edge<'a> = &'a cfg::CFGEdge;
|
||||
|
||||
pub struct LabelledCFG<'a, 'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
pub cfg: &'a cfg::CFG,
|
||||
pub name: String,
|
||||
/// `labelled_edges` controls whether we emit labels on the edges.
|
||||
pub labelled_edges: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LabelledCFG<'a, 'tcx> {
|
||||
fn local_id_to_string(&self, local_id: hir::ItemLocalId) -> String {
|
||||
assert!(self.cfg.owner_def_id.is_local());
|
||||
let hir_id = hir::HirId {
|
||||
owner: self.tcx.hir().def_index_to_hir_id(self.cfg.owner_def_id.index).owner,
|
||||
local_id
|
||||
};
|
||||
let s = self.tcx.hir().node_to_string(hir_id);
|
||||
|
||||
// Replacing newlines with `\\l` causes each line to be left-aligned,
|
||||
// improving presentation of (long) pretty-printed expressions.
|
||||
if s.contains("\n") {
|
||||
let mut s = s.replace("\n", "\\l");
|
||||
// Apparently left-alignment applies to the line that precedes
|
||||
// `\l`, not the line that follows; so, add `\l` at end of string
|
||||
// if not already present, ensuring last line gets left-aligned
|
||||
// as well.
|
||||
let mut last_two: Vec<_> =
|
||||
s.chars().rev().take(2).collect();
|
||||
last_two.reverse();
|
||||
if last_two != ['\\', 'l'] {
|
||||
s.push_str("\\l");
|
||||
}
|
||||
s
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'hir> dot::Labeller<'a> for LabelledCFG<'a, 'hir> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(&self.name[..]).unwrap() }
|
||||
|
||||
fn node_id(&'a self, &(i,_): &Node<'a>) -> dot::Id<'a> {
|
||||
dot::Id::new(format!("N{}", i.node_id())).unwrap()
|
||||
}
|
||||
|
||||
fn node_label(&'a self, &(i, n): &Node<'a>) -> dot::LabelText<'a> {
|
||||
if i == self.cfg.entry {
|
||||
dot::LabelText::LabelStr("entry".into())
|
||||
} else if i == self.cfg.exit {
|
||||
dot::LabelText::LabelStr("exit".into())
|
||||
} else if n.data.id() == hir::DUMMY_ITEM_LOCAL_ID {
|
||||
dot::LabelText::LabelStr("(dummy_node)".into())
|
||||
} else {
|
||||
let s = self.local_id_to_string(n.data.id());
|
||||
dot::LabelText::EscStr(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_label(&self, e: &Edge<'a>) -> dot::LabelText<'a> {
|
||||
let mut label = String::new();
|
||||
if !self.labelled_edges {
|
||||
return dot::LabelText::EscStr(label.into());
|
||||
}
|
||||
let mut put_one = false;
|
||||
for (i, &id) in e.data.exiting_scopes.iter().enumerate() {
|
||||
if put_one {
|
||||
label.push_str(",\\l");
|
||||
} else {
|
||||
put_one = true;
|
||||
}
|
||||
let s = self.local_id_to_string(id);
|
||||
label.push_str(&format!("exiting scope_{} {}",
|
||||
i,
|
||||
&s[..]));
|
||||
}
|
||||
dot::LabelText::EscStr(label.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::GraphWalk<'a> for &'a cfg::CFG {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> {
|
||||
let v: Vec<_> = self.graph.enumerated_nodes().collect();
|
||||
v.into()
|
||||
}
|
||||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
|
||||
self.graph.all_edges().iter().collect()
|
||||
}
|
||||
fn source(&'a self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.source();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
fn target(&'a self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.target();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'hir> dot::GraphWalk<'a> for LabelledCFG<'a, 'hir> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.cfg.nodes() }
|
||||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.cfg.edges() }
|
||||
fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) }
|
||||
fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) }
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
//! Module that constructs a control-flow graph representing an item.
|
||||
//! Uses `Graph` as the underlying representation.
|
||||
|
||||
use rustc_data_structures::graph::implementation as graph;
|
||||
use rustc::ty::TyCtxt;
|
||||
use rustc::hir;
|
||||
use rustc::hir::def_id::DefId;
|
||||
|
||||
mod construct;
|
||||
pub mod graphviz;
|
||||
|
||||
pub struct CFG {
|
||||
owner_def_id: DefId,
|
||||
pub(crate) graph: CFGGraph,
|
||||
pub(crate) entry: CFGIndex,
|
||||
exit: CFGIndex,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum CFGNodeData {
|
||||
AST(hir::ItemLocalId),
|
||||
Entry,
|
||||
Exit,
|
||||
Dummy,
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl CFGNodeData {
|
||||
pub(crate) fn id(&self) -> hir::ItemLocalId {
|
||||
if let CFGNodeData::AST(id) = *self {
|
||||
id
|
||||
} else {
|
||||
hir::DUMMY_ITEM_LOCAL_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CFGEdgeData {
|
||||
pub(crate) exiting_scopes: Vec<hir::ItemLocalId>
|
||||
}
|
||||
|
||||
pub(crate) type CFGIndex = graph::NodeIndex;
|
||||
|
||||
pub(crate) type CFGGraph = graph::Graph<CFGNodeData, CFGEdgeData>;
|
||||
|
||||
pub(crate) type CFGNode = graph::Node<CFGNodeData>;
|
||||
|
||||
pub(crate) type CFGEdge = graph::Edge<CFGEdgeData>;
|
||||
|
||||
impl CFG {
|
||||
pub fn new(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG {
|
||||
construct::construct(tcx, body)
|
||||
}
|
||||
}
|
@ -1,672 +0,0 @@
|
||||
//! A module for propagating forward dataflow information. The analysis
|
||||
//! assumes that the items to be propagated can be represented as bits
|
||||
//! and thus uses bitvectors. Your job is simply to specify the so-called
|
||||
//! GEN and KILL bits for each expression.
|
||||
|
||||
use crate::cfg::{self, CFGIndex};
|
||||
use std::mem;
|
||||
use std::usize;
|
||||
use log::debug;
|
||||
|
||||
use rustc_data_structures::graph::implementation::OUTGOING;
|
||||
|
||||
use rustc::util::nodemap::FxHashMap;
|
||||
use rustc::hir;
|
||||
use rustc::hir::intravisit;
|
||||
use rustc::hir::print as pprust;
|
||||
use rustc::ty::TyCtxt;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EntryOrExit {
|
||||
Entry,
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataFlowContext<'tcx, O> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
/// a name for the analysis using this dataflow instance
|
||||
analysis_name: &'static str,
|
||||
|
||||
/// the data flow operator
|
||||
oper: O,
|
||||
|
||||
/// number of bits to propagate per id
|
||||
bits_per_id: usize,
|
||||
|
||||
/// number of words we will use to store bits_per_id.
|
||||
/// equal to bits_per_id/usize::BITS rounded up.
|
||||
words_per_id: usize,
|
||||
|
||||
// mapping from node to cfg node index
|
||||
// FIXME (#6298): Shouldn't this go with CFG?
|
||||
local_id_to_index: FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
|
||||
// Bit sets per cfg node. The following three fields (`gens`, `kills`,
|
||||
// and `on_entry`) all have the same structure. For each id in
|
||||
// `id_range`, there is a range of words equal to `words_per_id`.
|
||||
// So, to access the bits for any given id, you take a slice of
|
||||
// the full vector (see the method `compute_id_range()`).
|
||||
/// bits generated as we exit the cfg node. Updated by `add_gen()`.
|
||||
gens: Vec<usize>,
|
||||
|
||||
/// bits killed as we exit the cfg node, or non-locally jump over
|
||||
/// it. Updated by `add_kill(KillFrom::ScopeEnd)`.
|
||||
scope_kills: Vec<usize>,
|
||||
|
||||
/// bits killed as we exit the cfg node directly; if it is jumped
|
||||
/// over, e.g., via `break`, the kills are not reflected in the
|
||||
/// jump's effects. Updated by `add_kill(KillFrom::Execution)`.
|
||||
action_kills: Vec<usize>,
|
||||
|
||||
/// bits that are valid on entry to the cfg node. Updated by
|
||||
/// `propagate()`.
|
||||
on_entry: Vec<usize>,
|
||||
}
|
||||
|
||||
pub trait BitwiseOperator {
|
||||
/// Joins two predecessor bits together, typically either `|` or `&`
|
||||
fn join(&self, succ: usize, pred: usize) -> usize;
|
||||
}
|
||||
|
||||
/// Parameterization for the precise form of data flow that is used.
|
||||
pub trait DataFlowOperator : BitwiseOperator {
|
||||
/// Specifies the initial value for each bit in the `on_entry` set
|
||||
fn initial_value(&self) -> bool;
|
||||
}
|
||||
|
||||
struct PropagationContext<'a, 'tcx, O> {
|
||||
dfcx: &'a mut DataFlowContext<'tcx, O>,
|
||||
changed: bool,
|
||||
}
|
||||
|
||||
fn get_cfg_indices(id: hir::ItemLocalId,
|
||||
index: &FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>)
|
||||
-> &[CFGIndex] {
|
||||
index.get(&id).map_or(&[], |v| &v[..])
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> {
|
||||
fn has_bitset_for_local_id(&self, n: hir::ItemLocalId) -> bool {
|
||||
assert!(n != hir::DUMMY_ITEM_LOCAL_ID);
|
||||
self.local_id_to_index.contains_key(&n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> pprust::PpAnn for DataFlowContext<'tcx, O> {
|
||||
fn nested(&self, state: &mut pprust::State<'_>, nested: pprust::Nested) {
|
||||
pprust::PpAnn::nested(self.tcx.hir(), state, nested)
|
||||
}
|
||||
fn pre(&self,
|
||||
ps: &mut pprust::State<'_>,
|
||||
node: pprust::AnnNode<'_>) {
|
||||
let id = match node {
|
||||
pprust::AnnNode::Name(_) => return,
|
||||
pprust::AnnNode::Expr(expr) => expr.hir_id.local_id,
|
||||
pprust::AnnNode::Block(blk) => blk.hir_id.local_id,
|
||||
pprust::AnnNode::Item(_) |
|
||||
pprust::AnnNode::SubItem(_) => return,
|
||||
pprust::AnnNode::Pat(pat) => pat.hir_id.local_id,
|
||||
pprust::AnnNode::Arm(arm) => arm.hir_id.local_id,
|
||||
};
|
||||
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(self.bits_per_id > 0);
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let on_entry = &self.on_entry[start.. end];
|
||||
let entry_str = bits_to_string(on_entry);
|
||||
|
||||
let gens = &self.gens[start.. end];
|
||||
let gens_str = if gens.iter().any(|&u| u != 0) {
|
||||
format!(" gen: {}", bits_to_string(gens))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let action_kills = &self.action_kills[start .. end];
|
||||
let action_kills_str = if action_kills.iter().any(|&u| u != 0) {
|
||||
format!(" action_kill: {}", bits_to_string(action_kills))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let scope_kills = &self.scope_kills[start .. end];
|
||||
let scope_kills_str = if scope_kills.iter().any(|&u| u != 0) {
|
||||
format!(" scope_kill: {}", bits_to_string(scope_kills))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
ps.synth_comment(
|
||||
format!("id {}: {}{}{}{}", id.as_usize(), entry_str,
|
||||
gens_str, action_kills_str, scope_kills_str));
|
||||
ps.s.space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_local_id_to_index(body: Option<&hir::Body>,
|
||||
cfg: &cfg::CFG)
|
||||
-> FxHashMap<hir::ItemLocalId, Vec<CFGIndex>> {
|
||||
let mut index = FxHashMap::default();
|
||||
|
||||
// FIXME(#15020) Would it be better to fold formals from decl
|
||||
// into cfg itself? i.e., introduce a fn-based flow-graph in
|
||||
// addition to the current block-based flow-graph, rather than
|
||||
// have to put traversals like this here?
|
||||
if let Some(body) = body {
|
||||
add_entries_from_fn_body(&mut index, body, cfg.entry);
|
||||
}
|
||||
|
||||
cfg.graph.each_node(|node_idx, node| {
|
||||
if let cfg::CFGNodeData::AST(id) = node.data {
|
||||
index.entry(id).or_default().push(node_idx);
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
return index;
|
||||
|
||||
/// Adds mappings from the ast nodes for the formal bindings to
|
||||
/// the entry-node in the graph.
|
||||
fn add_entries_from_fn_body(index: &mut FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
body: &hir::Body,
|
||||
entry: CFGIndex) {
|
||||
use rustc::hir::intravisit::Visitor;
|
||||
|
||||
struct Formals<'a> {
|
||||
entry: CFGIndex,
|
||||
index: &'a mut FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
}
|
||||
let mut formals = Formals { entry: entry, index: index };
|
||||
for param in &body.params {
|
||||
formals.visit_pat(¶m.pat);
|
||||
}
|
||||
impl<'a, 'v> Visitor<'v> for Formals<'a> {
|
||||
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'v> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &hir::Pat) {
|
||||
self.index.entry(p.hir_id.local_id).or_default().push(self.entry);
|
||||
intravisit::walk_pat(self, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flag used by `add_kill` to indicate whether the provided kill
|
||||
/// takes effect only when control flows directly through the node in
|
||||
/// question, or if the kill's effect is associated with any
|
||||
/// control-flow directly through or indirectly over the node.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum KillFrom {
|
||||
/// A `ScopeEnd` kill is one that takes effect when any control
|
||||
/// flow goes over the node. A kill associated with the end of the
|
||||
/// scope of a variable declaration `let x;` is an example of a
|
||||
/// `ScopeEnd` kill.
|
||||
ScopeEnd,
|
||||
|
||||
/// An `Execution` kill is one that takes effect only when control
|
||||
/// flow goes through the node to completion. A kill associated
|
||||
/// with an assignment statement `x = expr;` is an example of an
|
||||
/// `Execution` kill.
|
||||
Execution,
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> {
|
||||
pub fn new(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
analysis_name: &'static str,
|
||||
body: Option<&hir::Body>,
|
||||
cfg: &cfg::CFG,
|
||||
oper: O,
|
||||
bits_per_id: usize,
|
||||
) -> DataFlowContext<'tcx, O> {
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
let words_per_id = (bits_per_id + usize_bits - 1) / usize_bits;
|
||||
let num_nodes = cfg.graph.all_nodes().len();
|
||||
|
||||
debug!("DataFlowContext::new(analysis_name: {}, \
|
||||
bits_per_id={}, words_per_id={}) \
|
||||
num_nodes: {}",
|
||||
analysis_name, bits_per_id, words_per_id,
|
||||
num_nodes);
|
||||
|
||||
let entry = if oper.initial_value() { usize::MAX } else {0};
|
||||
|
||||
let zeroes = vec![0; num_nodes * words_per_id];
|
||||
let gens = zeroes.clone();
|
||||
let kills1 = zeroes.clone();
|
||||
let kills2 = zeroes;
|
||||
let on_entry = vec![entry; num_nodes * words_per_id];
|
||||
|
||||
let local_id_to_index = build_local_id_to_index(body, cfg);
|
||||
|
||||
DataFlowContext {
|
||||
tcx,
|
||||
analysis_name,
|
||||
words_per_id,
|
||||
local_id_to_index,
|
||||
bits_per_id,
|
||||
oper,
|
||||
gens,
|
||||
action_kills: kills1,
|
||||
scope_kills: kills2,
|
||||
on_entry,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_gen(&mut self, id: hir::ItemLocalId, bit: usize) {
|
||||
//! Indicates that `id` generates `bit`
|
||||
debug!("{} add_gen(id={:?}, bit={})",
|
||||
self.analysis_name, id, bit);
|
||||
assert!(self.local_id_to_index.contains_key(&id));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &mut self.gens[start.. end];
|
||||
set_bit(gens, bit);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_kill(&mut self, kind: KillFrom, id: hir::ItemLocalId, bit: usize) {
|
||||
//! Indicates that `id` kills `bit`
|
||||
debug!("{} add_kill(id={:?}, bit={})",
|
||||
self.analysis_name, id, bit);
|
||||
assert!(self.local_id_to_index.contains_key(&id));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let kills = match kind {
|
||||
KillFrom::Execution => &mut self.action_kills[start.. end],
|
||||
KillFrom::ScopeEnd => &mut self.scope_kills[start.. end],
|
||||
};
|
||||
set_bit(kills, bit);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_gen_kill(&self, cfgidx: CFGIndex, bits: &mut [usize]) {
|
||||
//! Applies the gen and kill sets for `cfgidx` to `bits`
|
||||
debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [before]",
|
||||
self.analysis_name, cfgidx, mut_bits_to_string(bits));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &self.gens[start.. end];
|
||||
bitwise(bits, gens, &Union);
|
||||
let kills = &self.action_kills[start.. end];
|
||||
bitwise(bits, kills, &Subtract);
|
||||
let kills = &self.scope_kills[start.. end];
|
||||
bitwise(bits, kills, &Subtract);
|
||||
|
||||
debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [after]",
|
||||
self.analysis_name, cfgidx, mut_bits_to_string(bits));
|
||||
}
|
||||
|
||||
fn compute_id_range(&self, cfgidx: CFGIndex) -> (usize, usize) {
|
||||
let n = cfgidx.node_id();
|
||||
let start = n * self.words_per_id;
|
||||
let end = start + self.words_per_id;
|
||||
|
||||
assert!(start < self.gens.len());
|
||||
assert!(end <= self.gens.len());
|
||||
assert!(self.gens.len() == self.action_kills.len());
|
||||
assert!(self.gens.len() == self.scope_kills.len());
|
||||
assert!(self.gens.len() == self.on_entry.len());
|
||||
|
||||
(start, end)
|
||||
}
|
||||
|
||||
|
||||
pub fn each_bit_on_entry<F>(&self, id: hir::ItemLocalId, mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit that is set on entry to `id`.
|
||||
//! Only useful after `propagate()` has been called.
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return true;
|
||||
}
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
if !self.each_bit_for_node(EntryOrExit::Entry, cfgidx, |i| f(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn each_bit_for_node<F>(&self, e: EntryOrExit, cfgidx: CFGIndex, f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit that is set on entry/exit to `cfgidx`.
|
||||
//! Only useful after `propagate()` has been called.
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return true;
|
||||
}
|
||||
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let on_entry = &self.on_entry[start.. end];
|
||||
let temp_bits;
|
||||
let slice = match e {
|
||||
EntryOrExit::Entry => on_entry,
|
||||
EntryOrExit::Exit => {
|
||||
let mut t = on_entry.to_vec();
|
||||
self.apply_gen_kill(cfgidx, &mut t);
|
||||
temp_bits = t;
|
||||
&temp_bits[..]
|
||||
}
|
||||
};
|
||||
debug!("{} each_bit_for_node({:?}, cfgidx={:?}) bits={}",
|
||||
self.analysis_name, e, cfgidx, bits_to_string(slice));
|
||||
self.each_bit(slice, f)
|
||||
}
|
||||
|
||||
pub fn each_gen_bit<F>(&self, id: hir::ItemLocalId, mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit in the gen set for `id`.
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return true;
|
||||
}
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &self.gens[start.. end];
|
||||
debug!("{} each_gen_bit(id={:?}, gens={})",
|
||||
self.analysis_name, id, bits_to_string(gens));
|
||||
if !self.each_bit(gens, |i| f(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn each_bit<F>(&self, words: &[usize], mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Helper for iterating over the bits in a bit set.
|
||||
//! Returns false on the first call to `f` that returns false;
|
||||
//! if all calls to `f` return true, then returns true.
|
||||
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
for (word_index, &word) in words.iter().enumerate() {
|
||||
if word != 0 {
|
||||
let base_index = word_index * usize_bits;
|
||||
for offset in 0..usize_bits {
|
||||
let bit = 1 << offset;
|
||||
if (word & bit) != 0 {
|
||||
// N.B., we round up the total number of bits
|
||||
// that we store in any given bit set so that
|
||||
// it is an even multiple of usize::BITS. This
|
||||
// means that there may be some stray bits at
|
||||
// the end that do not correspond to any
|
||||
// actual value. So before we callback, check
|
||||
// whether the bit_index is greater than the
|
||||
// actual value the user specified and stop
|
||||
// iterating if so.
|
||||
let bit_index = base_index + offset as usize;
|
||||
if bit_index >= self.bits_per_id {
|
||||
return true;
|
||||
} else if !f(bit_index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn add_kills_from_flow_exits(&mut self, cfg: &cfg::CFG) {
|
||||
//! Whenever you have a `break` or `continue` statement, flow
|
||||
//! exits through any number of enclosing scopes on its way to
|
||||
//! the new destination. This function infers the kill bits of
|
||||
//! those control operators based on the kill bits associated
|
||||
//! with those scopes.
|
||||
//!
|
||||
//! This is usually called (if it is called at all), after
|
||||
//! all add_gen and add_kill calls, but before propagate.
|
||||
|
||||
debug!("{} add_kills_from_flow_exits", self.analysis_name);
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return;
|
||||
}
|
||||
cfg.graph.each_edge(|_edge_index, edge| {
|
||||
let flow_exit = edge.source();
|
||||
let (start, end) = self.compute_id_range(flow_exit);
|
||||
let mut orig_kills = self.scope_kills[start.. end].to_vec();
|
||||
|
||||
let mut changed = false;
|
||||
for &id in &edge.data.exiting_scopes {
|
||||
let opt_cfg_idx = self.local_id_to_index.get(&id);
|
||||
match opt_cfg_idx {
|
||||
Some(indices) => {
|
||||
for &cfg_idx in indices {
|
||||
let (start, end) = self.compute_id_range(cfg_idx);
|
||||
let kills = &self.scope_kills[start.. end];
|
||||
if bitwise(&mut orig_kills, kills, &Union) {
|
||||
debug!("scope exits: scope id={:?} \
|
||||
(node={:?} of {:?}) added killset: {}",
|
||||
id, cfg_idx, indices,
|
||||
bits_to_string(kills));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} \
|
||||
no cfg_idx for exiting_scope={:?}",
|
||||
self.analysis_name, flow_exit, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
let bits = &mut self.scope_kills[start.. end];
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [before]",
|
||||
self.analysis_name, flow_exit, mut_bits_to_string(bits));
|
||||
bits.copy_from_slice(&orig_kills[..]);
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [after]",
|
||||
self.analysis_name, flow_exit, mut_bits_to_string(bits));
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// N.B. `Clone + 'static` only needed for pretty printing.
|
||||
impl<'tcx, O: DataFlowOperator + Clone + 'static> DataFlowContext<'tcx, O> {
|
||||
pub fn propagate(&mut self, cfg: &cfg::CFG, body: &hir::Body) {
|
||||
//! Performs the data flow analysis.
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Optimize the surprisingly common degenerate case.
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let words_per_id = self.words_per_id;
|
||||
let mut propcx = PropagationContext {
|
||||
dfcx: &mut *self,
|
||||
changed: true
|
||||
};
|
||||
|
||||
let nodes_po = cfg.graph.nodes_in_postorder(OUTGOING, cfg.entry);
|
||||
let mut temp = vec![0; words_per_id];
|
||||
let mut num_passes = 0;
|
||||
while propcx.changed {
|
||||
num_passes += 1;
|
||||
propcx.changed = false;
|
||||
propcx.reset(&mut temp);
|
||||
propcx.walk_cfg(cfg, &nodes_po, &mut temp);
|
||||
}
|
||||
debug!("finished in {} iterations", num_passes);
|
||||
}
|
||||
|
||||
debug!("Dataflow result for {}:", self.analysis_name);
|
||||
debug!("{}", pprust::to_string(self, |s| {
|
||||
s.cbox(pprust::INDENT_UNIT);
|
||||
s.ibox(0);
|
||||
s.print_expr(&body.value)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: DataFlowOperator> PropagationContext<'_, 'tcx, O> {
|
||||
fn walk_cfg(&mut self,
|
||||
cfg: &cfg::CFG,
|
||||
nodes_po: &[CFGIndex],
|
||||
in_out: &mut [usize]) {
|
||||
debug!("DataFlowContext::walk_cfg(in_out={}) {}",
|
||||
bits_to_string(in_out), self.dfcx.analysis_name);
|
||||
assert!(self.dfcx.bits_per_id > 0);
|
||||
|
||||
// Iterate over nodes in reverse post-order.
|
||||
for &node_index in nodes_po.iter().rev() {
|
||||
let node = cfg.graph.node(node_index);
|
||||
debug!("DataFlowContext::walk_cfg idx={:?} id={:?} begin in_out={}",
|
||||
node_index, node.data.id(), bits_to_string(in_out));
|
||||
|
||||
let (start, end) = self.dfcx.compute_id_range(node_index);
|
||||
|
||||
// Initialize local bitvector with state on-entry.
|
||||
in_out.copy_from_slice(&self.dfcx.on_entry[start.. end]);
|
||||
|
||||
// Compute state on-exit by applying transfer function to
|
||||
// state on-entry.
|
||||
self.dfcx.apply_gen_kill(node_index, in_out);
|
||||
|
||||
// Propagate state on-exit from node into its successors.
|
||||
self.propagate_bits_into_graph_successors_of(in_out, cfg, node_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, bits: &mut [usize]) {
|
||||
let e = if self.dfcx.oper.initial_value() {usize::MAX} else {0};
|
||||
for b in bits {
|
||||
*b = e;
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_graph_successors_of(&mut self,
|
||||
pred_bits: &[usize],
|
||||
cfg: &cfg::CFG,
|
||||
cfgidx: CFGIndex) {
|
||||
for (_, edge) in cfg.graph.outgoing_edges(cfgidx) {
|
||||
self.propagate_bits_into_entry_set_for(pred_bits, edge);
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_entry_set_for(&mut self,
|
||||
pred_bits: &[usize],
|
||||
edge: &cfg::CFGEdge) {
|
||||
let source = edge.source();
|
||||
let cfgidx = edge.target();
|
||||
debug!("{} propagate_bits_into_entry_set_for(pred_bits={}, {:?} to {:?})",
|
||||
self.dfcx.analysis_name, bits_to_string(pred_bits), source, cfgidx);
|
||||
assert!(self.dfcx.bits_per_id > 0);
|
||||
|
||||
let (start, end) = self.dfcx.compute_id_range(cfgidx);
|
||||
let changed = {
|
||||
// (scoping mutable borrow of self.dfcx.on_entry)
|
||||
let on_entry = &mut self.dfcx.on_entry[start.. end];
|
||||
bitwise(on_entry, pred_bits, &self.dfcx.oper)
|
||||
};
|
||||
if changed {
|
||||
debug!("{} changed entry set for {:?} to {}",
|
||||
self.dfcx.analysis_name, cfgidx,
|
||||
bits_to_string(&self.dfcx.on_entry[start.. end]));
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mut_bits_to_string(words: &mut [usize]) -> String {
|
||||
bits_to_string(words)
|
||||
}
|
||||
|
||||
fn bits_to_string(words: &[usize]) -> String {
|
||||
let mut result = String::new();
|
||||
let mut sep = '[';
|
||||
|
||||
// Note: this is a little endian printout of bytes.
|
||||
|
||||
for &word in words {
|
||||
let mut v = word;
|
||||
for _ in 0..mem::size_of::<usize>() {
|
||||
result.push(sep);
|
||||
result.push_str(&format!("{:02x}", v & 0xFF));
|
||||
v >>= 8;
|
||||
sep = '-';
|
||||
}
|
||||
}
|
||||
result.push(']');
|
||||
return result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bitwise<Op: BitwiseOperator>(out_vec: &mut [usize],
|
||||
in_vec: &[usize],
|
||||
op: &Op) -> bool {
|
||||
assert_eq!(out_vec.len(), in_vec.len());
|
||||
let mut changed = false;
|
||||
for (out_elt, in_elt) in out_vec.iter_mut().zip(in_vec) {
|
||||
let old_val = *out_elt;
|
||||
let new_val = op.join(old_val, *in_elt);
|
||||
*out_elt = new_val;
|
||||
changed |= old_val != new_val;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn set_bit(words: &mut [usize], bit: usize) -> bool {
|
||||
debug!("set_bit: words={} bit={}",
|
||||
mut_bits_to_string(words), bit_str(bit));
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
let word = bit / usize_bits;
|
||||
let bit_in_word = bit % usize_bits;
|
||||
let bit_mask = 1 << bit_in_word;
|
||||
debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, bit_mask);
|
||||
let oldv = words[word];
|
||||
let newv = oldv | bit_mask;
|
||||
words[word] = newv;
|
||||
oldv != newv
|
||||
}
|
||||
|
||||
fn bit_str(bit: usize) -> String {
|
||||
let byte = bit >> 3;
|
||||
let lobits = 1 << (bit & 0b111);
|
||||
format!("[{}:{}-{:02x}]", bit, byte, lobits)
|
||||
}
|
||||
|
||||
struct Union;
|
||||
impl BitwiseOperator for Union {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a | b }
|
||||
}
|
||||
struct Subtract;
|
||||
impl BitwiseOperator for Subtract {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a & !b }
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
//! This module provides linkage between rustc::middle::graph and
|
||||
//! libgraphviz traits, specialized to attaching borrowck analysis
|
||||
//! data to rendered labels.
|
||||
|
||||
pub use Variant::*;
|
||||
|
||||
pub(crate) use crate::cfg::graphviz::{Node, Edge};
|
||||
use crate::cfg::graphviz as cfg_dot;
|
||||
use crate::cfg::CFGIndex;
|
||||
use crate::borrowck::{self, BorrowckCtxt, LoanPath};
|
||||
use crate::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
|
||||
use log::debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Variant {
|
||||
Loans,
|
||||
Moves,
|
||||
Assigns,
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
pub fn short_name(&self) -> &'static str {
|
||||
match *self {
|
||||
Loans => "loans",
|
||||
Moves => "moves",
|
||||
Assigns => "assigns",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataflowLabeller<'a, 'tcx> {
|
||||
pub inner: cfg_dot::LabelledCFG<'a, 'tcx>,
|
||||
pub variants: Vec<Variant>,
|
||||
pub borrowck_ctxt: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
pub analysis_data: &'a borrowck::AnalysisData<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> {
|
||||
fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
|
||||
let id = n.1.data.id();
|
||||
debug!("dataflow_for({:?}, id={:?}) {:?}", e, id, self.variants);
|
||||
let mut sets = String::new();
|
||||
let mut seen_one = false;
|
||||
for &variant in &self.variants {
|
||||
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));
|
||||
}
|
||||
sets
|
||||
}
|
||||
|
||||
fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node<'_>, v: Variant) -> String {
|
||||
let cfgidx = n.0;
|
||||
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, F>(
|
||||
&self,
|
||||
e: EntryOrExit,
|
||||
cfgidx: CFGIndex,
|
||||
dfcx: &DataFlowContext<'tcx, O>,
|
||||
mut to_lp: F,
|
||||
) -> String
|
||||
where
|
||||
F: FnMut(usize) -> Rc<LoanPath<'tcx>>,
|
||||
{
|
||||
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);
|
||||
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;
|
||||
let l: &borrowck::Loan<'_> = &all_loans[loan_index];
|
||||
l.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: &borrowck::move_data::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: &borrowck::move_data::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> for DataflowLabeller<'a, 'tcx> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
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(EntryOrExit::Entry, n);
|
||||
let suffix = self.dataflow_for(EntryOrExit::Exit, n);
|
||||
let inner_label = self.inner.node_label(n);
|
||||
inner_label
|
||||
.prefix_line(dot::LabelText::LabelStr(prefix.into()))
|
||||
.suffix_line(dot::LabelText::LabelStr(suffix.into()))
|
||||
}
|
||||
fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> dot::GraphWalk<'a> for DataflowLabeller<'a, 'tcx> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
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) }
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
#![feature(in_band_lifetimes)]
|
||||
#![feature(nll)]
|
||||
|
||||
#![recursion_limit="256"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rustc;
|
||||
|
||||
pub use borrowck::check_crate;
|
||||
pub use borrowck::build_borrowck_dataflow_data_for_fn;
|
||||
|
||||
mod borrowck;
|
||||
|
||||
pub mod graphviz;
|
||||
|
||||
mod dataflow;
|
||||
pub mod cfg;
|
||||
|
||||
pub use borrowck::provide;
|
Loading…
Reference in New Issue
Block a user