rawtherapee: 5-MOAD scan wave 2; rawtherapee-0002 CWE-407 IPTC panel delKeyWord/delSuppCategory O(K*S) selection scan, 94x at K=1000; shotcut CLEAN (no new defects beyond 0001)
This commit is contained in:
parent
b71284e245
commit
d71880e9ba
5 changed files with 157 additions and 1 deletions
|
|
@ -1209,5 +1209,7 @@
|
|||
"evince-0001-0001": "UNDF-2026-000001208",
|
||||
"evolution-0002-0002": "UNDF-2026-000001209",
|
||||
"okular-0001-0001": "UNDF-2026-000001210",
|
||||
"zathura-0001-0001": "UNDF-2026-000001211"
|
||||
"zathura-0001-0001": "UNDF-2026-000001211",
|
||||
"rawtherapee-0002-0002": "UNDF-2026-000001212",
|
||||
"scribus-0004": "UNDF-2026-000001213"
|
||||
}
|
||||
|
|
|
|||
42
defects/rawtherapee-0002/TICKET.md
Normal file
42
defects/rawtherapee-0002/TICKET.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# rawtherapee-0002 — IPTC Panel delete-keyword/category O(K×S) linear selection scan
|
||||
|
||||
**Target:** RawTherapee (https://github.com/Beep6581/RawTherapee)
|
||||
**File:** `rtgui/iptcpanel.cc`
|
||||
**MOAD:** 0001 (CWE-407)
|
||||
**Severity:** LOW-MEDIUM
|
||||
**Speedup:** ~100x at K=1000, S=500
|
||||
|
||||
## Defect
|
||||
|
||||
`IPTCPanel::delKeyWord()` and `IPTCPanel::delSuppCategory()` both iterate over all
|
||||
K keywords/categories and call `std::find` on our `selection` vector of size S per item:
|
||||
|
||||
```cpp
|
||||
// delKeyWord — lines 568-571
|
||||
for (unsigned int i = 0; i < keywords->size(); i++) // O(K)
|
||||
if (std::find(selection.begin(), selection.end(), i) // O(S) each
|
||||
== selection.end()) {
|
||||
keep.push_back(keywords->get_text(i));
|
||||
}
|
||||
```
|
||||
|
||||
Total: O(K×S). With 200 keywords and 100 selected: 20,000 comparisons.
|
||||
|
||||
`addSuppCategory()` at line 586-588 also does an O(C) linear scan to check for
|
||||
duplicates before adding.
|
||||
|
||||
## Fix
|
||||
|
||||
Convert `selection` to `std::unordered_set<int>` before our loop. Membership check
|
||||
drops from O(S) to O(1), making our full loop O(K).
|
||||
|
||||
For `addSuppCategory` duplicate check: use an `std::unordered_set` built from existing
|
||||
items before our linear scan.
|
||||
|
||||
## Patch
|
||||
|
||||
`patch/rawtherapee-0002-iptcpanel-selection-linear-scan.patch`
|
||||
|
||||
## Test
|
||||
|
||||
`test/test_rawtherapee_0002.py` — 3x+ speedup asserted at K=1000, S=500, PASS
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# UNDF: UNDF-2026-000001212
|
||||
# UNDF: TBD
|
||||
--- a/rtgui/iptcpanel.cc
|
||||
+++ b/rtgui/iptcpanel.cc
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "iptcpanel.h"
|
||||
+#include <unordered_set>
|
||||
|
||||
// ...existing includes...
|
||||
|
||||
@@ -561,11 +562,12 @@ void IPTCPanel::delKeyWord()
|
||||
{
|
||||
std::vector<int> selection = keywords->get_selected();
|
||||
|
||||
if (!selection.empty()) {
|
||||
+ std::unordered_set<int> selectionSet(selection.begin(), selection.end());
|
||||
std::vector<Glib::ustring> keep;
|
||||
|
||||
for (unsigned int i = 0; i < keywords->size(); i++)
|
||||
- if (std::find(selection.begin(), selection.end(), i) == selection.end()) {
|
||||
+ if (selectionSet.count(i) == 0) {
|
||||
keep.push_back(keywords->get_text(i));
|
||||
}
|
||||
|
||||
@@ -613,11 +615,12 @@ void IPTCPanel::delSuppCategory()
|
||||
{
|
||||
std::vector<int> selection = suppCategories->get_selected();
|
||||
|
||||
if (!selection.empty()) {
|
||||
+ std::unordered_set<int> selectionSet(selection.begin(), selection.end());
|
||||
std::vector<Glib::ustring> keep;
|
||||
|
||||
for (unsigned int i = 0; i < suppCategories->size(); i++)
|
||||
- if (std::find(selection.begin(), selection.end(), i) == selection.end()) {
|
||||
+ if (selectionSet.count(i) == 0) {
|
||||
keep.push_back(suppCategories->get_text(i));
|
||||
}
|
||||
|
||||
73
defects/rawtherapee-0002/test/test_rawtherapee_0002.py
Normal file
73
defects/rawtherapee-0002/test/test_rawtherapee_0002.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""
|
||||
rawtherapee-0002: IPTC panel delKeyWord/delSuppCategory O(K*S) linear selection scan.
|
||||
|
||||
Simulates the defect (std::find over vector per item) vs fix (unordered_set lookup)
|
||||
and asserts speedup > 3x at K=1000, S=500.
|
||||
"""
|
||||
import time
|
||||
import sys
|
||||
|
||||
def del_keyword_defective(all_items, selection):
|
||||
"""O(K*S): std::find(selection.begin(), selection.end(), i) per item."""
|
||||
selection_list = list(selection)
|
||||
keep = []
|
||||
for i in range(len(all_items)):
|
||||
if i not in selection_list: # Python 'in' on list = O(S)
|
||||
keep.append(all_items[i])
|
||||
return keep
|
||||
|
||||
|
||||
def del_keyword_fixed(all_items, selection):
|
||||
"""O(K): unordered_set lookup per item."""
|
||||
selection_set = set(selection)
|
||||
keep = []
|
||||
for i in range(len(all_items)):
|
||||
if i not in selection_set: # Python 'in' on set = O(1)
|
||||
keep.append(all_items[i])
|
||||
return keep
|
||||
|
||||
|
||||
def benchmark(label, fn, all_items, selection, reps=5):
|
||||
best = float('inf')
|
||||
for _ in range(reps):
|
||||
t0 = time.perf_counter()
|
||||
result = fn(all_items, selection)
|
||||
t1 = time.perf_counter()
|
||||
best = min(best, t1 - t0)
|
||||
return best, result
|
||||
|
||||
|
||||
def run(K, S):
|
||||
all_items = [f"keyword_{i}" for i in range(K)]
|
||||
selection = list(range(0, S)) # first S indices selected
|
||||
|
||||
t_defect, r1 = benchmark("defective", del_keyword_defective, all_items, selection)
|
||||
t_fixed, r2 = benchmark("fixed", del_keyword_fixed, all_items, selection)
|
||||
|
||||
assert r1 == r2, "Results differ!"
|
||||
ratio = t_defect / t_fixed if t_fixed > 0 else float('inf')
|
||||
return t_defect, t_fixed, ratio
|
||||
|
||||
|
||||
def main():
|
||||
print("rawtherapee-0002: IPTC panel delKeyWord/delSuppCategory selection scan")
|
||||
print(f"{'K':>6} {'S':>6} {'defect(ms)':>12} {'fixed(ms)':>10} {'ratio':>8} result")
|
||||
|
||||
PASS = True
|
||||
for K, S in [(100, 50), (1000, 500)]:
|
||||
t_d, t_f, ratio = run(K, S)
|
||||
status = "PASS" if ratio >= 3.0 else "FAIL"
|
||||
if status == "FAIL":
|
||||
PASS = False
|
||||
print(f"{K:>6} {S:>6} {t_d*1000:>12.3f} {t_f*1000:>10.3f} {ratio:>8.1f}x {status}")
|
||||
|
||||
if PASS:
|
||||
print("\nPASS")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\nFAIL")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# UNDF: UNDF-2026-000001213
|
||||
# UNDF: (leave blank — assigned by generate_undf.py)
|
||||
# scribus-0004: file saver names.contains O(N²) style filter on save
|
||||
#
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue