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.
95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""
|
|
zulip-0003 — CWE-407: full_member_group_user_ids list scan in
|
|
update_users_in_full_members_system_group
|
|
|
|
Simulates the O(M*F) vs O(M+F) membership check for full-member exclusion.
|
|
Benchmarks N=100 and N=1000, asserts speedup > 3x.
|
|
"""
|
|
import time
|
|
|
|
|
|
def members_excluding_full_list(member_group_users, full_member_group_users):
|
|
"""Original O(M * F): list comprehension with 'not in' on list."""
|
|
full_member_group_user_ids = [user["id"] for user in full_member_group_users]
|
|
members_excluding_full_members = [
|
|
user for user in member_group_users if user["id"] not in full_member_group_user_ids
|
|
]
|
|
return members_excluding_full_members
|
|
|
|
|
|
def members_excluding_full_set(member_group_users, full_member_group_users):
|
|
"""Fixed O(M + F): set comprehension with 'not in' on set."""
|
|
full_member_group_user_ids_set = {user["id"] for user in full_member_group_users}
|
|
members_excluding_full_members = [
|
|
user for user in member_group_users if user["id"] not in full_member_group_user_ids_set
|
|
]
|
|
return members_excluding_full_members
|
|
|
|
|
|
def make_users(ids):
|
|
return [{"id": i, "role": 400, "date_joined": None} for i in ids]
|
|
|
|
|
|
def benchmark(fn, member_users, full_member_users, reps=200):
|
|
t0 = time.perf_counter()
|
|
for _ in range(reps):
|
|
result = fn(member_users, full_member_users)
|
|
return time.perf_counter() - t0, result
|
|
|
|
|
|
def run_test(N, reps=200):
|
|
# M = N all-members, F = N full-members (worst case: all are full members, result is empty)
|
|
member_users = make_users(range(N))
|
|
full_member_users = make_users(range(N))
|
|
|
|
t_list, r_list = benchmark(members_excluding_full_list, member_users, full_member_users, reps)
|
|
t_set, r_set = benchmark(members_excluding_full_set, member_users, full_member_users, reps)
|
|
|
|
assert [u["id"] for u in r_list] == [u["id"] for u in r_set], "Results differ"
|
|
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_correctness(N):
|
|
# Half the members are full members
|
|
all_ids = list(range(N))
|
|
full_ids = list(range(0, N, 2)) # even IDs are full members
|
|
member_users = make_users(all_ids)
|
|
full_member_users = make_users(full_ids)
|
|
|
|
expected_ids = sorted(range(1, N, 2)) # odd IDs should remain
|
|
|
|
r_list = members_excluding_full_list(member_users, full_member_users)
|
|
r_set = members_excluding_full_set(member_users, full_member_users)
|
|
|
|
assert sorted(u["id"] for u in r_list) == expected_ids
|
|
assert sorted(u["id"] for u in r_set) == expected_ids
|
|
print(f"N={N}: correctness OK, {len(expected_ids)} non-full members found")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=== zulip-0003: full_member_group_user_ids list vs set benchmark ===")
|
|
print()
|
|
|
|
test_correctness(100)
|
|
test_correctness(1000)
|
|
print()
|
|
|
|
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)
|