diff --git a/src/doc/reference.md b/src/doc/reference.md index 2fce37eccae1..5be180007f99 100644 --- a/src/doc/reference.md +++ b/src/doc/reference.md @@ -2022,6 +2022,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/librustc/middle/ty/util.rs b/src/librustc/middle/ty/util.rs index b546438f392a..43757df3d3da 100644 --- a/src/librustc/middle/ty/util.rs +++ b/src/librustc/middle/ty/util.rs @@ -578,6 +578,16 @@ impl<'tcx> ty::ctxt<'tcx> { }); let generics = adt.type_scheme(self).generics; + // 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 via a type + // parameter, even if such data is otherwise reachable. + if self.has_attr(dtor_method, "unsafe_destructor_blind_to_params") { + debug!("typ: {:?} assumed blind and thus is dtorck-safe", adt); + return false; + } + // In `impl<'a> Drop ...`, we automatically assume // `'a` is meaningful and thus represents a bound // through which we could reach borrowed data. @@ -592,6 +602,14 @@ impl<'tcx> ty::ctxt<'tcx> { return true; } + // RFC 1238: *any* type parameter at all makes this a dtor of + // interest (i.e. cannot-assume-parametricity from RFC 1238.) + if generics.has_type_params(subst::TypeSpace) { + debug!("typ: {:?} has interesting dtor due to type 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() { diff --git a/src/librustc_typeck/check/dropck.rs b/src/librustc_typeck/check/dropck.rs index 870a81e510ee..941fa5f99108 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/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 18c6d74d62ec..e364716bdf29 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 + // (Needs an RFC link) + ("unsafe_destructor_blind_to_params", "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("unsafe_destructor_blind_to_params", + "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