Skip to content

Commit 222ae8b

Browse files
committed
auto merge of #17815 : typelist/rust/recursive-structs, r=brson
The representability-checking routine ```is_type_representable``` failed to detect structural recursion in some cases, leading to stack overflow later on. The first problem was in the loop in the ```find_nonrepresentable``` function. We were improperly terminating the iteration if we saw a ```ContainsRecursive``` condition. We should have kept going in case a later member of the struct (or enum, etc) being examined was ```SelfRecursive```. The example from #17431 triggered this issue: ```rust use std::sync::Mutex; struct Foo { foo: Mutex<Option<Foo>> } impl Foo { fn bar(self) {} } fn main() {} ``` I'm not 100% sure, but I think the ```ty_enum``` case of ```fn type_structurally_recursive``` had a similar problem, since it could ```break``` on ```ContainsRecursive``` before looking at all variants. I've replaced this with a ```flat_map``` call. The second problem was that we were failing to identify code like ```struct Foo { foo: Option<Option<Foo>> }``` as SelfRecursive, even though we correctly identified ```struct Foo { foo: Option<Foo> }```. This was caused by using DefId's for the ```ContainsRecursive``` check, which meant the nested ```Option```s were identified as illegally recursive (because ```ContainsRecursive``` is not an error, we would then keep compiling and eventually hit a stack overflow). In order to make sure that we can recurse through the different ```Option``` invocations, I've changed the type of ```seen``` from ```Vec<DefId>``` to ```Vec<t>``` and added a separate ```same_type``` function to check whether two types are the same when generics are taken into account. Now we only return ```ContainsRecursive``` when this stricter check is satisfied. (There's probably a better way to do this, and I'm not sure my code is entirely correct--but my knowledge of rustc internals is pretty limited, so any help here would be appreciated!) Note that the ```SelfRecursive``` check is still comparing ```DefId```s--this is necessary to prevent code like this from being allowed: ```rust struct Foo { x: Bar<Foo> } struct Bar<T> { x: Bar<Foo> } ``` All four of the new ```issue-17431``` tests cause infinite recursion on master, and errors with this pull request. I wrote the extra ```issue-3008-4.rs``` test to make sure I wasn't introducing a regression. Fixes #17431.
2 parents 9b80efd + 53ddf2e commit 222ae8b

File tree

9 files changed

+240
-62
lines changed

9 files changed

+240
-62
lines changed

src/librustc/middle/ty.rs

