destructor checker (dropck).
Largely adapted from pcwalton's original branch, with following notable modifications: Use `regionck::type_must_outlive` to generate `SafeDestructor` constraints. (this plugged some soundness holes in the analysis). Avoid exponential time blowup on compile-fail/huge-struct.rs by keeping the breadcrumbs until end of traversal. Avoid premature return from regionck::visit_expr. Factored drop-checking code out into dropck module. Added `SafeDestructor` to enum `SubregionOrigin` (for error reporting). ---- Since this imposes restrictions on the lifetimes used in types with destructors, this is a (wait for it) [breaking-change]
This commit is contained in:
parent
81383bd869
commit
e02b6d1748
7 changed files with 261 additions and 4 deletions
|
@ -242,7 +242,7 @@ impl<'a, 'tcx> ErrorReporting<'tcx> for InferCtxt<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubSupConflict(var_origin, _, sub_r, _, sup_r) => {
|
SubSupConflict(var_origin, _, sub_r, _, sup_r) => {
|
||||||
debug!("processing SubSupConflict");
|
debug!("processing SubSupConflict sub: {:?} sup: {:?}", sub_r, sup_r);
|
||||||
match free_regions_from_same_fn(self.tcx, sub_r, sup_r) {
|
match free_regions_from_same_fn(self.tcx, sub_r, sup_r) {
|
||||||
Some(ref same_frs) => {
|
Some(ref same_frs) => {
|
||||||
var_origins.push(var_origin);
|
var_origins.push(var_origin);
|
||||||
|
@ -709,6 +709,23 @@ impl<'a, 'tcx> ErrorReporting<'tcx> for InferCtxt<'a, 'tcx> {
|
||||||
sup,
|
sup,
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
infer::SafeDestructor(span) => {
|
||||||
|
self.tcx.sess.span_err(
|
||||||
|
span,
|
||||||
|
"unsafe use of destructor: destructor might be called \
|
||||||
|
while references are dead");
|
||||||
|
// FIXME (22171): terms "super/subregion" are suboptimal
|
||||||
|
note_and_explain_region(
|
||||||
|
self.tcx,
|
||||||
|
"superregion: ",
|
||||||
|
sup,
|
||||||
|
"");
|
||||||
|
note_and_explain_region(
|
||||||
|
self.tcx,
|
||||||
|
"subregion: ",
|
||||||
|
sub,
|
||||||
|
"");
|
||||||
|
}
|
||||||
infer::BindingTypeIsNotValidAtDecl(span) => {
|
infer::BindingTypeIsNotValidAtDecl(span) => {
|
||||||
self.tcx.sess.span_err(
|
self.tcx.sess.span_err(
|
||||||
span,
|
span,
|
||||||
|
@ -1629,6 +1646,12 @@ impl<'a, 'tcx> ErrorReportingHelpers<'tcx> for InferCtxt<'a, 'tcx> {
|
||||||
&format!("...so that the declared lifetime parameter bounds \
|
&format!("...so that the declared lifetime parameter bounds \
|
||||||
are satisfied")[]);
|
are satisfied")[]);
|
||||||
}
|
}
|
||||||
|
infer::SafeDestructor(span) => {
|
||||||
|
self.tcx.sess.span_note(
|
||||||
|
span,
|
||||||
|
"...so that references are valid when the destructor \
|
||||||
|
runs")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,9 @@ pub enum SubregionOrigin<'tcx> {
|
||||||
|
|
||||||
// An auto-borrow that does not enclose the expr where it occurs
|
// An auto-borrow that does not enclose the expr where it occurs
|
||||||
AutoBorrow(Span),
|
AutoBorrow(Span),
|
||||||
|
|
||||||
|
// Region constraint arriving from destructor safety
|
||||||
|
SafeDestructor(Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Times when we replace late-bound regions with variables:
|
/// Times when we replace late-bound regions with variables:
|
||||||
|
@ -1197,6 +1200,7 @@ impl<'tcx> SubregionOrigin<'tcx> {
|
||||||
CallReturn(a) => a,
|
CallReturn(a) => a,
|
||||||
AddrOf(a) => a,
|
AddrOf(a) => a,
|
||||||
AutoBorrow(a) => a,
|
AutoBorrow(a) => a,
|
||||||
|
SafeDestructor(a) => a,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1259,6 +1263,7 @@ impl<'tcx> Repr<'tcx> for SubregionOrigin<'tcx> {
|
||||||
CallReturn(a) => format!("CallReturn({})", a.repr(tcx)),
|
CallReturn(a) => format!("CallReturn({})", a.repr(tcx)),
|
||||||
AddrOf(a) => format!("AddrOf({})", a.repr(tcx)),
|
AddrOf(a) => format!("AddrOf({})", a.repr(tcx)),
|
||||||
AutoBorrow(a) => format!("AutoBorrow({})", a.repr(tcx)),
|
AutoBorrow(a) => format!("AutoBorrow({})", a.repr(tcx)),
|
||||||
|
SafeDestructor(a) => format!("SafeDestructor({})", a.repr(tcx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1399,6 +1399,8 @@ impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> {
|
||||||
for upper_bound in &upper_bounds {
|
for upper_bound in &upper_bounds {
|
||||||
if !self.is_subregion_of(lower_bound.region,
|
if !self.is_subregion_of(lower_bound.region,
|
||||||
upper_bound.region) {
|
upper_bound.region) {
|
||||||
|
debug!("pushing SubSupConflict sub: {:?} sup: {:?}",
|
||||||
|
lower_bound.region, upper_bound.region);
|
||||||
errors.push(SubSupConflict(
|
errors.push(SubSupConflict(
|
||||||
(*self.var_origins.borrow())[node_idx.index as uint].clone(),
|
(*self.var_origins.borrow())[node_idx.index as uint].clone(),
|
||||||
lower_bound.origin.clone(),
|
lower_bound.origin.clone(),
|
||||||
|
|
154
src/librustc_typeck/check/dropck.rs
Normal file
154
src/librustc_typeck/check/dropck.rs
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2014-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 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use check::regionck::{self, Rcx};
|
||||||
|
|
||||||
|
use middle::infer;
|
||||||
|
use middle::region;
|
||||||
|
use middle::ty::{self, Ty};
|
||||||
|
use util::ppaux::{Repr};
|
||||||
|
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
|
||||||
|
pub fn check_safety_of_destructor_if_necessary<'a, 'tcx>(rcx: &mut Rcx<'a, 'tcx>,
|
||||||
|
typ: ty::Ty<'tcx>,
|
||||||
|
span: Span,
|
||||||
|
scope: region::CodeExtent) {
|
||||||
|
debug!("check_safety_of_destructor_if_necessary typ: {} scope: {:?}",
|
||||||
|
typ.repr(rcx.tcx()), scope);
|
||||||
|
|
||||||
|
// types that have been traversed so far by `traverse_type_if_unseen`
|
||||||
|
let mut breadcrumbs: Vec<Ty<'tcx>> = Vec::new();
|
||||||
|
|
||||||
|
iterate_over_potentially_unsafe_regions_in_type(
|
||||||
|
rcx,
|
||||||
|
&mut breadcrumbs,
|
||||||
|
typ,
|
||||||
|
span,
|
||||||
|
scope,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>(
|
||||||
|
rcx: &mut Rcx<'a, 'tcx>,
|
||||||
|
breadcrumbs: &mut Vec<Ty<'tcx>>,
|
||||||
|
ty_root: ty::Ty<'tcx>,
|
||||||
|
span: Span,
|
||||||
|
scope: region::CodeExtent,
|
||||||
|
depth: uint)
|
||||||
|
{
|
||||||
|
let origin = |&:| infer::SubregionOrigin::SafeDestructor(span);
|
||||||
|
let mut walker = ty_root.walk();
|
||||||
|
while let Some(typ) = walker.next() {
|
||||||
|
// Avoid recursing forever.
|
||||||
|
if breadcrumbs.contains(&typ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("iterate_over_potentially_unsafe_regions_in_type \
|
||||||
|
{}typ: {} scope: {:?} has_dtor: {}",
|
||||||
|
(0..depth).map(|_| ' ').collect::<String>(),
|
||||||
|
typ.repr(rcx.tcx()), scope, has_dtor);
|
||||||
|
|
||||||
|
if has_dtor {
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
let parent_region =
|
||||||
|
match rcx.tcx().region_maps.opt_encl_scope(scope) {
|
||||||
|
Some(parent_scope) => ty::ReScope(parent_scope),
|
||||||
|
None => rcx.tcx().sess.span_bug(
|
||||||
|
span, format!("no enclosing scope found for scope: {:?}",
|
||||||
|
scope).as_slice()),
|
||||||
|
};
|
||||||
|
|
||||||
|
regionck::type_must_outlive(rcx, origin(), typ, parent_region);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Okay, `typ` itself is itself not reachable by a
|
||||||
|
// destructor; but it may contain substructure that has a
|
||||||
|
// destructor.
|
||||||
|
|
||||||
|
match typ.sty {
|
||||||
|
ty::ty_struct(struct_did, substs) => {
|
||||||
|
// Don't recurse; we extract type's substructure,
|
||||||
|
// so do not process subparts of type expression.
|
||||||
|
walker.skip_current_subtree();
|
||||||
|
|
||||||
|
let fields =
|
||||||
|
ty::lookup_struct_fields(rcx.tcx(), struct_did);
|
||||||
|
for field in fields.iter() {
|
||||||
|
let field_type =
|
||||||
|
ty::lookup_field_type(rcx.tcx(),
|
||||||
|
struct_did,
|
||||||
|
field.id,
|
||||||
|
substs);
|
||||||
|
iterate_over_potentially_unsafe_regions_in_type(
|
||||||
|
rcx,
|
||||||
|
breadcrumbs,
|
||||||
|
field_type,
|
||||||
|
span,
|
||||||
|
scope,
|
||||||
|
depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ty::ty_enum(enum_did, substs) => {
|
||||||
|
// Don't recurse; we extract type's substructure,
|
||||||
|
// so do not process subparts of type expression.
|
||||||
|
walker.skip_current_subtree();
|
||||||
|
|
||||||
|
let all_variant_info =
|
||||||
|
ty::substd_enum_variants(rcx.tcx(),
|
||||||
|
enum_did,
|
||||||
|
substs);
|
||||||
|
for variant_info in all_variant_info.iter() {
|
||||||
|
for argument_type in variant_info.args.iter() {
|
||||||
|
iterate_over_potentially_unsafe_regions_in_type(
|
||||||
|
rcx,
|
||||||
|
breadcrumbs,
|
||||||
|
*argument_type,
|
||||||
|
span,
|
||||||
|
scope,
|
||||||
|
depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ty::ty_rptr(..) | ty::ty_ptr(_) | ty::ty_bare_fn(..) => {
|
||||||
|
// Don't recurse, since references, pointers,
|
||||||
|
// boxes, and bare functions don't own instances
|
||||||
|
// of the types appearing within them.
|
||||||
|
walker.skip_current_subtree();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// You might be tempted to pop breadcrumbs here after
|
||||||
|
// processing type's internals above, but then you hit
|
||||||
|
// exponential time blowup e.g. on
|
||||||
|
// compile-fail/huge-struct.rs. Instead, we do not remove
|
||||||
|
// anything from the breadcrumbs vector during any particular
|
||||||
|
// traversal, and instead clear it after the whole traversal
|
||||||
|
// is done.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -127,6 +127,7 @@ use syntax::ptr::P;
|
||||||
use syntax::visit::{self, Visitor};
|
use syntax::visit::{self, Visitor};
|
||||||
|
|
||||||
mod assoc;
|
mod assoc;
|
||||||
|
pub mod dropck;
|
||||||
pub mod _match;
|
pub mod _match;
|
||||||
pub mod vtable;
|
pub mod vtable;
|
||||||
pub mod writeback;
|
pub mod writeback;
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
//! contents.
|
//! contents.
|
||||||
|
|
||||||
use astconv::AstConv;
|
use astconv::AstConv;
|
||||||
|
use check::dropck;
|
||||||
use check::FnCtxt;
|
use check::FnCtxt;
|
||||||
use check::regionmanip;
|
use check::regionmanip;
|
||||||
use check::vtable;
|
use check::vtable;
|
||||||
|
@ -171,6 +172,7 @@ pub struct Rcx<'a, 'tcx: 'a> {
|
||||||
|
|
||||||
// id of AST node being analyzed (the subject of the analysis).
|
// id of AST node being analyzed (the subject of the analysis).
|
||||||
subject: SubjectNode,
|
subject: SubjectNode,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the validity region of `def` -- that is, how long is `def` valid?
|
/// Returns the validity region of `def` -- that is, how long is `def` valid?
|
||||||
|
@ -198,7 +200,8 @@ impl<'a, 'tcx> Rcx<'a, 'tcx> {
|
||||||
Rcx { fcx: fcx,
|
Rcx { fcx: fcx,
|
||||||
repeating_scope: initial_repeating_scope,
|
repeating_scope: initial_repeating_scope,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
region_bound_pairs: Vec::new() }
|
region_bound_pairs: Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tcx(&self) -> &'a ty::ctxt<'tcx> {
|
pub fn tcx(&self) -> &'a ty::ctxt<'tcx> {
|
||||||
|
@ -469,6 +472,10 @@ fn constrain_bindings_in_pat(pat: &ast::Pat, rcx: &mut Rcx) {
|
||||||
type_of_node_must_outlive(
|
type_of_node_must_outlive(
|
||||||
rcx, infer::BindingTypeIsNotValidAtDecl(span),
|
rcx, infer::BindingTypeIsNotValidAtDecl(span),
|
||||||
id, var_region);
|
id, var_region);
|
||||||
|
|
||||||
|
let var_scope = tcx.region_maps.var_scope(id);
|
||||||
|
let typ = rcx.resolve_node_type(id);
|
||||||
|
dropck::check_safety_of_destructor_if_necessary(rcx, typ, span, var_scope);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,6 +524,40 @@ fn visit_expr(rcx: &mut Rcx, expr: &ast::Expr) {
|
||||||
*/
|
*/
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If necessary, constrain destructors in the unadjusted form of this
|
||||||
|
// expression.
|
||||||
|
let cmt_result = {
|
||||||
|
let mc = mc::MemCategorizationContext::new(rcx.fcx);
|
||||||
|
mc.cat_expr_unadjusted(expr)
|
||||||
|
};
|
||||||
|
match cmt_result {
|
||||||
|
Ok(head_cmt) => {
|
||||||
|
check_safety_of_rvalue_destructor_if_necessary(rcx,
|
||||||
|
head_cmt,
|
||||||
|
expr.span);
|
||||||
|
}
|
||||||
|
Err(..) => {
|
||||||
|
rcx.fcx.tcx().sess.span_note(expr.span,
|
||||||
|
"cat_expr_unadjusted Errd during dtor check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If necessary, constrain destructors in this expression. This will be
|
||||||
|
// the adjusted form if there is an adjustment.
|
||||||
|
let cmt_result = {
|
||||||
|
let mc = mc::MemCategorizationContext::new(rcx.fcx);
|
||||||
|
mc.cat_expr(expr)
|
||||||
|
};
|
||||||
|
match cmt_result {
|
||||||
|
Ok(head_cmt) => {
|
||||||
|
check_safety_of_rvalue_destructor_if_necessary(rcx, head_cmt, expr.span);
|
||||||
|
}
|
||||||
|
Err(..) => {
|
||||||
|
rcx.fcx.tcx().sess.span_note(expr.span,
|
||||||
|
"cat_expr Errd during dtor check");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match expr.node {
|
match expr.node {
|
||||||
|
@ -995,6 +1036,33 @@ pub fn mk_subregion_due_to_dereference(rcx: &mut Rcx,
|
||||||
minimum_lifetime, maximum_lifetime)
|
minimum_lifetime, maximum_lifetime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_safety_of_rvalue_destructor_if_necessary<'a, 'tcx>(rcx: &mut Rcx<'a, 'tcx>,
|
||||||
|
cmt: mc::cmt<'tcx>,
|
||||||
|
span: Span) {
|
||||||
|
match cmt.cat {
|
||||||
|
mc::cat_rvalue(region) => {
|
||||||
|
match region {
|
||||||
|
ty::ReScope(rvalue_scope) => {
|
||||||
|
let typ = rcx.resolve_type(cmt.ty);
|
||||||
|
dropck::check_safety_of_destructor_if_necessary(rcx,
|
||||||
|
typ,
|
||||||
|
span,
|
||||||
|
rvalue_scope);
|
||||||
|
}
|
||||||
|
ty::ReStatic => {}
|
||||||
|
region => {
|
||||||
|
rcx.tcx()
|
||||||
|
.sess
|
||||||
|
.span_bug(span,
|
||||||
|
format!("unexpected rvalue region in rvalue \
|
||||||
|
destructor safety checking: `{}`",
|
||||||
|
region.repr(rcx.tcx())).as_slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Invoked on any index expression that occurs. Checks that if this is a slice being indexed, the
|
/// Invoked on any index expression that occurs. Checks that if this is a slice being indexed, the
|
||||||
/// lifetime of the pointer includes the deref expr.
|
/// lifetime of the pointer includes the deref expr.
|
||||||
|
@ -1404,7 +1472,7 @@ fn link_reborrowed_region<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures that all borrowed data reachable via `ty` outlives `region`.
|
/// Ensures that all borrowed data reachable via `ty` outlives `region`.
|
||||||
fn type_must_outlive<'a, 'tcx>(rcx: &mut Rcx<'a, 'tcx>,
|
pub fn type_must_outlive<'a, 'tcx>(rcx: &mut Rcx<'a, 'tcx>,
|
||||||
origin: infer::SubregionOrigin<'tcx>,
|
origin: infer::SubregionOrigin<'tcx>,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
region: ty::Region)
|
region: ty::Region)
|
||||||
|
|
|
@ -147,7 +147,9 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
item.span,
|
item.span,
|
||||||
item.id,
|
item.id,
|
||||||
Some(&mut this.cache));
|
Some(&mut this.cache));
|
||||||
for variant in &variants {
|
debug!("check_type_defn at bounds_checker.scope: {:?}", bounds_checker.scope);
|
||||||
|
|
||||||
|
for variant in &variants {
|
||||||
for field in &variant.fields {
|
for field in &variant.fields {
|
||||||
// Regions are checked below.
|
// Regions are checked below.
|
||||||
bounds_checker.check_traits_in_ty(field.ty);
|
bounds_checker.check_traits_in_ty(field.ty);
|
||||||
|
@ -182,6 +184,7 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
item.span,
|
item.span,
|
||||||
item.id,
|
item.id,
|
||||||
Some(&mut this.cache));
|
Some(&mut this.cache));
|
||||||
|
debug!("check_item_type at bounds_checker.scope: {:?}", bounds_checker.scope);
|
||||||
|
|
||||||
let type_scheme = ty::lookup_item_type(fcx.tcx(), local_def(item.id));
|
let type_scheme = ty::lookup_item_type(fcx.tcx(), local_def(item.id));
|
||||||
let item_ty = fcx.instantiate_type_scheme(item.span,
|
let item_ty = fcx.instantiate_type_scheme(item.span,
|
||||||
|
@ -200,6 +203,7 @@ impl<'ccx, 'tcx> CheckTypeWellFormedVisitor<'ccx, 'tcx> {
|
||||||
item.span,
|
item.span,
|
||||||
item.id,
|
item.id,
|
||||||
Some(&mut this.cache));
|
Some(&mut this.cache));
|
||||||
|
debug!("check_impl at bounds_checker.scope: {:?}", bounds_checker.scope);
|
||||||
|
|
||||||
// Find the impl self type as seen from the "inside" --
|
// Find the impl self type as seen from the "inside" --
|
||||||
// that is, with all type parameters converted from bound
|
// that is, with all type parameters converted from bound
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue