Skip to content

Commit 87cd2c0

Browse files
committed
Auto merge of #28861 - pnkfelix:fsk-nonparam-dropck-issue28498, r=arielb1
implement RFC 1238: nonparametric dropck. cc #28498 cc @nikomatsakis
2 parents 439311d + a445f23 commit 87cd2c0

29 files changed

+770
-103
lines changed

src/doc/nomicon/dropck.md

+154-5
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,162 @@ section:
115115
**For a generic type to soundly implement drop, its generics arguments must
116116
strictly outlive it.**
117117

118-
This rule is sufficient but not necessary to satisfy the drop checker. That is,
119-
if your type obeys this rule then it's definitely sound to drop. However
120-
there are special cases where you can fail to satisfy this, but still
121-
successfully pass the borrow checker. These are the precise rules that are
122-
currently up in the air.
118+
Obeying this rule is (usually) necessary to satisfy the borrow
119+
checker; obeying it is sufficient but not necessary to be
120+
sound. That is, if your type obeys this rule then it's definitely
121+
sound to drop.
122+
123+
The reason that it is not always necessary to satisfy the above rule
124+
is that some Drop implementations will not access borrowed data even
125+
though their type gives them the capability for such access.
126+
127+
For example, this variant of the above `Inspector` example will never
128+
accessed borrowed data:
129+
130+
```rust,ignore
131+
struct Inspector<'a>(&'a u8, &'static str);
132+
133+
impl<'a> Drop for Inspector<'a> {
134+
fn drop(&mut self) {
135+
println!("Inspector(_, {}) knows when *not* to inspect.", self.1);
136+
}
137+
}
138+
139+
fn main() {
140+
let (inspector, days);
141+
days = Box::new(1);
142+
inspector = Inspector(&days, "gadget");
143+
// Let's say `days` happens to get dropped first.
144+
// Even when Inspector is dropped, its destructor will not access the
145+
// borrowed `days`.
146+
}
147+
```
148+
149+
Likewise, this variant will also never access borrowed data:
150+
151+
```rust,ignore
152+
use std::fmt;
153+
154+
struct Inspector<T: fmt::Display>(T, &'static str);
155+
156+
impl<T: fmt::Display> Drop for Inspector<T> {
157+
fn drop(&mut self) {
158+
println!("Inspector(_, {}) knows when *not* to inspect.", self.1);
159+
}
160+
}
161+
162+
fn main() {
163+
let (inspector, days): (Inspector<&u8>, Box<u8>);
164+
days = Box::new(1);
165+
inspector = Inspector(&days, "gadget");
166+
// Let's say `days` happens to get dropped first.
167+
// Even when Inspector is dropped, its destructor will not access the
168+
// borrowed `days`.
169+
}
170+
```
171+
172+
However, *both* of the above variants are rejected by the borrow
173+
checker during the analysis of `fn main`, saying that `days` does not
174+
live long enough.
175+
176+
The reason is that the borrow checking analysis of `main` does not
177+
know about the internals of each Inspector's Drop implementation. As
178+
far as the borrow checker knows while it is analyzing `main`, the body
179+
of an inspector's destructor might access that borrowed data.
180+
181+
Therefore, the drop checker forces all borrowed data in a value to
182+
strictly outlive that value.
183+
184+
# An Escape Hatch
185+
186+
The precise rules that govern drop checking may be less restrictive in
187+
the future.
188+
189+
The current analysis is deliberately conservative and trivial; it forces all
190+
borrowed data in a value to outlive that value, which is certainly sound.
191+
192+
Future versions of the language may make the analysis more precise, to
193+
reduce the number of cases where sound code is rejected as unsafe.
194+
This would help address cases such as the two Inspectors above that
195+
know not to inspect during destruction.
196+
197+
In the meantime, there is an unstable attribute that one can use to
198+
assert (unsafely) that a generic type's destructor is *guaranteed* to
199+
not access any expired data, even if its type gives it the capability
200+
to do so.
201+
202+
That attribute is called `unsafe_destructor_blind_to_params`.
203+
To deploy it on the Inspector example from above, we would write:
204+
205+
```rust,ignore
206+
struct Inspector<'a>(&'a u8, &'static str);
207+
208+
impl<'a> Drop for Inspector<'a> {
209+
#[unsafe_destructor_blind_to_params]
210+
fn drop(&mut self) {
211+
println!("Inspector(_, {}) knows when *not* to inspect.", self.1);
212+
}
213+
}
214+
```
215+
216+
This attribute has the word `unsafe` in it because the compiler is not
217+
checking the implicit assertion that no potentially expired data
218+
(e.g. `self.0` above) is accessed.
219+
220+
It is sometimes obvious that no such access can occur, like the case above.
221+
However, when dealing with a generic type parameter, such access can
222+
occur indirectly. Examples of such indirect access are:
223+
* invoking a callback,
224+
* via a trait method call.
225+
226+
(Future changes to the language, such as impl specialization, may add
227+
other avenues for such indirect access.)
228+
229+
Here is an example of invoking a callback:
230+
231+
```rust,ignore
232+
struct Inspector<T>(T, &'static str, Box<for <'r> fn(&'r T) -> String>);
233+
234+
impl<T> Drop for Inspector<T> {
235+
fn drop(&mut self) {
236+
// The `self.2` call could access a borrow e.g. if `T` is `&'a _`.
237+
println!("Inspector({}, {}) unwittingly inspects expired data.",
238+
(self.2)(&self.0), self.1);
239+
}
240+
}
241+
```
242+
243+
Here is an example of a trait method call:
244+
245+
```rust,ignore
246+
use std::fmt;
247+
248+
struct Inspector<T: fmt::Display>(T, &'static str);
249+
250+
impl<T: fmt::Display> Drop for Inspector<T> {
251+
fn drop(&mut self) {
252+
// There is a hidden call to `<T as Display>::fmt` below, which
253+
// could access a borrow e.g. if `T` is `&'a _`
254+
println!("Inspector({}, {}) unwittingly inspects expired data.",
255+
self.0, self.1);
256+
}
257+
}
258+
```
259+
260+
And of course, all of these accesses could be further hidden within
261+
some other method invoked by the destructor, rather than being written
262+
directly within it.
263+
264+
In all of the above cases where the `&'a u8` is accessed in the
265+
destructor, adding the `#[unsafe_destructor_blind_to_params]`
266+
attribute makes the type vulnerable to misuse that the borrower
267+
checker will not catch, inviting havoc. It is better to avoid adding
268+
the attribute.
269+
270+
# Is that all about drop checker?
123271

124272
It turns out that when writing unsafe code, we generally don't need to
125273
worry at all about doing the right thing for the drop checker. However there
126274
is one special case that you need to worry about, which we will look at in
127275
the next section.
276+

src/doc/reference.md

+14
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,20 @@ macro scope.
19291929
- `simd` - on certain tuple structs, derive the arithmetic operators, which
19301930
lower to the target's SIMD instructions, if any; the `simd` feature gate
19311931
is necessary to use this attribute.
1932+
- `unsafe_destructor_blind_to_params` - on `Drop::drop` method, asserts that the
1933+
destructor code (and all potential specializations of that code) will
1934+
never attempt to read from nor write to any references with lifetimes
1935+
that come in via generic parameters. This is a constraint we cannot
1936+
currently express via the type system, and therefore we rely on the
1937+
programmer to assert that it holds. Adding this to a Drop impl causes
1938+
the associated destructor to be considered "uninteresting" by the
1939+
Drop-Check rule, and thus it can help sidestep data ordering
1940+
constraints that would otherwise be introduced by the Drop-Check
1941+
rule. Such sidestepping of the constraints, if done incorrectly, can
1942+
lead to undefined behavior (in the form of reading or writing to data
1943+
outside of its dynamic extent), and thus this attribute has the word
1944+
"unsafe" in its name. To use this, the
1945+
`unsafe_destructor_blind_to_params` feature gate must be enabled.
19321946
- `unsafe_no_drop_flag` - on structs, remove the flag that prevents
19331947
destructors from being run twice. Destructors might be run multiple times on
19341948
the same object with this attribute. To use this, the `unsafe_no_drop_flag` feature

src/liballoc/arc.rs

+1
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ impl<T: ?Sized> Drop for Arc<T> {
550550
///
551551
/// } // implicit drop
552552
/// ```
553+
#[unsafe_destructor_blind_to_params]
553554
#[inline]
554555
fn drop(&mut self) {
555556
// This structure has #[unsafe_no_drop_flag], so this drop glue may run

src/liballoc/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494
#![feature(unboxed_closures)]
9595
#![feature(unique)]
9696
#![feature(unsafe_no_drop_flag, filling_drop)]
97+
// SNAP 1af31d4
98+
#![allow(unused_features)]
99+
// SNAP 1af31d4
100+
#![allow(unused_attributes)]
101+
#![feature(dropck_parametricity)]
97102
#![feature(unsize)]
98103
#![feature(core_slice_ext)]
99104
#![feature(core_str_ext)]

src/liballoc/raw_vec.rs

+1
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ impl<T> RawVec<T> {
445445
}
446446

447447
impl<T> Drop for RawVec<T> {
448+
#[unsafe_destructor_blind_to_params]
448449
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
449450
fn drop(&mut self) {
450451
let elem_size = mem::size_of::<T>();

src/liballoc/rc.rs

+1
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ impl<T: ?Sized> Drop for Rc<T> {
451451
///
452452
/// } // implicit drop
453453
/// ```
454+
#[unsafe_destructor_blind_to_params]
454455
fn drop(&mut self) {
455456
unsafe {
456457
let ptr = *self._ptr;

src/libarena/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@
3838
#![feature(ptr_as_ref)]
3939
#![feature(raw)]
4040
#![feature(staged_api)]
41+
#![feature(dropck_parametricity)]
4142
#![cfg_attr(test, feature(test))]
4243

44+
// SNAP 1af31d4
45+
#![allow(unused_features)]
46+
// SNAP 1af31d4
47+
#![allow(unused_attributes)]
48+
4349
extern crate alloc;
4450

4551
use std::cell::{Cell, RefCell};
@@ -510,6 +516,7 @@ impl<T> TypedArena<T> {
510516
}
511517

512518
impl<T> Drop for TypedArena<T> {
519+
#[unsafe_destructor_blind_to_params]
513520
fn drop(&mut self) {
514521
unsafe {
515522
// Determine how much was filled.

src/libcollections/btree/node.rs

+3
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,14 @@ impl<T> DoubleEndedIterator for RawItems<T> {
275275
}
276276

277277
impl<T> Drop for RawItems<T> {
278+
#[unsafe_destructor_blind_to_params]
278279
fn drop(&mut self) {
279280
for _ in self {}
280281
}
281282
}
282283

283284
impl<K, V> Drop for Node<K, V> {
285+
#[unsafe_destructor_blind_to_params]
284286
fn drop(&mut self) {
285287
if self.keys.is_null() ||
286288
(unsafe { self.keys.get() as *const K as usize == mem::POST_DROP_USIZE })
@@ -1419,6 +1421,7 @@ impl<K, V> TraversalImpl for MoveTraversalImpl<K, V> {
14191421
}
14201422

14211423
impl<K, V> Drop for MoveTraversalImpl<K, V> {
1424+
#[unsafe_destructor_blind_to_params]
14221425
fn drop(&mut self) {
14231426
// We need to cleanup the stored values manually, as the RawItems destructor would run
14241427
// after our deallocation.

src/libcollections/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
#![allow(trivial_casts)]
3333
#![cfg_attr(test, allow(deprecated))] // rand
3434

35+
// SNAP 1af31d4
36+
#![allow(unused_features)]
37+
// SNAP 1af31d4
38+
#![allow(unused_attributes)]
39+
3540
#![feature(alloc)]
3641
#![feature(box_patterns)]
3742
#![feature(box_syntax)]
@@ -59,6 +64,7 @@
5964
#![feature(unboxed_closures)]
6065
#![feature(unicode)]
6166
#![feature(unique)]
67+
#![feature(dropck_parametricity)]
6268
#![feature(unsafe_no_drop_flag, filling_drop)]
6369
#![feature(decode_utf16)]
6470
#![feature(utf8_error)]

src/libcollections/linked_list.rs

+1
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ impl<T> LinkedList<T> {
655655

656656
#[stable(feature = "rust1", since = "1.0.0")]
657657
impl<T> Drop for LinkedList<T> {
658+
#[unsafe_destructor_blind_to_params]
658659
fn drop(&mut self) {
659660
// Dissolve the linked_list in a loop.
660661
// Just dropping the list_head can lead to stack exhaustion

src/libcollections/vec.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ impl<T: Ord> Ord for Vec<T> {
13851385

13861386
#[stable(feature = "rust1", since = "1.0.0")]
13871387
impl<T> Drop for Vec<T> {
1388+
#[unsafe_destructor_blind_to_params]
13881389
fn drop(&mut self) {
13891390
// NOTE: this is currently abusing the fact that ZSTs can't impl Drop.
13901391
// Or rather, that impl'ing Drop makes them not zero-sized. This is

src/libcollections/vec_deque.rs

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ impl<T: Clone> Clone for VecDeque<T> {
6464

6565
#[stable(feature = "rust1", since = "1.0.0")]
6666
impl<T> Drop for VecDeque<T> {
67+
#[unsafe_destructor_blind_to_params]
6768
fn drop(&mut self) {
6869
self.clear();
6970
// RawVec handles deallocation

0 commit comments

Comments
 (0)