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:
+12
-2
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user