fix(parsers/python): resolve self/cls-aliased method calls in the call graph#115
Open
gadievron wants to merge 1 commit into
Open
fix(parsers/python): resolve self/cls-aliased method calls in the call graph#115gadievron wants to merge 1 commit into
gadievron wants to merge 1 commit into
Conversation
…l graph `_resolve_call_node` matched a method-call receiver only when it was literally named `self`/`cls`, so an aliased receiver — `obj = self; obj.method()` or `alias = cls; alias.method()` in a classmethod — produced NO call-graph edge. The inherited/self method then looked unreachable (a false negative in the reachable set, the dangerous direction for reachability-based analysis). Fix: in `_extract_calls_from_code`, collect local names single-bound to `self`/`cls` (`_collect_self_aliases`) and pass them to `_resolve_call_node`, which now routes any `<alias>.method()` through `_resolve_self_call`. Only single, unconditional bindings count — a name reassigned to anything else is not treated as an alias, so no spurious edge is created. The change only ADDS previously-dropped edges; it never removes one (raises reachability, never lowers it) and is local to the receiver-resolution branch (no architecture change). Tests (tests/parsers/python/test_call_graph_self_calls.py): RED before / GREEN after — `test_self_alias_method_call_edge`, `test_cls_alias_method_call_edge`, plus `test_reassigned_alias_does_not_force_self_edge` (soundness guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
Merge-order note (not a defect — flagging for landing order) This is an explicit follow-up to #112 (same |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
CallGraphBuilder._resolve_call_node(libs/openant-core/parsers/python/call_graph_builder.py) matched a method-call receiver only when it was the literal nameselforcls. A receiver that is a local alias of self/cls —obj = self; obj.method(), oralias = cls; alias.method()inside a classmethod — fell through to_resolve_module_call, which finds no import/class for a plain local variable and returnsNone. The self/class-method call produced no call-graph edge, so the callee can look unreachable (a false negative in the reachable set).Reproduction
Before:
call_graph["m.py:C.caller"]is[](theC.caller -> C.targetedge is missing).How
_collect_self_aliases(tree): scans the function body for local names single-bound toself/cls(a name assigned more than once, or ever rebound to anything else, is not treated as an alias — so no spurious edge)._extract_calls_from_codebuildsself_aliases = {'self','cls'} | _collect_self_aliases(tree)and passes it to_resolve_call_node._resolve_call_nodetakesself_aliases(defaulting to{'self','cls'}so existing callers are unchanged) and resolves any<alias>.method()through the existing_resolve_self_call. The two priorself/clsbranches collapse into oneobj.id in self_aliasescheck.This only adds edges that were previously dropped; it never removes one — so it raises reachability, never lowers it. The change is confined to the receiver-resolution branch (no new subsystem / data model).
Regression test
libs/openant-core/tests/parsers/python/test_call_graph_self_calls.py(+62 lines):test_self_alias_method_call_edge—obj = self; obj.target()edge present.test_cls_alias_method_call_edge—alias = cls; alias.target()edge present.test_reassigned_alias_does_not_force_self_edge— soundness guard: a name reassigned away from self is NOT aliased.RED (before fix):
GREEN (after fix), full
tests/parsers/python/:Compatibility
None.
_resolve_call_node's new parameter is optional and defaults to the previous behavior. No public API change.Notes
The canonical per-parser
tests/parsers/python/test_callgraph_symmetry.py/test_python_schema_completeness.pydo not yet exist in this repo (a pre-existing gap across all parsers, not introduced here); this fix extends the existingtest_call_graph_self_calls.py.Author notes
_collect_self_aliases(new), theobj.id in self_aliasesreceiver branch, and theself_aliasesthread-through in_extract_calls_from_code.obj = self; obj.target()— previously noC.caller -> C.targetedge; now resolved.= self/= clsbindings count, pinned bytest_reassigned_alias_does_not_force_self_edge.Coordination with open PRs
#112 adds a
_build_alias_mapto the same resolution path, but for function-value aliases (fn = helper; fn()) — its docstring notes class/method targets are out of scope. This PR handles the distinct self/cls alias case (obj = self; obj.method()), which #112 leaves unresolved. The two are complementary but touch the same code; they should be unified rather than stacked. Happy to rebase onto #112 and fold the self/cls aliasing into its alias map.