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

Check for unbounded recursion during dropck #22777

Merged
merged 2 commits into from
Mar 2, 2015

Conversation

pnkfelix
Copy link
Member

Check for unbounded recursion during dropck.

Such recursion can be introduced by the erroneous use of non-regular types (aka types employing polymorphic recursion), which Rust does not support.

Fix #22443

@rust-highfive
Copy link
Collaborator

r? @nikomatsakis

(rust_highfive has picked a reviewer for you, use r? to override)

// except according to those terms.

// Issue 22443: Reject code using non-regular types that would
// otherwise cause dropck to loop infinitely.
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the difference between this and the first test?

Copy link
Member Author

Choose a reason for hiding this comment

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

the comment below says why. (The bug report observed different behaviors, both bad, depending on the order of Digit versus Box)

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, I think it'd be helpful to write in the comment something like "In contrast to Digit after Box, which is the other test."

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, will do

@nikomatsakis
Copy link
Contributor

r+ modulo nit

@pnkfelix
Copy link
Member Author

@bors r=nikomatsakis ed18e6b rollup

@pnkfelix
Copy link
Member Author

(Note for rollup builders: I marked this as rollup, but do note that it adds a new error code, which increases its chances for collision with another PR that also adds the same error code, especially since the same error code might be added in a different file.)

steveklabnik added a commit to steveklabnik/rust that referenced this pull request Feb 26, 2015
Check for unbounded recursion during dropck.

Such recursion can be introduced by the erroneous use of non-regular types (aka types employing polymorphic recursion), which Rust does not support.

Fix rust-lang#22443
@Manishearth
Copy link
Member

This caused a slew of dropck overflows in the rollup.

a sample:

/opt/rust/src/libsyntax/fold.rs:992:17: 992:56 error: overflow while adding drop-check rules for util::small_vector::IntoIter<ast::ImplItem> [E0320]
/opt/rust/src/libsyntax/fold.rs:992                 folder.fold_impl_item(item).into_iter()
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/rust/src/libsyntax/fold.rs:992:17: 992:56 note: overflowed on struct ast::StructField_ field kind type: ast::StructFieldKind
/opt/rust/src/libsyntax/fold.rs:992                 folder.fold_impl_item(item).into_iter()
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/rust/src/libsyntax/fold.rs:992:17: 992:44 error: overflow while adding drop-check rules for util::small_vector::SmallVector<ast::ImplItem> [E0320]
/opt/rust/src/libsyntax/fold.rs:992                 folder.fold_impl_item(item).into_iter()
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/rust/src/libsyntax/fold.rs:992:17: 992:44 note: overflowed on struct ast::StructField_ field kind type: ast::StructFieldKind
/opt/rust/src/libsyntax/fold.rs:992                 folder.fold_impl_item(item).into_iter()
                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/rust/src/libsyntax/fold.rs:1028:13: 1029:44 error: overflow while adding drop-check rules for core::iter::Map<util::small_vector::IntoIter<ptr::P<ast::Method>>, [closure /opt/rust/src/libsyntax/fold.rs:1029:22: 1029:43]> [E0320]
/opt/rust/src/libsyntax/fold.rs:1028             folder.fold_method(method).into_iter()
/opt/rust/src/libsyntax/fold.rs:1029                 .map(|m| ProvidedMethod(m)).collect()
/opt/rust/src/libsyntax/fold.rs:1028:13: 1029:44 note: overflowed on struct ast::StructField_ field kind type: ast::StructFieldKind
/opt/rust/src/libsyntax/fold.rs:1028             folder.fold_method(method).into_iter()
/opt/rust/src/libsyntax/fold.rs:1029                 .map(|m| ProvidedMethod(m)).collect()

@Manishearth
Copy link
Member

Full log

Looks like they were caused during make check for various crates

@pnkfelix
Copy link
Member Author

weird! Okay, I'll look into that.

@pnkfelix
Copy link
Member Author

@bors r-

@pnkfelix
Copy link
Member Author

@Manishearth wow, I totally failed to make rustc-stage2 (and apparently nothing in run-pass has a structure that exercises something like this for make check-stage1-rpass...) ; sorry about that!

@Manishearth
Copy link
Member

Hah. I don't think anyone expects rustc-stage2 from anything but large PRs.

So, this PR needs rustc-stage2 to be tested properly? I don't envy your next few days 😬 (I had to do the same for -Z extra-plugins)

@pnkfelix
Copy link
Member Author

@Manishearth no, now that I know about it, I can make a test applicable for the stage1 compiler (and put it into run-pass). (Also, make check-stage1 would have caught this; but not check-stage1-rpass)

@Manishearth
Copy link
Member

That sounds less painful 😃

@pnkfelix
Copy link
Member Author

Reduced test case:

#![allow(non_camel_case_types)]

pub fn noop_fold_impl_item() -> SmallVector<ImplItem> {
    loop  { }
}

pub struct SmallVector<T>(P<T>);
pub struct ImplItem(P<S01_Method>);

struct P<T>(Box<T>);

