mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-19 10:21:34 +00:00
fix(js): shadow a file-scope callback's own params (#4448)
A closure passed in a module-level call statement is walked under the file node (#3124), but its params and locals were never registered for that walk. A parameter passed on as a call argument then resolved against the corpus-wide label index and produced an indirect_call to any function with the same name, including module-private ones in other packages. Record the closure's bindings in closure_locals_by_body, the same way the #2568 branch already does for const-assigned closures.
This commit is contained in:
1 parent
fe085ceddd
commit
60cfd51b7d
2 files changed
+80
No files matched your search
@@ -3809,6 +3809,14 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
|
||||
# `_js_scan_member_assignments`, so it lands in
|
||||
# `_tracked_body_ids` and walk_calls' own boundary check
|
||||
# skips re-descending into it from here.
|
||||
# #4448: the walk runs under the file node, so the
|
||||
# closure's own params/locals must ride along as this
|
||||
# body's extra_locals (as in the #2568 branch); otherwise
|
||||
# `forEach(function (value) { s.on(value) })` resolves the
|
||||
# parameter `value` to any same-named function in the corpus.
|
||||
if closure_locals_by_body is not None:
|
||||
closure_locals_by_body[id(closure_body)] = (
|
||||
_js_local_bound_names(closure, source))
|
||||
function_bodies.append((file_nid, closure_body))
|
||||
assign = next((c for c in node.children
|
||||
if c.type == "assignment_expression"), None)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""A parameter of a file-scope anonymous callback must shadow same-named
|
||||
functions elsewhere in the corpus (#4448).
|
||||
|
||||
#3124 walks the body of a closure passed in a module-level call statement
|
||||
(`items.forEach(function (value) { ... })`, AngularJS `.controller(...)`,
|
||||
`it("...", () => { ... })`) under the file node, but did not register the
|
||||
closure's own params/locals for that walk. A parameter passed on as a call
|
||||
argument was then resolved against the corpus-wide label index and became an
|
||||
`indirect_call` from the file to any function named like it. Inside a named
|
||||
function the same code was already handled (#2241, #2568).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
_TARGET = {"b.js": "function value(x) { return x; }\n"}
|
||||
|
||||
|
||||
def _indirect(tmp_path, a_js: str):
|
||||
files = {"a.js": a_js, **_TARGET}
|
||||
for name, body in files.items():
|
||||
(tmp_path / name).write_text(body)
|
||||
r = extract([tmp_path / n for n in files],
|
||||
cache_root=tmp_path / "graphify-out", parallel=False)
|
||||
lbl = {n["id"]: n["label"] for n in r["nodes"]}
|
||||
return {(lbl.get(e["source"]), lbl.get(e["target"])) for e in r["edges"]
|
||||
if e["relation"] == "indirect_call"}
|
||||
|
||||
|
||||
def test_callback_param_passed_to_a_member_call_is_not_a_reference(tmp_path):
|
||||
assert _indirect(tmp_path, "items.forEach(function (value) { s.on(value); });\n") == set()
|
||||
|
||||
|
||||
def test_callback_param_passed_to_a_bare_call_is_not_a_reference(tmp_path):
|
||||
assert _indirect(tmp_path, "items.forEach(function (value) { emit(value); });\n") == set()
|
||||
|
||||
|
||||
def test_param_of_a_nested_file_scope_callback_is_not_a_reference(tmp_path):
|
||||
src = "m.c(function (s) { items.forEach(function (value) { s.on(value); }); });\n"
|
||||
assert _indirect(tmp_path, src) == set()
|
||||
|
||||
|
||||
def test_arrow_callback_params_are_not_references(tmp_path):
|
||||
src = "items.forEach((value) => emit(value));\nitems.map(value => emit(value));\n"
|
||||
assert _indirect(tmp_path, src) == set()
|
||||
|
||||
|
||||
def test_callback_var_is_not_a_reference(tmp_path):
|
||||
src = "items.forEach(function () { var value = 1; emit(value); });\n"
|
||||
assert _indirect(tmp_path, src) == set()
|
||||
|
||||
|
||||
def test_unbound_name_inside_a_file_scope_callback_still_resolves(tmp_path):
|
||||
assert _indirect(tmp_path, "items.forEach(function (x) { emit(value); });\n") == {("a.js", "value()")}
|
||||
|
||||
|
||||
def test_a_sibling_callbacks_param_does_not_shadow(tmp_path):
|
||||
src = "m.c(function (value) { emit(1); }, function () { emit(value); });\n"
|
||||
assert _indirect(tmp_path, src) == {("a.js", "value()")}
|
||||
|
||||
|
||||
def test_param_of_a_callback_inside_a_test_block_is_not_a_reference(tmp_path):
|
||||
# The inner arrow is itself an expression statement inside the `it`
|
||||
# callback, so the #3124 branch tracks it as its own body too.
|
||||
src = (
|
||||
"describe('d', () => {\n"
|
||||
" it('t', async () => {\n"
|
||||
" intercept((value) => respond(value, 1));\n"
|
||||
" });\n"
|
||||
"});\n"
|
||||
)
|
||||
assert _indirect(tmp_path, src) == set()
|
||||
Reference in new issue
Block a user