Skip to content

fix(complexity): receiver-aware self-recursion detection (#599)#699

Open
lg320531124 wants to merge 1 commit into
DeusData:mainfrom
lg320531124:fix/receiver-aware-recursion-detection
Open

fix(complexity): receiver-aware self-recursion detection (#599)#699
lg320531124 wants to merge 1 commit into
DeusData:mainfrom
lg320531124:fix/receiver-aware-recursion-detection

Conversation

@lg320531124

Copy link
Copy Markdown

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, every unguarded_recursion=true node also had self_recursive=true, and a large fraction were not recursive at all.

False positives from #599:

node body why not recursion
Order.save, ProductPriceHistory.save super().save(*a, **k) calls the parent class method
MPApiError.__init__ super().__init__(...) parent ctor
ApiClient.get (TS) return this.request(...) / axios.get(...) different receiver
*.error / *.warn wrappers console.error(...) different receiver

Root cause

internal/cbm/cbm.c stripped the callee to its short name with strrchr(callee_name, '.') and compared only that against the enclosing def name. super().save() → callee_name "super.save" → short "save" == def name "save" → flagged, even though super is the parent class, never self.

Fix

Add is_self_receiver(): treat a call as self-recursion only when the callee resolves to the same instance/class.

  • bare names (free function calling itself) → self-recursion (preserves prior behavior)
  • self / this / cls / @self receivers → self-recursion (same object)
  • super receiver → NOT self (parent class)
  • any other receiver (axios, console, this.request on a different instance) → NOT self

The 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_recursion covers the maintainer-named cases:

  1. super().save() inside savenot recursive (parent call); self.save() in the same method still trips it
  2. axios.get() inside getnot recursive (different receiver)
  3. self.recur() inside recur → recursive (same object), guarded → not unguarded
complexity_recursion_in_loop_unguarded   PASS
complexity_guarded_recursion             PASS
complexity_receiver_aware_recursion      PASS   ← new
5721 passed, 0 failed

Scope

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

@lg320531124 lg320531124 requested a review from DeusData as a code owner June 29, 2026 14:15
@lg320531124

Copy link
Copy Markdown
Author

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: super().save() and same-named wrappers on other receivers (axios.get, console.error) no longer trip self_recursive/unguarded_recursion, while genuine self-calls (bare names, self/this/cls receivers) still do. Regression test complexity_receiver_aware_recursion covers the super() case you named. Local: 5721 passed, 0 failed.

@DeusData

Copy link
Copy Markdown
Owner

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.

@lg320531124

Copy link
Copy Markdown
Author

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.

@DeusData DeusData added bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency. labels Jun 29, 2026
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>
@lg320531124 lg320531124 force-pushed the fix/receiver-aware-recursion-detection branch from f9fa960 to 993f79e Compare July 1, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency.

2 participants