-
Notifications
You must be signed in to change notification settings - Fork 13.5k
Support borrow, scope and move visualization through RLS #42733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
6cdf539
11d23f3
894dc91
069dba1
0f3456c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,11 +37,12 @@ mod move_error; | |
|
||
pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, | ||
body: hir::BodyId) | ||
-> (Vec<Loan<'tcx>>, move_data::MoveData<'tcx>) { | ||
-> (Vec<SafeLoan>, 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: bccx, | ||
safe_loans: Vec::new(), | ||
all_loans: Vec::new(), | ||
item_ub: region::CodeExtent::Misc(body.node_id), | ||
move_data: MoveData::new(), | ||
|
@@ -53,14 +54,15 @@ pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, | |
.consume_body(body); | ||
|
||
glcx.report_potential_errors(); | ||
let GatherLoanCtxt { all_loans, move_data, .. } = glcx; | ||
(all_loans, move_data) | ||
let GatherLoanCtxt { safe_loans, all_loans, move_data, .. } = glcx; | ||
(safe_loans, all_loans, move_data) | ||
} | ||
|
||
struct GatherLoanCtxt<'a, 'tcx: 'a> { | ||
bccx: &'a BorrowckCtxt<'a, 'tcx>, | ||
move_data: move_data::MoveData<'tcx>, | ||
move_error_collector: move_error::MoveErrorCollector<'tcx>, | ||
safe_loans: Vec<SafeLoan>, | ||
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. | ||
|
@@ -343,7 +345,43 @@ impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> { | |
// Create the loan record (if needed). | ||
let loan = match restr { | ||
RestrictionResult::Safe => { | ||
// No restrictions---no loan record necessary | ||
let loan_scope = match *loan_region { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should have moved this and the below |
||
ty::ReScope(scope) => scope, | ||
|
||
ty::ReEarlyBound(ref br) => { | ||
self.bccx.region_maps.early_free_extent(self.tcx(), br) | ||
} | ||
|
||
ty::ReFree(ref fr) => { | ||
self.bccx.region_maps.free_extent(self.tcx(), fr) | ||
} | ||
|
||
ty::ReStatic => self.item_ub, | ||
|
||
ty::ReEmpty | | ||
ty::ReLateBound(..) | | ||
ty::ReVar(..) | | ||
ty::ReSkolemized(..) | | ||
ty::ReErased => { | ||
span_bug!( | ||
cmt.span, | ||
"invalid borrow lifetime: {:?}", | ||
loan_region); | ||
} | ||
}; | ||
debug!("loan_scope = {:?}", loan_scope); | ||
|
||
let borrow_scope = region::CodeExtent::Misc(borrow_id); | ||
let loan_scope = self.compute_gen_scope(borrow_scope, loan_scope); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this be enough to show the span when the loan is being held (this is for temporary loans, I believe)? It seems correct when visualizing, but I'm definitely no expert on the internals of the borrow checker. |
||
|
||
let safe_loan = SafeLoan { | ||
kind: req_kind, | ||
loan_scope: loan_scope, | ||
span: borrow_span, | ||
}; | ||
self.safe_loans.push(safe_loan); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also like to not run through this section unless we're running save analysis. However, I don't see a clear, clean way to notify this section of code about that. Any recommendations on that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't worry too much about that. Save-analysis isn't really the direction we want things to go anyway -- that is, having a distinct "save-analysis mode". I think we hope that eventually the RLS will just be polling the results of regular queries. |
||
|
||
// No restrictions---no normal loan record necessary | ||
return; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ pub use self::mir::elaborate_drops::ElaborateDrops; | |
|
||
use self::InteriorKind::*; | ||
|
||
use rustc::hir::BodyId; | ||
use rustc::hir::map as hir_map; | ||
use rustc::hir::map::blocks::FnLikeNode; | ||
use rustc::cfg; | ||
|
@@ -39,15 +40,18 @@ use rustc::middle::free_region::RegionRelations; | |
use rustc::ty::{self, TyCtxt}; | ||
use rustc::ty::maps::Providers; | ||
|
||
use std::collections::HashMap; | ||
|
||
use std::fmt; | ||
use std::rc::Rc; | ||
use std::hash::{Hash, Hasher}; | ||
use syntax::ast; | ||
use syntax_pos::{MultiSpan, Span}; | ||
use syntax::{ast, codemap}; | ||
use syntax_pos::{MultiSpan, Span, NO_EXPANSION}; | ||
use errors::DiagnosticBuilder; | ||
|
||
use rustc::hir; | ||
use rustc::hir::intravisit::{self, Visitor}; | ||
use borrowck::move_data::MoveData; | ||
|
||
pub mod check_loans; | ||
|
||
|
@@ -62,27 +66,50 @@ pub struct LoanDataFlowOperator; | |
|
||
pub type LoanDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, LoanDataFlowOperator>; | ||
|
||
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) { | ||
for body_owner_def_id in tcx.body_owners() { | ||
tcx.borrowck(body_owner_def_id); | ||
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, save_analysis: bool) -> Option<HashMap<DefId, AnalysisResult<'a, 'tcx>>> { | ||
// FIXME: nashenas88 support this through TyCtxt | ||
if save_analysis { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this at all. The main reason I didn't modify how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I think that we want to be moving towards a world where save-analysis doesn't "alter" anything in the normal control-flow, but just performs queries and uses the results. To that end, I think we should probably just keep |
||
let mut map = HashMap::new(); | ||
for body_owner_def_id in tcx.body_owners() { | ||
let result = borrowck(tcx, body_owner_def_id, save_analysis).unwrap(); | ||
map.insert(body_owner_def_id, result); | ||
} | ||
Some(map) | ||
} else { | ||
for body_owner_def_id in tcx.body_owners() { | ||
tcx.borrowck(body_owner_def_id); | ||
} | ||
None | ||
} | ||
} | ||
|
||
pub fn provide(providers: &mut Providers) { | ||
*providers = Providers { | ||
borrowck, | ||
borrowck: borrowck_provider, | ||
..*providers | ||
}; | ||
} | ||
|
||
/// Collection of conclusions determined via borrow checker analyses. | ||
pub struct AnalysisData<'a, 'tcx: 'a> { | ||
pub safe_loans: Vec<SafeLoan>, | ||
pub all_loans: Vec<Loan<'tcx>>, | ||
pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>, | ||
pub move_data: move_data::FlowedMoveData<'a, 'tcx>, | ||
} | ||
|
||
fn borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId) { | ||
pub struct AnalysisResult<'a, 'tcx: 'a> { | ||
pub bccx: BorrowckCtxt<'a, 'tcx>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not crazy about keeping the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I remember bringing this up in IRC at some point (not This |
||
pub safe_loans: Vec<SafeLoan>, | ||
pub all_loans: Vec<Loan<'tcx>>, | ||
pub move_data: move_data::MoveData<'tcx>, | ||
} | ||
|
||
fn borrowck_provider<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId) { | ||
borrowck(tcx, owner_def_id, false); | ||
} | ||
|
||
fn borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId, save_analysis: bool) -> Option<AnalysisResult<'a, 'tcx>> { | ||
debug!("borrowck(body_owner_def_id={:?})", owner_def_id); | ||
|
||
let owner_id = tcx.hir.as_local_node_id(owner_def_id).unwrap(); | ||
|
@@ -94,7 +121,7 @@ fn borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId) { | |
// those things (notably the synthesized constructors from | ||
// tuple structs/variants) do not have an associated body | ||
// and do not need borrowchecking. | ||
return; | ||
return None; | ||
} | ||
_ => { } | ||
} | ||
|
@@ -103,12 +130,12 @@ fn borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId) { | |
let attributes = tcx.get_attrs(owner_def_id); | ||
let tables = tcx.typeck_tables_of(owner_def_id); | ||
let region_maps = tcx.region_maps(owner_def_id); | ||
let mut bccx = &mut BorrowckCtxt { tcx, tables, region_maps, owner_def_id }; | ||
let mut bccx = BorrowckCtxt { tcx, tables, region_maps, owner_def_id }; | ||
|
||
let body = bccx.tcx.hir.body(body_id); | ||
|
||
if bccx.tcx.has_attr(owner_def_id, "rustc_mir_borrowck") { | ||
mir::borrowck_mir(bccx, owner_id, &attributes); | ||
mir::borrowck_mir(&mut bccx, owner_id, &attributes); | ||
} else { | ||
// Eventually, borrowck will always read the MIR, but at the | ||
// moment we do not. So, for now, we always force MIR to be | ||
|
@@ -122,17 +149,29 @@ fn borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, owner_def_id: DefId) { | |
} | ||
|
||
let cfg = cfg::CFG::new(bccx.tcx, &body); | ||
let AnalysisData { all_loans, | ||
let AnalysisData { safe_loans, | ||
all_loans, | ||
loans: loan_dfcx, | ||
move_data: flowed_moves } = | ||
build_borrowck_dataflow_data(bccx, &cfg, body_id); | ||
build_borrowck_dataflow_data(&mut bccx, &cfg, body_id); | ||
|
||
check_loans::check_loans(&bccx, &loan_dfcx, &flowed_moves, &all_loans[..], body); | ||
|
||
check_loans::check_loans(bccx, &loan_dfcx, &flowed_moves, &all_loans, body); | ||
if save_analysis { | ||
Some(AnalysisResult { | ||
bccx, | ||
safe_loans, | ||
all_loans, | ||
move_data: flowed_moves.move_data, | ||
}) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>, | ||
cfg: &cfg::CFG, | ||
body_id: hir::BodyId) | ||
body_id: BodyId) | ||
-> AnalysisData<'a, 'tcx> | ||
{ | ||
// Check the body of fn items. | ||
|
@@ -143,7 +182,7 @@ fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>, | |
visitor.visit_body(body); | ||
visitor.result() | ||
}; | ||
let (all_loans, move_data) = | ||
let (safe_loans, all_loans, move_data) = | ||
gather_loans::gather_loans_in_fn(this, body_id); | ||
|
||
let mut loan_dfcx = | ||
|
@@ -168,16 +207,17 @@ fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>, | |
id_range, | ||
body); | ||
|
||
AnalysisData { all_loans: all_loans, | ||
AnalysisData { safe_loans: safe_loans, | ||
all_loans: all_loans, | ||
loans: loan_dfcx, | ||
move_data:flowed_moves } | ||
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<'a, 'tcx, 'tcx>, | ||
body_id: hir::BodyId, | ||
body_id: BodyId, | ||
cfg: &cfg::CFG) | ||
-> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'a, 'tcx>) | ||
{ | ||
|
@@ -209,6 +249,26 @@ pub struct BorrowckCtxt<'a, 'tcx: 'a> { | |
/////////////////////////////////////////////////////////////////////////// | ||
// Loans and loan paths | ||
|
||
pub struct SafeLoan { | ||
kind: ty::BorrowKind, | ||
loan_scope: region::CodeExtent, | ||
span: Span, | ||
} | ||
|
||
impl SafeLoan { | ||
pub fn kind(&self) -> ty::BorrowKind { | ||
self.kind | ||
} | ||
|
||
pub fn loan_scope(&self) -> region::CodeExtent { | ||
self.loan_scope | ||
} | ||
|
||
pub fn span(&self) -> Span { | ||
self.span | ||
} | ||
} | ||
|
||
/// Record of a loan that was issued. | ||
pub struct Loan<'tcx> { | ||
index: usize, | ||
|
@@ -236,6 +296,47 @@ impl<'tcx> Loan<'tcx> { | |
pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> { | ||
self.loan_path.clone() | ||
} | ||
|
||
pub fn kind(&self) -> ty::BorrowKind { | ||
self.kind | ||
} | ||
|
||
pub fn span(&self) -> Span { | ||
self.span | ||
} | ||
|
||
pub fn span_in_def(&self, def_id: DefId, tcx: TyCtxt) -> Option<Span> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any ideas for a better name? This is to generate the span (if it exists) for the "scope" of the value pointed to by the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely a comment would be good -- with some examples. I would think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason the helper is here is that |
||
let fn_span = tcx.hir | ||
.as_local_node_id(def_id) | ||
.map(|node_id| tcx.hir.span(node_id)); | ||
|
||
let gen_span = self.gen_scope.span(&tcx.hir) | ||
.and_then(|s| fn_span.and_then(|n| Self::get_unexpanded_span(s, n))); | ||
|
||
let kill_span = self.kill_scope.span(&tcx.hir) | ||
.and_then(|s| fn_span.and_then(|n| Self::get_unexpanded_span(s, n))); | ||
|
||
match (gen_span, kill_span) { | ||
(Some(gen_span), Some(kill_span)) => { | ||
Some(Span { | ||
lo: gen_span.lo, | ||
hi: kill_span.hi, | ||
ctxt: NO_EXPANSION | ||
}) | ||
}, | ||
_ => None, | ||
} | ||
} | ||
|
||
fn get_unexpanded_span(input_span: Span, wrapping_span: Span) -> Option<Span> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the input span is being defined within a macro, but I want it in context of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The answer to a question like this is always "add a comment". =) That would not have been obvious to me. I think an example of some Rust code where this is relevant would be even better. |
||
// Walk up the macro expansion chain until we reach a non-expanded span. | ||
let span = codemap::original_sp(input_span, wrapping_span); | ||
if span == wrapping_span { | ||
None // We traversed to the top and couldn't find any matching span | ||
} else { | ||
Some(span) | ||
} | ||
} | ||
} | ||
|
||
#[derive(Eq)] | ||
|
@@ -269,6 +370,15 @@ impl<'tcx> LoanPath<'tcx> { | |
LoanPath { kind: kind, ty: ty } | ||
} | ||
|
||
pub fn ref_node_id<'a>(&self) -> ast::NodeId { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a comment too -- I guess it returns the "node-id of the base variable or upvar of the path"? I feel like there probably exists a helper like this...but maybe not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote this well over 6 months ago, so during that time someone might have written a helper for this. Back then I only saw other usages of this in |
||
match self.kind { | ||
LpVar(node_id) => node_id, | ||
LpUpvar(upvar_id) => upvar_id.var_id, | ||
LpDowncast(ref base, ..) | | ||
LpExtend(ref base, ..) => base.ref_node_id(), | ||
} | ||
} | ||
|
||
fn to_type(&self) -> ty::Ty<'tcx> { self.ty } | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,8 @@ extern crate core; // for NonZero | |
|
||
pub use borrowck::check_crate; | ||
pub use borrowck::build_borrowck_dataflow_data_for_fn; | ||
pub use borrowck::{AnalysisData, BorrowckCtxt, ElaborateDrops}; | ||
pub use borrowck::{AnalysisData, AnalysisResult, BorrowckCtxt, ElaborateDrops, Loan, SafeLoan}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is tied to the comments I made regarding the use of |
||
pub use borrowck::move_data::{MoveData, Move, Assignment}; | ||
|
||
// NB: This module needs to be declared first so diagnostics are | ||
// registered before they are used. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I feel like the right fix here might be removing the notion of
SafeLoan
altogether, and adjusting the follow-on code, per the strategy I describe in the NLL RFC.