fix(extract): harden #2072 src-layout resolution (adversarial-review fixes)

Three issues found reviewing #2072:
- The import-edge repoint loop matched by target id regardless of the edge's
  language, so a non-Python dotted import (C# `using Pkg.Mod;`, Java/Go) whose
  dangling target coincided with a Python alias got repointed onto a Python file,
  fabricating a cross-language import. Gate the rewrite on the edge being
  Python-sourced.
- The resolver ancestor walk probed package dirs too, resolving an absolute
  `from helpers import x` to a sibling in the current package (Python-2 implicit-
  relative semantics) even when `helpers` is external. Only probe sys.path-root
  candidates (ancestors without their own __init__.py).
- Bound the __init__.py package-root chain walk by the path depth so a
  pathological `/__init__.py` can't loop.
Added a cross-language-guard regression test; fixed the tautological (or->and)
ambiguity assertion.
This commit is contained in:
safishamsi
2026-07-21 14:22:50 +01:00
parent afa7c0d9d3
commit 10b710561a
3 changed files with 49 additions and 3 deletions
+12 -2
View File
@@ -212,7 +212,9 @@ def _repoint_python_package_imports(paths, all_nodes, all_edges, root) -> None:
continue # top-level file: scan-root-relative id already matches
d = Path(p).resolve().parent
levels = 0
while (d / "__init__.py").is_file():
# Bounded by the number of dirs between the file and the scan root, so a
# pathological `/__init__.py` chain can't loop forever.
while levels < len(parts) - 1 and (d / "__init__.py").is_file():
levels += 1
d = d.parent
if levels == 0:
@@ -235,7 +237,15 @@ def _repoint_python_package_imports(paths, all_nodes, all_edges, root) -> None:
if not alias_map:
return
for e in all_edges:
if isinstance(e, dict) and e.get("relation") in ("imports", "imports_from"):
# Only repoint edges emitted from a Python file: a non-Python import edge
# (e.g. C# `using Pkg.Mod;`, Java/Go dotted imports) can have a dangling
# target string that coincides with a Python alias, and repointing it
# would fabricate a cross-language import edge (#2072 review).
if (
isinstance(e, dict)
and e.get("relation") in ("imports", "imports_from")
and str(e.get("source_file", "")).lower().endswith((".py", ".pyi"))
):
tgt = e.get("target")
if tgt in alias_map:
e["target"] = alias_map[tgt]
+8
View File
@@ -1645,6 +1645,14 @@ def _resolve_python_module_path(module_name: str, current_path: Path, root: Path
break # left the scan root; stop walking up
if anc == root:
continue # already probed root/rel above
# Only probe sys.path-root candidates — dirs that are NOT themselves part
# of a package. Probing a package dir would resolve an absolute
# `from helpers import x` to a sibling in the current package (Python-2
# implicit-relative semantics), fabricating edges to what may be an
# external dependency (#2072 review). A src-layout root (src/, no
# __init__.py) is still probed.
if (anc / "__init__.py").is_file():
continue
cand = _probe_python_module_candidate(anc / rel)
if cand is not None:
return cand
+29 -1
View File
@@ -120,6 +120,34 @@ def test_ambiguous_package_alias_is_not_repointed(tmp_path):
# repointed onto either file — no fabricated cross-tree import edge.
imports = _import_edges(G)
targets = {v for _, _, v in imports}
assert "a_src_pkg_mod" not in targets or "b_src_pkg_mod" not in targets, (
# Neither file may be chosen — an ambiguous alias must stay dangling.
assert "a_src_pkg_mod" not in targets and "b_src_pkg_mod" not in targets, (
f"ambiguous alias was repointed to a specific file: {imports}"
)
def test_non_python_import_edge_is_not_repointed(tmp_path):
"""#2072 review: the alias map is Python-only, but a non-Python import edge
whose dangling target coincides with a Python alias must NOT be repointed
onto a Python file (that would fabricate a cross-language import)."""
pkg = tmp_path / "src" / "pkg"
pkg.mkdir(parents=True)
(pkg / "__init__.py").write_text("")
(pkg / "mod.py").write_text("def f():\n return 1\n")
# Simulate a non-Python (C#) import edge whose target string collides with the
# Python alias `pkg_mod`, by hand-building the extraction the way extract emits.
result = extract([pkg / "__init__.py", pkg / "mod.py"], cache_root=tmp_path / "c",
root=tmp_path, parallel=False)
result["nodes"].append(
{"id": "app_cs", "label": "app.cs", "file_type": "code", "source_file": "app.cs"}
)
result["edges"].append(
{"source": "app_cs", "target": "pkg_mod", "relation": "imports",
"confidence": "EXTRACTED", "source_file": "app.cs"}
)
G = build_from_json(result, root=str(tmp_path))
# The C# edge's target must remain the (dangling, dropped) `pkg_mod`, never
# repointed to the Python file node src_pkg_mod.
assert not any(v == "src_pkg_mod" and u == "app_cs" for _, u, v in _import_edges(G)), (
"non-Python import edge was repointed onto a Python file (#2072 review)"
)