-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
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. |
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iter()
is redundant.
Some(_) => { | ||
bug!("shadow_seen stack contains non-ADT type: {:?}", ty); | ||
} |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
src/test/ui/issues/issue-74224.rs
Outdated
@@ -0,0 +1,11 @@ | |||
struct A<T> { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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)
@davidtwco Thanks for your suggestions! I have implemented them now. |
@bors r+ |
📌 Commit 98728c2 has been approved by |
// mentioned above). | ||
let mut nested_seen: Vec<Ty<'_>> = vec![]; | ||
result = Some( | ||
match is_type_structurally_recursive( |
There was a problem hiding this comment.
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.
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.
☀️ Test successful - checks-actions |
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:
When checking
A
, the compiler correctly determines that it has an infinite size (because the "root" type isA
, andA
occurs, albeit with different type arguments, as a nested type inA
).However, when checking
B
, it also recurses intoA
, but nowB
is the root type, and it only checks for exact matches ofA
, but sinceA
never precisely contains itself (onlyA<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 checksA
separately and stops ifA
already turns out to be infinite. If not (such as forOption<T>
), the second phase checks whether the root type (B
) is ever nested inside itself, e.g.:Special care needs to be taken for mutually recursive types, e.g.:
Here, both
A
andB
both areSelfRecursive
and contain a recursive type. The current behavior, which I have maintained, is to treat bothA
andB
asSelfRecursive
, and accordingly report errors for both.