You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The unconditional_recursion lint can trigger in divergent functions #54444
@estebank It's wrong because the function can diverge without recursively calling itself.
They're both literally the same example, I just replaced one form of divergence with another.
@estebank Both "stuck in an infinite loop" and "panics" are different from "infinitely self-recurses".
What we want to detect is unguardedself-recursion, that is, when you forgot to check some condition that would give you a convergent or divergent "base case", that would stop the infinite recursion.
The message of the lint doesn't help my point, it should be more clearly about "infinite recursion".
While usually you're missing a condition, or you're calling the wrong function, it's also possible that you're using infinite recursion to infinitely repeat something - but we don't have guaranteed tail calls, so what you really want to do is rewrite the infinite recursion into an infinite loop.
Wouldn't this need a backwards dataflow analysis to catch this case? It doesn't looks like the bit would ever be propagated to a return block like this. A backwards analysis would instead propagate it to the entry block, if I understand correctly.
So I guess the property you'd propagate backwards is "always reaches self-call", which means every block starts at false (except those that contain a self-call) and the merge operator is &.
The funny thing about this being a 1-bit-per-block analysis is that instead of a BitMatrix you can use a simple BitSet<BasicBlock> and fixpoint is also easier to achieve. Rough sketch:
letmut results = BitSet::empty(body.basic_blocks());letmut queue:VecDeque<_> = self_calls.iter().collect();whileletSome(bb) = queue.pop_front(){if results.contains(bb){// Already propagated.continue;}let always_reaches_self_call = match body[bb].terminator.kind{TerminatorKind::Call(..)if self_calls.contains(bb) => true,TerminatorKind::SwitchInt(branches)
=> branches.iter().all(|&target| results.contains(target)),TerminatorKind::Goto{ target }// ... and all other single target variants, ignoring any cleanup edges
=> result.contains(target),// We only ever go backwards through the CFG, these should be impossible to hit.TerminatorKind::Return | TerminatorKind::Resume => unreachable!(),};if always_reaches_self_call {// This is a newly confirmed-to-always-reach-self-call block.assert!(results.insert(bb));// Propagate backwards through the CFG.
queue.extend(body.predecessors(bb));}}let fn_always_reaches_self_call = results[START_BLOCK];
Activity
estebank commentedon Sep 21, 2018
Isn't the first one kind of correct, if misleading? The second one is indeed problematic.
cramertj commentedon Sep 21, 2018
Technically correct, because it can't return at all ;)
eddyb commentedon Sep 21, 2018
@estebank It's wrong because the function can diverge without recursively calling itself.
They're both literally the same example, I just replaced one form of divergence with another.
estebank commentedon Sep 24, 2018
@eddyb mmmm....
In my mind I read this as
while the latter, being
panic!it is also!, but can terminate (while theloophas no side-effects).You're correct, but I'd love it if we could somehow differentiate when evaluating divergence between "can't return" and "can't return but can panic".
eddyb commentedon Sep 26, 2018
@estebank Both "stuck in an infinite loop" and "panics" are different from "infinitely self-recurses".
What we want to detect is unguarded self-recursion, that is, when you forgot to check some condition that would give you a convergent or divergent "base case", that would stop the infinite recursion.
The message of the lint doesn't help my point, it should be more clearly about "infinite recursion".
While usually you're missing a condition, or you're calling the wrong function, it's also possible that you're using infinite recursion to infinitely repeat something - but we don't have guaranteed tail calls, so what you really want to do is rewrite the infinite recursion into an infinite loop.
steveklabnik commentedon Mar 27, 2020
Triage: no change
jonas-schievink commentedon Apr 4, 2020
Since we currently lint this:
Wouldn't this need a backwards dataflow analysis to catch this case? It doesn't looks like the bit would ever be propagated to a return block like this. A backwards analysis would instead propagate it to the entry block, if I understand correctly.
eddyb commentedon Apr 5, 2020
@jonas-schievink You're right, there's no merge for either the
panic!(...)or theloop {}examples, that could collect the information.Presumably we want to tell these apart:
vs
So I guess the property you'd propagate backwards is "always reaches self-call", which means every block starts at
false(except those that contain a self-call) and the merge operator is&.The funny thing about this being a 1-bit-per-block analysis is that instead of a
BitMatrixyou can use a simpleBitSet<BasicBlock>and fixpoint is also easier to achieve. Rough sketch:Rollup merge of rust-lang#70822 - jonas-schievink:curse-of-the-recurs…
Rollup merge of rust-lang#70973 - ecstatic-morse:recursion-lint, r=jo…