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

Fix stack overflow when checking for structural recursion #85012

Merged
merged 3 commits into from
May 11, 2021

Conversation

FabianWolff
Copy link
Contributor

@FabianWolff FabianWolff commented May 6, 2021

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:

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.:

struct Foo { x: Option<Option<Foo>> }

Special care needs to be taken for mutually recursive types, e.g.:

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.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @davidtwco (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 6, 2021
// If we have encountered an ADT definition that we have not seen
// before (no need to check them twice), recurse to see whether that
// definition is SelfRecursive. If so, we must be ContainsRecursive.
if shadow_seen.iter().len() > 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iter() is redundant.

Comment on lines 139 to 141
Some(_) => {
bug!("shadow_seen stack contains non-ADT type: {:?}", ty);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe shadow stack could store already extracted &'tcx ty::AdtDef?

@@ -28,8 +28,18 @@ pub fn ty_is_representable<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, sp: Span) -> R
// 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: Vec<Ty<'_>> = Vec::new();
let mut shadow_seen: Vec<Ty<'_>> = Vec::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you update the comment above and describe what does it mean for a type to be present on seen stack and shadow_seen stack.

@@ -0,0 +1,11 @@
struct A<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has been an effort to organize tests by the area of compiler they exercise, and we try to avoid additional tests into issues directory.

Maybe we could have a new directory for structural recursion check, if no other directory is appropriate?

Copy link
Member

@davidtwco davidtwco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some minor nits (in addition to the existing comments)

compiler/rustc_ty_utils/src/representability.rs Outdated Show resolved Hide resolved
compiler/rustc_ty_utils/src/representability.rs Outdated Show resolved Hide resolved
@davidtwco davidtwco added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 10, 2021
@FabianWolff
Copy link
Contributor Author

@davidtwco Thanks for your suggestions! I have implemented them now.

@davidtwco
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented May 10, 2021

📌 Commit 98728c2 has been approved by davidtwco

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 10, 2021
// mentioned above).
let mut nested_seen: Vec<Ty<'_>> = vec![];
result = Some(
match is_type_structurally_recursive(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This independent check for structural recursion belongs somewhere outside this fold, since it not specific to a field. Outside this function most likely, since the check is not strictly about the inner types like the name are_inner_types_recursive would suggest. Maybe in is_type_structurally_recursive_inner? The same is the case for shadow_seen.first() == Some(def) a few lines earlier.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request 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
Copy link
Contributor

bors commented May 11, 2021

⌛ Testing commit 98728c2 with merge d4d129d...

@bors
Copy link
Contributor

bors commented May 11, 2021

☀️ Test successful - checks-actions
Approved by: davidtwco
Pushing d4d129d to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label May 11, 2021
@bors bors merged commit d4d129d into rust-lang:master May 11, 2021
@rustbot rustbot added this to the 1.54.0 milestone May 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
6 participants