Skip to content

Commit d4d129d

Browse files
committed
Auto merge of #85012 - FabianWolff:struct-rec, r=davidtwco
Fix stack overflow when checking for structural recursion This pull request aims to fix #74224 and fix #84611. The current logic for detecting ADTs with structural recursion is flawed because it only looks at the root type, and then for exact matches. What I mean by this is that for examples such as: ```rust struct A<T> { x: T, y: A<A<T>>, } struct B { z: A<usize> } fn main() {} ``` When checking `A`, the compiler correctly determines that it has an infinite size (because the "root" type is `A`, and `A` occurs, albeit with different type arguments, as a nested type in `A`). However, when checking `B`, it also recurses into `A`, but now `B` is the root type, and it only checks for _exact_ matches of `A`, but since `A` never precisely contains itself (only `A<A<T>>`, `A<A<A<T>>>`, etc.), an endless recursion ensues until the stack overflows. In this PR, I have attempted to fix this behavior by implementing a two-phase checking: When checking `B`, my code first checks `A` _separately_ and stops if `A` already turns out to be infinite. If not (such as for `Option<T>`), the second phase checks whether the root type (`B`) is ever nested inside itself, e.g.: ```rust struct Foo { x: Option<Option<Foo>> } ``` Special care needs to be taken for mutually recursive types, e.g.: ```rust struct A<T> { z: T, x: B<T>, } struct B<T> { y: A<T> } ``` Here, both `A` and `B` both _are_ `SelfRecursive` and _contain_ a recursive type. The current behavior, which I have maintained, is to treat both `A` and `B` as `SelfRecursive`, and accordingly report errors for both.
2 parents 79e50bf + 98728c2 commit d4d129d

File tree

7 files changed

+338
-19
lines changed

7 files changed

+338
-19
lines changed

compiler/rustc_ty_utils/src/representability.rs

