Skip to content

Commit 38ab63c

Browse files
committed
feat(schema): L2 backfill of body call-node callee (null→id)
1 parent 195c2f4 commit 38ab63c

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

codeanalyzer/core.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from codeanalyzer.schema.assign_ids import assign_ids
2222
from codeanalyzer.schema.l1_body import populate_l1_body
23+
from codeanalyzer.schema.l2_callees import backfill_callees
2324
from codeanalyzer.schema.py_schema import PyCallEdge
2425
from codeanalyzer.semantic_analysis.call_graph import (
2526
filter_external_edges,
@@ -459,8 +460,11 @@ def analyze(self) -> Analysis:
459460
.build()
460461
)
461462

462-
assign_ids(app, self.options.app_name or self.project_dir.name)
463+
app_name = self.options.app_name or self.project_dir.name
464+
sig_to_id = assign_ids(app, app_name)
463465
populate_l1_body(app)
466+
if self.analysis_level >= 2:
467+
backfill_callees(app, sig_to_id)
464468

465469
# L3/L4 dataflow emission rebuilt on the v2 tree in Stage 3+
466470

codeanalyzer/schema/l2_callees.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""L2 refinement: fill each L1 `call` body node's `callee` (null→id) from the
2+
call site's resolved signature — the one sanctioned value change. A declared
3+
target becomes its can:// id; an external/library target keeps its dotted
4+
signature; an unresolved call site leaves `callee` absent."""
5+
from __future__ import annotations
6+
from codeanalyzer.schema.py_schema import PyApplication, PyClass, PyCallable
7+
8+
9+
def _do_callable(c: PyCallable, sig_to_id: dict) -> None:
10+
for cs in c.call_sites or []:
11+
if cs.callee_signature is None:
12+
continue
13+
key = f"{cs.start_line}:{cs.start_column}"
14+
node = c.body.get(key)
15+
if node is None or node.kind != "call":
16+
continue
17+
node.callee = sig_to_id.get(cs.callee_signature, cs.callee_signature)
18+
for ic in (c.inner_callables or {}).values():
19+
_do_callable(ic, sig_to_id)
20+
for icl in (c.inner_classes or {}).values():
21+
_do_class(icl, sig_to_id)
22+
23+
24+
def _do_class(cl: PyClass, sig_to_id: dict) -> None:
25+
for m in (cl.methods or {}).values():
26+
_do_callable(m, sig_to_id)
27+
for ic in (cl.inner_classes or {}).values():
28+
_do_class(ic, sig_to_id)
29+
30+
31+
def backfill_callees(app: PyApplication, sig_to_id: dict) -> None:
32+
for mod in app.symbol_table.values():
33+
for fn in (mod.functions or {}).values():
34+
_do_callable(fn, sig_to_id)
35+
for cl in (mod.classes or {}).values():
36+
_do_class(cl, sig_to_id)

test/test_v2_l2.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from codeanalyzer.schema.l2_callees import backfill_callees
2+
from codeanalyzer.schema.py_schema import (
3+
PyApplication, PyModule, PyCallable, PyCallsite, BodyNode,
4+
)
5+
6+
def _app_with_one_call(callee_sig):
7+
cs = PyCallsite(method_name="g", start_line=2, start_column=4, end_line=2,
8+
end_column=7, callee_signature=callee_sig)
9+
fn = PyCallable(name="f", path="m.py", signature="m.f", call_sites=[cs],
10+
body={"2:4": BodyNode(kind="call", callee=None)})
11+
mod = PyModule(file_path="m.py", module_name="m", functions={"f": fn})
12+
return PyApplication(symbol_table={"m.py": mod}), fn
13+
14+
def test_declared_callee_resolves_to_can_id():
15+
app, fn = _app_with_one_call("m.g")
16+
backfill_callees(app, {"m.g": "can://python/app/m.py/g()"})
17+
assert fn.body["2:4"].callee == "can://python/app/m.py/g()"
18+
19+
def test_external_callee_keeps_dotted_signature():
20+
app, fn = _app_with_one_call("requests.get")
21+
backfill_callees(app, {"m.g": "can://python/app/m.py/g()"}) # requests.get not declared
22+
assert fn.body["2:4"].callee == "requests.get"
23+
24+
def test_unresolved_callsite_leaves_callee_absent():
25+
app, fn = _app_with_one_call(None)
26+
backfill_callees(app, {})
27+
assert fn.body["2:4"].callee is None

0 commit comments

Comments
 (0)