Skip to content

Commit f51176d

Browse files
committed
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.)
1 parent f90c386 commit f51176d

File tree

1 file changed

+185
-8
lines changed

1 file changed

+185
-8
lines changed

src/librustc_typeck/check/dropck.rs

+185-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use check::regionck::{self, Rcx};
1212

1313
use middle::infer;
1414
use middle::region;
15+
use middle::subst;
1516
use middle::ty::{self, Ty};
1617
use util::ppaux::{Repr};
1718

@@ -46,31 +47,207 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>(
4647
{
4748
let origin = |&:| infer::SubregionOrigin::SafeDestructor(span);
4849
let mut walker = ty_root.walk();
50+
let opt_phantom_data_def_id = rcx.tcx().lang_items.phantom_data();
51+
52+
let destructor_for_type = rcx.tcx().destructor_for_type.borrow();
53+
4954
while let Some(typ) = walker.next() {
5055
// Avoid recursing forever.
5156
if breadcrumbs.contains(&typ) {
5257
continue;
5358
}
5459
breadcrumbs.push(typ);
5560

56-
let has_dtor = match typ.sty {
57-
ty::ty_struct(struct_did, _) => ty::has_dtor(rcx.tcx(), struct_did),
58-
ty::ty_enum(enum_did, _) => ty::has_dtor(rcx.tcx(), enum_did),
59-
_ => false,
61+
// If we encounter `PhantomData<T>`, then we should replace it
62+
// with `T`, the type it represents as owned by the
63+
// surrounding context, before doing further analysis.
64+
let typ = if let ty::ty_struct(struct_did, substs) = typ.sty {
65+
if opt_phantom_data_def_id == Some(struct_did) {
66+
let item_type = ty::lookup_item_type(rcx.tcx(), struct_did);
67+
let tp_def = item_type.generics.types
68+
.opt_get(subst::TypeSpace, 0).unwrap();
69+
let new_typ = substs.type_for_def(tp_def);
70+
debug!("replacing phantom {} with {}",
71+
typ.repr(rcx.tcx()), new_typ.repr(rcx.tcx()));
72+
new_typ
73+
} else {
74+
typ
75+
}
76+
} else {
77+
typ
78+
};
79+
80+
let opt_type_did = match typ.sty {
81+
ty::ty_struct(struct_did, _) => Some(struct_did),
82+
ty::ty_enum(enum_did, _) => Some(enum_did),
83+
_ => None,
6084
};
6185

86+
let opt_dtor =
87+
opt_type_did.and_then(|did| destructor_for_type.get(&did));
88+
6289
debug!("iterate_over_potentially_unsafe_regions_in_type \
63-
{}typ: {} scope: {:?} has_dtor: {}",
90+
{}typ: {} scope: {:?} opt_dtor: {:?}",
6491
(0..depth).map(|_| ' ').collect::<String>(),
65-
typ.repr(rcx.tcx()), scope, has_dtor);
92+
typ.repr(rcx.tcx()), scope, opt_dtor);
93+
94+
// If `typ` has a destructor, then we must ensure that all
95+
// borrowed data reachable via `typ` must outlive the parent
96+
// of `scope`. This is handled below.
97+
//
98+
// However, there is an important special case: by
99+
// parametricity, any generic type parameters have *no* trait
100+
// bounds in the Drop impl can not be used in any way (apart
101+
// from being dropped), and thus we can treat data borrowed
102+
// via such type parameters remains unreachable.
103+
//
104+
// For example, consider `impl<T> Drop for Vec<T> { ... }`,
105+
// which does have to be able to drop instances of `T`, but
106+
// otherwise cannot read data from `T`.
107+
//
108+
// Of course, for the type expression passed in for any such
109+
// unbounded type parameter `T`, we must resume the recursive
110+
// analysis on `T` (since it would be ignored by
111+
// type_must_outlive).
112+
//
113+
// FIXME (pnkfelix): Long term, we could be smart and actually
114+
// feed which generic parameters can be ignored *into* `fn
115+
// type_must_outlive` (or some generalization thereof). But
116+
// for the short term, it probably covers most cases of
117+
// interest to just special case Drop impls where: (1.) there
118+
// are no generic lifetime parameters and (2.) *all* generic
119+
// type parameters are unbounded. If both conditions hold, we
120+
// simply skip the `type_must_outlive` call entirely (but
121+
// resume the recursive checking of the type-substructure).
122+
123+
let has_dtor_of_interest;
124+
125+
if let Some(&dtor_method_did) = opt_dtor {
126+
let impl_did = ty::impl_of_method(rcx.tcx(), dtor_method_did)
127+
.unwrap_or_else(|| {
128+
rcx.tcx().sess.span_bug(
129+
span, "no Drop impl found for drop method")
130+
});
131+
132+
let dtor_typescheme = ty::lookup_item_type(rcx.tcx(), impl_did);
133+
let dtor_generics = dtor_typescheme.generics;
134+
135+
let has_pred_of_interest = dtor_generics.predicates.iter().any(|pred| {
136+
// In `impl<T> Drop where ...`, we automatically
137+
// assume some predicate will be meaningful and thus
138+
// represents a type through which we could reach
139+
// borrowed data. However, there can be implicit
140+
// predicates (namely for Sized), and so we still need
141+
// to walk through and filter out those cases.
142+
143+
let result = match *pred {
144+
ty::Predicate::Trait(ty::Binder(ref t_pred)) => {
145+
let def_id = t_pred.trait_ref.def_id;
146+
match rcx.tcx().lang_items.to_builtin_kind(def_id) {
147+
Some(ty::BoundSend) |
148+
Some(ty::BoundSized) |
149+
Some(ty::BoundCopy) |
150+
Some(ty::BoundSync) => false,
151+
_ => true,
152+
}
153+
}
154+
ty::Predicate::Equate(..) |
155+
ty::Predicate::RegionOutlives(..) |
156+
ty::Predicate::TypeOutlives(..) |
157+
ty::Predicate::Projection(..) => {
158+
// we assume all of these where-clauses may
159+
// give the drop implementation the capabilty
160+
// to access borrowed data.
161+
true
162+
}
163+
};
164+
165+
if result {
166+
debug!("typ: {} has interesting dtor due to generic preds, e.g. {}",
167+
typ.repr(rcx.tcx()), pred.repr(rcx.tcx()));
168+
}
169+
170+
result
171+
});
172+
173+
let has_type_param_of_interest = dtor_generics.types.iter().any(|t| {
174+
let &ty::ParamBounds {
175+
ref region_bounds, builtin_bounds, ref trait_bounds,
176+
ref projection_bounds,
177+
} = &t.bounds;
178+
179+
// Belt-and-suspenders: The current set of builtin
180+
// bounds {Send, Sized, Copy, Sync} do not introduce
181+
// any new capability to access borrowed data hidden
182+
// behind a type parameter.
183+
//
184+
// In case new builtin bounds get added that do not
185+
// satisfy that property, ensure `builtin_bounds \
186+
// {Send,Sized,Copy,Sync}` is empty.
187+
188+
let mut builtin_bounds = builtin_bounds;
189+
builtin_bounds.remove(&ty::BoundSend);
190+
builtin_bounds.remove(&ty::BoundSized);
191+
builtin_bounds.remove(&ty::BoundCopy);
192+
builtin_bounds.remove(&ty::BoundSync);
193+
194+
let has_bounds =
195+
!region_bounds.is_empty() ||
196+
!builtin_bounds.is_empty() ||
197+
!trait_bounds.is_empty() ||
198+
!projection_bounds.is_empty();
199+
200+
if has_bounds {
201+
debug!("typ: {} has interesting dtor due to \
202+
bounds on param {}",
203+
typ.repr(rcx.tcx()), t.name);
204+
}
205+
206+
has_bounds
207+
208+
});
209+
210+
// In `impl<'a> Drop ...`, we automatically assume
211+
// `'a` is meaningful and thus represents a bound
212+
// through which we could reach borrowed data.
213+
//
214+
// FIXME (pnkfelix): In the future it would be good to
215+
// extend the language to allow the user to express,
216+
// in the impl signature, that a lifetime is not
217+
// actually used (something like `where 'a: ?Live`).
218+
let has_region_param_of_interest =
219+
dtor_generics.has_region_params(subst::TypeSpace);
220+
221+
has_dtor_of_interest =
222+
has_region_param_of_interest ||
223+
has_type_param_of_interest ||
224+
has_pred_of_interest;
225+
226+
if has_dtor_of_interest {
227+
debug!("typ: {} has interesting dtor, due to \
228+
region params: {} type params: {} or pred: {}",
229+
typ.repr(rcx.tcx()),
230+
has_region_param_of_interest,
231+
has_type_param_of_interest,
232+
has_pred_of_interest);
233+
} else {
234+
debug!("typ: {} has dtor, but it is uninteresting",
235+
typ.repr(rcx.tcx()));
236+
}
237+
238+
} else {
239+
debug!("typ: {} has no dtor, and thus is uninteresting",
240+
typ.repr(rcx.tcx()));
241+
has_dtor_of_interest = false;
242+
}
66243

67-
if has_dtor {
244+
if has_dtor_of_interest {
68245
// If `typ` has a destructor, then we must ensure that all
69246
// borrowed data reachable via `typ` must outlive the
70247
// parent of `scope`. (It does not suffice for it to
71248
// outlive `scope` because that could imply that the
72249
// borrowed data is torn down in between the end of
73-
// `scope` and when the destructor itself actually runs.
250+
// `scope` and when the destructor itself actually runs.)
74251

75252
let parent_region =
76253
match rcx.tcx().region_maps.opt_encl_scope(scope) {

0 commit comments

Comments
 (0)