struct S01_Method(P<S02_Generics>);
struct S02_Generics(P<S03_TyParam>);
struct S03_TyParam(P<S04_TyParamBound>);
struct S04_TyParamBound(S05_PolyTraitRef);
struct S05_PolyTraitRef(S06_TraitRef);
struct S06_TraitRef(S07_Path);
struct S07_Path(Vec<S08_PathSegment>);
struct S08_PathSegment(S09_PathParameters);
struct S09_PathParameters(P<S10_ParenthesizedParameterData>);
struct S10_ParenthesizedParameterData(Option<P<S11_Ty>>);
struct S11_Ty(P<S12_Expr>);
struct S12_Expr(P<S13_Block>);
struct S13_Block(Vec<P<S14_Stmt>>);
struct S14_Stmt(P<S15_Decl>);
struct S15_Decl(P<S16_Local>);
struct S16_Local(P<S17_Pat>);
struct S17_Pat(P<S18_Mac>);
struct S18_Mac(Vec<P<S19_TokenTree>>);
struct S19_TokenTree(P<S20_Token>);
struct S20_Token(P<S21_Nonterminal>);
struct S21_Nonterminal(P<S22_Item>);
struct S22_Item(P<S23_EnumDef>);
struct S23_EnumDef(Vec<P<S24_Variant>>);
struct S24_Variant(P<S25_VariantKind>);
struct S25_VariantKind(P<S26_StructDef>);
struct S26_StructDef(Vec<P<S27_StructField>>);
struct S27_StructField(P<S28_StructFieldKind>);
struct S28_StructFieldKind;

pub fn main() {}

@bors
Copy link
Contributor

bors commented Feb 28, 2015

☔ The latest upstream changes (presumably #22895) made this pull request unmergeable. Please resolve the merge conflicts.

@pnkfelix
Copy link
Member Author

So, after walking through the debug log, I see that a likely problem here is that I am incrementing the recursion counter even for the recursion that descends from struct A(B); to the definition of struct B { ... }. So the counter is being increased too fast in this analysis, and even simple programs like the one above (which was derived from the libsyntax crate) will fail to compile.

BUT that kind of recursive descent is just standard traversal over the inductive structure of the type; i.e., it cannot lead to infinite loops.

The only kind of traversal that can lead to infinite loops are those that go through references, i.e. cases like &B, Box<B>, Vec<B>, ....; such references fall into two categories: non-owning references and owning references. Non-owning references like &B are ignored by dropck. Owning references are captured by Box<B> and PhantomData<B> (and in the future, Box<B> should just be another instance of PhantomData<B>.

SO: For the short term, my plan is to keep a separate counter that is incremented only when the recursion traverses through an owning reference, as outlined above.


For the long term, however, I am a little worried that such an approach does not scale up properly. I.e. one is still going to be able to make programs (probably real programs) that will exceed the threshold, and I am not convinced the solution of "just tell such crates to increase the value of the recursion counter" will be satisfying in practice.

Instead of just using a fixed recursion limit, we may want to instead make the recursion threshold a function of the number of nodes in the AST, or some similar value that can be easily derived from the static text. E.g. we instead made the threshold where we signal an error be equal to #nodes * recursion_limit (where we of course would use saturated multiply there, and perhaps even 64-bit arithmetic for it).

Anyway, that's just a stray thought. For this PR, I'm certainly going to take the short-cut of just not increasing this counter so fast.

@pnkfelix
Copy link
Member Author

pnkfelix commented Mar 1, 2015

hmm, my simplistic attempt to apply the strategy from the previous comment does not seem to actually catch the test from #22443

Update: ah, I somehow forgot that we still treat Box specially and thus the PhantomData in its definition does not yet have an effect here.

Count recursion across phantom data separately from all recursion,
and treat `Box<T>` just as if it were carrying `PhantomData<T>`.

(Regression tests are in followup commit.)

The practical effect of this is just to increment the `xref_depth`
counter, the same way that `Vec` and other types carrying
`PhantomData` do.
@pnkfelix
Copy link
Member Author

pnkfelix commented Mar 1, 2015

@bors r=nikomatsakis

@bors
Copy link
Contributor

bors commented Mar 1, 2015

@bors r=pnkfelix 180ef47

@pnkfelix
Copy link
Member Author

pnkfelix commented Mar 1, 2015

(Note again for rollup builders: I marked this as rollup, but do note that it adds a new error code, which increases its chances for collision with another PR that also adds the same error code, especially since the same error code might be added in a different file. On the bright side, I did run make check-stage2 ahead of time this time. :) )

@pnkfelix
Copy link
Member Author

pnkfelix commented Mar 1, 2015

@bors r=nikomatsakis 180ef47

Manishearth added a commit to Manishearth/rust that referenced this pull request Mar 1, 2015
 Check for unbounded recursion during dropck.

Such recursion can be introduced by the erroneous use of non-regular types (aka types employing polymorphic recursion), which Rust does not support.

Fix rust-lang#22443
@bors bors merged commit 180ef47 into rust-lang:master Mar 2, 2015
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.

Infinite loop/stack overflow in rustc (dropck) on non-regular data type
5 participants