Skip to content

Commit 056363f

Browse files
committed
auto merge of #11839 : typelist/rust/issue3008, r=huonw
It was possible to trigger a stack overflow in rustc because the routine used to verify enum representability, type_structurally_contains, would recurse on inner types until hitting the original type. The overflow condition was when a different structurally recursive type (enum or struct) was contained in the type being checked. I suspect my solution isn't as efficient as it could be. I pondered adding a cache of previously-seen types to avoid duplicating work (if enums A and B both contain type C, my code goes through C twice), but I didn't want to do anything that may not be necessary. I'm a new contributor, so please pay particular attention to any unidiomatic code, misuse of terminology, bad naming of tests, or similar horribleness :) Updated to verify struct representability as well. Fixes #3008. Fixes #3779.
2 parents 3cb72a3 + 8d097b3 commit 056363f

File tree

6 files changed

+188
-58
lines changed

6 files changed

+188
-58
lines changed

src/librustc/middle/ty.rs

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,53 +2348,104 @@ pub fn is_instantiable(cx: ctxt, r_ty: t) -> bool {
23482348
!subtypes_require(cx, &mut seen, r_ty, r_ty)
23492349
}
23502350

2351-
pub fn type_structurally_contains(cx: ctxt, ty: t, test: |x: &sty| -> bool)
2352-
-> bool {
2353-
let sty = &get(ty).sty;
2354-
debug!("type_structurally_contains: {}",
2355-
::util::ppaux::ty_to_str(cx, ty));
2356-
if test(sty) { return true; }
2357-
match *sty {
2358-
ty_enum(did, ref substs) => {
2359-
for variant in (*enum_variants(cx, did)).iter() {
2360-
for aty in variant.args.iter() {
2361-
let sty = subst(cx, substs, *aty);
2362-
if type_structurally_contains(cx, sty, |x| test(x)) { return true; }
2351+
/// Describes whether a type is representable. For types that are not
2352+
/// representable, 'SelfRecursive' and 'ContainsRecursive' are used to
2353+
/// distinguish between types that are recursive with themselves and types that
2354+
/// contain a different recursive type. These cases can therefore be treated
2355+
/// differently when reporting errors.
2356+
#[deriving(Eq)]
2357+
pub enum Representability {
2358+
Representable,
2359+
SelfRecursive,
2360+
ContainsRecursive,
2361+
}
2362+
2363+
/// Check whether a type is representable. This means it cannot contain unboxed
2364+
/// structural recursion. This check is needed for structs and enums.
2365+
pub fn is_type_representable(cx: ctxt, ty: t) -> Representability {
2366+
2367+
// Iterate until something non-representable is found
2368+
fn find_nonrepresentable<It: Iterator<t>>(cx: ctxt, seen: &mut ~[DefId],
2369+
mut iter: It) -> Representability {
2370+
for ty in iter {
2371+
let r = type_structurally_recursive(cx, seen, ty);
2372+
if r != Representable {
2373+
return r
23632374
}
23642375
}
2365-
return false;
2366-
}
2367-
ty_struct(did, ref substs) => {
2368-
let r = lookup_struct_fields(cx, did);
2369-
for field in r.iter() {
2370-
let ft = lookup_field_type(cx, did, field.id, substs);
2371-
if type_structurally_contains(cx, ft, |x| test(x)) { return true; }
2376+
Representable
2377+
}
2378+
2379+
// Does the type `ty` directly (without indirection through a pointer)
2380+
// contain any types on stack `seen`?
2381+
fn type_structurally_recursive(cx: ctxt, seen: &mut ~[DefId],
2382+
ty: t) -> Representability {
2383+
debug!("type_structurally_recursive: {}",
2384+
::util::ppaux::ty_to_str(cx, ty));
2385+
2386+
// Compare current type to previously seen types
2387+
match get(ty).sty {
2388+
ty_struct(did, _) |
2389+
ty_enum(did, _) => {
2390+
for (i, &seen_did) in seen.iter().enumerate() {
2391+
if did == seen_did {
2392+
return if i == 0 { SelfRecursive }
2393+
else { ContainsRecursive }
2394+
}
2395+
}
2396+
}
2397+
_ => (),
23722398
}
2373-
return false;
2374-
}
23752399

2376-
ty_tup(ref ts) => {
2377-
for tt in ts.iter() {
2378-
if type_structurally_contains(cx, *tt, |x| test(x)) { return true; }
2400+
// Check inner types
2401+
match get(ty).sty {
2402+
// Tuples
2403+
ty_tup(ref ts) => {
2404+
find_nonrepresentable(cx, seen, ts.iter().map(|t| *t))
2405+
}
2406+
// Fixed-length vectors.
2407+
// FIXME(#11924) Behavior undecided for zero-length vectors.
2408+
ty_vec(mt, vstore_fixed(_)) => {
2409+
type_structurally_recursive(cx, seen, mt.ty)
2410+
}
2411+
2412+
// Push struct and enum def-ids onto `seen` before recursing.
2413+
ty_struct(did, ref substs) => {
2414+
seen.push(did);
2415+
let fields = struct_fields(cx, did, substs);
2416+
let r = find_nonrepresentable(cx, seen,
2417+
fields.iter().map(|f| f.mt.ty));
2418+
seen.pop();
2419+
r
2420+
}
2421+
ty_enum(did, ref substs) => {
2422+
seen.push(did);
2423+
let vs = enum_variants(cx, did);
2424+
2425+
let mut r = Representable;
2426+
for variant in vs.iter() {
2427+
let iter = variant.args.iter().map(|aty| subst(cx, substs, *aty));
2428+
r = find_nonrepresentable(cx, seen, iter);
2429+
2430+
if r != Representable { break }
2431+
}
2432+
2433+
seen.pop();
2434+
r
2435+
}
2436+
2437+
_ => Representable,
23792438
}
2380-
return false;
2381-
}
2382-
ty_vec(ref mt, vstore_fixed(_)) => {
2383-
return type_structurally_contains(cx, mt.ty, test);
2384-
}
2385-
_ => return false
23862439
}
2387-
}
23882440

2389-
pub fn type_structurally_contains_uniques(cx: ctxt, ty: t) -> bool {
2390-
return type_structurally_contains(cx, ty, |sty| {
2391-
match *sty {
2392-
ty_uniq(_) |
2393-
ty_vec(_, vstore_uniq) |
2394-
ty_str(vstore_uniq) => true,
2395-
_ => false,
2396-
}
2397-
});
2441+
debug!("is_type_representable: {}",
2442+
::util::ppaux::ty_to_str(cx, ty));
2443+
2444+
// To avoid a stack overflow when checking an enum variant or struct that
2445+
// contains a different, structurally recursive type, maintain a stack
2446+
// of seen types and check recursion for each of them (issues #3008, #3779).
2447+
let mut seen: ~[DefId] = ~[];
2448+
type_structurally_recursive(cx, &mut seen, ty)
23982449
}
23992450

24002451
pub fn type_is_trait(ty: t) -> bool {

src/librustc/middle/typeck/check/mod.rs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,10 @@ pub fn check_no_duplicate_fields(tcx: ty::ctxt,
531531
pub fn check_struct(ccx: @CrateCtxt, id: ast::NodeId, span: Span) {
532532
let tcx = ccx.tcx;
533533

534-
// Check that the class is instantiable
534+
// Check that the struct is representable
535+
check_representable(tcx, span, id, "struct");
536+
537+
// Check that the struct is instantiable
535538
check_instantiable(tcx, span, id);
536539

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

3416+
/// Checks whether a type can be represented in memory. In particular, it
3417+
/// identifies types that contain themselves without indirection through a
3418+
/// pointer, which would mean their size is unbounded. This is different from
3419+
/// the question of whether a type can be instantiated. See the definition of
3420+
/// `check_instantiable`.
3421+
pub fn check_representable(tcx: ty::ctxt,
3422+
sp: Span,
3423+
item_id: ast::NodeId,
3424+
designation: &str) {
3425+
let rty = ty::node_id_to_type(tcx, item_id);
3426+
3427+
// Check that it is possible to represent this type. This call identifies
3428+
// (1) types that contain themselves and (2) types that contain a different
3429+
// recursive type. It is only necessary to throw an error on those that
3430+
// contain themselves. For case 2, there must be an inner type that will be
3431+
// caught by case 1.
3432+
match ty::is_type_representable(tcx, rty) {
3433+
ty::SelfRecursive => {
3434+
tcx.sess.span_err(
3435+
sp, format!("illegal recursive {} type; \
3436+
wrap the inner value in a box to make it representable",
3437+
designation));
3438+
}
3439+
ty::Representable | ty::ContainsRecursive => (),
3440+
}
3441+
}
3442+
34133443
/// Checks whether a type can be created without an instance of itself.
34143444
/// This is similar but different from the question of whether a type
34153445
/// can be represented. For example, the following type:
@@ -3565,7 +3595,6 @@ pub fn check_enum_variants(ccx: @CrateCtxt,
35653595
return variants;
35663596
}
35673597

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

3583-
// Check that it is possible to represent this enum:
3584-
let mut outer = true;
3585-
let did = local_def(id);
3586-
if ty::type_structurally_contains(ccx.tcx, rty, |sty| {
3587-
match *sty {
3588-
ty::ty_enum(id, _) if id == did => {
3589-
if outer { outer = false; false }
3590-
else { true }
3591-
}
3592-
_ => false
3593-
}
3594-
}) {
3595-
ccx.tcx.sess.span_err(sp,
3596-
"illegal recursive enum type; \
3597-
wrap the inner value in a box to make it representable");
3598-
}
3612+
// Check that it is possible to represent this enum.
3613+
check_representable(ccx.tcx, sp, id, "enum");
35993614

36003615
// Check that it is possible to instantiate this enum:
36013616
//

src/test/compile-fail/issue-3008-1.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
enum foo { foo(bar) }
12+
enum bar { bar_none, bar_some(bar) } //~ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
13+
14+
fn main() {
15+
}

src/test/compile-fail/issue-3008-2.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
enum foo { foo(bar) }
12+
struct bar { x: bar } //~ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
13+
//~^ ERROR this type cannot be instantiated without an instance of itself
14+
15+
fn main() {
16+
}
17+

src/test/compile-fail/issue-3008-3.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
enum E1 { V1(E2<E1>), }
12+
enum E2<T> { V2(E2<E1>), } //~ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
13+
14+
fn main() {
15+
}

src/test/compile-fail/issue-3779.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
struct S { //~ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
12+
element: Option<S>
13+
}
14+
15+
fn main() {
16+
let x = S { element: None };
17+
}

0 commit comments

Comments
 (0)