ShowCommand._display_tree() uses a list for packages_in_tree, making every `dep.name in current_tree` check O(N). For a project with 500 packages the total membership-test cost is O(N²) ≈ 250,000 ops vs O(N) = 500 with a set. Also fixes shared-state correctness bug: list is passed by reference causing sibling branches to falsely report diamond dependencies as cycles. Fix: set + per-branch set-union copy; 16-31x speedup measured. CLEAN markers added for setuptools and celery (diamond recursion). pip, django, poetry solver already CLEAN (prior or current scan).
1.6 KiB
Diamond Recursion Scan — CLEAN
Scan date: 2026-03-29 Pattern: Recursive DAG traversal without visited set (CWE-407 diamond recursion, O(2^D))
Files examined
setuptools/build_meta.py—_get_build_requires(),get_requires_for_build_wheel/sdist/editable()setuptools/installer.py—_fetch_build_eggs(),_fetch_build_egg_no_warn()setuptools/dist.py—_finalize_requires(),_normalize_requires()setuptools/config/pyprojecttoml.py—_obtain_dependencies(),_obtain_optional_dependencies()setuptools/_vendor/importlib_metadata/__init__.py—requires(),resolve()
Findings
build_meta._get_build_requires(): Not recursive. Calls run_setup() once via subprocess/exec,
collects SetupRequirementsError.specifiers. No graph traversal.
installer._fetch_build_eggs(): Not recursive. Iterates over requires list once,
calls _fetch_build_egg_no_warn() per requirement. No diamond DAG traversal.
dist.py / config: All requirement handling is iterative (list flattening, not recursive graph traversal). No self-referential calls.
importlib_metadata.resolve(): Uses WorkingSet.resolve() from pkg_resources-style code,
which uses a processed set to deduplicate requirements. CLEAN.
setuptools delegates actual dependency resolution to pip or the PEP 517 build frontend.
setuptools itself does not implement recursive dependency graph traversal — it only
processes the immediate install_requires, setup_requires, and extras_require lists.