12
12
//! does not exceed the lifetime of the value being borrowed.
13
13
14
14
use borrowck:: * ;
15
+ use rustc:: hir;
15
16
use rustc:: middle:: expr_use_visitor as euv;
16
17
use rustc:: middle:: mem_categorization as mc;
17
18
use rustc:: middle:: mem_categorization:: Categorization ;
@@ -60,6 +61,32 @@ struct GuaranteeLifetimeContext<'a, 'tcx: 'a> {
60
61
cmt_original : mc:: cmt < ' tcx >
61
62
}
62
63
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
+
63
90
impl < ' a , ' tcx > GuaranteeLifetimeContext < ' a , ' tcx > {
64
91
65
92
fn check ( & self , cmt : & mc:: cmt < ' tcx > , discr_scope : Option < ast:: NodeId > ) -> R {
@@ -73,11 +100,50 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
73
100
match cmt. cat {
74
101
Categorization :: Rvalue ( ..) |
75
102
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
+
77
110
Categorization :: Deref ( _, _, mc:: BorrowedPtr ( ..) ) | // L-Deref-Borrowed
78
111
Categorization :: Deref ( _, _, mc:: Implicit ( ..) ) |
79
112
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)
81
147
}
82
148
83
149
Categorization :: StaticItem => {
@@ -92,14 +158,30 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
92
158
}
93
159
}
94
160
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`
97
163
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
+ }
100
180
} else {
101
181
Ok ( ( ) )
102
- }
182
+ } ;
183
+ debug ! ( "check_scope: {:?} {:?}: {:?}" , self . loan_region, max_scope, ret) ;
184
+ ret
103
185
}
104
186
105
187
fn scope ( & self , cmt : & mc:: cmt ) -> ty:: Region {
@@ -135,10 +217,188 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
135
217
}
136
218
}
137
219
138
- fn report_error ( & self , code : bckerr_code ) {
220
+ fn report_error ( & self , code : bckerr_code < ' tcx > ) {
139
221
self . bccx . report ( BckError { cmt : self . cmt_original . clone ( ) ,
140
222
span : self . span ,
141
223
cause : BorrowViolation ( self . cause ) ,
142
224
code : code } ) ;
143
225
}
144
226
}
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