Skip to content

Commit f173ce7

Browse files
committed
FIXME Checkpoint third prototype code for addressing issue rust-lang#31567.
The main thing to fix is that there is too much text, too much code, and too much ad-hoc unchecked reasoning. (Regarding the text, the obvious fix is to recast the presentation of my comments into something appropriate for the README.md file.)
1 parent edfec66 commit f173ce7

File tree

1 file changed

+268
-8
lines changed

1 file changed

+268
-8
lines changed

src/librustc_borrowck/borrowck/gather_loans/lifetime.rs

+268-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! does not exceed the lifetime of the value being borrowed.
1313
1414
use borrowck::*;
15+
use rustc::hir;
1516
use rustc::middle::expr_use_visitor as euv;
1617
use rustc::middle::mem_categorization as mc;
1718
use rustc::middle::mem_categorization::Categorization;
@@ -60,6 +61,32 @@ struct GuaranteeLifetimeContext<'a, 'tcx: 'a> {
6061
cmt_original: mc::cmt<'tcx>
6162
}
6263

64+
enum ScopeConstraint<'tcx> {
65+
/// This constraint arises when the guaranator for the data does
66+
/// not live long enough. For example, given `x: &'a &'b &'c T`,
67+
/// `**x` (which has type &'c T`) can only be borrowed for at most
68+
/// the lifetime `'b`.
69+
ShortLivedShallowGuarantor { max_scope: ty::Region },
70+
71+
/// This scope constraint arises when re-borrowing via a `&mut`
72+
/// reference to the borrowed data where the object owning the
73+
/// `&mut` has a destructor.
74+
DestructorMutBaseGuarantor {
75+
max_scope: ty::Region,
76+
base_guarantor: mc::cmt<'tcx>,
77+
dtor_ty: ty::Ty<'tcx>,
78+
}
79+
}
80+
81+
impl<'tcx> ScopeConstraint<'tcx> {
82+
fn max_scope(&self) -> ty::Region {
83+
match *self {
84+
ScopeConstraint::ShortLivedShallowGuarantor { max_scope } => max_scope,
85+
ScopeConstraint::DestructorMutBaseGuarantor { max_scope, .. } => max_scope,
86+
}
87+
}
88+
}
89+
6390
impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
6491

6592
fn check(&self, cmt: &mc::cmt<'tcx>, discr_scope: Option<ast::NodeId>) -> R {
@@ -73,11 +100,50 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
73100
match cmt.cat {
74101
Categorization::Rvalue(..) |
75102
Categorization::Local(..) | // L-Local
76-
Categorization::Upvar(..) |
103+
Categorization::Upvar(..) => {
104+
let constraint = ScopeConstraint::ShortLivedShallowGuarantor {
105+
max_scope: self.scope(cmt),
106+
};
107+
self.check_scope(constraint)
108+
}
109+
77110
Categorization::Deref(_, _, mc::BorrowedPtr(..)) | // L-Deref-Borrowed
78111
Categorization::Deref(_, _, mc::Implicit(..)) |
79112
Categorization::Deref(_, _, mc::UnsafePtr(..)) => {
80-
self.check_scope(self.scope(cmt))
113+
let tcx = self.bccx.tcx;
114+
115+
// Issue #31567: if guarantor has a destructor, then
116+
// that destructor may access guarantor's borrowed
117+
// content. Thus must either (1.) restrict lifetime of
118+
// caller's borrow, or (2.) ensure that neither borrow
119+
// is an unaliasable borrow.
120+
//
121+
// Note that checking (2.) reduces to just ensuring
122+
// that the guarantor's borrow is aliasable, since one
123+
// cannot safely turn an aliasable borrow into an
124+
// unique one.
125+
126+
match mut_guarantor_with_drop_impl(tcx, cmt) {
127+
Some((base_guarantor, dtor_ty)) => {
128+
// case (1.)
129+
let scope = self.scope(&base_guarantor);
130+
let constraint = ScopeConstraint::DestructorMutBaseGuarantor {
131+
max_scope: scope,
132+
base_guarantor: base_guarantor,
133+
dtor_ty: dtor_ty,
134+
};
135+
self.check_scope(constraint)?;
136+
}
137+
None => {
138+
// case (2.), satisfied by analysis in
139+
// mut_guarantor_with_drop_impl.
140+
}
141+
}
142+
143+
let constraint = ScopeConstraint::ShortLivedShallowGuarantor {
144+
max_scope: self.scope(cmt),
145+
};
146+
self.check_scope(constraint)
81147
}
82148

83149
Categorization::StaticItem => {
@@ -92,14 +158,30 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
92158
}
93159
}
94160

95-
fn check_scope(&self, max_scope: ty::Region) -> R {
96-
//! Reports an error if `loan_region` is larger than `max_scope`
161+
fn check_scope(&self, constraint: ScopeConstraint<'tcx>) -> R {
162+
//! Reports an error if `loan_region` is larger than `constraint.max_scope`
97163
98-
if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
99-
Err(self.report_error(err_out_of_scope(max_scope, self.loan_region)))
164+
let max_scope = constraint.max_scope();
165+
let ret = if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
166+
match constraint {
167+
ScopeConstraint::ShortLivedShallowGuarantor { .. } =>
168+
Err(self.report_error(err_out_of_scope(max_scope, self.loan_region))),
169+
ScopeConstraint::DestructorMutBaseGuarantor {
170+
base_guarantor, dtor_ty, .. } => {
171+
self.report_error(err_out_of_scope_dtor {
172+
superscope: max_scope,
173+
subscope: self.loan_region,
174+
owner: base_guarantor.clone(),
175+
dtor_ty: dtor_ty,
176+
});
177+
Ok(())
178+
}
179+
}
100180
} else {
101181
Ok(())
102-
}
182+
};
183+
debug!("check_scope: {:?} {:?}: {:?}", self.loan_region, max_scope, ret);
184+
ret
103185
}
104186

