dropck: treat parametric types as safe for dropping.

Handles e.g. `impl<T> Drop for Vec<T>` as parametric: If `T` does not
have any drop code that could read from borrowed data of lifetime `'a`,
then we infer that the drop code for `Vec<T>` also cannot read from
borrowed data of lifetime `'a`, and therefore we do not need to inject
the SafeDestructor constraint for it.

Notably, this enables us to continue storing cyclic structure, without
any `unsafe` code, in `Vec`, without allowing (unsound) destructors on
such cyclic data. (Later commits have tests illustrating these two
cases in run-pass and compile-fail, respectively.)

(This is "Condition (B.)" in Drop-Check rule described in RFC 769.)
This commit is contained in:
Felix S. Klock II 2015-01-21 23:43:29 +01:00
parent f90c3864b6
commit f51176df01

View File

@ -12,6 +12,7 @@ use check::regionck::{self, Rcx};
use middle::infer;
use middle::region;
use middle::subst;
use middle::ty::{self, Ty};
use util::ppaux::{Repr};
@ -46,6 +47,10 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>(
{
let origin = |&:| infer::SubregionOrigin::SafeDestructor(span);
let mut walker = ty_root.walk();
let opt_phantom_data_def_id = rcx.tcx().lang_items.phantom_data();
let destructor_for_type = rcx.tcx().destructor_for_type.borrow();
while let Some(typ) = walker.next() {
// Avoid recursing forever.
if breadcrumbs.contains(&typ) {
@ -53,24 +58,196 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>(
}
breadcrumbs.push(typ);
let has_dtor = match typ.sty {
ty::ty_struct(struct_did, _) => ty::has_dtor(rcx.tcx(), struct_did),
ty::ty_enum(enum_did, _) => ty::has_dtor(rcx.tcx(), enum_did),
_ => false,
// If we encounter `PhantomData<T>`, then we should replace it
// with `T`, the type it represents as owned by the
// surrounding context, before doing further analysis.
let typ = if let ty::ty_struct(struct_did, substs) = typ.sty {
if opt_phantom_data_def_id == Some(struct_did) {
let item_type = ty::lookup_item_type(rcx.tcx(), struct_did);
let tp_def = item_type.generics.types
.opt_get(subst::TypeSpace, 0).unwrap();
let new_typ = substs.type_for_def(tp_def);
debug!("replacing phantom {} with {}",
typ.repr(rcx.tcx()), new_typ.repr(rcx.tcx()));
new_typ
} else {
typ
}
} else {
typ
};
debug!("iterate_over_potentially_unsafe_regions_in_type \
{}typ: {} scope: {:?} has_dtor: {}",
(0..depth).map(|_| ' ').collect::<String>(),
typ.repr(rcx.tcx()), scope, has_dtor);
let opt_type_did = match typ.sty {
ty::ty_struct(struct_did, _) => Some(struct_did),
ty::ty_enum(enum_did, _) => Some(enum_did),
_ => None,
};
if has_dtor {
let opt_dtor =
opt_type_did.and_then(|did| destructor_for_type.get(&did));
debug!("iterate_over_potentially_unsafe_regions_in_type \
{}typ: {} scope: {:?} opt_dtor: {:?}",
(0..depth).map(|_| ' ').collect::<String>(),
typ.repr(rcx.tcx()), scope, opt_dtor);
// If `typ` has a destructor, then we must ensure that all
// 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.
//
// For example, consider `impl<T> Drop for Vec<T> { ... }`,
// which does have to be able to drop instances of `T`, but
// otherwise cannot read data from `T`.
//
// Of course, for the type expression passed in for any such
// 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).
let has_dtor_of_interest;
if let Some(&dtor_method_did) = opt_dtor {
let impl_did = ty::impl_of_method(rcx.tcx(), dtor_method_did)
.unwrap_or_else(|| {
rcx.tcx().sess.span_bug(
span, "no Drop impl found for drop method")
});
let dtor_typescheme = ty::lookup_item_type(rcx.tcx(), impl_did);
let dtor_generics = dtor_typescheme.generics;
let has_pred_of_interest = dtor_generics.predicates.iter().any(|pred| {
// In `impl<T> Drop where ...`, we automatically
// assume some predicate will be meaningful and thus
// represents a type through which we could reach
// borrowed data. However, there can be implicit
// predicates (namely for Sized), and so we still need
// to walk through and filter out those cases.
let result = match *pred {
ty::Predicate::Trait(ty::Binder(ref t_pred)) => {
let def_id = t_pred.trait_ref.def_id;
match rcx.tcx().lang_items.to_builtin_kind(def_id) {
Some(ty::BoundSend) |
Some(ty::BoundSized) |
Some(ty::BoundCopy) |
Some(ty::BoundSync) => false,
_ => true,
}
}
ty::Predicate::Equate(..) |
ty::Predicate::RegionOutlives(..) |
ty::Predicate::TypeOutlives(..) |
ty::Predicate::Projection(..) => {
// we assume all of these where-clauses may
// give the drop implementation the capabilty
// to access borrowed data.
true
}
};
if result {
debug!("typ: {} has interesting dtor due to generic preds, e.g. {}",
typ.repr(rcx.tcx()), pred.repr(rcx.tcx()));
}
result
});
let has_type_param_of_interest = dtor_generics.types.iter().any(|t| {
let &ty::ParamBounds {
ref region_bounds, builtin_bounds, ref trait_bounds,
ref projection_bounds,
} = &t.bounds;
// Belt-and-suspenders: The current set of builtin
// bounds {Send, Sized, Copy, Sync} do not introduce
// any new capability to access borrowed data hidden
// behind a type parameter.
//
// In case new builtin bounds get added that do not
// satisfy that property, ensure `builtin_bounds \
// {Send,Sized,Copy,Sync}` is empty.
let mut builtin_bounds = builtin_bounds;
builtin_bounds.remove(&ty::BoundSend);
builtin_bounds.remove(&ty::BoundSized);
builtin_bounds.remove(&ty::BoundCopy);
builtin_bounds.remove(&ty::BoundSync);
let has_bounds =
!region_bounds.is_empty() ||
!builtin_bounds.is_empty() ||
!trait_bounds.is_empty() ||
!projection_bounds.is_empty();
if has_bounds {
debug!("typ: {} has interesting dtor due to \
bounds on param {}",
typ.repr(rcx.tcx()), t.name);
}
has_bounds
});
// In `impl<'a> Drop ...`, we automatically assume
// `'a` is meaningful and thus represents a bound
// through which we could reach borrowed data.
//
// 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`).
let has_region_param_of_interest =
dtor_generics.has_region_params(subst::TypeSpace);
has_dtor_of_interest =
has_region_param_of_interest ||
has_type_param_of_interest ||
has_pred_of_interest;
if has_dtor_of_interest {
debug!("typ: {} has interesting dtor, due to \
region params: {} type params: {} or pred: {}",
typ.repr(rcx.tcx()),
has_region_param_of_interest,
has_type_param_of_interest,
has_pred_of_interest);
} else {
debug!("typ: {} has dtor, but it is uninteresting",
typ.repr(rcx.tcx()));
}
} else {
debug!("typ: {} has no dtor, and thus is uninteresting",
typ.repr(rcx.tcx()));
has_dtor_of_interest = false;
}
if has_dtor_of_interest {
// If `typ` has a destructor, then we must ensure that all
// borrowed data reachable via `typ` must outlive the
// parent of `scope`. (It does not suffice for it to
// outlive `scope` because that could imply that the
// borrowed data is torn down in between the end of
// `scope` and when the destructor itself actually runs.
// `scope` and when the destructor itself actually runs.)
let parent_region =
match rcx.tcx().region_maps.opt_encl_scope(scope) {