fix(complexity): receiver-aware self-recursion detection (#599)#699
fix(complexity): receiver-aware self-recursion detection (#599)#699lg320531124 wants to merge 1 commit into
Conversation
|
Ready for review — all 14 CI checks green across the full platform matrix (ubuntu-latest/arm, macos-14/15-intel, windows CLANG64), plus dco/analyze/lint/CodeQL/security gates. Closes #599 (and #592 dup). Implements the receiver-aware call classification you specified: |
|
Huge thanks for opening this PR and for the work you put into it. The maintainer shop is currently full, so this may sit for a bit before it gets a proper review. We will come back to this as soon as possible with real feedback; I wanted to make sure it did not sit unacknowledged in the meantime. |
|
Thanks for the acknowledgment — totally understand the review backlog. No rush on my end; happy to rebase or expand the test matrix whenever you're ready to take a look. |
Self-recursion was flagged whenever the callee's short name matched the enclosing method name, ignoring the receiver. super().save() inside a method named save, and axios.get() inside a method named get, were both misclassified as self-recursion — inflating is_recursive and unguarded_recursion. Only treat a call as self-recursion when the callee resolves to the same instance/class: bare names (free functions) and self/this/cls/@self receivers. super (parent class) and any other receiver now correctly do not count, even when the method name matches. Adds is_self_receiver() and a regression test covering super(), axios.get, and self.recur() — the super() case the maintainer named in DeusData#599. Signed-off-by: lg320531124 <lg320531124@users.noreply.github.com>
f9fa960 to
993f79e
Compare
Problem
Closes #599. Closes #592 (duplicate).
The recursion detector flagged a function/method as self-recursive whenever its body contained a call whose bare name matched the function name — ignoring the receiver. This made
super().method()overrides and same-named delegating wrappers false positives. In the reporter's 93k-node repo, everyunguarded_recursion=truenode also hadself_recursive=true, and a large fraction were not recursive at all.False positives from #599:
Order.save,ProductPriceHistory.savesuper().save(*a, **k)MPApiError.__init__super().__init__(...)ApiClient.get(TS)return this.request(...)/axios.get(...)*.error/*.warnwrappersconsole.error(...)Root cause
internal/cbm/cbm.cstripped the callee to its short name withstrrchr(callee_name, '.')and compared only that against the enclosing def name.super().save()→ callee_name"super.save"→ short"save"== def name"save"→ flagged, even thoughsuperis the parent class, neverself.Fix
Add
is_self_receiver(): treat a call as self-recursion only when the callee resolves to the same instance/class.self/this/cls/@selfreceivers → self-recursion (same object)superreceiver → NOT self (parent class)axios,console,this.requeston a different instance) → NOT selfThe check is applied where the name comparison already happens, so all three signals stay in sync:
is_recursive,recursion_in_loop,unguarded_recursion. No blanket suppression of the recursion flag — just receiver-aware classification, exactly as the maintainer specified in #599.Regression test
complexity_receiver_aware_recursioncovers the maintainer-named cases:super().save()insidesave→ not recursive (parent call);self.save()in the same method still trips itaxios.get()insideget→ not recursive (different receiver)self.recur()insiderecur→ recursive (same object), guarded → not unguardedScope
Two files, +90/-2. No changes to the call-graph extraction (callee_name already carries the receiver from
extract_calls.c); only the classification predicate changes.Signed-off-by: lg320531124 lg320531124@users.noreply.github.com