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:
parent
f90c3864b6
commit
f51176df01
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user