diff --git a/src/doc/nomicon/dropck.md b/src/doc/nomicon/dropck.md index 98d269ff762..7c097c91266 100644 --- a/src/doc/nomicon/dropck.md +++ b/src/doc/nomicon/dropck.md @@ -115,13 +115,162 @@ section: **For a generic type to soundly implement drop, its generics arguments must strictly outlive it.** -This rule is sufficient but not necessary to satisfy the drop checker. That is, -if your type obeys this rule then it's definitely sound to drop. However -there are special cases where you can fail to satisfy this, but still -successfully pass the borrow checker. These are the precise rules that are -currently up in the air. +Obeying this rule is (usually) necessary to satisfy the borrow +checker; obeying it is sufficient but not necessary to be +sound. That is, if your type obeys this rule then it's definitely +sound to drop. + +The reason that it is not always necessary to satisfy the above rule +is that some Drop implementations will not access borrowed data even +though their type gives them the capability for such access. + +For example, this variant of the above `Inspector` example will never +accessed borrowed data: + +```rust,ignore +struct Inspector<'a>(&'a u8, &'static str); + +impl<'a> Drop for Inspector<'a> { + fn drop(&mut self) { + println!("Inspector(_, {}) knows when *not* to inspect.", self.1); + } +} + +fn main() { + let (inspector, days); + days = Box::new(1); + inspector = Inspector(&days, "gadget"); + // Let's say `days` happens to get dropped first. + // Even when Inspector is dropped, its destructor will not access the + // borrowed `days`. +} +``` + +Likewise, this variant will also never access borrowed data: + +```rust,ignore +use std::fmt; + +struct Inspector(T, &'static str); + +impl Drop for Inspector { + fn drop(&mut self) { + println!("Inspector(_, {}) knows when *not* to inspect.", self.1); + } +} + +fn main() { + let (inspector, days): (Inspector<&u8>, Box); + days = Box::new(1); + inspector = Inspector(&days, "gadget"); + // Let's say `days` happens to get dropped first. + // Even when Inspector is dropped, its destructor will not access the + // borrowed `days`. +} +``` + +However, *both* of the above variants are rejected by the borrow +checker during the analysis of `fn main`, saying that `days` does not +live long enough. + +The reason is that the borrow checking analysis of `main` does not +know about the internals of each Inspector's Drop implementation. As +far as the borrow checker knows while it is analyzing `main`, the body +of an inspector's destructor might access that borrowed data. + +Therefore, the drop checker forces all borrowed data in a value to +strictly outlive that value. + +# An Escape Hatch + +The precise rules that govern drop checking may be less restrictive in +the future. + +The current analysis is deliberately conservative and trivial; it forces all +borrowed data in a value to outlive that value, which is certainly sound. + +Future versions of the language may make the analysis more precise, to +reduce the number of cases where sound code is rejected as unsafe. +This would help address cases such as the two Inspectors above that +know not to inspect during destruction. + +In the meantime, there is an unstable attribute that one can use to +assert (unsafely) that a generic type's destructor is *guaranteed* to +not access any expired data, even if its type gives it the capability +to do so. + +That attribute is called `unsafe_destructor_blind_to_params`. +To deploy it on the Inspector example from above, we would write: + +```rust,ignore +struct Inspector<'a>(&'a u8, &'static str); + +impl<'a> Drop for Inspector<'a> { + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { + println!("Inspector(_, {}) knows when *not* to inspect.", self.1); + } +} +``` + +This attribute has the word `unsafe` in it because the compiler is not +checking the implicit assertion that no potentially expired data +(e.g. `self.0` above) is accessed. + +It is sometimes obvious that no such access can occur, like the case above. +However, when dealing with a generic type parameter, such access can +occur indirectly. Examples of such indirect access are: + * invoking a callback, + * via a trait method call. + +(Future changes to the language, such as impl specialization, may add +other avenues for such indirect access.) + +Here is an example of invoking a callback: + +```rust,ignore +struct Inspector(T, &'static str, Box fn(&'r T) -> String>); + +impl Drop for Inspector { + fn drop(&mut self) { + // The `self.2` call could access a borrow e.g. if `T` is `&'a _`. + println!("Inspector({}, {}) unwittingly inspects expired data.", + (self.2)(&self.0), self.1); + } +} +``` + +Here is an example of a trait method call: + +```rust,ignore +use std::fmt; + +struct Inspector(T, &'static str); + +impl Drop for Inspector { + fn drop(&mut self) { + // There is a hidden call to `::fmt` below, which + // could access a borrow e.g. if `T` is `&'a _` + println!("Inspector({}, {}) unwittingly inspects expired data.", + self.0, self.1); + } +} +``` + +And of course, all of these accesses could be further hidden within +some other method invoked by the destructor, rather than being written +directly within it. + +In all of the above cases where the `&'a u8` is accessed in the +destructor, adding the `#[unsafe_destructor_blind_to_params]` +attribute makes the type vulnerable to misuse that the borrower +checker will not catch, inviting havoc. It is better to avoid adding +the attribute. + +# Is that all about drop checker? It turns out that when writing unsafe code, we generally don't need to worry at all about doing the right thing for the drop checker. However there is one special case that you need to worry about, which we will look at in the next section. + diff --git a/src/doc/reference.md b/src/doc/reference.md index c85b7973ee7..ac0fbea8ad8 100644 --- a/src/doc/reference.md +++ b/src/doc/reference.md @@ -1929,6 +1929,20 @@ macro scope. - `simd` - on certain tuple structs, derive the arithmetic operators, which lower to the target's SIMD instructions, if any; the `simd` feature gate is necessary to use this attribute. +- `unsafe_destructor_blind_to_params` - on `Drop::drop` method, asserts that the + destructor code (and all potential specializations of that code) will + never attempt to read from nor write to any references with lifetimes + that come in via generic parameters. This is a constraint we cannot + currently express via the type system, and therefore we rely on the + programmer to assert that it holds. Adding this to a Drop impl causes + the associated destructor to be considered "uninteresting" by the + Drop-Check rule, and thus it can help sidestep data ordering + constraints that would otherwise be introduced by the Drop-Check + rule. Such sidestepping of the constraints, if done incorrectly, can + lead to undefined behavior (in the form of reading or writing to data + outside of its dynamic extent), and thus this attribute has the word + "unsafe" in its name. To use this, the + `unsafe_destructor_blind_to_params` feature gate must be enabled. - `unsafe_no_drop_flag` - on structs, remove the flag that prevents destructors from being run twice. Destructors might be run multiple times on the same object with this attribute. To use this, the `unsafe_no_drop_flag` feature diff --git a/src/liballoc/arc.rs b/src/liballoc/arc.rs index f66f1f13dcb..36989c2daad 100644 --- a/src/liballoc/arc.rs +++ b/src/liballoc/arc.rs @@ -550,6 +550,7 @@ impl Drop for Arc { /// /// } // implicit drop /// ``` + #[unsafe_destructor_blind_to_params] #[inline] fn drop(&mut self) { // This structure has #[unsafe_no_drop_flag], so this drop glue may run diff --git a/src/liballoc/lib.rs b/src/liballoc/lib.rs index 98c729aaba4..8ecc78a231e 100644 --- a/src/liballoc/lib.rs +++ b/src/liballoc/lib.rs @@ -94,6 +94,11 @@ #![feature(unboxed_closures)] #![feature(unique)] #![feature(unsafe_no_drop_flag, filling_drop)] +// SNAP 1af31d4 +#![allow(unused_features)] +// SNAP 1af31d4 +#![allow(unused_attributes)] +#![feature(dropck_parametricity)] #![feature(unsize)] #![feature(core_slice_ext)] #![feature(core_str_ext)] diff --git a/src/liballoc/raw_vec.rs b/src/liballoc/raw_vec.rs index dd2db6fab08..49d37698154 100644 --- a/src/liballoc/raw_vec.rs +++ b/src/liballoc/raw_vec.rs @@ -445,6 +445,7 @@ impl RawVec { } impl Drop for RawVec { + #[unsafe_destructor_blind_to_params] /// Frees the memory owned by the RawVec *without* trying to Drop its contents. fn drop(&mut self) { let elem_size = mem::size_of::(); diff --git a/src/liballoc/rc.rs b/src/liballoc/rc.rs index 2f0bf1281b3..06d03161ca1 100644 --- a/src/liballoc/rc.rs +++ b/src/liballoc/rc.rs @@ -451,6 +451,7 @@ impl Drop for Rc { /// /// } // implicit drop /// ``` + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { unsafe { let ptr = *self._ptr; diff --git a/src/libarena/lib.rs b/src/libarena/lib.rs index 57e82720e8b..62463ecabbf 100644 --- a/src/libarena/lib.rs +++ b/src/libarena/lib.rs @@ -38,8 +38,14 @@ #![feature(ptr_as_ref)] #![feature(raw)] #![feature(staged_api)] +#![feature(dropck_parametricity)] #![cfg_attr(test, feature(test))] +// SNAP 1af31d4 +#![allow(unused_features)] +// SNAP 1af31d4 +#![allow(unused_attributes)] + extern crate alloc; use std::cell::{Cell, RefCell}; @@ -510,6 +516,7 @@ impl TypedArena { } impl Drop for TypedArena { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { unsafe { // Determine how much was filled. diff --git a/src/libcollections/btree/node.rs b/src/libcollections/btree/node.rs index 5ac8a698e98..bde0d0e6b5f 100644 --- a/src/libcollections/btree/node.rs +++ b/src/libcollections/btree/node.rs @@ -275,12 +275,14 @@ impl DoubleEndedIterator for RawItems { } impl Drop for RawItems { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { for _ in self {} } } impl Drop for Node { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { if self.keys.is_null() || (unsafe { self.keys.get() as *const K as usize == mem::POST_DROP_USIZE }) @@ -1419,6 +1421,7 @@ impl TraversalImpl for MoveTraversalImpl { } impl Drop for MoveTraversalImpl { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { // We need to cleanup the stored values manually, as the RawItems destructor would run // after our deallocation. diff --git a/src/libcollections/lib.rs b/src/libcollections/lib.rs index 03ee8ba31b1..4292c200fbe 100644 --- a/src/libcollections/lib.rs +++ b/src/libcollections/lib.rs @@ -32,6 +32,11 @@ #![allow(trivial_casts)] #![cfg_attr(test, allow(deprecated))] // rand +// SNAP 1af31d4 +#![allow(unused_features)] +// SNAP 1af31d4 +#![allow(unused_attributes)] + #![feature(alloc)] #![feature(box_patterns)] #![feature(box_syntax)] @@ -59,6 +64,7 @@ #![feature(unboxed_closures)] #![feature(unicode)] #![feature(unique)] +#![feature(dropck_parametricity)] #![feature(unsafe_no_drop_flag, filling_drop)] #![feature(decode_utf16)] #![feature(utf8_error)] diff --git a/src/libcollections/linked_list.rs b/src/libcollections/linked_list.rs index 891e8b7b2c9..fca7d3b26fc 100644 --- a/src/libcollections/linked_list.rs +++ b/src/libcollections/linked_list.rs @@ -655,6 +655,7 @@ impl LinkedList { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for LinkedList { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { // Dissolve the linked_list in a loop. // Just dropping the list_head can lead to stack exhaustion diff --git a/src/libcollections/vec.rs b/src/libcollections/vec.rs index bcde523307c..d374c0959f3 100644 --- a/src/libcollections/vec.rs +++ b/src/libcollections/vec.rs @@ -1385,6 +1385,7 @@ impl Ord for Vec { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for Vec { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { // NOTE: this is currently abusing the fact that ZSTs can't impl Drop. // Or rather, that impl'ing Drop makes them not zero-sized. This is diff --git a/src/libcollections/vec_deque.rs b/src/libcollections/vec_deque.rs index f7efe9a38df..d438c27a96f 100644 --- a/src/libcollections/vec_deque.rs +++ b/src/libcollections/vec_deque.rs @@ -64,6 +64,7 @@ impl Clone for VecDeque { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for VecDeque { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { self.clear(); // RawVec handles deallocation diff --git a/src/librustc/middle/ty/util.rs b/src/librustc/middle/ty/util.rs index b546438f392..a8f7a4db133 100644 --- a/src/librustc/middle/ty/util.rs +++ b/src/librustc/middle/ty/util.rs @@ -566,84 +566,34 @@ impl<'tcx> ty::ctxt<'tcx> { } } - /// Returns true if this ADT is a dtorck type, i.e. whether it being - /// safe for destruction requires it to be alive + /// Returns true if this ADT is a dtorck type. + /// + /// Invoking the destructor of a dtorck type during usual cleanup + /// (e.g. the glue emitted for stack unwinding) requires all + /// lifetimes in the type-structure of `adt` to strictly outlive + /// the adt value itself. + /// + /// If `adt` is not dtorck, then the adt's destructor can be + /// invoked even when there are lifetimes in the type-structure of + /// `adt` that do not strictly outlive the adt value itself. + /// (This allows programs to make cyclic structures without + /// resorting to unasfe means; see RFCs 769 and 1238). pub fn is_adt_dtorck(&self, adt: ty::AdtDef<'tcx>) -> bool { let dtor_method = match adt.destructor() { Some(dtor) => dtor, None => return false }; - let impl_did = self.impl_of_method(dtor_method).unwrap_or_else(|| { - self.sess.bug(&format!("no Drop impl for the dtor of `{:?}`", adt)) - }); - let generics = adt.type_scheme(self).generics; - // In `impl<'a> Drop ...`, we automatically assume - // `'a` is meaningful and thus represents a bound - // through which we could reach borrowed data. + // RFC 1238: if the destructor method is tagged with the + // attribute `unsafe_destructor_blind_to_params`, then the + // compiler is being instructed to *assume* that the + // destructor will not access borrowed data, + // even if such data is otherwise reachable. // - // FIXME (pnkfelix): In the future it would be good to - // extend the language to allow the user to express, - // in the impl signature, that a lifetime is not - // actually used (something like `where 'a: ?Live`). - if generics.has_region_params(subst::TypeSpace) { - debug!("typ: {:?} has interesting dtor due to region params", - adt); - return true; - } - - let mut seen_items = Vec::new(); - let mut items_to_inspect = vec![impl_did]; - while let Some(item_def_id) = items_to_inspect.pop() { - if seen_items.contains(&item_def_id) { - continue; - } - - for pred in self.lookup_predicates(item_def_id).predicates { - let result = match pred { - ty::Predicate::Equate(..) | - ty::Predicate::RegionOutlives(..) | - ty::Predicate::TypeOutlives(..) | - ty::Predicate::WellFormed(..) | - ty::Predicate::ObjectSafe(..) | - ty::Predicate::Projection(..) => { - // For now, assume all these where-clauses - // may give drop implementation capabilty - // to access borrowed data. - true - } - - ty::Predicate::Trait(ty::Binder(ref t_pred)) => { - let def_id = t_pred.trait_ref.def_id; - if self.trait_items(def_id).len() != 0 { - // If trait has items, assume it adds - // capability to access borrowed data. - true - } else { - // Trait without items is itself - // uninteresting from POV of dropck. - // - // However, may have parent w/ items; - // so schedule checking of predicates, - items_to_inspect.push(def_id); - // and say "no capability found" for now. - false - } - } - }; - - if result { - debug!("typ: {:?} has interesting dtor due to generic preds, e.g. {:?}", - adt, pred); - return true; - } - } - - seen_items.push(item_def_id); - } - - debug!("typ: {:?} is dtorck-safe", adt); - false + // Such access can be in plain sight (e.g. dereferencing + // `*foo.0` of `Foo<'a>(&'a u32)`) or indirectly hidden + // (e.g. calling `foo.0.clone()` of `Foo`). + return !self.has_attr(dtor_method, "unsafe_destructor_blind_to_params"); } } diff --git a/src/librustc_typeck/check/dropck.rs b/src/librustc_typeck/check/dropck.rs index dfe8bd78c1b..d68959b99be 100644 --- a/src/librustc_typeck/check/dropck.rs +++ b/src/librustc_typeck/check/dropck.rs @@ -217,19 +217,16 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( /// /// ---- /// -/// The Drop Check Rule is the following: +/// The simplified (*) Drop Check Rule is the following: /// /// Let `v` be some value (either temporary or named) and 'a be some /// lifetime (scope). If the type of `v` owns data of type `D`, where /// -/// * (1.) `D` has a lifetime- or type-parametric Drop implementation, and -/// * (2.) the structure of `D` can reach a reference of type `&'a _`, and -/// * (3.) either: -/// * (A.) the Drop impl for `D` instantiates `D` at 'a directly, -/// i.e. `D<'a>`, or, -/// * (B.) the Drop impl for `D` has some type parameter with a -/// trait bound `T` where `T` is a trait that has at least -/// one method, +/// * (1.) `D` has a lifetime- or type-parametric Drop implementation, +/// (where that `Drop` implementation does not opt-out of +/// this check via the `unsafe_destructor_blind_to_params` +/// attribute), and +/// * (2.) the structure of `D` can reach a reference of type `&'a _`, /// /// then 'a must strictly outlive the scope of v. /// @@ -237,6 +234,35 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( /// /// This function is meant to by applied to the type for every /// expression in the program. +/// +/// ---- +/// +/// (*) The qualifier "simplified" is attached to the above +/// definition of the Drop Check Rule, because it is a simplification +/// of the original Drop Check rule, which attempted to prove that +/// some `Drop` implementations could not possibly access data even if +/// it was technically reachable, due to parametricity. +/// +/// However, (1.) parametricity on its own turned out to be a +/// necessary but insufficient condition, and (2.) future changes to +/// the language are expected to make it impossible to ensure that a +/// `Drop` implementation is actually parametric with respect to any +/// particular type parameter. (In particular, impl specialization is +/// expected to break the needed parametricity property beyond +/// repair.) +/// +/// Therefore we have scaled back Drop-Check to a more conservative +/// rule that does not attempt to deduce whether a `Drop` +/// implementation could not possible access data of a given lifetime; +/// instead Drop-Check now simply assumes that if a destructor has +/// access (direct or indirect) to a lifetime parameter, then that +/// lifetime must be forced to outlive that destructor's dynamic +/// extent. We then provide the `unsafe_destructor_blind_to_params` +/// attribute as a way for destructor implementations to opt-out of +/// this conservative assumption (and thus assume the obligation of +/// ensuring that they do not access data nor invoke methods of +/// values that have been previously dropped). +/// pub fn check_safety_of_destructor_if_necessary<'a, 'tcx>(rcx: &mut Rcx<'a, 'tcx>, typ: ty::Ty<'tcx>, span: Span, @@ -356,13 +382,18 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'b, 'tcx>( // borrowed data reachable via `typ` must outlive the parent // of `scope`. This is handled below. // - // However, there is an important special case: by - // parametricity, any generic type parameters have *no* trait - // bounds in the Drop impl can not be used in any way (apart - // from being dropped), and thus we can treat data borrowed - // via such type parameters remains unreachable. + // However, there is an important special case: for any Drop + // impl that is tagged as "blind" to their parameters, + // we assume that data borrowed via such type parameters + // remains unreachable via that Drop impl. + // + // For example, consider: + // + // ```rust + // #[unsafe_destructor_blind_to_params] + // impl Drop for Vec { ... } + // ``` // - // For example, consider `impl Drop for Vec { ... }`, // which does have to be able to drop instances of `T`, but // otherwise cannot read data from `T`. // @@ -370,16 +401,6 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'b, 'tcx>( // unbounded type parameter `T`, we must resume the recursive // analysis on `T` (since it would be ignored by // type_must_outlive). - // - // FIXME (pnkfelix): Long term, we could be smart and actually - // feed which generic parameters can be ignored *into* `fn - // type_must_outlive` (or some generalization thereof). But - // for the short term, it probably covers most cases of - // interest to just special case Drop impls where: (1.) there - // are no generic lifetime parameters and (2.) *all* generic - // type parameters are unbounded. If both conditions hold, we - // simply skip the `type_must_outlive` call entirely (but - // resume the recursive checking of the type-substructure). if has_dtor_of_interest(tcx, ty) { debug!("iterate_over_potentially_unsafe_regions_in_type \ {}ty: {} - is a dtorck type!", diff --git a/src/libstd/collections/hash/table.rs b/src/libstd/collections/hash/table.rs index 32541353a0a..e8796dd10b4 100644 --- a/src/libstd/collections/hash/table.rs +++ b/src/libstd/collections/hash/table.rs @@ -999,6 +999,7 @@ impl Clone for RawTable { } impl Drop for RawTable { + #[unsafe_destructor_blind_to_params] fn drop(&mut self) { if self.capacity == 0 || self.capacity == mem::POST_DROP_USIZE { return; diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 10c7190ca0c..9af766ad2af 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -199,6 +199,11 @@ test(no_crate_inject, attr(deny(warnings))), test(attr(allow(dead_code, deprecated, unused_variables, unused_mut))))] +// SNAP 1af31d4 +#![allow(unused_features)] +// SNAP 1af31d4 +#![allow(unused_attributes)] + #![feature(alloc)] #![feature(allow_internal_unstable)] #![feature(associated_consts)] @@ -241,6 +246,7 @@ #![feature(unboxed_closures)] #![feature(unicode)] #![feature(unique)] +#![feature(dropck_parametricity)] #![feature(unsafe_no_drop_flag, filling_drop)] #![feature(decode_utf16)] #![feature(unwind_attributes)] diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 8efeaa330c0..1d545268e57 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -136,6 +136,10 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option, Status // switch to Accepted; see RFC 320) ("unsafe_no_drop_flag", "1.0.0", None, Active), + // Allows using the unsafe_destructor_blind_to_params attribute; + // RFC 1238 + ("dropck_parametricity", "1.3.0", Some(28498), Active), + // Allows the use of custom attributes; RFC 572 ("custom_attribute", "1.0.0", None, Active), @@ -339,6 +343,11 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat ("unsafe_no_drop_flag", Whitelisted, Gated("unsafe_no_drop_flag", "unsafe_no_drop_flag has unstable semantics \ and may be removed in the future")), + ("unsafe_destructor_blind_to_params", + Normal, + Gated("dropck_parametricity", + "unsafe_destructor_blind_to_params has unstable semantics \ + and may be removed in the future")), ("unwind", Whitelisted, Gated("unwind_attributes", "#[unwind] is experimental")), // used in resolve diff --git a/src/test/compile-fail/feature-gate-dropck-ugeh.rs b/src/test/compile-fail/feature-gate-dropck-ugeh.rs new file mode 100644 index 00000000000..aa479a987a3 --- /dev/null +++ b/src/test/compile-fail/feature-gate-dropck-ugeh.rs @@ -0,0 +1,40 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Ensure that attempts to use the unsafe attribute are feature-gated. + +// Example adapted from RFC 1238 text (just left out the feature gate). + +// https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md +// #example-of-the-unguarded-escape-hatch + +// #![feature(dropck_parametricity)] + +use std::cell::Cell; + +struct Concrete<'a>(u32, Cell>>); + +struct Foo { data: Vec } + +impl Drop for Foo { + #[unsafe_destructor_blind_to_params] // This is the UGEH attribute + //~^ ERROR unsafe_destructor_blind_to_params has unstable semantics + fn drop(&mut self) { } +} + +fn main() { + let mut foo = Foo { data: Vec::new() }; + foo.data.push(Concrete(0, Cell::new(None))); + foo.data.push(Concrete(0, Cell::new(None))); + + foo.data[0].1.set(Some(&foo.data[1])); + foo.data[1].1.set(Some(&foo.data[0])); +} + diff --git a/src/test/compile-fail/issue28498-reject-ex1.rs b/src/test/compile-fail/issue28498-reject-ex1.rs new file mode 100644 index 00000000000..cee7c57c201 --- /dev/null +++ b/src/test/compile-fail/issue28498-reject-ex1.rs @@ -0,0 +1,48 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Example taken from RFC 1238 text + +// https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md +// #examples-of-code-that-will-start-to-be-rejected + +// Compare against test/run-pass/issue28498-must-work-ex2.rs + +use std::cell::Cell; + +struct Concrete<'a>(u32, Cell>>); + +struct Foo { data: Vec } + +fn potentially_specialized_wrt_t(t: &T) { + // Hypothetical code that does one thing for generic T and then is + // specialized for T == Concrete (and the specialized form can + // then access a reference held in concrete tuple). + // + // (We don't have specialization yet, but we want to allow for it + // in the future.) +} + +impl Drop for Foo { + fn drop(&mut self) { + potentially_specialized_wrt_t(&self.data[0]) + } +} + +fn main() { + let mut foo = Foo { data: Vec::new() }; + foo.data.push(Concrete(0, Cell::new(None))); + foo.data.push(Concrete(0, Cell::new(None))); + + foo.data[0].1.set(Some(&foo.data[1])); + //~^ ERROR `foo.data` does not live long enough + foo.data[1].1.set(Some(&foo.data[0])); + //~^ ERROR `foo.data` does not live long enough +} diff --git a/src/test/compile-fail/issue28498-reject-lifetime-param.rs b/src/test/compile-fail/issue28498-reject-lifetime-param.rs new file mode 100644 index 00000000000..92028c7a818 --- /dev/null +++ b/src/test/compile-fail/issue28498-reject-lifetime-param.rs @@ -0,0 +1,48 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate that having a lifetime param causes dropck to reject code +// that might indirectly access previously dropped value. +// +// Compare with run-pass/issue28498-ugeh-with-lifetime-param.rs + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo<'a>(u32, &'a ScribbleOnDrop); + +impl<'a> Drop for Foo<'a> { + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is unsound, + // because destructor accesses borrowed data in `self.1` + // and we must force that to strictly outlive `self`. + println!("Dropping Foo({}, {:?})", self.0, self.1); + } +} + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped); + //~^ ERROR `last_dropped` does not live long enough + foo1 = Foo(1, &first_dropped); + //~^ ERROR `first_dropped` does not live long enough + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +} diff --git a/src/test/compile-fail/issue28498-reject-passed-to-fn.rs b/src/test/compile-fail/issue28498-reject-passed-to-fn.rs new file mode 100644 index 00000000000..27378b1e0be --- /dev/null +++ b/src/test/compile-fail/issue28498-reject-passed-to-fn.rs @@ -0,0 +1,50 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate that a type param in negative position causes dropck to reject code +// that might indirectly access previously dropped value. +// +// Compare with run-pass/issue28498-ugeh-with-passed-to-fn.rs + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo(u32, T, Box fn(&'r T) -> String>); + +impl Drop for Foo { + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is unsound, + // because we pass `T` to the callback in `self.2` + // below, and thus potentially read from borrowed data. + println!("Dropping Foo({}, {})", self.0, (self.2)(&self.1)); + } +} + +fn callback(s: & &ScribbleOnDrop) -> String { format!("{:?}", s) } + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped, Box::new(callback)); + //~^ ERROR `last_dropped` does not live long enough + foo1 = Foo(1, &first_dropped, Box::new(callback)); + //~^ ERROR `first_dropped` does not live long enough + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +} diff --git a/src/test/compile-fail/issue28498-reject-trait-bound.rs b/src/test/compile-fail/issue28498-reject-trait-bound.rs new file mode 100644 index 00000000000..3904d68ba19 --- /dev/null +++ b/src/test/compile-fail/issue28498-reject-trait-bound.rs @@ -0,0 +1,50 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate that having a trait bound causes dropck to reject code +// that might indirectly access previously dropped value. +// +// Compare with run-pass/issue28498-ugeh-with-trait-bound.rs + +use std::fmt; + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo(u32, T); + +impl Drop for Foo { + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is unsound, + // because we access `T` fmt method when we pass `self.1` + // below, and thus potentially read from borrowed data. + println!("Dropping Foo({}, {:?})", self.0, self.1); + } +} + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped); + //~^ ERROR `last_dropped` does not live long enough + foo1 = Foo(1, &first_dropped); + //~^ ERROR `first_dropped` does not live long enough + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +} diff --git a/src/test/run-pass/issue-24805-dropck-itemless.rs b/src/test/run-pass/issue-24805-dropck-itemless.rs index 4512bcc2000..9fa48202298 100644 --- a/src/test/run-pass/issue-24805-dropck-itemless.rs +++ b/src/test/run-pass/issue-24805-dropck-itemless.rs @@ -13,6 +13,8 @@ #![allow(non_camel_case_types)] +#![feature(dropck_parametricity)] + trait UserDefined { } impl UserDefined for i32 { } @@ -26,7 +28,10 @@ impl<'a, T> UserDefined for &'a T { } macro_rules! impl_drop { ($Bound:ident, $Id:ident) => { struct $Id(T); - impl Drop for $Id { fn drop(&mut self) { } } + impl Drop for $Id { + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { } + } } } diff --git a/src/test/run-pass/issue28498-must-work-ex1.rs b/src/test/run-pass/issue28498-must-work-ex1.rs new file mode 100644 index 00000000000..83a978d57b9 --- /dev/null +++ b/src/test/run-pass/issue28498-must-work-ex1.rs @@ -0,0 +1,27 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Example taken from RFC 1238 text + +// https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md +// #examples-of-code-that-must-continue-to-work + +use std::cell::Cell; + +struct Concrete<'a>(u32, Cell>>); + +fn main() { + let mut data = Vec::new(); + data.push(Concrete(0, Cell::new(None))); + data.push(Concrete(0, Cell::new(None))); + + data[0].1.set(Some(&data[1])); + data[1].1.set(Some(&data[0])); +} diff --git a/src/test/run-pass/issue28498-must-work-ex2.rs b/src/test/run-pass/issue28498-must-work-ex2.rs new file mode 100644 index 00000000000..93652a1b612 --- /dev/null +++ b/src/test/run-pass/issue28498-must-work-ex2.rs @@ -0,0 +1,30 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Example taken from RFC 1238 text + +// https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md +// #examples-of-code-that-must-continue-to-work + +use std::cell::Cell; + +struct Concrete<'a>(u32, Cell>>); + +struct Foo { data: Vec } + +fn main() { + let mut foo = Foo { data: Vec::new() }; + foo.data.push(Concrete(0, Cell::new(None))); + foo.data.push(Concrete(0, Cell::new(None))); + + foo.data[0].1.set(Some(&foo.data[1])); + foo.data[1].1.set(Some(&foo.data[0])); +} + diff --git a/src/test/run-pass/issue28498-ugeh-ex1.rs b/src/test/run-pass/issue28498-ugeh-ex1.rs new file mode 100644 index 00000000000..b07831f552c --- /dev/null +++ b/src/test/run-pass/issue28498-ugeh-ex1.rs @@ -0,0 +1,37 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Example taken from RFC 1238 text + +// https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md +// #example-of-the-unguarded-escape-hatch + +#![feature(dropck_parametricity)] +use std::cell::Cell; + +struct Concrete<'a>(u32, Cell>>); + +struct Foo { data: Vec } + +impl Drop for Foo { + // Below is the UGEH attribute + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { } +} + +fn main() { + let mut foo = Foo { data: Vec::new() }; + foo.data.push(Concrete(0, Cell::new(None))); + foo.data.push(Concrete(0, Cell::new(None))); + + foo.data[0].1.set(Some(&foo.data[1])); + foo.data[1].1.set(Some(&foo.data[0])); +} + diff --git a/src/test/run-pass/issue28498-ugeh-with-lifetime-param.rs b/src/test/run-pass/issue28498-ugeh-with-lifetime-param.rs new file mode 100644 index 00000000000..19be2568e4f --- /dev/null +++ b/src/test/run-pass/issue28498-ugeh-with-lifetime-param.rs @@ -0,0 +1,48 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate the use of the unguarded escape hatch with a lifetime param +// to assert that destructor will not access any dead data. +// +// Compare with compile-fail/issue28498-reject-lifetime-param.rs + +#![feature(dropck_parametricity)] + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo<'a>(u32, &'a ScribbleOnDrop); + +impl<'a> Drop for Foo<'a> { + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is sound, + // because destructor never accesses `self.1`. + println!("Dropping Foo({}, _)", self.0); + } +} + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped); + foo1 = Foo(1, &first_dropped); + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +} diff --git a/src/test/run-pass/issue28498-ugeh-with-passed-to-fn.rs b/src/test/run-pass/issue28498-ugeh-with-passed-to-fn.rs new file mode 100644 index 00000000000..bb430ea3e33 --- /dev/null +++ b/src/test/run-pass/issue28498-ugeh-with-passed-to-fn.rs @@ -0,0 +1,56 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate the use of the unguarded escape hatch with a type param in negative position +// to assert that destructor will not access any dead data. +// +// Compare with compile-fail/issue28498-reject-lifetime-param.rs + +// Demonstrate that a type param in negative position causes dropck to reject code +// that might indirectly access previously dropped value. +// +// Compare with run-pass/issue28498-ugeh-with-passed-to-fn.rs + +#![feature(dropck_parametricity)] + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo(u32, T, Box fn(&'r T) -> String>); + +impl Drop for Foo { + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is sound, + // because destructor never passes a `self.1` to the callback + // (in `self.2`) despite having it available. + println!("Dropping Foo({}, _)", self.0); + } +} + +fn callback(s: & &ScribbleOnDrop) -> String { format!("{:?}", s) } + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped, Box::new(callback)); + foo1 = Foo(1, &first_dropped, Box::new(callback)); + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +} diff --git a/src/test/run-pass/issue28498-ugeh-with-trait-bound.rs b/src/test/run-pass/issue28498-ugeh-with-trait-bound.rs new file mode 100644 index 00000000000..2e9633b3a21 --- /dev/null +++ b/src/test/run-pass/issue28498-ugeh-with-trait-bound.rs @@ -0,0 +1,51 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Demonstrate the use of the unguarded escape hatch with a trait bound +// to assert that destructor will not access any dead data. +// +// Compare with compile-fail/issue28498-reject-trait-bound.rs + +#![feature(dropck_parametricity)] + +use std::fmt; + +#[derive(Debug)] +struct ScribbleOnDrop(String); + +impl Drop for ScribbleOnDrop { + fn drop(&mut self) { + self.0 = format!("DROPPED"); + } +} + +struct Foo(u32, T); + +impl Drop for Foo { + #[unsafe_destructor_blind_to_params] + fn drop(&mut self) { + // Use of `unsafe_destructor_blind_to_params` is sound, + // because destructor never accesses the `Debug::fmt` method + // of `T`, despite having it available. + println!("Dropping Foo({}, _)", self.0); + } +} + +fn main() { + let (last_dropped, foo0); + let (foo1, first_dropped); + + last_dropped = ScribbleOnDrop(format!("last")); + first_dropped = ScribbleOnDrop(format!("first")); + foo0 = Foo(0, &last_dropped); + foo1 = Foo(1, &first_dropped); + + println!("foo0.1: {:?} foo1.1: {:?}", foo0.1, foo1.1); +}