105187
fn scope(&self, cmt: &mc::cmt) -> ty::Region {
@@ -135,10 +217,188 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
135217
}
136218
}
137219

138-
fn report_error(&self, code: bckerr_code) {
220+
fn report_error(&self, code: bckerr_code<'tcx>) {
139221
self.bccx.report(BckError { cmt: self.cmt_original.clone(),
140222
span: self.span,
141223
cause: BorrowViolation(self.cause),
142224
code: code });
143225
}
144226
}
227+
228+
fn has_user_defined_dtor<'tcx>(ty: ty::Ty<'tcx>) -> bool
229+
{
230+
match ty.sty {
231+
ty::TyEnum(type_def, _) | ty::TyStruct(type_def, _) => {
232+
type_def.destructor().is_some()
233+
}
234+
_ => false,
235+
}
236+
}
237+
238+
fn mut_guarantor_with_drop_impl<'tcx>(_ctxt: &ty::TyCtxt<'tcx>,
239+
orig_cmt: &mc::cmt<'tcx>)
240+
-> Option<(mc::cmt<'tcx>, ty::Ty<'tcx>)>
241+
{
242+
// Someone is borrowing `orig_cmt`. Our goal is to ensure that the
243+
// lifetime of the borrow does not overlap the execution of any
244+
// destructors that have `&mut` access to the borrowed data.
245+
//
246+
// This is a little tricky to get right. Consider a case like
247+
// this: given `z: &'a mut D(&'b mut u8, u8)`, with a (user-
248+
// defined) `impl Drop for D`.
249+
//
250+
// Is this: `&*((*z).0): &'b u8` allowed?
251+
//
252+
// How about this: `&mut *((*z).0): &'b u8`?
253+
//
254+
// The answer: Neither is allowed, because we only know that the
255+
// destructor won't run until sometime after `'a` expires.
256+
//
257+
// (There are other pre-existing checks, like the RESTRICTIONS
258+
// clause R-Deref-Imm-Borrowed, that handle similar cases. So it
259+
// is important that we stay focused on the problem at hand:
260+
// identifying all potential future-scheduled destructor
261+
// invocations with `&mut` access to data being borrowed here.)
262+
263+
// First, some expository examples. We will assume that `x` is a
264+
// local variable of some type `X` and associated scope lifetime
265+
// `'x`. The structure of `X` will be implied from context; any
266+
// fields of the structure, `.f1`, `.f2`, etc have corresponding
267+
// types `F1`, `F2`, etc.
268+
//
269+
// All examples will make explicit the borrow being requested,
270+
// using type ascription to declare the lifetime of the requested
271+
// borrow.
272+
//
273+
// `&x: &'lt X` is always okay with this check: a borrow of a
274+
// local will always expire before the execution of that local's
275+
// destructor.
276+
//
277+
// `&*x: &'lt _` is ok (assuming `X = &'y mut _` or `X = &'y _`):
278+
// the borrowed data must outlive `'y`, and `'y: 'lt`; thus there
279+
// is no relevant destructor that will run before `'lt` expires.
280+
//
281+
// `&x.f1: &'lt F1` ok if !(X:Drop) or 'lt < 'x.
282+
//
283+
// `&*(x.f1): &'lt _` ok if !(X:Drop) or 'lt < 'x or F1 != `&mut _`.
284+
//
285+
// Note that the conditions here are the combination of the same
286+
// set of conditions as the previous case, plus an additional
287+
// escape clause based on the structure of `F1`. The type `F1` has
288+
// to be *some* reference type (since we are dereferencing it),
289+
// and the unsafety we are checking for is only exposed if `F1` is
290+
// a `&mut _`.
291+
//
292+
// `&x.f1.f2: &'lt X` ok if (!(X:Drop) and !(F1:Drop)) or 'lt < 'x.
293+
//
294+
// One last interesting example:
295+
//
296+
// `&(*(x.f1.f2)).f3: &'lt F3` ok if (!(X:Drop) and !(F1:Drop)) or
297+
// 'lt < 'x or `F2 != &'y mut _`.
298+
//
299+
// This last example is interesting because it illustrates that we
300+
// need not care about whether `F3:Drop` when we only access it as
301+
// a field off of a dereference: we know `x.f1.f2` is a reference
302+
// to some structure holding the `.f3: F3`, which means the
303+
// destructor for `F3` will never run. We also know that `F2` has
304+
// no destructor (since it must be of reference type). However,
305+
// *either* of the types `X` or `F1` could have a destructor that
306+
// could access the mutable state.
307+
//
308+
// In other words, as we decend the structure of the cmt making
309+
// our way to its foundational owner, each time that we encounter
310+
// a `Deref`, we can disregard any destructor-laden types that we
311+
// encountered on our way here.
312+
313+
// From the above examples, I generalize as follows:
314+
//
315+
// /// Returns min. bound on how long before a dtor could access
316+
// /// the borrowed data. If None then no such dtor exists.
317+
//
318+
// fn base_guarantor(LV) -> Option<Lifetime> {
319+
// fn recur(LV, found_drop: bool) -> Option<Lifetime> {
320+
// match LV {
321+
// X => if found_drop { Some(SCOPE(X)) } else { None }
322+
// *LV' => if TYPE(LV) != `&mut _` { None } else { recur(LV', false) },
323+
// LV'.f => recur(LV', found_drop || TYPE(LV'): Drop),
324+
// }
325+
// }
326+
// recur(LV, false)
327+
// }
328+
//
329+
// However, the above is an ad-hoc generalization from the
330+
// examples; I have not yet really proven to myself that this is
331+
// fundamentally correct.
332+
333+
let mut cmt = orig_cmt;
334+
let mut found_drop: Option<ty::Ty<'tcx>> = None;
335+
let base;
336+
loop {
337+
debug!("mut_guarantor_with_drop_impl cmt: {:?} found_drop: {:?}",
338+
cmt, found_drop);
339+
340+
// Note on upvar: If base cmt is upvar reference, then its ty
341+
// is not reliable (mem_categorization just plugs in `TyErr`).
342+
// That is why I am breaking out in those cases below.
343+
match cmt.cat {
344+
Categorization::Rvalue(..) |
345+
Categorization::StaticItem |
346+
Categorization::Local(..) |
347+
Categorization::Upvar(..) => {
348+
base = cmt.clone();
349+
break;
350+
}
351+
352+
Categorization::Interior(ref b, _) => {
353+
if let Categorization::Upvar(..) = b.cat {
354+
// see Note on upvar above.
355+
base = cmt.clone();
356+
break;
357+
}
358+
359+
// accumulate potential destructors on LV.f
360+
if found_drop.is_none() && has_user_defined_dtor(b.ty) {
361+
found_drop = Some(b.ty);
362+
}
363+
cmt = b;
364+
}
365+
366+
Categorization::Downcast(ref b, _) |
367+
Categorization::Deref(ref b, _, mc::UnsafePtr(..)) |
368+
Categorization::Deref(ref b, _, mc::Unique) => {
369+
if let Categorization::Upvar(..) = b.cat {
370+
// see Note on upvar above.
371+
base = cmt.clone();
372+
break;
373+
}
374+
cmt = b;
375+
}
376+
377+
Categorization::Deref(ref b, _, mc::BorrowedPtr(..)) |
378+
Categorization::Deref(ref b, _, mc::Implicit(..)) => {
379+
if let Categorization::Upvar(..) = b.cat {
380+
// see Note on upvar above.
381+
base = cmt.clone();
382+
break;
383+
}
384+
cmt = b;
385+
386+
// reset potential destructors on *LV.
387+
found_drop = None;
388+
389+
if let ty::TyRef(_, ty::TypeAndMut { ty: _, mutbl }) = b.ty.sty
390+
{
391+
if mutbl != hir::MutMutable { return None; }
392+
}
393+
}
394+
};
395+
}
396+
397+
let ret = if let Some(dtor_ty) = found_drop {
398+
Some((base, dtor_ty))
399+
} else {
400+
None
401+
};
402+
debug!("mut_guarantor_with_drop_impl cmt: {:?} => {:?}", orig_cmt, ret);
403+
ret
404+
}

0 commit comments

Comments
 (0)