-
Notifications
You must be signed in to change notification settings - Fork 13k
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 finding blanket impls #56722
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
@nikomatsakis: Are there any changes that you'd like me to make? |
@Aaron1011 sorry, I haven't really reviewed this yet. I actually have it on my calendar to do later today (around 4pm if I stick to my schedule =) so I'll let you know! I'm trying hard to catch up, but conferences and other things have left me with quite a backlog. :( |
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.
The code makes sense, but I can't escape the feeling that the bug is a failure to incremental the recursion depth in the recursive obligations, and not that we need a distinct recursion tracker for evaluation. Do you have ready access to the stack backtrace that causes the overflow in the example? (I can do a local build, too)
src/librustc/traits/select.rs
Outdated
@@ -661,14 +665,15 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
&mut self, | |||
stack: TraitObligationStackList<'o, 'tcx>, | |||
predicates: I, | |||
recursion_depth: usize |
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.
can we call this the evaluation_depth
or something like that?
It also feels like there should be a comment that links to the explanation below of why we have a distinct recursion depth tracking in use here.
src/librustc/traits/select.rs
Outdated
@@ -719,7 +742,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
obligation.cause.span, | |||
) { | |||
Some(obligations) => { | |||
self.evaluate_predicates_recursively(previous_stack, obligations.iter()) | |||
self.evaluate_predicates_recursively(previous_stack, obligations.iter(), |
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.
Still, I feel like the intention here was that the predicates we evaluate recursively would have a recursion depth that was incremented relative to obligation.recursion_depth
-- but there are clearly some cases where that's not true. Perhaps that is really the bug?
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.
I think you're right - it should be possible to increment the recursion depth of the obligations before passing them to evaluate_predicates_recursively
, and check that recursion depth at the start of evaluate_predicates_recursively
.
@nikomatsakis: Here's the bottom part of the stack trace: https://gist.github.com/Aaron1011/6ee3bab4a34d6ef895cfb19d25bcd36c |
c0f4080
to
9af9d8b
Compare
@nikomatsakis: I've modified the PR to update the depth on nested obligations, instead of passing a separate |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
It looks as though the new overflow check is getting hit before the old one, resulting in slightly worse error messages on overflow. I'll investigate more later today. |
@Aaron1011 did you ever get a chance to investigate? |
75b9e60
to
6c8398b
Compare
@nikomatsakis: Sorry for the delay. I ended up chaging the
I don't think that's it's possible to avoid these UI changes without significantly complicating the implementation. Since they only represent slight modifications to existing error messages, I think it should be fine. |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
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.
Nice -- I made a few suggestions, one of which may affect some of the tests in question.
src/librustc/traits/select.rs
Outdated
|
||
match obligation.predicate { | ||
ty::Predicate::Trait(ref t) => { | ||
debug_assert!(!t.has_escaping_bound_vars()); | ||
let obligation = obligation.with(t.clone()); | ||
let mut obligation = obligation.with(t.clone()); | ||
obligation.recursion_depth += 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.
I don't think we need to increase the recursion depth here -- this is just translating the predicate from one form to another.
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.
I believe it is necessary here. Consider the following call stack:
evaluate_predicate_recursively
evaluate_trait_predicate_recursively
evaluate_stack
evaluate_candidate
evaluate_predicates_recursively
evaluate_predicate_recursively
Here, we've recursed back to evaluate_predicate_recursively
without ever incrementing the recursion depth. To ensure that repeatedly recursing through the ty::Predicate::Trait
match arm doesn't lead to a stack overflow, we need to increment the depth here.
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.
I think that there should be an increase in between steps 4 and 5. In particular, the "nested obligations" returned by evaluate_candidate
ought to be at a recursion level one higher than the candidate that spawned them.
@@ -813,10 +827,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
ty::Predicate::Projection(ref data) => { | |||
let project_obligation = obligation.with(data.clone()); | |||
match project::poly_project_and_unify_type(self, &project_obligation) { | |||
Ok(Some(subobligations)) => { | |||
Ok(Some(mut subobligations)) => { | |||
self.add_depth(subobligations.iter_mut(), obligation.recursion_depth); |
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.
and here
src/librustc/traits/select.rs
Outdated
@@ -1155,6 +1170,38 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
.insert(trait_ref, WithDepNode::new(dep_node, result)); | |||
} | |||
|
|||
// Due to caching of projection results, it's possible for a subobligation |
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.
I don't really think this is the reason why. It's more that things like subtype
and wf
don't take the "current recursion depth" as input and seem to just produce obligations at recursion depth 0, right? I would describe this function as a "workaround" for that scenario (which strikes me as odd but prob not worth changing in this PR)
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.
I was referring to the ty::Predicate::Projection
match arm, where we call poly_project_and_unify_type
. If the projection result is cached, the subobligations will have an 'old' recursion_depth
.
I'll update the comment to mention that several different factors create the need to ensure that our recursion_depth
doesn't decrease.
☔ The latest upstream changes (presumably #55517) made this pull request unmergeable. Please resolve the merge conflicts. |
Currently, SelectionContext tries to prevent stack overflow by keeping track of the current recursion depth. However, this depth tracking is only used when performing normal section (which includes confirmation). No such tracking is performed for evaluate_obligation_recursively, which can allow a stack overflow to occur. To fix this, this commit tracks the current predicate evaluation depth. This is done separately from the existing obligation depth tracking: an obligation overflow can occur across multiple calls to 'select' (e.g. when fulfilling a trait), while a predicate evaluation overflow can only happen as a result of a deep recursive call stack. Fixes rust-lang#56701
c2747f2
to
726bdec
Compare
@nikomatsakis: Are there any other changes you'd like me to make? |
@Aaron1011 will look shortly, trying to keep up with reviews, sorry =) |
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.
As I noted below, I still think this increment is unnecessary, and I'd prefer to remove it, as it is somewhat confusing (although I imagine it does no harm). If you think I'm wrong though, definitely let me know.
Er, huh, I can't figure out the GH display. |
I am referring specifically to this comment, but let's take the replies out of that comment thread, since that is always kind of confusing. An example of where rust/src/librustc/traits/select.rs Lines 2760 to 2767 in 14ea6e5
|
@nikomatsakis: You were right! I've removed the uncessary increment (which also made some of the UI tests more closely resemble the current output). |
@bors r+ |
📌 Commit 9b68dcd has been approved by |
…sakis Fix stack overflow when finding blanket impls Currently, SelectionContext tries to prevent stack overflow by keeping track of the current recursion depth. However, this depth tracking is only used when performing normal section (which includes confirmation). No such tracking is performed for evaluate_obligation_recursively, which can allow a stack overflow to occur. To fix this, this commit tracks the current predicate evaluation depth. This is done separately from the existing obligation depth tracking: an obligation overflow can occur across multiple calls to 'select' (e.g. when fulfilling a trait), while a predicate evaluation overflow can only happen as a result of a deep recursive call stack. Fixes #56701 I've re-used `tcx.sess.recursion_limit` when checking for predication evaluation overflows. This is such a weird corner case that I don't believe it's necessary to have a separate setting controlling the maximum depth.
☀️ Test successful - checks-travis, status-appveyor |
Currently, SelectionContext tries to prevent stack overflow by keeping
track of the current recursion depth. However, this depth tracking is
only used when performing normal section (which includes confirmation).
No such tracking is performed for evaluate_obligation_recursively, which
can allow a stack overflow to occur.
To fix this, this commit tracks the current predicate evaluation depth.
This is done separately from the existing obligation depth tracking:
an obligation overflow can occur across multiple calls to 'select' (e.g.
when fulfilling a trait), while a predicate evaluation overflow can only
happen as a result of a deep recursive call stack.
Fixes #56701
I've re-used
tcx.sess.recursion_limit
when checking for predication evaluation overflows. This is such a weird corner case that I don't believe it's necessary to have a separate setting controlling the maximum depth.