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

Bug fixes for struct+enum representability check #17815

Merged
merged 1 commit into from
Oct 18, 2014

Conversation

isabelmu
Copy link
Contributor

@isabelmu isabelmu commented Oct 6, 2014

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:

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 Options 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 DefIds--this is necessary to prevent code like this from being allowed:

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.

@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 @brson (or someone else) soon.

@isabelmu
Copy link
Contributor Author

isabelmu commented Oct 6, 2014

@huonw reviewed my last round of changes to this, pull request #11839

ContainsRecursive,
SelfRecursive,
Copy link
Member

Choose a reason for hiding this comment

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

I take it this ordering is now important; could you add a comment describing this? (Or at least noting it.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, will do

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you know whether #[deriving(Ord)] is guaranteed to order the variants so the first listed is the 'least' and the last is the 'greatest'? Or is it better if I don't rely on this?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think we do have that guarantee.

@huonw
Copy link
Member

huonw commented Oct 7, 2014

This was caused by using DefId's for the ContainsRecursive check, which meant the nested Options were identified as illegally recursive (because ContainsRecursive is not an error, we would then keep compiling and eventually hit a stack overflow).

I'm confused by this sentence; is it meant to say "identified as legally recursive"?

mut iter: It) -> Representability {
let mut r = Representable;

for ty in iter {
Copy link
Member

Choose a reason for hiding this comment

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

Theoretically this whole function could be: iter.fold(Representable, |r, ty| cmp::max(r, type_structurally_recursive(cx, sp, seen, ty)). Especially since the short-circuit case is the error case, and hence compilation will soon stop anyway.

@huonw
Copy link
Member

huonw commented Oct 7, 2014

I don't see any tests for SelfRecursive enums; could you add some, just to ensure the right thing is happening for them? (e.g. the first test case you give has infinite recursion with enums too.)

Thanks so much for really focusing on this @typelist!

@isabelmu
Copy link
Contributor Author

isabelmu commented Oct 7, 2014

This was caused by using DefId's for the ContainsRecursive check, which meant
the nested Options were identified as illegally recursive (because ContainsRecursive
is not an error, we would then keep compiling and eventually hit a stack overflow).

I'm confused by this sentence; is it meant to say "identified as legally recursive"?

No. struct Foo { Option<Option<Foo>> } was identified as ContainsRecursive, meaning Foo contains a different, illegally recursive type, Option. ContainsRecursive isn't considered an error; it doesn't need to be, because if the inner type were really illegally recursive, we'd catch that when we type check Option, and then we'd report Option as SelfRecursive. The ContainsRecursive check is meant to stop us from recursing through Option indefinitely, but did so incorrectly in this case.

@pnkfelix
Copy link
Member

pnkfelix commented Oct 7, 2014

cc me

@isabelmu
Copy link
Contributor Author

isabelmu commented Oct 9, 2014

I think I've addressed the comments so far. (Thanks especially for the suggestions for terser use of iterators, @huonw!)

Maybe this code will have to change again at some point to take associated types/items into account? (I haven't really looked into it yet.)

bors added a commit that referenced this pull request Oct 18, 2014
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.
@bors bors closed this Oct 18, 2014
@bors bors merged commit 53ddf2e into rust-lang:master Oct 18, 2014
lnicola pushed a commit to lnicola/rust that referenced this pull request Aug 29, 2024
…p, r=Veykril

feat: Allow declaring cfg groups in rust-project.json, to help sharing common cfgs

Closes rust-lang#17815.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

rustc does not detect some recursive struct definitions
6 participants