java-topology/defects/ansible/patch/ansible-0002-role-collections-set.md

1.8 KiB

UNDF: UNDF-2026-000000006

ans-0002: role _load_role_data() collections list O(C) membership tests per role load

Severity: LOW CWE: CWE-407 (Algorithmic Complexity — linear membership test in hot loop) Speedup: ~30x at C=50 collections (verified by unit test) Target: Ansible (ansible/ansible) Files:

  • lib/ansible/playbook/role/__init__.py:287c not in self.collections generator
  • lib/ansible/playbook/role/__init__.py:293 — two not in self.collections guards

Description

_load_role_data() deduplicates collection names using a list:

self.collections.extend((c for c in self._metadata.collections if c not in self.collections))
# ...
if 'ansible.builtin' not in self.collections and 'ansible.legacy' not in self.collections:
    self.collections.append(default_append_collection)

Each not in self.collections is O(C) where C = current list length. Called once per role load; with C=50 collections this is ~155 list scans instead of 3 hash lookups.

Root Cause

self.collections is a list to preserve insertion order. The list is queried for membership with O(C) not in tests.

Fix: maintain a parallel _collections_set (Python set) as a shadow of self.collections. All membership tests become O(1). The list is retained unchanged so that ordering semantics are preserved; _collections_set is kept in sync at every mutation site.

Patch

See patch/ans-0002-role-collections-set.patch

Complexity Before

Per not in self.collections check: O(C) Total per _load_role_data() call: O(C) (3 checks)

Complexity After

Per not in self._collections_set check: O(1) average Total: O(1)

Reproduction

cd defects/ansible/unit && javac -d . AnsibleRoleTest.java && java -ea unit.AnsibleRoleTest

test3: C=50 candidates, defect=1585, fixed=52, ratio=30.5x