+117-62
Original file line numberDiff line numberDiff line change
@@ -2792,99 +2792,151 @@ pub fn is_instantiable(cx: &ctxt, r_ty: t) -> bool {
27922792
/// distinguish between types that are recursive with themselves and types that
27932793
/// contain a different recursive type. These cases can therefore be treated
27942794
/// differently when reporting errors.
2795-
#[deriving(PartialEq)]
2795+
///
2796+
/// The ordering of the cases is significant. They are sorted so that cmp::max
2797+
/// will keep the "more erroneous" of two values.
2798+
#[deriving(PartialOrd, Ord, Eq, PartialEq, Show)]
27962799
pub enum Representability {
27972800
Representable,
2798-
SelfRecursive,
27992801
ContainsRecursive,
2802+
SelfRecursive,
28002803
}
28012804

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

28062809
// Iterate until something non-representable is found
2807-
fn find_nonrepresentable<It: Iterator<t>>(cx: &ctxt, sp: Span, seen: &mut Vec<DefId>,
2810+
fn find_nonrepresentable<It: Iterator<t>>(cx: &ctxt, sp: Span, seen: &mut Vec<t>,
28082811
mut iter: It) -> Representability {
2809-
for ty in iter {
2810-
let r = type_structurally_recursive(cx, sp, seen, ty);
2811-
if r != Representable {
2812-
return r
2813-
}
2814-
}
2815-
Representable
2812+
iter.fold(Representable,
2813+
|r, ty| cmp::max(r, is_type_structurally_recursive(cx, sp, seen, ty)))
28162814
}
28172815

2818-
// Does the type `ty` directly (without indirection through a pointer)
2819-
// contain any types on stack `seen`?
2820-
fn type_structurally_recursive(cx: &ctxt, sp: Span, seen: &mut Vec<DefId>,
2821-
ty: t) -> Representability {
2822-
debug!("type_structurally_recursive: {}",
2823-
::util::ppaux::ty_to_string(cx, ty));
2824-
2825-
// Compare current type to previously seen types
2826-
match get(ty).sty {
2827-
ty_struct(did, _) |
2828-
ty_enum(did, _) => {
2829-
for (i, &seen_did) in seen.iter().enumerate() {
2830-
if did == seen_did {
2831-
return if i == 0 { SelfRecursive }
2832-
else { ContainsRecursive }
2833-
}
2834-
}
2835-
}
2836-
_ => (),
2837-
}
2838-
2839-
// Check inner types
2816+
fn are_inner_types_recursive(cx: &ctxt, sp: Span,
2817+
seen: &mut Vec<t>, ty: t) -> Representability {
28402818
match get(ty).sty {
2841-
// Tuples
28422819
ty_tup(ref ts) => {
28432820
find_nonrepresentable(cx, sp, seen, ts.iter().map(|t| *t))
28442821
}
28452822
// Fixed-length vectors.
28462823
// FIXME(#11924) Behavior undecided for zero-length vectors.
28472824
ty_vec(ty, Some(_)) => {
2848-
type_structurally_recursive(cx, sp, seen, ty)
2825+
is_type_structurally_recursive(cx, sp, seen, ty)
28492826
}
2850-
2851-
// Push struct and enum def-ids onto `seen` before recursing.
28522827
ty_struct(did, ref substs) => {
2853-
seen.push(did);
28542828
let fields = struct_fields(cx, did, substs);
2855-
let r = find_nonrepresentable(cx, sp, seen,
2856-
fields.iter().map(|f| f.mt.ty));
2857-
seen.pop();
2858-
r
2829+
find_nonrepresentable(cx, sp, seen, fields.iter().map(|f| f.mt.ty))
28592830
}
2860-
28612831
ty_enum(did, ref substs) => {
2862-
seen.push(did);
28632832
let vs = enum_variants(cx, did);
2833+
let iter = vs.iter()
2834+
.flat_map(|variant| { variant.args.iter() })
2835+
.map(|aty| { aty.subst_spanned(cx, substs, Some(sp)) });
28642836

2865-
let mut r = Representable;
2866-
for variant in vs.iter() {
2867-
let iter = variant.args.iter().map(|aty| {
2868-
aty.subst_spanned(cx, substs, Some(sp))
2869-
});
2870-
r = find_nonrepresentable(cx, sp, seen, iter);
2837+
find_nonrepresentable(cx, sp, seen, iter)
2838+
}
2839+
ty_unboxed_closure(did, _) => {
2840+
let upvars = unboxed_closure_upvars(cx, did);
2841+
find_nonrepresentable(cx, sp, seen, upvars.iter().map(|f| f.ty))
2842+
}
2843+
_ => Representable,
2844+
}
2845+
}
28712846

2872-
if r != Representable { break }
2847+
fn same_struct_or_enum_def_id(ty: t, did: DefId) -> bool {
2848+
match get(ty).sty {
2849+
ty_struct(ty_did, _) | ty_enum(ty_did, _) => {
2850+
ty_did == did
2851+
}
2852+
_ => false
2853+
}
2854+
}
2855+
2856+
fn same_type(a: t, b: t) -> bool {
2857+
match (&get(a).sty, &get(b).sty) {
2858+
(&ty_struct(did_a, ref substs_a), &ty_struct(did_b, ref substs_b)) |
2859+
(&ty_enum(did_a, ref substs_a), &ty_enum(did_b, ref substs_b)) => {
2860+
if did_a != did_b {
2861+
return false;
28732862
}
28742863

2875-
seen.pop();
2876-
r
2877-
}
2864+
let types_a = substs_a.types.get_slice(subst::TypeSpace);
2865+
let types_b = substs_b.types.get_slice(subst::TypeSpace);
28782866

2879-
ty_unboxed_closure(did, _) => {
2880-
let upvars = unboxed_closure_upvars(cx, did);
2881-
find_nonrepresentable(cx,
2882-
sp,
2883-
seen,
2884-
upvars.iter().map(|f| f.ty))
2867+
let mut pairs = types_a.iter().zip(types_b.iter());
2868+
2869+
pairs.all(|(&a, &b)| same_type(a, b))
2870+
}
2871+
_ => {
2872+
type_id(a) == type_id(b)
28852873
}
2874+
}
2875+
}
28862876

2887-
_ => Representable,
2877+
// Does the type `ty` directly (without indirection through a pointer)
2878+
// contain any types on stack `seen`?
2879+
fn is_type_structurally_recursive(cx: &ctxt, sp: Span, seen: &mut Vec<t>,
2880+
ty: t) -> Representability {
2881+
debug!("is_type_structurally_recursive: {}",
2882+
::util::ppaux::ty_to_string(cx, ty));
2883+
2884+
match get(ty).sty {
2885+
ty_struct(did, _) | ty_enum(did, _) => {
2886+
{
2887+
// Iterate through stack of previously seen types.
2888+
let mut iter = seen.iter();
2889+
2890+
// The first item in `seen` is the type we are actually curious about.
2891+
// We want to return SelfRecursive if this type contains itself.
2892+
// It is important that we DON'T take generic parameters into account
2893+
// for this check, so that Bar<T> in this example counts as SelfRecursive:
2894+
//
2895+
// struct Foo;
2896+
// struct Bar<T> { x: Bar<Foo> }
2897+
2898+
match iter.next() {
2899+
Some(&seen_type) => {
2900+
if same_struct_or_enum_def_id(seen_type, did) {
2901+
debug!("SelfRecursive: {} contains {}",
2902+
::util::ppaux::ty_to_string(cx, seen_type),
2903+
::util::ppaux::ty_to_string(cx, ty));
2904+
return SelfRecursive;
2905+
}
2906+
}
2907+
None => {}
2908+
}
2909+
2910+
// We also need to know whether the first item contains other types that
2911+
// are structurally recursive. If we don't catch this case, we will recurse
2912+
// infinitely for some inputs.
2913+
//
2914+
// It is important that we DO take generic parameters into account here,
2915+
// so that code like this is considered SelfRecursive, not ContainsRecursive:
2916+
//
2917+
// struct Foo { Option<Option<Foo>> }
2918+
2919+
for &seen_type in iter {
2920+
if same_type(ty, seen_type) {
2921+
debug!("ContainsRecursive: {} contains {}",
2922+
::util::ppaux::ty_to_string(cx, seen_type),
2923+
::util::ppaux::ty_to_string(cx, ty));
2924+
return ContainsRecursive;
2925+
}
2926+
}
2927+
}
2928+
2929+
// For structs and enums, track all previously seen types by pushing them
2930+
// onto the 'seen' stack.
2931+
seen.push(ty);
2932+
let out = are_inner_types_recursive(cx, sp, seen, ty);
2933+
seen.pop();
2934+
out
2935+
}
2936+
_ => {
2937+
// No need to push in other cases.
2938+
are_inner_types_recursive(cx, sp, seen, ty)
2939+
}
28882940
}
28892941
}
28902942

@@ -2894,8 +2946,11 @@ pub fn is_type_representable(cx: &ctxt, sp: Span, ty: t) -> Representability {
28942946
// To avoid a stack overflow when checking an enum variant or struct that
28952947
// contains a different, structurally recursive type, maintain a stack
28962948
// of seen types and check recursion for each of them (issues #3008, #3779).
2897-
let mut seen: Vec<DefId> = Vec::new();
2898-
type_structurally_recursive(cx, sp, &mut seen, ty)
2949+
let mut seen: Vec<t> = Vec::new();
2950+
let r = is_type_structurally_recursive(cx, sp, &mut seen, ty);
2951+
debug!("is_type_representable: {} is {}",
2952+
::util::ppaux::ty_to_string(cx, ty), r);
2953+
r
28992954
}
29002955

29012956
pub fn type_is_trait(ty: t) -> bool {
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 Foo { foo: Option<Option<Foo>> }
12+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
13+
14+
impl Foo { fn bar(&self) {} }
15+
16+
fn main() {}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 Baz { q: Option<Foo> }
12+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
13+
14+
struct Foo { q: Option<Baz> }
15+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
16+
17+
impl Foo { fn bar(&self) {} }
18+
19+
fn main() {}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
use std::sync::Mutex;
12+
13+
struct Foo { foo: Mutex<Option<Foo>> }
14+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
15+
16+
impl Foo { fn bar(&self) {} }
17+
18+
fn main() {}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 Foo<T> { foo: Option<Option<Foo<T>>> }
12+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
13+
14+
impl<T> Foo<T> { fn bar(&self) {} }
15+
16+
fn main() {}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 Foo { foo: Bar<Foo> }
12+
struct Bar<T> { x: Bar<Foo> }
13+
//~^ ERROR illegal recursive struct type; wrap the inner value in a box to make it representable
14+
15+
impl Foo { fn foo(&self) {} }
16+
17+
fn main() {
18+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
use std::sync::Mutex;
12+
13+
enum Foo { X(Mutex<Option<Foo>>) }
14+
//~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
15+
16+
impl Foo { fn bar(self) {} }
17+
18+
fn main() {}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 { Voo(Option<Option<Foo>>) }
12+
//~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
13+
14+
impl Foo { fn bar(&self) {} }
15+
16+
fn main() { }

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

+2
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ enum E1 { V1(E2<E1>), }
1212
enum E2<T> { V2(E2<E1>), }
1313
//~^ ERROR illegal recursive enum type; wrap the inner value in a box to make it representable
1414

15+
impl E1 { fn foo(&self) {} }
16+
1517
fn main() {
1618
}

0 commit comments

Comments
 (0)