Skip to content

Commit

Permalink
Check enum and struct representability properly (issues #3008 and #3779)
Browse files Browse the repository at this point in the history
  • Loading branch information
isabelmu committed Jan 30, 2014
1 parent f84b729 commit 7a93157
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 58 deletions.
130 changes: 90 additions & 40 deletions src/librustc/middle/ty.rs
Expand Up @@ -2348,53 +2348,103 @@ pub fn is_instantiable(cx: ctxt, r_ty: t) -> bool {
!subtypes_require(cx, &mut seen, r_ty, r_ty)
}

pub fn type_structurally_contains(cx: ctxt, ty: t, test: |x: &sty| -> bool)
-> bool {
let sty = &get(ty).sty;
debug!("type_structurally_contains: {}",
::util::ppaux::ty_to_str(cx, ty));
if test(sty) { return true; }
match *sty {
ty_enum(did, ref substs) => {
for variant in (*enum_variants(cx, did)).iter() {
for aty in variant.args.iter() {
let sty = subst(cx, substs, *aty);
if type_structurally_contains(cx, sty, |x| test(x)) { return true; }
/// Describes whether a type is representable. For types that are not
/// representable, 'SelfRecursive' and 'ContainsRecursive' are used to
/// distinguish between types that are recursive with themselves and types that
/// contain a different recursive type. These cases can therefore be treated
/// differently when reporting errors.
#[deriving(Eq)]
pub enum Representability {
Representable,
SelfRecursive,
ContainsRecursive,
}

/// Check whether a type is representable. This means it cannot contain unboxed
/// structural recursion. This check is needed for structs and enums.
pub fn is_type_representable(cx: ctxt, ty: t) -> Representability {

// Iterate until something non-representable is found
fn find_nonrepresentable<It: Iterator<t>>(cx: ctxt, seen: &mut ~[DefId],
mut iter: It) -> Representability {
for ty in iter {
let r = type_structurally_recursive(cx, seen, ty);
if r != Representable {
return r
}
}
return false;
}
ty_struct(did, ref substs) => {
let r = lookup_struct_fields(cx, did);
for field in r.iter() {
let ft = lookup_field_type(cx, did, field.id, substs);
if type_structurally_contains(cx, ft, |x| test(x)) { return true; }
Representable
}

// Does the type `ty` directly (without indirection through a pointer)
// contain any types on stack `seen`?
fn type_structurally_recursive(cx: ctxt, seen: &mut ~[DefId],
ty: t) -> Representability {
debug!("type_structurally_recursive: {}",
::util::ppaux::ty_to_str(cx, ty));

// Compare current type to previously seen types
match get(ty).sty {
ty_struct(did, _) |
ty_enum(did, _) => {
for (i, &seen_did) in seen.iter().enumerate() {
if did == seen_did {
return if i == 0 { SelfRecursive }
else { ContainsRecursive }
}
}
}
_ => (),
}
return false;
}

ty_tup(ref ts) => {
for tt in ts.iter() {
if type_structurally_contains(cx, *tt, |x| test(x)) { return true; }
// Check inner types
match get(ty).sty {
// Tuples
ty_tup(ref ts) => {
find_nonrepresentable(cx, seen, ts.iter().map(|t| *t))
}
// Non-zero fixed-length vectors.
ty_vec(mt, vstore_fixed(len)) if len != 0 => {
type_structurally_recursive(cx, seen, mt.ty)
}

// Push struct and enum def-ids onto `seen` before recursing.
ty_struct(did, ref substs) => {
seen.push(did);
let fields = struct_fields(cx, did, substs);
let r = find_nonrepresentable(cx, seen,
fields.iter().map(|f| f.mt.ty));
seen.pop();
r
}
ty_enum(did, ref substs) => {
seen.push(did);
let vs = enum_variants(cx, did);

let mut r = Representable;
for variant in vs.iter() {
let iter = variant.args.iter().map(|aty| subst(cx, substs, *aty));
r = find_nonrepresentable(cx, seen, iter);

if r != Representable { break }
}

seen.pop();
r
}

_ => Representable,
}
return false;
}
ty_vec(ref mt, vstore_fixed(_)) => {
return type_structurally_contains(cx, mt.ty, test);
}
_ => return false
}
}

pub fn type_structurally_contains_uniques(cx: ctxt, ty: t) -> bool {
return type_structurally_contains(cx, ty, |sty| {
match *sty {
ty_uniq(_) |
ty_vec(_, vstore_uniq) |
ty_str(vstore_uniq) => true,
_ => false,
}
});
debug!("is_type_representable: {}",
::util::ppaux::ty_to_str(cx, ty));

// To avoid a stack overflow when checking an enum variant or struct that
// contains a different, structurally recursive type, maintain a stack
// of seen types and check recursion for each of them (issues #3008, #3779).
let mut seen: ~[DefId] = ~[];
type_structurally_recursive(cx, &mut seen, ty)
}

pub fn type_is_trait(ty: t) -> bool {
Expand Down
51 changes: 33 additions & 18 deletions src/librustc/middle/typeck/check/mod.rs
Expand Up @@ -531,7 +531,10 @@ pub fn check_no_duplicate_fields(tcx: ty::ctxt,
pub fn check_struct(ccx: @CrateCtxt, id: ast::NodeId, span: Span) {
let tcx = ccx.tcx;

// Check that the class is instantiable
// Check that the struct is representable
check_representable(tcx, span, id, "struct");

// Check that the struct is instantiable
check_instantiable(tcx, span, id);

if ty::lookup_simd(tcx, local_def(id)) {
Expand Down Expand Up @@ -3410,6 +3413,33 @@ pub fn check_const_with_ty(fcx: @FnCtxt,
writeback::resolve_type_vars_in_expr(fcx, e);
}

/// Checks whether a type can be represented in memory. In particular, it
/// identifies types that contain themselves without indirection through a
/// pointer, which would mean their size is unbounded. This is different from
/// the question of whether a type can be instantiated. See the definition of
/// `check_instantiable`.
pub fn check_representable(tcx: ty::ctxt,
sp: Span,
item_id: ast::NodeId,
designation: &str) {
let rty = ty::node_id_to_type(tcx, item_id);

// Check that it is possible to represent this type. This call identifies
// (1) types that contain themselves and (2) types that contain a different
// recursive type. It is only necessary to throw an error on those that
// contain themselves. For case 2, there must be an inner type that will be
// caught by case 1.
match ty::is_type_representable(tcx, rty) {
ty::SelfRecursive => {
tcx.sess.span_err(
sp, format!("illegal recursive {} type; \
wrap the inner value in a box to make it representable",
designation));
}
ty::Representable | ty::ContainsRecursive => (),
}
}

/// Checks whether a type can be created without an instance of itself.
/// This is similar but different from the question of whether a type
/// can be represented. For example, the following type:
Expand Down Expand Up @@ -3565,7 +3595,6 @@ pub fn check_enum_variants(ccx: @CrateCtxt,
return variants;
}

let rty = ty::node_id_to_type(ccx.tcx, id);
let hint = ty::lookup_repr_hint(ccx.tcx, ast::DefId { crate: ast::LOCAL_CRATE, node: id });
if hint != attr::ReprAny && vs.len() <= 1 {
ccx.tcx.sess.span_err(sp, format!("unsupported representation for {}variant enum",
Expand All @@ -3580,22 +3609,8 @@ pub fn check_enum_variants(ccx: @CrateCtxt,
enum_var_cache.get().insert(local_def(id), @variants);
}

// Check that it is possible to represent this enum:
let mut outer = true;
let did = local_def(id);
if ty::type_structurally_contains(ccx.tcx, rty, |sty| {
match *sty {
ty::ty_enum(id, _) if id == did => {
if outer { outer = false; false }
else { true }
}
_ => false
}
}) {
ccx.tcx.sess.span_err(sp,
"illegal recursive enum type; \
wrap the inner value in a box to make it representable");
}
// Check that it is possible to represent this enum.
check_representable(ccx.tcx, sp, id, "enum");

// Check that it is possible to instantiate this enum:
//
Expand Down

0 comments on commit 7a93157

Please sign in to comment.