Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checking for structural recursion can cause a stack overflow #84611

Closed
lcnr opened this issue Apr 27, 2021 · 2 comments · Fixed by #85012
Closed

checking for structural recursion can cause a stack overflow #84611

lcnr opened this issue Apr 27, 2021 · 2 comments · Fixed by #85012
Labels
C-bug Category: This is a bug. I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.

Comments

@lcnr
Copy link
Contributor

lcnr commented Apr 27, 2021

struct Foo<T> {
    x: Foo<[T; 1]>,
    y: T,
}

struct Bar {
    x: Foo<Bar>,
}

has the following output:

error[E0072]: recursive type `Foo` has infinite size
 --> src/lib.rs:1:1
  |
1 | struct Foo<T> {
  | ^^^^^^^^^^^^^ recursive type has infinite size
2 |     x: Foo<[T; 1]>,
  |        ----------- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
  |
2 |     x: Box<Foo<[T; 1]>>,
  |        ^^^^           ^


thread 'rustc' has overflowed its stack
fatal runtime error: stack overflow

the stack overflow should not happen ^^

@lcnr lcnr added the C-bug Category: This is a bug. label Apr 27, 2021
@lcnr
Copy link
Contributor Author

lcnr commented Apr 27, 2021

the recursion happens in

// We also need to know whether the first item contains other types
// that are structurally recursive. If we don't catch this case, we
// will recurse infinitely for some inputs.
//
// It is important that we DO take generic parameters into account
// here, so that code like this is considered SelfRecursive, not
// ContainsRecursive:
//
// struct Foo { Option<Option<Foo>> }
for &seen_type in iter {
if ty::TyS::same_type(ty, seen_type) {
debug!("ContainsRecursive: {:?} contains {:?}", seen_type, ty);
return Representability::ContainsRecursive;
}
}
}

Because Foo is not trivially self recursive this check is never hit and we keep looking into Foo<[Bar; 1]>, Foo<[[Bar; 1]; 1]> and so on.

@jonas-schievink jonas-schievink added the I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics. label Apr 27, 2021
@lcnr
Copy link
Contributor Author

lcnr commented Apr 27, 2021

cc #74224

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue May 10, 2021
Fix stack overflow when checking for structural recursion

This pull request aims to fix rust-lang#74224 and fix rust-lang#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.
@bors bors closed this as completed in d4d129d May 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants