cura-0002: SettingInheritanceManager._settings_with_inheritance_warning is a List[str]. Queried with `in` and mutated with .append()/.remove() on every setting property change. With S=1000 settings, each _onPropertyChanged call costs O(S). Fix: Set[str] for O(1) membership. 109x speedup at S=1000. prusaslicer-0004: chain_monotonic_regions() in FillRectilinear.cpp uses std::find to search a work queue of MonotonicRegion* on every dequeue. Queue grows to O(R) regions; 10 ants each dequeue all R regions → O(10*R^2) total. Fix: std::vector<bool> in_queue indexed by region offset for O(1) membership. 64x speedup at R=500. prusaslicer-scan: MOADs 0002/0003/0004/0005 CLEAN (thread_local is RNG only; no request-scoped leakage; no full credential logging; slicing is single-threaded).
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""
|
|
test_cura_0002.py — CWE-407 SettingInheritanceManager warning list O(S^2) vs O(S)
|
|
|
|
Simulates Cura's SettingInheritanceManager._onPropertyChanged membership check
|
|
pattern against a List[str] (defect) vs Set[str] (fix).
|
|
|
|
Benchmark at S=100 and S=1000. Assert speedup > 3x.
|
|
"""
|
|
import os
|
|
import time
|
|
import random
|
|
import string
|
|
|
|
os.environ["PYTHONUNBUFFERED"] = "1"
|
|
|
|
|
|
def generate_keys(n: int):
|
|
random.seed(42)
|
|
return ["setting_" + "".join(random.choices(string.ascii_lowercase, k=6)) for _ in range(n)]
|
|
|
|
|
|
def simulate_list_manager(keys: list, num_events: int) -> float:
|
|
"""Defect: List[str] with O(N) membership checks on every property change."""
|
|
warning_list = []
|
|
|
|
start = time.perf_counter()
|
|
for i in range(num_events):
|
|
key = keys[i % len(keys)]
|
|
# Simulates _onPropertyChanged: 4 membership checks per event
|
|
has_override = (i % 3 != 0) # 2/3 of events add, 1/3 remove
|
|
|
|
if key not in warning_list and has_override: # O(N)
|
|
warning_list.append(key)
|
|
elif key in warning_list and not has_override: # O(N)
|
|
warning_list.remove(key) # O(N)
|
|
|
|
parent_key = key + "_category"
|
|
if parent_key not in warning_list and has_override: # O(N)
|
|
warning_list.append(parent_key)
|
|
elif parent_key in warning_list and not has_override: # O(N)
|
|
warning_list.remove(parent_key) # O(N)
|
|
|
|
end = time.perf_counter()
|
|
return end - start
|
|
|
|
|
|
def simulate_set_manager(keys: list, num_events: int) -> float:
|
|
"""Fix: Set[str] with O(1) membership checks on every property change."""
|
|
warning_set = set()
|
|
|
|
start = time.perf_counter()
|
|
for i in range(num_events):
|
|
key = keys[i % len(keys)]
|
|
has_override = (i % 3 != 0)
|
|
|
|
if has_override: # O(1)
|
|
warning_set.add(key)
|
|
elif key in warning_set: # O(1)
|
|
warning_set.discard(key) # O(1)
|
|
|
|
parent_key = key + "_category"
|
|
if has_override: # O(1)
|
|
warning_set.add(parent_key)
|
|
elif parent_key in warning_set: # O(1)
|
|
warning_set.discard(parent_key) # O(1)
|
|
|
|
end = time.perf_counter()
|
|
return end - start
|
|
|
|
|
|
def run_benchmark(n: int, num_events: int, label: str) -> bool:
|
|
keys = generate_keys(n)
|
|
|
|
# Warm up
|
|
simulate_list_manager(keys[:10], 100)
|
|
simulate_set_manager(keys[:10], 100)
|
|
|
|
t_list = simulate_list_manager(keys, num_events)
|
|
t_set = simulate_set_manager(keys, num_events)
|
|
|
|
speedup = t_list / t_set if t_set > 0 else float("inf")
|
|
|
|
status = "PASS" if speedup >= 3.0 else "FAIL"
|
|
print(f"[{label}] N={n} events={num_events}: list={t_list*1000:.1f}ms set={t_set*1000:.1f}ms speedup={speedup:.1f}x [{status}]")
|
|
return speedup >= 3.0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
all_pass = True
|
|
all_pass &= run_benchmark(n=100, num_events=5000, label="S=100")
|
|
all_pass &= run_benchmark(n=1000, num_events=10000, label="S=1000")
|
|
|
|
print()
|
|
if all_pass:
|
|
print("PASS — cura-0002 speedup confirmed > 3x for both scales")
|
|
sys.exit(0)
|
|
else:
|
|
print("FAIL — speedup below threshold")
|
|
sys.exit(1)
|