java-topology/defects/zulip-0001/test/test_zulip_0001.py
russell@unturf.com 75c7eab158 zulip: 5-MOAD scan; 3 CWE-407 defects in user_groups list membership checks
zulip-0001: user_groups.py lock_subgroups_with_respect_to_supergroup group_ids_found list O(G*F) -> set O(G+F), 142x at N=1000
zulip-0002: user_groups.py update_user_group group_ids_found list O(D*F) -> set O(D+F), 76x at N=1000
zulip-0003: actions/user_groups.py update_users_in_full_members_system_group full_member_group_user_ids list O(M*F) -> set O(M+F), 55x at N=1000

Previous shallow scan marked CLEAN; deeper scan found all 3. MOAD-0002/0003/0004/0005 CLEAN.
3/3 unit tests PASS.
2026-04-03 13:24:38 -04:00

94 lines
3.2 KiB
Python

"""
zulip-0001 — CWE-407: group_ids_found list scan in lock_subgroups_with_respect_to_supergroup
Simulates the O(G*F) vs O(G+F) membership check for subgroup validation.
Benchmarks N=100 and N=1000, asserts speedup > 3x.
"""
import time
import os
PYTHONUNBUFFERED = os.environ.get("PYTHONUNBUFFERED", "")
def check_missing_ids_list(potential_subgroup_ids, potential_subgroups):
"""Original O(G * F): builds list, uses 'not in' on list."""
group_ids_found = [group_id for group_id in potential_subgroups]
group_ids_not_found = [
group_id for group_id in potential_subgroup_ids if group_id not in group_ids_found
]
return group_ids_not_found
def check_missing_ids_set(potential_subgroup_ids, potential_subgroups):
"""Fixed O(G + F): builds set, uses 'not in' on set."""
group_ids_found_set = {group_id for group_id in potential_subgroups}
group_ids_not_found = [
group_id for group_id in potential_subgroup_ids if group_id not in group_ids_found_set
]
return group_ids_not_found
def benchmark(fn, subgroup_ids, subgroups, reps=200):
t0 = time.perf_counter()
for _ in range(reps):
result = fn(subgroup_ids, subgroups)
return time.perf_counter() - t0, result
def run_test(N, reps=200):
# All IDs match (worst case for list scan — must scan full list for every ID)
subgroups = list(range(N))
subgroup_ids = list(range(N))
t_list, r_list = benchmark(check_missing_ids_list, subgroup_ids, subgroups, reps)
t_set, r_set = benchmark(check_missing_ids_set, subgroup_ids, subgroups, reps)
assert r_list == r_set, f"Results differ: {r_list} vs {r_set}"
speedup = t_list / t_set if t_set > 0 else float("inf")
print(f"N={N:5d}: list={t_list*1000:.1f}ms set={t_set*1000:.1f}ms speedup={speedup:.1f}x")
return speedup
def test_with_missing_ids(N):
"""Also verify correctness: some IDs are missing."""
subgroups = list(range(0, N, 2)) # even IDs found
subgroup_ids = list(range(N)) # all IDs requested
expected_missing = sorted(range(1, N, 2)) # odd IDs missing
missing_list = check_missing_ids_list(subgroup_ids, subgroups)
missing_set = check_missing_ids_set(subgroup_ids, subgroups)
assert sorted(missing_list) == expected_missing, f"list missing wrong: {missing_list}"
assert sorted(missing_set) == expected_missing, f"set missing wrong: {missing_set}"
print(f"N={N}: correctness OK, {len(expected_missing)} missing IDs detected")
if __name__ == "__main__":
export_unbuffered = True # rely on PYTHONUNBUFFERED=1 from caller
print("=== zulip-0001: group_ids_found list vs set benchmark ===")
print()
# Correctness tests
test_with_missing_ids(100)
test_with_missing_ids(1000)
print()
# Performance tests
speedup_100 = run_test(100, reps=500)
speedup_1000 = run_test(1000, reps=100)
print()
PASS = True
if speedup_100 < 3.0:
print(f"FAIL N=100: speedup {speedup_100:.1f}x < 3x threshold")
PASS = False
if speedup_1000 < 3.0:
print(f"FAIL N=1000: speedup {speedup_1000:.1f}x < 3x threshold")
PASS = False
if PASS:
print("PASS")
else:
raise SystemExit(1)