+200-19
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,26 @@ pub enum Representability {
2525
pub fn ty_is_representable<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, sp: Span) -> Representability {
2626
debug!("is_type_representable: {:?}", ty);
2727
// To avoid a stack overflow when checking an enum variant or struct that
28-
// contains a different, structurally recursive type, maintain a stack
29-
// of seen types and check recursion for each of them (issues #3008, #3779).
28+
// contains a different, structurally recursive type, maintain a stack of
29+
// seen types and check recursion for each of them (issues #3008, #3779,
30+
// #74224, #84611). `shadow_seen` contains the full stack and `seen` only
31+
// the one for the current type (e.g. if we have structs A and B, B contains
32+
// a field of type A, and we're currently looking at B, then `seen` will be
33+
// cleared when recursing to check A, but `shadow_seen` won't, so that we
34+
// can catch cases of mutual recursion where A also contains B).
3035
let mut seen: Vec<Ty<'_>> = Vec::new();
36+
let mut shadow_seen: Vec<&'tcx ty::AdtDef> = Vec::new();
3137
let mut representable_cache = FxHashMap::default();
32-
let r = is_type_structurally_recursive(tcx, sp, &mut seen, &mut representable_cache, ty);
38+
let mut force_result = false;
39+
let r = is_type_structurally_recursive(
40+
tcx,
41+
sp,
42+
&mut seen,
43+
&mut shadow_seen,
44+
&mut representable_cache,
45+
ty,
46+
&mut force_result,
47+
);
3348
debug!("is_type_representable: {:?} is {:?}", ty, r);
3449
r
3550
}
@@ -48,21 +63,38 @@ fn are_inner_types_recursive<'tcx>(
4863
tcx: TyCtxt<'tcx>,
4964
sp: Span,
5065
seen: &mut Vec<Ty<'tcx>>,
66+
shadow_seen: &mut Vec<&'tcx ty::AdtDef>,
5167
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
5268
ty: Ty<'tcx>,
69+
force_result: &mut bool,
5370
) -> Representability {
71+
debug!("are_inner_types_recursive({:?}, {:?}, {:?})", ty, seen, shadow_seen);
5472
match ty.kind() {
5573
ty::Tuple(..) => {
5674
// Find non representable
57-
fold_repr(
58-
ty.tuple_fields().map(|ty| {
59-
is_type_structurally_recursive(tcx, sp, seen, representable_cache, ty)
60-
}),
61-
)
75+
fold_repr(ty.tuple_fields().map(|ty| {
76+
is_type_structurally_recursive(
77+
tcx,
78+
sp,
79+
seen,
80+
shadow_seen,
81+
representable_cache,
82+
ty,
83+
force_result,
84+
)
85+
}))
6286
}
6387
// Fixed-length vectors.
6488
// FIXME(#11924) Behavior undecided for zero-length vectors.
65-
ty::Array(ty, _) => is_type_structurally_recursive(tcx, sp, seen, representable_cache, ty),
89+
ty::Array(ty, _) => is_type_structurally_recursive(
90+
tcx,
91+
sp,
92+
seen,
93+
shadow_seen,
94+
representable_cache,
95+
ty,
96+
force_result,
97+
),
6698
ty::Adt(def, substs) => {
6799
// Find non representable fields with their spans
68100
fold_repr(def.all_fields().map(|field| {
@@ -76,12 +108,128 @@ fn are_inner_types_recursive<'tcx>(
76108
Some(hir::Node::Field(field)) => field.ty.span,
77109
_ => sp,
78110
};
79-
match is_type_structurally_recursive(tcx, span, seen, representable_cache, ty) {
80-
Representability::SelfRecursive(_) => {
81-
Representability::SelfRecursive(vec![span])
111+
112+
let mut result = None;
113+
114+
// First, we check whether the field type per se is representable.
115+
// This catches cases as in #74224 and #84611. There is a special
116+
// case related to mutual recursion, though; consider this example:
117+
//
118+
// struct A<T> {
119+
// z: T,
120+
// x: B<T>,
121+
// }
122+
//
123+
// struct B<T> {
124+
// y: A<T>
125+
// }
126+
//
127+
// Here, without the following special case, both A and B are
128+
// ContainsRecursive, which is a problem because we only report
129+
// errors for SelfRecursive. We fix this by detecting this special
130+
// case (shadow_seen.first() is the type we are originally
131+
// interested in, and if we ever encounter the same AdtDef again,
132+
// we know that it must be SelfRecursive) and "forcibly" returning
133+
// SelfRecursive (by setting force_result, which tells the calling
134+
// invocations of are_inner_types_representable to forward the
135+
// result without adjusting).
136+
if shadow_seen.len() > seen.len() && shadow_seen.first() == Some(def) {
137+
*force_result = true;
138+
result = Some(Representability::SelfRecursive(vec![span]));
139+
}
140+
141+
if result == None {
142+
result = Some(Representability::Representable);
143+
144+
// Now, we check whether the field types per se are representable, e.g.
145+
// for struct Foo { x: Option<Foo> }, we first check whether Option<_>
146+
// by itself is representable (which it is), and the nesting of Foo
147+
// will be detected later. This is necessary for #74224 and #84611.
148+
149+
// If we have encountered an ADT definition that we have not seen
150+
// before (no need to check them twice), recurse to see whether that
151+
// definition is SelfRecursive. If so, we must be ContainsRecursive.
152+
if shadow_seen.len() > 1
153+
&& !shadow_seen
154+
.iter()
155+
.take(shadow_seen.len() - 1)
156+
.any(|seen_def| seen_def == def)
157+
{
158+
let adt_def_id = def.did;
159+
let raw_adt_ty = tcx.type_of(adt_def_id);
160+
debug!("are_inner_types_recursive: checking nested type: {:?}", raw_adt_ty);
161+
162+
// Check independently whether the ADT is SelfRecursive. If so,
163+
// we must be ContainsRecursive (except for the special case
164+
// mentioned above).
165+
let mut nested_seen: Vec<Ty<'_>> = vec![];
166+
result = Some(
167+
match is_type_structurally_recursive(
168+
tcx,
169+
span,
170+
&mut nested_seen,
171+
shadow_seen,
172+
representable_cache,
173+
raw_adt_ty,
174+
force_result,
175+
) {
176+
Representability::SelfRecursive(_) => {
177+
if *force_result {
178+
Representability::SelfRecursive(vec![span])
179+
} else {
180+
Representability::ContainsRecursive
181+
}
182+
}
183+
x => x,
184+
},
185+
);
186+
}
187+
188+
// We only enter the following block if the type looks representable
189+
// so far. This is necessary for cases such as this one (#74224):
190+
//
191+
// struct A<T> {
192+
// x: T,
193+
// y: A<A<T>>,
194+
// }
195+
//
196+
// struct B {
197+
// z: A<usize>
198+
// }
199+
//
200+
// When checking B, we recurse into A and check field y of type
201+
// A<A<usize>>. We haven't seen this exact type before, so we recurse
202+
// into A<A<usize>>, which contains, A<A<A<usize>>>, and so forth,
203+
// ad infinitum. We can prevent this from happening by first checking
204+
// A separately (the code above) and only checking for nested Bs if
205+
// A actually looks representable (which it wouldn't in this example).
206+
if result == Some(Representability::Representable) {
207+
// Now, even if the type is representable (e.g. Option<_>),
208+
// it might still contribute to a recursive type, e.g.:
209+
// struct Foo { x: Option<Option<Foo>> }
210+
// These cases are handled by passing the full `seen`
211+
// stack to is_type_structurally_recursive (instead of the
212+
// empty `nested_seen` above):
213+
result = Some(
214+
match is_type_structurally_recursive(
215+
tcx,
216+
span,
217+
seen,
218+
shadow_seen,
219+
representable_cache,
220+
ty,
221+
force_result,
222+
) {
223+
Representability::SelfRecursive(_) => {
224+
Representability::SelfRecursive(vec![span])
225+
}
226+
x => x,
227+
},
228+
);
82229
}
83-
x => x,
84230
}
231+
232+
result.unwrap()
85233
}))
86234
}
87235
ty::Closure(..) => {
@@ -106,8 +254,10 @@ fn is_type_structurally_recursive<'tcx>(
106254
tcx: TyCtxt<'tcx>,
107255
sp: Span,
108256
seen: &mut Vec<Ty<'tcx>>,
257+
shadow_seen: &mut Vec<&'tcx ty::AdtDef>,
109258
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
110259
ty: Ty<'tcx>,
260+
force_result: &mut bool,
111261
) -> Representability {
112262
debug!("is_type_structurally_recursive: {:?} {:?}", ty, sp);
113263
if let Some(representability) = representable_cache.get(ty) {
@@ -118,8 +268,15 @@ fn is_type_structurally_recursive<'tcx>(
118268
return representability.clone();
119269
}
120270

121-
let representability =
122-
is_type_structurally_recursive_inner(tcx, sp, seen, representable_cache, ty);
271+
let representability = is_type_structurally_recursive_inner(
272+
tcx,
273+
sp,
274+
seen,
275+
shadow_seen,
276+
representable_cache,
277+
ty,
278+
force_result,
279+
);
123280

124281
representable_cache.insert(ty, representability.clone());
125282
representability
@@ -129,12 +286,16 @@ fn is_type_structurally_recursive_inner<'tcx>(
129286
tcx: TyCtxt<'tcx>,
130287
sp: Span,
131288
seen: &mut Vec<Ty<'tcx>>,
289+
shadow_seen: &mut Vec<&'tcx ty::AdtDef>,
132290
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
133291
ty: Ty<'tcx>,
292+
force_result: &mut bool,
134293
) -> Representability {
135294
match ty.kind() {
136295
ty::Adt(def, _) => {
137296
{
297+
debug!("is_type_structurally_recursive_inner: adt: {:?}, seen: {:?}", ty, seen);
298+
138299
// Iterate through stack of previously seen types.
139300
let mut iter = seen.iter();
140301

@@ -158,8 +319,10 @@ fn is_type_structurally_recursive_inner<'tcx>(
158319
// will recurse infinitely for some inputs.
159320
//
160321
// It is important that we DO take generic parameters into account
161-
// here, so that code like this is considered SelfRecursive, not
162-
// ContainsRecursive:
322+
// here, because nesting e.g. Options is allowed (as long as the
323+
// definition of Option doesn't itself include an Option field, which
324+
// would be a case of SelfRecursive above). The following, too, counts
325+
// as SelfRecursive:
163326
//
164327
// struct Foo { Option<Option<Foo>> }
165328

@@ -174,13 +337,31 @@ fn is_type_structurally_recursive_inner<'tcx>(
174337
// For structs and enums, track all previously seen types by pushing them
175338
// onto the 'seen' stack.
176339
seen.push(ty);
177-
let out = are_inner_types_recursive(tcx, sp, seen, representable_cache, ty);
340+
shadow_seen.push(def);
341+
let out = are_inner_types_recursive(
342+
tcx,
343+
sp,
344+
seen,
345+
shadow_seen,
346+
representable_cache,
347+
ty,
348+
force_result,
349+
);
350+
shadow_seen.pop();
178351
seen.pop();
179352
out
180353
}
181354
_ => {
182355
// No need to push in other cases.
183-
are_inner_types_recursive(tcx, sp, seen, representable_cache, ty)
356+
are_inner_types_recursive(
357+
tcx,
358+
sp,
359+
seen,
360+
shadow_seen,
361+
representable_cache,
362+
ty,
363+
force_result,
364+
)
184365
}
185366
}
186367
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct A<T> {
2+
//~^ ERROR recursive type `A` has infinite size
3+
x: T,
4+
y: A<A<T>>,
5+
}
6+
7+
struct B {
8+
z: A<usize>
9+
}
10+
11+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0072]: recursive type `A` has infinite size
2+
--> $DIR/issue-74224.rs:1:1
3+
|
4+
LL | struct A<T> {
5+
| ^^^^^^^^^^^ recursive type has infinite size
6+
...
7+
LL | y: A<A<T>>,
8+
| ------- recursive without indirection
9+
|
10+
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `A` representable
11+
|
12+
LL | y: Box<A<A<T>>>,
13+
| ^^^^ ^
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0072`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct Foo<T> {
2+
//~^ ERROR recursive type `Foo` has infinite size
3+
x: Foo<[T; 1]>,
4+
y: T,
5+
}
6+
7+
struct Bar {
8+
x: Foo<Bar>,
9+
}
10+
11+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0072]: recursive type `Foo` has infinite size
2+
--> $DIR/issue-84611.rs:1:1
3+
|
4+
LL | struct Foo<T> {
5+
| ^^^^^^^^^^^^^ recursive type has infinite size
6+
LL |
7+
LL | x: Foo<[T; 1]>,
8+
| ----------- recursive without indirection
9+
|
10+
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
11+
|
12+
LL | x: Box<Foo<[T; 1]>>,
13+
| ^^^^ ^
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0072`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
struct A<T> {
2+
//~^ ERROR recursive type `A` has infinite size
3+
x: T,
4+
y: B<T>,
5+
}
6+
7+
struct B<T> {
8+
//~^ ERROR recursive type `B` has infinite size
9+
z: A<T>
10+
}
11+
12+
struct C<T> {
13+
//~^ ERROR recursive type `C` has infinite size
14+
x: T,
15+
y: Option<Option<D<T>>>,
16+
}
17+
18+
struct D<T> {
19+
//~^ ERROR recursive type `D` has infinite size
20+
z: Option<Option<C<T>>>,
21+
}
22+
23+
fn main() {}

0 commit comments

Comments
 (0)